diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index f7a029b5c..fc1b10f56 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -147,6 +147,7 @@ provideModalBehavior({ const { installationModal, + fetchExistingInstanceNames, handleCreate, handleBrowseModpacks, searchModpacks, @@ -945,6 +946,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload) ref="installationModal" type="instance" show-snapshot-toggle + :fetch-existing-instance-names="fetchExistingInstanceNames" :search-modpacks="searchModpacks" :get-project-versions="getProjectVersions" @create="handleCreate" diff --git a/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue index 30383702f..ead9416bf 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue @@ -109,6 +109,7 @@ watch( const removing = ref(false) async function removeProfile() { removing.value = true + const path = props.instance.path trackEvent('InstanceRemove', { loader: props.instance.loader, @@ -116,7 +117,7 @@ async function removeProfile() { }) await router.push({ path: '/' }) - await remove(props.instance.path).catch(handleError) + await remove(path).catch(handleError) } const messages = defineMessages({ diff --git a/apps/app-frontend/src/pages/instance/Index.vue b/apps/app-frontend/src/pages/instance/Index.vue index 07e868b34..52c51bcfb 100644 --- a/apps/app-frontend/src/pages/instance/Index.vue +++ b/apps/app-frontend/src/pages/instance/Index.vue @@ -246,7 +246,6 @@ :options="options" :offline="offline" :playing="playing" - :versions="modrinthVersions" :installed="instance.install_stage !== 'installed'" :is-server-instance="isServerInstance" :open-settings="() => settingsModal?.show(1)" @@ -332,7 +331,7 @@ import InstanceSettingsModal from '@/components/ui/modal/InstanceSettingsModal.v import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue' import NavTabs from '@/components/ui/NavTabs.vue' import { trackEvent } from '@/helpers/analytics' -import { get_project_v3, get_version_many } from '@/helpers/cache.js' +import { get_project_v3 } from '@/helpers/cache.js' import { process_listener, profile_listener } from '@/helpers/events' import { get_by_profile_path } from '@/helpers/process' import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile' @@ -362,7 +361,6 @@ window.addEventListener('online', () => { }) const instance = ref() -const modrinthVersions = ref([]) const playing = ref(false) const loading = ref(false) const exportModal = ref>() @@ -385,7 +383,6 @@ const loadingServerPing = ref(false) async function fetchInstance() { isServerInstance.value = false linkedProjectV3.value = undefined - modrinthVersions.value = [] ping.value = undefined playersOnline.value = undefined loadingServerPing.value = false @@ -402,14 +399,6 @@ async function fetchInstance() { if (linkedProjectV3.value?.minecraft_server != null) { isServerInstance.value = true } - - if (linkedProjectV3.value && linkedProjectV3.value.versions) { - const versions = await get_version_many(linkedProjectV3.value.versions, 'must_revalidate') - modrinthVersions.value = versions.sort( - (a: Labrinth.Versions.v2.Version, b: Labrinth.Versions.v2.Version) => - dayjs(b.date_published).valueOf() - dayjs(a.date_published).valueOf(), - ) - } } catch (error) { handleError(error as Error) } @@ -605,18 +594,23 @@ const handleOptionsClick = async (args: { option: string; item: unknown }) => { const unlistenProfiles = await profile_listener( async (event: { profile_path_id: string; event: string }) => { - if (event.profile_path_id === route.params.id) { - if (event.event === 'removed') { - await router.push({ - path: '/', - }) - return + if (event.profile_path_id !== route.params.id) return + if (event.event === 'removed' || route.path === '/') { + if (route.path !== '/') { + await router.push({ path: '/' }) } - instance.value = await get(route.params.id as string).catch(handleError) - if (!instance.value?.linked_data?.project_id) { - linkedProjectV3.value = undefined - isServerInstance.value = false + return + } + instance.value = await get(route.params.id as string).catch((err) => { + if (String(err).includes('not managed')) { + router.push({ path: '/' }) + return undefined } + return handleError(err) + }) + if (!instance.value?.linked_data?.project_id) { + linkedProjectV3.value = undefined + isServerInstance.value = false } }, ) diff --git a/apps/app-frontend/src/pages/instance/Logs.vue b/apps/app-frontend/src/pages/instance/Logs.vue index 13e5efec7..38fba56b0 100644 --- a/apps/app-frontend/src/pages/instance/Logs.vue +++ b/apps/app-frontend/src/pages/instance/Logs.vue @@ -150,10 +150,6 @@ const props = defineProps({ return false }, }, - versions: { - type: Array, - required: true, - }, installed: { type: Boolean, default() { diff --git a/apps/app-frontend/src/pages/instance/Mods.vue b/apps/app-frontend/src/pages/instance/Mods.vue index a7f298523..d0044143f 100644 --- a/apps/app-frontend/src/pages/instance/Mods.vue +++ b/apps/app-frontend/src/pages/instance/Mods.vue @@ -36,7 +36,7 @@ : (updatingProject?.version?.id ?? '') " :is-app="true" - :is-modpack="updatingModpack" + :project-type="updatingModpack ? 'modpack' : updatingProject?.project_type" :project-icon-url=" updatingModpack ? linkedModpackProject?.icon_url : updatingProject?.project?.icon_url " @@ -73,6 +73,7 @@ import { type OverflowMenuOption, provideAppBackup, provideContentManager, + useDebugLogger, useVIntl, } from '@modrinth/ui' import { ContentCardLayout as ContentPageLayout } from '@modrinth/ui' @@ -165,10 +166,10 @@ const { formatMessage } = useVIntl() const { handleError, addNotification } = injectNotificationManager() const { installingItems } = injectContentInstall() const router = useRouter() +const debug = useDebugLogger('Mods:ContentUpdate') const props = defineProps<{ instance: GameInstance - versions: Labrinth.Versions.v2.Version[] isServerInstance?: boolean openSettings?: () => void }>() @@ -349,6 +350,18 @@ async function handleUpdate(id: string) { const item = projects.value.find((p) => p.file_name === id) if (!item?.has_update || !item.project?.id || !item.version?.id) return + debug('handleUpdate triggered', { + fileName: item.file_name, + projectType: item.project_type, + projectId: item.project.id, + projectTitle: item.project.title, + currentVersionId: item.version.id, + currentVersionNumber: item.version.version_number, + updateVersionId: item.update_version_id, + instanceGameVersion: props.instance.game_version, + instanceLoader: props.instance.loader, + }) + updatingModpack.value = false updatingProject.value = item updatingProjectVersions.value = [] @@ -365,7 +378,24 @@ async function handleUpdate(id: string) { loadingVersions.value = false - if (!versions) return + if (!versions) { + debug('handleUpdate: no versions returned', { projectId: item.project.id }) + return + } + + debug('handleUpdate: fetched versions', { + projectId: item.project.id, + projectType: item.project_type, + totalVersions: versions.length, + versionSample: versions.slice(0, 5).map((v) => ({ + id: v.id, + number: v.version_number, + loaders: v.loaders, + gameVersions: v.game_versions, + })), + currentVersionInList: versions.some((v) => v.id === item.version?.id), + updateVersionInList: versions.some((v) => v.id === item.update_version_id), + }) versions.sort( (a, b) => new Date(b.date_published).getTime() - new Date(a.date_published).getTime(), @@ -493,9 +523,17 @@ function handleModpackUpdateCancel() { pendingModpackUpdateVersion.value = null } -async function handleModalUpdate(selectedVersion: Labrinth.Versions.v2.Version) { +async function handleModalUpdate( + selectedVersion: Labrinth.Versions.v2.Version, + event?: MouseEvent, +) { if (updatingModpack.value) { - handleModpackUpdateRequest(selectedVersion) + if (event?.shiftKey) { + pendingModpackUpdateVersion.value = selectedVersion + await handleModpackUpdateConfirm() + } else { + handleModpackUpdateRequest(selectedVersion) + } } else if (updatingProject.value) { const mod = updatingProject.value diff --git a/apps/app-frontend/src/pages/instance/Overview.vue b/apps/app-frontend/src/pages/instance/Overview.vue index 06b73e04f..1221d2717 100644 --- a/apps/app-frontend/src/pages/instance/Overview.vue +++ b/apps/app-frontend/src/pages/instance/Overview.vue @@ -1,7 +1,5 @@ diff --git a/apps/app-frontend/src/pages/instance/Worlds.vue b/apps/app-frontend/src/pages/instance/Worlds.vue index 08e8d7eca..b78866097 100644 --- a/apps/app-frontend/src/pages/instance/Worlds.vue +++ b/apps/app-frontend/src/pages/instance/Worlds.vue @@ -134,7 +134,6 @@ import { RadialHeader, StyledInput, } from '@modrinth/ui' -import type { Version } from '@modrinth/utils' import { platform } from '@tauri-apps/plugin-os' import { computed, onUnmounted, ref, watch } from 'vue' import { useRoute } from 'vue-router' @@ -202,7 +201,6 @@ const props = defineProps<{ options: InstanceType | null offline: boolean playing: boolean - versions: Version[] installed: boolean }>() diff --git a/apps/app-frontend/src/providers/setup/creation-modal.ts b/apps/app-frontend/src/providers/setup/creation-modal.ts index f3ed6fc03..8d4b1b3ae 100644 --- a/apps/app-frontend/src/providers/setup/creation-modal.ts +++ b/apps/app-frontend/src/providers/setup/creation-modal.ts @@ -13,9 +13,14 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM const router = useRouter() const installationModal = useTemplateRef('installationModal') - provide('showCreationModal', async () => { + + async function fetchExistingInstanceNames(): Promise { const instances = await list().catch(handleError) - installationModal.value?.show(instances?.length ?? 0) + return instances?.map((i) => i.name) ?? [] + } + + provide('showCreationModal', () => { + installationModal.value?.show() }) async function handleCreate(config: CreationFlowContextValue) { @@ -57,9 +62,10 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM ? null : (config.selectedLoaderVersion.value ?? config.loaderVersionType.value) const iconPath = config.instanceIconPath.value ?? null + const name = config.instanceName.value.trim() || config.autoInstanceName.value await create( - config.instanceName.value, + name, config.selectedGameVersion.value, loader, loaderVersion, @@ -68,7 +74,7 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM ).catch(handleError) trackEvent('InstanceCreate', { - profile_name: config.instanceName.value, + profile_name: name, game_version: config.selectedGameVersion.value, loader, loader_version: loaderVersion, @@ -102,6 +108,7 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM return { installationModal, + fetchExistingInstanceNames, handleCreate, handleBrowseModpacks, searchModpacks, diff --git a/packages/assets/generated-icons.ts b/packages/assets/generated-icons.ts index c8817bd6d..9905214c3 100644 --- a/packages/assets/generated-icons.ts +++ b/packages/assets/generated-icons.ts @@ -11,7 +11,9 @@ import _ArchiveIcon from './icons/archive.svg?component' import _ArrowBigRightDashIcon from './icons/arrow-big-right-dash.svg?component' import _ArrowBigUpDashIcon from './icons/arrow-big-up-dash.svg?component' import _ArrowDownIcon from './icons/arrow-down.svg?component' +import _ArrowDownAZIcon from './icons/arrow-down-a-z.svg?component' import _ArrowDownLeftIcon from './icons/arrow-down-left.svg?component' +import _ArrowDownZAIcon from './icons/arrow-down-z-a.svg?component' import _ArrowLeftIcon from './icons/arrow-left.svg?component' import _ArrowLeftRightIcon from './icons/arrow-left-right.svg?component' import _ArrowUpIcon from './icons/arrow-up.svg?component' @@ -38,6 +40,7 @@ import _BracesIcon from './icons/braces.svg?component' import _BrushCleaningIcon from './icons/brush-cleaning.svg?component' import _BugIcon from './icons/bug.svg?component' import _CalendarIcon from './icons/calendar.svg?component' +import _CalendarArrowDownIcon from './icons/calendar-arrow-down.svg?component' import _CardIcon from './icons/card.svg?component' import _ChangeSkinIcon from './icons/change-skin.svg?component' import _ChartIcon from './icons/chart.svg?component' @@ -54,6 +57,8 @@ import _ClearIcon from './icons/clear.svg?component' import _ClientIcon from './icons/client.svg?component' import _ClipboardCopyIcon from './icons/clipboard-copy.svg?component' import _ClockIcon from './icons/clock.svg?component' +import _ClockArrowDownIcon from './icons/clock-arrow-down.svg?component' +import _ClockArrowUpIcon from './icons/clock-arrow-up.svg?component' import _CloudIcon from './icons/cloud.svg?component' import _CodeIcon from './icons/code.svg?component' import _CoffeeIcon from './icons/coffee.svg?component' @@ -358,6 +363,7 @@ import _ToggleLeftIcon from './icons/toggle-left.svg?component' import _ToggleRightIcon from './icons/toggle-right.svg?component' import _TransferIcon from './icons/transfer.svg?component' import _TrashIcon from './icons/trash.svg?component' +import _TrashExclamationIcon from './icons/trash-exclamation.svg?component' import _TriangleAlertIcon from './icons/triangle-alert.svg?component' import _UnderlineIcon from './icons/underline.svg?component' import _UndoIcon from './icons/undo.svg?component' @@ -390,7 +396,9 @@ export const ArchiveIcon = _ArchiveIcon export const ArrowBigRightDashIcon = _ArrowBigRightDashIcon export const ArrowBigUpDashIcon = _ArrowBigUpDashIcon export const ArrowDownIcon = _ArrowDownIcon +export const ArrowDownAZIcon = _ArrowDownAZIcon export const ArrowDownLeftIcon = _ArrowDownLeftIcon +export const ArrowDownZAIcon = _ArrowDownZAIcon export const ArrowLeftIcon = _ArrowLeftIcon export const ArrowLeftRightIcon = _ArrowLeftRightIcon export const ArrowUpIcon = _ArrowUpIcon @@ -417,6 +425,7 @@ export const BracesIcon = _BracesIcon export const BrushCleaningIcon = _BrushCleaningIcon export const BugIcon = _BugIcon export const CalendarIcon = _CalendarIcon +export const CalendarArrowDownIcon = _CalendarArrowDownIcon export const CardIcon = _CardIcon export const ChangeSkinIcon = _ChangeSkinIcon export const ChartIcon = _ChartIcon @@ -433,6 +442,8 @@ export const ClearIcon = _ClearIcon export const ClientIcon = _ClientIcon export const ClipboardCopyIcon = _ClipboardCopyIcon export const ClockIcon = _ClockIcon +export const ClockArrowDownIcon = _ClockArrowDownIcon +export const ClockArrowUpIcon = _ClockArrowUpIcon export const CloudIcon = _CloudIcon export const CodeIcon = _CodeIcon export const CoffeeIcon = _CoffeeIcon @@ -737,6 +748,7 @@ export const ToggleLeftIcon = _ToggleLeftIcon export const ToggleRightIcon = _ToggleRightIcon export const TransferIcon = _TransferIcon export const TrashIcon = _TrashIcon +export const TrashExclamationIcon = _TrashExclamationIcon export const TriangleAlertIcon = _TriangleAlertIcon export const UnderlineIcon = _UnderlineIcon export const UndoIcon = _UndoIcon diff --git a/packages/assets/icons/arrow-down-a-z.svg b/packages/assets/icons/arrow-down-a-z.svg new file mode 100644 index 000000000..535836005 --- /dev/null +++ b/packages/assets/icons/arrow-down-a-z.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/packages/assets/icons/arrow-down-z-a.svg b/packages/assets/icons/arrow-down-z-a.svg new file mode 100644 index 000000000..f3cd72a45 --- /dev/null +++ b/packages/assets/icons/arrow-down-z-a.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/packages/assets/icons/calendar-arrow-down.svg b/packages/assets/icons/calendar-arrow-down.svg new file mode 100644 index 000000000..693976338 --- /dev/null +++ b/packages/assets/icons/calendar-arrow-down.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/packages/assets/icons/clock-arrow-down.svg b/packages/assets/icons/clock-arrow-down.svg new file mode 100644 index 000000000..0485d4b2a --- /dev/null +++ b/packages/assets/icons/clock-arrow-down.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/assets/icons/clock-arrow-up.svg b/packages/assets/icons/clock-arrow-up.svg new file mode 100644 index 000000000..67254e2d4 --- /dev/null +++ b/packages/assets/icons/clock-arrow-up.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/assets/icons/trash-exclamation.svg b/packages/assets/icons/trash-exclamation.svg new file mode 100644 index 000000000..90e4f1098 --- /dev/null +++ b/packages/assets/icons/trash-exclamation.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/packages/assets/styles/variables.scss b/packages/assets/styles/variables.scss index 710672211..112cace44 100644 --- a/packages/assets/styles/variables.scss +++ b/packages/assets/styles/variables.scss @@ -2,6 +2,7 @@ --surface-1: #ebebeb; --surface-1-5: #ededed; --surface-2: #f5f5f5; + --surface-2-5: #eef1f5; --surface-3: #f8f8f8; --surface-4: #ffffff; --surface-5: #dddddd; @@ -226,6 +227,7 @@ html { --surface-1: #16181c; --surface-1-5: #1a1c20; --surface-2: #1d1f23; + --surface-2-5: #222429; --surface-3: #27292e; --surface-4: #34363c; --surface-5: #42444a; @@ -398,6 +400,7 @@ html { --surface-1: #000000; --surface-1-5: #050506; --surface-2: #09090a; + --surface-2-5: #0c0d11; --surface-3: #101013; --surface-4: #1b1b20; --surface-5: #25262b; @@ -419,5 +422,6 @@ html { } .retro-mode { + --surface-2-5: #3a3c3e; --brand-gradient-strong-bg: #3a3b38; } diff --git a/packages/tooling-config/tailwind/tailwind-preset.ts b/packages/tooling-config/tailwind/tailwind-preset.ts index 1d6abbe9a..91c3d520b 100644 --- a/packages/tooling-config/tailwind/tailwind-preset.ts +++ b/packages/tooling-config/tailwind/tailwind-preset.ts @@ -9,6 +9,7 @@ const config: Config = { 1: 'var(--surface-1)', 1.5: 'var(--surface-1-5)', 2: 'var(--surface-2)', + 2.5: 'var(--surface-2-5)', 3: 'var(--surface-3)', 4: 'var(--surface-4)', 5: 'var(--surface-5)', diff --git a/packages/ui/src/components/base/Toggle.vue b/packages/ui/src/components/base/Toggle.vue index fd49647ae..595fabd04 100644 --- a/packages/ui/src/components/base/Toggle.vue +++ b/packages/ui/src/components/base/Toggle.vue @@ -7,7 +7,7 @@ :disabled="disabled" class="relative inline-flex shrink-0 rounded-full m-0 transition-all duration-200 cursor-pointer border-none" :class="[ - small ? 'h-5 !w-[38px]' : 'h-8 !w-[52px]', + small ? 'h-5 !w-[40px]' : 'h-8 !w-[60px]', modelValue ? 'bg-brand' : 'bg-button-bg', disabled ? 'opacity-50 cursor-not-allowed' : 'btn-wrapper', ]" @@ -16,11 +16,11 @@ diff --git a/packages/ui/src/components/flows/creation-flow-modal/components/CustomSetupStage.vue b/packages/ui/src/components/flows/creation-flow-modal/components/CustomSetupStage.vue index 2013851f2..9c256c687 100644 --- a/packages/ui/src/components/flows/creation-flow-modal/components/CustomSetupStage.vue +++ b/packages/ui/src/components/flows/creation-flow-modal/components/CustomSetupStage.vue @@ -22,7 +22,10 @@
Name - +
diff --git a/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts b/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts index e93735538..026649997 100644 --- a/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts +++ b/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts @@ -3,6 +3,7 @@ import { computed, type ComputedRef, type Ref, ref, type ShallowRef, watch } fro import type { ComponentExposed } from 'vue-component-type-helpers' import { useDebugLogger } from '#ui/composables/debug-logger' +import { formatLoaderLabel } from '#ui/utils/loaders' import { createContext } from '../../../providers' import type { ImportableLauncher } from '../../../providers/instance-import' @@ -77,6 +78,7 @@ export interface CreationFlowContextValue { // Instance-specific state instanceName: Ref + autoInstanceName: ComputedRef instanceIcon: Ref instanceIconUrl: Ref instanceIconPath: Ref @@ -121,7 +123,7 @@ export interface CreationFlowContextValue { onBack: (() => void) | null // Methods - reset: (instanceCount?: number) => void + reset: (instanceCount?: number) => Promise setSetupType: (type: SetupType) => void setImportMode: () => void browseModpacks: () => void @@ -138,7 +140,6 @@ export const [injectCreationFlowContext, provideCreationFlowContext] = // TODO: replace with actual world count from the world list once available let worldCounter = 0 -let instanceCounter = 0 export interface CreationFlowOptions { availableLoaders?: string[] @@ -147,6 +148,7 @@ export interface CreationFlowOptions { isInitialSetup?: boolean initialLoader?: string initialGameVersion?: string + fetchExistingInstanceNames?: () => Promise onBack?: () => void searchModpacks?: (query: string, limit?: number) => Promise getProjectVersions?: (projectId: string) => Promise<{ id: string }[]> @@ -183,6 +185,8 @@ export function createCreationFlowContext( // Instance-specific state const instanceName = ref('') + const existingInstanceNames = ref([]) + const fetchExistingInstanceNames = options.fetchExistingInstanceNames ?? null const instanceIcon = ref(null) const instanceIconUrl = ref(null) const instanceIconPath = ref(null) @@ -200,6 +204,24 @@ export function createCreationFlowContext( const selectedLoaderVersion = ref(null) const showSnapshots = ref(false) + const autoInstanceName = computed(() => { + const loader = selectedLoader.value + const version = selectedGameVersion.value + if (!version) return '' + + const loaderName = loader ? formatLoaderLabel(loader) : 'Vanilla' + const baseName = `${loaderName} ${version}` + + const names = new Set(existingInstanceNames.value) + if (!names.has(baseName)) return baseName + + let counter = 1 + while (names.has(`${baseName} (${counter})`)) { + counter++ + } + return `${baseName} (${counter})` + }) + const modpackSelection = ref(null) const modpackFile = ref(null) const modpackFilePath = ref(null) @@ -227,15 +249,14 @@ export function createCreationFlowContext( () => setupType.value === 'vanilla' || selectedLoader.value === 'vanilla', ) - function reset(instanceCount?: number) { + async function reset() { + if (fetchExistingInstanceNames) { + existingInstanceNames.value = await fetchExistingInstanceNames() + } setupType.value = null isImportMode.value = false worldCounter++ worldName.value = flowType === 'world' ? `World ${worldCounter}` : '' - if (instanceCount != null) { - instanceCounter = instanceCount - } - instanceCounter++ gamemode.value = 'survival' difficulty.value = 'normal' worldSeed.value = '' @@ -245,7 +266,7 @@ export function createCreationFlowContext( generatorSettingsCustom.value = '' // Instance-specific - instanceName.value = flowType === 'instance' ? `New instance (${instanceCounter})` : '' + instanceName.value = '' instanceIconUrl.value = null instanceIcon.value = null instanceIconPath.value = null @@ -356,6 +377,7 @@ export function createCreationFlowContext( generatorSettingsMode, generatorSettingsCustom, instanceName, + autoInstanceName, instanceIcon, instanceIconUrl, instanceIconPath, diff --git a/packages/ui/src/components/flows/creation-flow-modal/index.vue b/packages/ui/src/components/flows/creation-flow-modal/index.vue index ef9f583ee..893a8ab79 100644 --- a/packages/ui/src/components/flows/creation-flow-modal/index.vue +++ b/packages/ui/src/components/flows/creation-flow-modal/index.vue @@ -31,6 +31,7 @@ const props = withDefaults( isInitialSetup?: boolean initialLoader?: string initialGameVersion?: string + fetchExistingInstanceNames?: () => Promise onBack?: (() => void) | null fade?: 'standard' | 'warning' | 'danger' searchModpacks?: (query: string, limit?: number) => Promise @@ -44,6 +45,7 @@ const props = withDefaults( isInitialSetup: false, initialLoader: undefined, initialGameVersion: undefined, + fetchExistingInstanceNames: undefined, onBack: null, }, ) @@ -69,6 +71,7 @@ const ctx = createCreationFlowContext( isInitialSetup: props.isInitialSetup, initialLoader: props.initialLoader, initialGameVersion: props.initialGameVersion, + fetchExistingInstanceNames: props.fetchExistingInstanceNames, onBack: props.onBack ?? undefined, searchModpacks: props.searchModpacks, getProjectVersions: props.getProjectVersions, @@ -76,8 +79,8 @@ const ctx = createCreationFlowContext( ) provideCreationFlowContext(ctx) -function show(instanceCount?: number) { - ctx.reset(instanceCount) +async function show() { + await ctx.reset() modal.value?.setStage(0) modal.value?.show() } diff --git a/packages/ui/src/components/flows/creation-flow-modal/stages/custom-setup-stage.ts b/packages/ui/src/components/flows/creation-flow-modal/stages/custom-setup-stage.ts index baa86af7e..701b018a7 100644 --- a/packages/ui/src/components/flows/creation-flow-modal/stages/custom-setup-stage.ts +++ b/packages/ui/src/components/flows/creation-flow-modal/stages/custom-setup-stage.ts @@ -6,7 +6,6 @@ import CustomSetupStage from '../components/CustomSetupStage.vue' import { type CreationFlowContextValue, flowTypeHeadings } from '../creation-flow-context' function isForwardBlocked(ctx: CreationFlowContextValue): boolean { - if (ctx.flowType === 'instance' && !ctx.instanceName.value?.trim()) return true if (!ctx.selectedGameVersion.value) return true if (!ctx.hideLoaderChips.value && !ctx.selectedLoader.value) return true if ( diff --git a/packages/ui/src/layouts/shared/content-tab/components/ContentCardItem.vue b/packages/ui/src/layouts/shared/content-tab/components/ContentCardItem.vue index 5e1e94f2f..9b74b2f3e 100644 --- a/packages/ui/src/layouts/shared/content-tab/components/ContentCardItem.vue +++ b/packages/ui/src/layouts/shared/content-tab/components/ContentCardItem.vue @@ -4,9 +4,11 @@ import { MoreVerticalIcon, OrganizationIcon, SpinnerIcon, + TrashExclamationIcon, TrashIcon, TriangleAlertIcon, } from '@modrinth/assets' +import { useMagicKeys } from '@vueuse/core' import { Tooltip } from 'floating-vue' import { computed, getCurrentInstance, ref } from 'vue' import type { RouteLocationRaw } from 'vue-router' @@ -64,7 +66,7 @@ const selected = defineModel('selected') const emit = defineEmits<{ 'update:enabled': [value: boolean] - delete: [] + delete: [event: MouseEvent] update: [] }>() @@ -74,6 +76,9 @@ const hasUpdateListener = computed(() => typeof instance?.vnode.props?.onUpdate const versionNumberRef = ref(null) const fileNameRef = ref(null) + +const { shift: shiftHeld } = useMagicKeys() +const deleteHovered = ref(false)