mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 03:55:59 +00:00
menu refactor + web account switcher (#7307)
* add account switcher, migrate context menus to overflow menu system, update version filter controls, only pad instance icons * app account switcher, improve some context menus, theme stuff * prepr * handle reauthentication * fix a couple sign in issues, admin page cleanup, fix org status badges, fix official account badge, open in local keybind, publish plus icons * rename generic menus to ButtonMenu (& types) * fix hydration issue w/ dropdowns + middleware error
This commit is contained in:
@@ -51,6 +51,7 @@
|
||||
:alt="instance.name"
|
||||
size="2rem"
|
||||
rounded="md"
|
||||
pad-transparent-corners
|
||||
/>
|
||||
<span class="truncate font-semibold text-contrast">{{ instance.name }}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ClipboardCopyIcon,
|
||||
EditIcon,
|
||||
EyeIcon,
|
||||
FolderOpenIcon,
|
||||
MinusIcon,
|
||||
PaletteIcon,
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
StarIcon,
|
||||
StopCircleIcon,
|
||||
TrashIcon,
|
||||
UploadIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { StarIcon } from '@modrinth/assets'
|
||||
import { ContextMenu, defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed, nextTick, onDeactivated, onUnmounted, ref, toRef, watch } from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
import ContextMenu from '@/components/ui/context-menu/index.vue'
|
||||
import IconEditorModal from '@/components/ui/instance_settings/icon-editor-modal/index.vue'
|
||||
import GroupInstancesModal from '@/components/ui/library/group-instances-modal.vue'
|
||||
import InstanceGroup from '@/components/ui/library/instance-group/index.vue'
|
||||
@@ -44,55 +30,9 @@ const messages = defineMessages({
|
||||
id: 'app.library.search.no-results.title',
|
||||
defaultMessage: 'No instances match your search.',
|
||||
},
|
||||
play: { id: 'app.library.instance.action.play', defaultMessage: 'Play' },
|
||||
stop: { id: 'app.library.instance.action.stop', defaultMessage: 'Stop' },
|
||||
addToFavorites: {
|
||||
id: 'app.library.instance.action.add-to-favorites',
|
||||
defaultMessage: 'Add to favorites',
|
||||
},
|
||||
removeFromFavorites: {
|
||||
id: 'app.library.instance.action.remove-from-favorites',
|
||||
defaultMessage: 'Remove from favorites',
|
||||
},
|
||||
addContent: { id: 'app.library.instance.action.add-content', defaultMessage: 'Add content' },
|
||||
viewInstance: {
|
||||
id: 'app.library.instance.action.view-instance',
|
||||
defaultMessage: 'View instance',
|
||||
},
|
||||
editIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon',
|
||||
defaultMessage: 'Edit icon',
|
||||
},
|
||||
selectIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.select',
|
||||
defaultMessage: 'Select icon',
|
||||
},
|
||||
replaceIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.replace',
|
||||
defaultMessage: 'Replace icon',
|
||||
},
|
||||
createIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.create',
|
||||
defaultMessage: 'Create an icon',
|
||||
},
|
||||
editCreatedIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.edit-created',
|
||||
defaultMessage: 'Edit icon',
|
||||
},
|
||||
removeIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.remove',
|
||||
defaultMessage: 'Remove icon',
|
||||
},
|
||||
duplicateInstance: {
|
||||
id: 'app.library.instance.action.duplicate',
|
||||
defaultMessage: 'Duplicate instance',
|
||||
},
|
||||
delete: { id: 'app.library.instance.action.delete', defaultMessage: 'Delete' },
|
||||
openFolder: { id: 'app.library.instance.action.open-folder', defaultMessage: 'Open folder' },
|
||||
copyPath: { id: 'app.library.instance.action.copy-path', defaultMessage: 'Copy path' },
|
||||
removeFromGroup: {
|
||||
id: 'app.library.instance.action.remove-from-group',
|
||||
defaultMessage: 'Remove from group',
|
||||
instanceActionsLabel: {
|
||||
id: 'app.library.instance.actions.label',
|
||||
defaultMessage: 'Instance actions',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -111,7 +51,6 @@ const {
|
||||
currentDeleteInstances,
|
||||
clearLibraryInstanceSelection,
|
||||
deleteInstance,
|
||||
handleInstanceOption,
|
||||
handleInstanceIconSaved,
|
||||
selectedLibraryInstances,
|
||||
setSelectedLibraryInstances,
|
||||
@@ -432,34 +371,10 @@ watch(selectedLibraryInstances, (selectedInstances) => {
|
||||
:config="currentIconEditorInstance?.icon_config"
|
||||
@saved="handleInstanceIconSaved"
|
||||
/>
|
||||
<ContextMenu :ref="setInstanceOptions" @option-clicked="handleInstanceOption">
|
||||
<template #play> <PlayIcon /> {{ formatMessage(messages.play) }} </template>
|
||||
<template #stop> <StopCircleIcon /> {{ formatMessage(messages.stop) }} </template>
|
||||
<template #add_to_favorites>
|
||||
<StarIcon /> {{ formatMessage(messages.addToFavorites) }}
|
||||
</template>
|
||||
<template #remove_from_favorites>
|
||||
<ContextMenu :ref="setInstanceOptions" :label="formatMessage(messages.instanceActionsLabel)">
|
||||
<template #remove_from_favorites="{ option }">
|
||||
<StarIcon style="color: var(--color-text-default); fill: var(--color-text-default)" />
|
||||
{{ formatMessage(messages.removeFromFavorites) }}
|
||||
</template>
|
||||
<template #add_content> <PlusIcon /> {{ formatMessage(messages.addContent) }} </template>
|
||||
<template #edit> <EyeIcon /> {{ formatMessage(messages.viewInstance) }} </template>
|
||||
<template #edit_icon> <EditIcon /> {{ formatMessage(messages.editIcon) }} </template>
|
||||
<template #select_icon> <UploadIcon /> {{ formatMessage(messages.selectIcon) }} </template>
|
||||
<template #replace_icon> <UploadIcon /> {{ formatMessage(messages.replaceIcon) }} </template>
|
||||
<template #create_icon> <PaletteIcon /> {{ formatMessage(messages.createIcon) }} </template>
|
||||
<template #edit_created_icon>
|
||||
<PaletteIcon /> {{ formatMessage(messages.editCreatedIcon) }}
|
||||
</template>
|
||||
<template #remove_icon> <TrashIcon /> {{ formatMessage(messages.removeIcon) }} </template>
|
||||
<template #duplicate>
|
||||
<ClipboardCopyIcon /> {{ formatMessage(messages.duplicateInstance) }}
|
||||
</template>
|
||||
<template #delete> <TrashIcon /> {{ formatMessage(messages.delete) }} </template>
|
||||
<template #open> <FolderOpenIcon /> {{ formatMessage(messages.openFolder) }} </template>
|
||||
<template #copy> <ClipboardCopyIcon /> {{ formatMessage(messages.copyPath) }} </template>
|
||||
<template #remove_from_group>
|
||||
<MinusIcon /> {{ formatMessage(messages.removeFromGroup) }}
|
||||
{{ option.label }}
|
||||
</template>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Accordion,
|
||||
Button,
|
||||
commonMessages,
|
||||
ContextMenu,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
InlineEditableText,
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
} from '@modrinth/ui'
|
||||
import { computed, inject, nextTick, onActivated, onDeactivated, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import ContextMenu from '@/components/ui/context-menu/index.vue'
|
||||
import GroupActionButtons from '@/components/ui/library/instance-group/group-action-buttons.vue'
|
||||
import InstanceCard from '@/components/ui/library/instance-group/instance-card.vue'
|
||||
import type {
|
||||
@@ -163,6 +163,10 @@ const messages = defineMessages({
|
||||
id: 'app.library.group.empty',
|
||||
defaultMessage: 'Drag and drop to add instances.',
|
||||
},
|
||||
groupActionsLabel: {
|
||||
id: 'app.library.group.actions.label',
|
||||
defaultMessage: 'Group actions',
|
||||
},
|
||||
})
|
||||
|
||||
function openInstanceContextMenu(event: MouseEvent, instanceId: string, instanceGroupId: string) {
|
||||
@@ -204,49 +208,45 @@ function requestGroupDeletion() {
|
||||
|
||||
function openGroupContextMenu(event: MouseEvent) {
|
||||
groupContextMenuOpen.value = true
|
||||
groupOptions.value?.showMenu(
|
||||
event,
|
||||
props.instanceGroup,
|
||||
isFavorites.value
|
||||
? [{ name: 'new_instance' }]
|
||||
: isCustomGroup.value
|
||||
? [
|
||||
{ name: 'new_instance' },
|
||||
{ name: 'add_instances' },
|
||||
{ name: 'edit_name' },
|
||||
{ type: 'divider' },
|
||||
{ name: 'delete_group', color: 'danger' },
|
||||
]
|
||||
: [{ name: 'new_instance' }],
|
||||
)
|
||||
}
|
||||
|
||||
function handleGroupOption({ option }: { option: string }) {
|
||||
if (option === 'new_instance') {
|
||||
showCreationModal?.()
|
||||
return
|
||||
}
|
||||
|
||||
if (option === 'add_instances') {
|
||||
openGroupInstancesModal(props.instanceGroup.id)
|
||||
return
|
||||
}
|
||||
|
||||
if (option === 'edit_name') {
|
||||
void groupNameInput.value?.startEditing()
|
||||
return
|
||||
}
|
||||
|
||||
if (option === 'delete_group') {
|
||||
requestGroupDeletion()
|
||||
}
|
||||
groupOptions.value?.open(event, [
|
||||
{
|
||||
id: 'new_instance',
|
||||
label: formatMessage(messages.newInstance),
|
||||
icon: PlusIcon,
|
||||
action: () => showCreationModal?.(),
|
||||
},
|
||||
{
|
||||
id: 'add_instances',
|
||||
label: formatMessage(messages.addToGroup),
|
||||
icon: SquarePlusIcon,
|
||||
shown: isCustomGroup.value,
|
||||
action: () => openGroupInstancesModal(props.instanceGroup.id),
|
||||
},
|
||||
{
|
||||
id: 'edit_name',
|
||||
label: formatMessage(messages.editGroupName),
|
||||
icon: EditIcon,
|
||||
shown: isCustomGroup.value,
|
||||
action: () => void groupNameInput.value?.startEditing(),
|
||||
},
|
||||
{ type: 'divider', shown: isCustomGroup.value },
|
||||
{
|
||||
id: 'delete_group',
|
||||
label: formatMessage(messages.deleteGroup),
|
||||
icon: TrashIcon,
|
||||
shown: isCustomGroup.value,
|
||||
tone: 'red',
|
||||
hoverFilledOnly: true,
|
||||
action: requestGroupDeletion,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
function prepareGroupToggle(event: PointerEvent) {
|
||||
const editor = groupNameInput.value
|
||||
const contextMenuWasOpen = groupContextMenuOpen.value
|
||||
if (contextMenuWasOpen) {
|
||||
groupOptions.value?.hideMenu()
|
||||
groupOptions.value?.close()
|
||||
}
|
||||
|
||||
shouldSkipGroupToggle = Boolean(
|
||||
@@ -523,16 +523,9 @@ onMounted(startInstanceGridResizeObserver)
|
||||
|
||||
<ContextMenu
|
||||
ref="groupOptions"
|
||||
@menu-closed="groupContextMenuOpen = false"
|
||||
@option-clicked="handleGroupOption"
|
||||
>
|
||||
<template #new_instance> <PlusIcon /> {{ formatMessage(messages.newInstance) }} </template>
|
||||
<template #add_instances>
|
||||
<SquarePlusIcon /> {{ formatMessage(messages.addToGroup) }}
|
||||
</template>
|
||||
<template #edit_name> <EditIcon /> {{ formatMessage(messages.editGroupName) }} </template>
|
||||
<template #delete_group> <TrashIcon /> {{ formatMessage(messages.deleteGroup) }} </template>
|
||||
</ContextMenu>
|
||||
:label="formatMessage(messages.groupActionsLabel)"
|
||||
@close="groupContextMenuOpen = false"
|
||||
/>
|
||||
|
||||
<NewModal
|
||||
ref="confirmDeleteGroupModal"
|
||||
|
||||
@@ -47,6 +47,7 @@ const versionRef = ref<HTMLElement | null>(null)
|
||||
:tint-by="instance.id"
|
||||
alt=""
|
||||
no-shadow
|
||||
pad-transparent-corners
|
||||
/>
|
||||
<slot name="loading" :compact="compactMode" />
|
||||
<div
|
||||
|
||||
@@ -242,6 +242,9 @@ defineExpose({
|
||||
openFolder,
|
||||
addContent,
|
||||
instance: props.instance,
|
||||
get playing() {
|
||||
return playing.value
|
||||
},
|
||||
})
|
||||
|
||||
useAppEvent('process', (event) => {
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
:alt="instance.name"
|
||||
size="2rem"
|
||||
rounded="md"
|
||||
pad-transparent-corners
|
||||
/>
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<span class="truncate font-semibold text-contrast">{{ instance.name }}</span>
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { formatLoader, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import {
|
||||
ClipboardCopyIcon,
|
||||
EditIcon,
|
||||
EyeIcon,
|
||||
FolderOpenIcon,
|
||||
MinusIcon,
|
||||
PaletteIcon,
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
StarIcon,
|
||||
StopCircleIcon,
|
||||
TrashIcon,
|
||||
UploadIcon,
|
||||
} from '@modrinth/assets'
|
||||
import type { ButtonMenuLeafOption, ButtonMenuOption } from '@modrinth/ui'
|
||||
import { defineMessages, formatLoader, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import { open as openDialog } from '@tauri-apps/plugin-dialog'
|
||||
import { useEventListener, useStorage } from '@vueuse/core'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -98,7 +113,7 @@ export type InstanceCard = {
|
||||
}
|
||||
|
||||
type InstanceContextMenu = {
|
||||
showMenu: (event: MouseEvent, item: InstanceCard, options: unknown[]) => void
|
||||
open: (event: MouseEvent, options: ButtonMenuOption[]) => void
|
||||
}
|
||||
|
||||
type ConfirmDeleteModal = {
|
||||
@@ -109,10 +124,58 @@ type IconEditorModal = {
|
||||
show: () => void
|
||||
}
|
||||
|
||||
type ContextMenuSelection = {
|
||||
option: string
|
||||
item: InstanceCard
|
||||
}
|
||||
const instanceActionMessages = defineMessages({
|
||||
play: { id: 'app.library.instance.action.play', defaultMessage: 'Play' },
|
||||
stop: { id: 'app.library.instance.action.stop', defaultMessage: 'Stop' },
|
||||
addToFavorites: {
|
||||
id: 'app.library.instance.action.add-to-favorites',
|
||||
defaultMessage: 'Add to favorites',
|
||||
},
|
||||
removeFromFavorites: {
|
||||
id: 'app.library.instance.action.remove-from-favorites',
|
||||
defaultMessage: 'Remove from favorites',
|
||||
},
|
||||
addContent: { id: 'app.library.instance.action.add-content', defaultMessage: 'Add content' },
|
||||
viewInstance: {
|
||||
id: 'app.library.instance.action.view-instance',
|
||||
defaultMessage: 'View instance',
|
||||
},
|
||||
editIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon',
|
||||
defaultMessage: 'Edit icon',
|
||||
},
|
||||
selectIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.select',
|
||||
defaultMessage: 'Select icon',
|
||||
},
|
||||
replaceIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.replace',
|
||||
defaultMessage: 'Replace icon',
|
||||
},
|
||||
createIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.create',
|
||||
defaultMessage: 'Create an icon',
|
||||
},
|
||||
editCreatedIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.edit-created',
|
||||
defaultMessage: 'Edit icon',
|
||||
},
|
||||
removeIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.remove',
|
||||
defaultMessage: 'Remove icon',
|
||||
},
|
||||
duplicateInstance: {
|
||||
id: 'app.library.instance.action.duplicate',
|
||||
defaultMessage: 'Duplicate instance',
|
||||
},
|
||||
delete: { id: 'app.library.instance.action.delete', defaultMessage: 'Delete' },
|
||||
openFolder: { id: 'app.library.instance.action.open-folder', defaultMessage: 'Open folder' },
|
||||
copyPath: { id: 'app.library.instance.action.copy-path', defaultMessage: 'Copy path' },
|
||||
removeFromGroup: {
|
||||
id: 'app.library.instance.action.remove-from-group',
|
||||
defaultMessage: 'Remove from group',
|
||||
},
|
||||
})
|
||||
|
||||
function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
const { handleError } = injectNotificationManager()
|
||||
@@ -160,7 +223,6 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
const currentDeleteInstances = computed(() =>
|
||||
instances.value.filter((instance) => instance.id === currentDeleteInstanceId.value),
|
||||
)
|
||||
const currentContextGroupId = ref<string | null>(null)
|
||||
const confirmDeleteModal = ref<ConfirmDeleteModal | null>(null)
|
||||
const iconEditorModal = ref<IconEditorModal | null>(null)
|
||||
const currentIconEditorInstanceId = ref<string | null>(null)
|
||||
@@ -1156,121 +1218,153 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
trackEvent('InstanceSaveCreatedIcon')
|
||||
}
|
||||
|
||||
const setInstanceGroups = async (item: InstanceCard, groupIds: string[]) =>
|
||||
await edit(item.instance.id, { group_ids: groupIds }).catch((error) =>
|
||||
handleError(toError(error)),
|
||||
)
|
||||
|
||||
const buildInstanceIconOptions = (item: InstanceCard): ButtonMenuLeafOption[] => [
|
||||
{
|
||||
id: item.instance.icon_path ? 'replace_icon' : 'select_icon',
|
||||
label: formatMessage(
|
||||
item.instance.icon_path
|
||||
? instanceActionMessages.replaceIcon
|
||||
: instanceActionMessages.selectIcon,
|
||||
),
|
||||
icon: UploadIcon,
|
||||
action: () => void selectInstanceIcon(item),
|
||||
},
|
||||
{
|
||||
id: item.instance.icon_config ? 'edit_created_icon' : 'create_icon',
|
||||
label: formatMessage(
|
||||
item.instance.icon_config
|
||||
? instanceActionMessages.editCreatedIcon
|
||||
: instanceActionMessages.createIcon,
|
||||
),
|
||||
icon: PaletteIcon,
|
||||
action: () => void openInstanceIconEditor(item),
|
||||
},
|
||||
{
|
||||
id: 'remove_icon',
|
||||
label: formatMessage(instanceActionMessages.removeIcon),
|
||||
icon: TrashIcon,
|
||||
shown: !!item.instance.icon_path,
|
||||
action: () => void removeInstanceIcon(item),
|
||||
},
|
||||
]
|
||||
|
||||
const handleInstanceContextMenu = (
|
||||
event: MouseEvent,
|
||||
item: InstanceCard,
|
||||
instanceGroupId: string,
|
||||
) => {
|
||||
currentContextGroupId.value =
|
||||
const removableGroupId =
|
||||
displayState.value.group === 'Group' &&
|
||||
instanceGroupId !== 'group:none' &&
|
||||
instanceGroupId !== FAVORITES_GROUP_ID
|
||||
? instanceGroupId
|
||||
: null
|
||||
const isFavorite = item.instance.group_ids.includes(FAVORITES_GROUP_ID)
|
||||
const canAddContent = !item.instance.quarantined && !item.instance.link
|
||||
|
||||
const baseOptions = [
|
||||
instanceOptions.value?.open(event, [
|
||||
{
|
||||
name: item.instance.group_ids.includes(FAVORITES_GROUP_ID)
|
||||
? 'remove_from_favorites'
|
||||
: 'add_to_favorites',
|
||||
id: 'stop',
|
||||
label: formatMessage(instanceActionMessages.stop),
|
||||
icon: StopCircleIcon,
|
||||
shown: item.playing,
|
||||
tone: 'red',
|
||||
action: () => void item.stop(null, 'InstanceGridContextMenu'),
|
||||
},
|
||||
{
|
||||
id: 'play',
|
||||
label: formatMessage(instanceActionMessages.play),
|
||||
icon: PlayIcon,
|
||||
shown: !item.playing && !item.instance.quarantined,
|
||||
tone: 'brand',
|
||||
action: () => void item.play(null, 'InstanceGridContextMenu'),
|
||||
},
|
||||
{
|
||||
id: isFavorite ? 'remove_from_favorites' : 'add_to_favorites',
|
||||
label: formatMessage(
|
||||
isFavorite
|
||||
? instanceActionMessages.removeFromFavorites
|
||||
: instanceActionMessages.addToFavorites,
|
||||
),
|
||||
icon: StarIcon,
|
||||
action: () =>
|
||||
void setInstanceGroups(
|
||||
item,
|
||||
isFavorite
|
||||
? item.instance.group_ids.filter((groupId) => groupId !== FAVORITES_GROUP_ID)
|
||||
: [...new Set([...item.instance.group_ids, FAVORITES_GROUP_ID])],
|
||||
),
|
||||
},
|
||||
{ type: 'divider' },
|
||||
...(!item.instance.quarantined && !item.instance.link
|
||||
? [{ name: 'add_content' }, { type: 'divider' }]
|
||||
: []),
|
||||
{ name: 'edit' },
|
||||
{ name: 'duplicate' },
|
||||
{ name: 'open' },
|
||||
{ name: 'copy' },
|
||||
{
|
||||
name: 'edit_icon',
|
||||
children: [
|
||||
{ name: item.instance.icon_path ? 'replace_icon' : 'select_icon' },
|
||||
{ name: item.instance.icon_config ? 'edit_created_icon' : 'create_icon' },
|
||||
...(item.instance.icon_path ? [{ name: 'remove_icon' }] : []),
|
||||
],
|
||||
id: 'add_content',
|
||||
label: formatMessage(instanceActionMessages.addContent),
|
||||
icon: PlusIcon,
|
||||
shown: canAddContent,
|
||||
action: () => void item.addContent(),
|
||||
},
|
||||
...(currentContextGroupId.value
|
||||
? [{ name: 'remove_from_group' }, { type: 'divider' }]
|
||||
: [{ type: 'divider' }]),
|
||||
{ name: 'delete', color: 'danger' },
|
||||
]
|
||||
|
||||
instanceOptions.value?.showMenu(
|
||||
event,
|
||||
item,
|
||||
item.playing
|
||||
? [{ name: 'stop', color: 'danger' }, ...baseOptions]
|
||||
: [
|
||||
...(item.instance.quarantined ? [] : [{ name: 'play', color: 'primary' }]),
|
||||
...baseOptions,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
const handleInstanceOption = async ({ option, item }: ContextMenuSelection) => {
|
||||
switch (option) {
|
||||
case 'play':
|
||||
await item.play(null, 'InstanceGridContextMenu')
|
||||
break
|
||||
case 'stop':
|
||||
await item.stop(null, 'InstanceGridContextMenu')
|
||||
break
|
||||
case 'add_content':
|
||||
await item.addContent()
|
||||
break
|
||||
case 'add_to_favorites':
|
||||
await edit(item.instance.id, {
|
||||
group_ids: [...new Set([...item.instance.group_ids, FAVORITES_GROUP_ID])],
|
||||
}).catch((error) => handleError(toError(error)))
|
||||
break
|
||||
case 'remove_from_favorites':
|
||||
await edit(item.instance.id, {
|
||||
group_ids: item.instance.group_ids.filter(
|
||||
(instanceGroupId) => instanceGroupId !== FAVORITES_GROUP_ID,
|
||||
{ type: 'divider', shown: canAddContent },
|
||||
{
|
||||
id: 'edit',
|
||||
label: formatMessage(instanceActionMessages.viewInstance),
|
||||
icon: EyeIcon,
|
||||
action: () => void item.seeInstance(),
|
||||
},
|
||||
{
|
||||
id: 'duplicate',
|
||||
label: formatMessage(instanceActionMessages.duplicateInstance),
|
||||
icon: ClipboardCopyIcon,
|
||||
disabled: item.instance.install_stage !== 'installed',
|
||||
action: () => void duplicateInstance(item.instance.id),
|
||||
},
|
||||
{
|
||||
id: 'open',
|
||||
label: formatMessage(instanceActionMessages.openFolder),
|
||||
icon: FolderOpenIcon,
|
||||
action: () => void item.openFolder(),
|
||||
},
|
||||
{
|
||||
id: 'copy',
|
||||
label: formatMessage(instanceActionMessages.copyPath),
|
||||
icon: ClipboardCopyIcon,
|
||||
action: () => void navigator.clipboard.writeText(item.instance.id),
|
||||
},
|
||||
{
|
||||
type: 'submenu',
|
||||
id: 'edit_icon',
|
||||
label: formatMessage(instanceActionMessages.editIcon),
|
||||
icon: EditIcon,
|
||||
options: buildInstanceIconOptions(item),
|
||||
},
|
||||
{
|
||||
id: 'remove_from_group',
|
||||
label: formatMessage(instanceActionMessages.removeFromGroup),
|
||||
icon: MinusIcon,
|
||||
shown: !!removableGroupId,
|
||||
action: () =>
|
||||
void setInstanceGroups(
|
||||
item,
|
||||
item.instance.group_ids.filter((groupId) => groupId !== removableGroupId),
|
||||
),
|
||||
}).catch((error) => handleError(toError(error)))
|
||||
break
|
||||
case 'edit':
|
||||
await item.seeInstance()
|
||||
break
|
||||
case 'select_icon':
|
||||
case 'replace_icon':
|
||||
await selectInstanceIcon(item)
|
||||
break
|
||||
case 'create_icon':
|
||||
case 'edit_created_icon':
|
||||
await openInstanceIconEditor(item)
|
||||
break
|
||||
case 'remove_icon':
|
||||
await removeInstanceIcon(item)
|
||||
break
|
||||
case 'duplicate':
|
||||
if (item.instance.install_stage === 'installed') {
|
||||
await duplicateInstance(item.instance.id)
|
||||
}
|
||||
break
|
||||
case 'open':
|
||||
await item.openFolder()
|
||||
break
|
||||
case 'copy':
|
||||
await navigator.clipboard.writeText(item.instance.id)
|
||||
break
|
||||
case 'remove_from_group':
|
||||
if (currentContextGroupId.value) {
|
||||
const groupId = currentContextGroupId.value
|
||||
await edit(item.instance.id, {
|
||||
group_ids: item.instance.group_ids.filter(
|
||||
(instanceGroupId) => instanceGroupId !== groupId,
|
||||
),
|
||||
}).catch((error) => handleError(toError(error)))
|
||||
}
|
||||
break
|
||||
case 'delete':
|
||||
currentDeleteInstanceId.value = item.instance.id
|
||||
confirmDeleteModal.value?.show()
|
||||
break
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
id: 'delete',
|
||||
label: formatMessage(instanceActionMessages.delete),
|
||||
icon: TrashIcon,
|
||||
tone: 'red',
|
||||
hoverFilledOnly: true,
|
||||
action: () => {
|
||||
currentDeleteInstanceId.value = item.instance.id
|
||||
confirmDeleteModal.value?.show()
|
||||
},
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1339,7 +1433,6 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
moveGroup,
|
||||
deleteInstance,
|
||||
handleInstanceContextMenu,
|
||||
handleInstanceOption,
|
||||
handleInstanceIconSaved,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user