From ef32d86a0e259a1b1fcfed6d77f168340a2c03fa Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Sat, 30 May 2026 16:16:11 +0100 Subject: [PATCH 1/3] feat: incompat modal improvement --- apps/app-frontend/src/App.vue | 6 + .../IncompatibilityWarningModal.vue | 539 ++++++++++++++---- .../src/providers/content-install.ts | 30 +- .../ui/src/components/base/StyledInput.vue | 3 - .../components/search/SearchSidebarFilter.vue | 2 +- .../src/layouts/shared/browse-tab/sidebar.vue | 7 + .../components/modals/ContentInstallModal.vue | 30 +- 7 files changed, 477 insertions(+), 140 deletions(-) diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 5036efc8a9..de2c8d9615 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -620,6 +620,12 @@ const updateToPlayModal = ref() const modrinthLoginFlowWaitModal = ref() +watch(incompatibilityWarningModal, (modal) => { + if (modal) { + setContentIncompatibilityWarningModal(modal) + } +}) + setupAuthProvider(credentials, async (_redirectPath) => { await signIn() }) diff --git a/apps/app-frontend/src/components/ui/install_flow/IncompatibilityWarningModal.vue b/apps/app-frontend/src/components/ui/install_flow/IncompatibilityWarningModal.vue index cfcd483290..a92aa0c4b0 100644 --- a/apps/app-frontend/src/components/ui/install_flow/IncompatibilityWarningModal.vue +++ b/apps/app-frontend/src/components/ui/install_flow/IncompatibilityWarningModal.vue @@ -1,185 +1,482 @@ - - +const messages = defineMessages({ + header: { + id: 'app.install.incompatibility-warning.header', + defaultMessage: 'Incompatibility warning', + }, + conflictSummary: { + id: 'app.install.incompatibility-warning.conflict-summary', + defaultMessage: + 'No available versions match {instance}. Select a version to install anyway. Dependencies will not be installed automatically.', + }, + searchPlaceholder: { + id: 'app.install.incompatibility-warning.search-placeholder', + defaultMessage: 'Search by version or game version...', + }, + downloadsLabel: { + id: 'app.install.incompatibility-warning.downloads', + defaultMessage: '{downloads} downloads', + }, + moreGameVersions: { + id: 'app.install.incompatibility-warning.more-game-versions', + defaultMessage: '+{count} more', + }, + moreGameVersionsTooltip: { + id: 'app.install.incompatibility-warning.more-game-versions-tooltip', + defaultMessage: 'More game versions: {versions}', + }, + noVersions: { + id: 'app.install.incompatibility-warning.no-versions', + defaultMessage: 'No versions found', + }, + installAnywayButton: { + id: 'app.install.incompatibility-warning.install-anyway', + defaultMessage: 'Install anyway', + }, +}) + diff --git a/apps/app-frontend/src/providers/content-install.ts b/apps/app-frontend/src/providers/content-install.ts index ac36c29d9f..ada6260219 100644 --- a/apps/app-frontend/src/providers/content-install.ts +++ b/apps/app-frontend/src/providers/content-install.ts @@ -49,7 +49,7 @@ interface IncompatibilityWarningModalRef { versions: Labrinth.Versions.v2.Version[], version: Labrinth.Versions.v2.Version, callback: (versionId?: string) => void, - ) => void + ) => void | Promise } const LOADER_ORDER = ['vanilla', 'fabric', 'quilt', 'neoforge', 'forge'] @@ -410,15 +410,35 @@ export function createContentInstall(opts: { async function handleInstallToInstance(instance: ContentInstallInstance) { const profile = profileMap[instance.id] const storeInstance = instances.value.find((i) => i.id === instance.id) - if (storeInstance) storeInstance.installing = true + if (!currentProject || !profile) { + opts.handleError('No project or instance found') + return + } const version = findPreferredVersion(currentVersions, currentProject, profile) if (!version) { - if (storeInstance) storeInstance.installing = false - opts.handleError('No compatible version found') + if (currentVersions.length > 0 && incompatibilityWarningModalRef) { + const onIncompatibleInstall = (versionId?: string) => { + if (versionId && storeInstance) { + storeInstance.installed = true + } + currentCallback(versionId) + } + await incompatibilityWarningModalRef.show( + profile, + currentProject, + currentVersions, + currentVersions[0], + onIncompatibleInstall, + ) + } else { + opts.handleError('No version found') + } return } + if (storeInstance) storeInstance.installing = true + const installedProjectIds: string[] = [] if (currentProject) { addInstallingItem(instance.id, currentProject, version) @@ -614,7 +634,7 @@ export function createContentInstall(opts: { removeInstallingItems(instancePath, installedProjectIds) } } else { - incompatibilityWarningModalRef?.show(instance, project, projectVersions, version, callback) + await incompatibilityWarningModalRef?.show(instance, project, projectVersions, version, callback) } } else { let versions = ( diff --git a/packages/ui/src/components/base/StyledInput.vue b/packages/ui/src/components/base/StyledInput.vue index 2c0e2f00a5..bb3503fcb5 100644 --- a/packages/ui/src/components/base/StyledInput.vue +++ b/packages/ui/src/components/base/StyledInput.vue @@ -71,9 +71,6 @@ variant === 'outlined' ? 'bg-transparent border border-solid border-button-bg rounded-l-xl border-r-0' : 'bg-surface-4 border-none rounded-xl', - { - 'placeholder:text-sm': type === 'search', - }, ]" @input="onInput" @focus="isFocused = true" diff --git a/packages/ui/src/components/search/SearchSidebarFilter.vue b/packages/ui/src/components/search/SearchSidebarFilter.vue index 2100d61643..4e644365cc 100644 --- a/packages/ui/src/components/search/SearchSidebarFilter.vue +++ b/packages/ui/src/components/search/SearchSidebarFilter.vue @@ -5,7 +5,7 @@ :button-class="buttonClass ?? 'flex flex-col gap-2 justify-start items-start'" :content-class="contentClass" title-wrapper-class="flex flex-col gap-2 justify-start items-start" - :open-by-default="!locked && (openByDefault !== undefined ? openByDefault : true)" + :open-by-default="openByDefault !== undefined ? openByDefault : true" > - - diff --git a/apps/app-frontend/src/providers/content-install.ts b/apps/app-frontend/src/providers/content-install.ts index ada6260219..4552c9268c 100644 --- a/apps/app-frontend/src/providers/content-install.ts +++ b/apps/app-frontend/src/providers/content-install.ts @@ -34,7 +34,7 @@ import { } from '@/store/install.js' interface ModalRef { - show: () => void + show: (initialVersionId?: string) => void hide: () => void } @@ -42,16 +42,6 @@ interface ModpackAlreadyInstalledModalRef { show: (instanceName: string, instancePath: string) => void } -interface IncompatibilityWarningModalRef { - show: ( - instance: GameInstance, - project: Labrinth.Projects.v2.Project, - versions: Labrinth.Versions.v2.Version[], - version: Labrinth.Versions.v2.Version, - callback: (versionId?: string) => void, - ) => void | Promise -} - const LOADER_ORDER = ['vanilla', 'fabric', 'quilt', 'neoforge', 'forge'] const SUPPORTED_LOADERS: Set = new Set(['vanilla', 'forge', 'fabric', 'quilt', 'neoforge']) const VANILLA_COMPATIBLE_LOADERS: Set = new Set(['minecraft', 'datapack']) @@ -91,7 +81,17 @@ export interface ContentInstallContext { setModpackAlreadyInstalledModal: (ref: ModpackAlreadyInstalledModalRef) => void handleModpackDuplicateCreateAnyway: () => Promise handleModpackDuplicateGoToInstance: (instancePath: string) => void - setIncompatibilityWarningModal: (ref: IncompatibilityWarningModalRef) => void + setIncompatibilityWarningModal: (ref: ModalRef) => void + incompatibilityWarningVersions: Ref + incompatibilityWarningCurrentGameVersion: Ref + incompatibilityWarningCurrentLoader: Ref + incompatibilityWarningProjectType: Ref + incompatibilityWarningProjectIconUrl: Ref + incompatibilityWarningProjectName: Ref + incompatibilityWarningMessage: Ref + incompatibilityWarningInstalling: Ref + handleIncompatibilityWarningInstall: (version: Labrinth.Versions.v2.Version) => Promise + handleIncompatibilityWarningCancel: () => void install: ( projectId: string, versionId?: string | null, @@ -124,6 +124,14 @@ export function createContentInstall(opts: { const projectInfo = ref(null) const installingItems = ref>(new Map()) + const incompatibilityWarningVersions = ref([]) + const incompatibilityWarningCurrentGameVersion = ref('') + const incompatibilityWarningCurrentLoader = ref('') + const incompatibilityWarningProjectType = ref(undefined) + const incompatibilityWarningProjectIconUrl = ref(undefined) + const incompatibilityWarningProjectName = ref(undefined) + const incompatibilityWarningMessage = ref(undefined) + const incompatibilityWarningInstalling = ref(false) function addInstallingItem( instancePath: string, @@ -239,11 +247,15 @@ export function createContentInstall(opts: { let modalRef: ModalRef | null = null let modpackAlreadyInstalledModalRef: ModpackAlreadyInstalledModalRef | null = null - let incompatibilityWarningModalRef: IncompatibilityWarningModalRef | null = null + let incompatibilityWarningModalRef: ModalRef | null = null let currentProject: Labrinth.Projects.v2.Project | null = null let currentVersions: Labrinth.Versions.v2.Version[] = [] let currentCallback: (versionId?: string) => void = () => {} let profileMap: Record = {} + let incompatibilityWarningInstance: GameInstance | null = null + let incompatibilityWarningProject: Labrinth.Projects.v2.Project | null = null + let incompatibilityWarningCallback: (versionId?: string) => void = () => {} + let incompatibilityWarningInstalled = false let pendingModpackInstall: { project: Labrinth.Projects.v2.Project @@ -424,7 +436,7 @@ export function createContentInstall(opts: { } currentCallback(versionId) } - await incompatibilityWarningModalRef.show( + await showIncompatibilityWarning( profile, currentProject, currentVersions, @@ -478,6 +490,75 @@ export function createContentInstall(opts: { } } + async function showIncompatibilityWarning( + instance: GameInstance, + project: Labrinth.Projects.v2.Project, + versions: Labrinth.Versions.v2.Version[], + version: Labrinth.Versions.v2.Version, + callback: (versionId?: string) => void, + ) { + incompatibilityWarningInstance = instance + incompatibilityWarningProject = project + incompatibilityWarningCallback = callback + incompatibilityWarningInstalled = false + incompatibilityWarningInstalling.value = false + incompatibilityWarningVersions.value = versions + incompatibilityWarningCurrentGameVersion.value = instance.game_version ?? '' + incompatibilityWarningCurrentLoader.value = instance.loader ?? '' + incompatibilityWarningProjectType.value = project.project_type + incompatibilityWarningProjectIconUrl.value = project.icon_url ?? undefined + incompatibilityWarningProjectName.value = project.title + + const compatibilityLabel = + project.project_type === 'resourcepack' || project.project_type === 'datapack' + ? (instance.game_version ?? '') + : `${instance.loader ?? ''} ${instance.game_version ?? ''}`.trim() + incompatibilityWarningMessage.value = `No available versions match ${compatibilityLabel}. Select a version to install anyway. Dependencies will not be installed automatically.` + + await nextTick() + incompatibilityWarningModalRef?.show(version.id) + trackEvent('ProjectInstallStart', { source: 'ProjectIncompatibilityWarningModal' }) + } + + async function handleIncompatibilityWarningInstall(version: Labrinth.Versions.v2.Version) { + if (!incompatibilityWarningInstance || !incompatibilityWarningProject) return + + incompatibilityWarningInstalling.value = true + try { + await add_project_from_version( + incompatibilityWarningInstance.path, + version.id, + 'standalone', + ) + } catch (err) { + opts.handleError(err) + incompatibilityWarningInstalling.value = false + return + } + + incompatibilityWarningInstalling.value = false + incompatibilityWarningInstalled = true + incompatibilityWarningCallback(version.id) + incompatibilityWarningModalRef?.hide() + + trackEvent('ProjectInstall', { + loader: incompatibilityWarningInstance.loader, + game_version: incompatibilityWarningInstance.game_version, + id: incompatibilityWarningProject.id, + version_id: version.id, + project_type: incompatibilityWarningProject.project_type, + title: incompatibilityWarningProject.title, + source: 'ProjectIncompatibilityWarningModal', + }) + } + + function handleIncompatibilityWarningCancel() { + if (!incompatibilityWarningInstalled) { + incompatibilityWarningCallback() + } + incompatibilityWarningInstalled = false + } + async function handleCreateAndInstall(data: { name: string iconPath: string | null @@ -634,7 +715,7 @@ export function createContentInstall(opts: { removeInstallingItems(instancePath, installedProjectIds) } } else { - await incompatibilityWarningModalRef?.show(instance, project, projectVersions, version, callback) + await showIncompatibilityWarning(instance, project, projectVersions, version, callback) } } else { let versions = ( @@ -688,9 +769,19 @@ export function createContentInstall(opts: { pendingModpackInstall = null opts.router.push(`/instance/${encodeURIComponent(instancePath)}`) }, - setIncompatibilityWarningModal(ref: IncompatibilityWarningModalRef) { + setIncompatibilityWarningModal(ref: ModalRef) { incompatibilityWarningModalRef = ref }, + incompatibilityWarningVersions, + incompatibilityWarningCurrentGameVersion, + incompatibilityWarningCurrentLoader, + incompatibilityWarningProjectType, + incompatibilityWarningProjectIconUrl, + incompatibilityWarningProjectName, + incompatibilityWarningMessage, + incompatibilityWarningInstalling, + handleIncompatibilityWarningInstall, + handleIncompatibilityWarningCancel, install, installingItems, } diff --git a/packages/ui/src/layouts/shared/content-tab/components/modals/ConfirmBulkUpdateModal.vue b/packages/ui/src/layouts/shared/content-tab/components/modals/ConfirmBulkUpdateModal.vue index 33bb748cc2..faa6477ff2 100644 --- a/packages/ui/src/layouts/shared/content-tab/components/modals/ConfirmBulkUpdateModal.vue +++ b/packages/ui/src/layouts/shared/content-tab/components/modals/ConfirmBulkUpdateModal.vue @@ -8,11 +8,13 @@ >
- {{ formatMessage(messages.admonitionBody, { count }) }} + {{ formatMessage(messages.admonitionBody, { count: visibleCount }) }} @@ -29,7 +31,7 @@
@@ -76,7 +78,7 @@ const messages = defineMessages({ }, }) -defineProps<{ +const props = defineProps<{ count: number server?: boolean backupTip?: string @@ -89,8 +91,12 @@ const emit = defineEmits<{ const modal = ref>() const backupCreator = ref>() const buttonsDisabled = ref(false) +const visibleCount = ref(props.count) +const visibleBackupTip = ref(props.backupTip) function show() { + visibleCount.value = props.count + visibleBackupTip.value = props.backupTip modal.value?.show() } diff --git a/packages/ui/src/layouts/shared/content-tab/components/modals/ConfirmDeletionModal.vue b/packages/ui/src/layouts/shared/content-tab/components/modals/ConfirmDeletionModal.vue index 64f0b1ad06..b617a5ea4a 100644 --- a/packages/ui/src/layouts/shared/content-tab/components/modals/ConfirmDeletionModal.vue +++ b/packages/ui/src/layouts/shared/content-tab/components/modals/ConfirmDeletionModal.vue @@ -3,7 +3,7 @@ ref="modal" :header=" formatMessage(messages.header, { - itemType: formatContentTypeSentence(formatMessage, itemType, count), + itemType: formatContentTypeSentence(formatMessage, visibleItemType, visibleCount), }) " :fade="variant === 'server' ? 'warning' : 'danger'" @@ -37,8 +37,8 @@ {{ formatMessage(messages.deleteButton, { - count, - itemType: formatContentTypeSentence(formatMessage, itemType, count), + count: visibleCount, + itemType: formatContentTypeSentence(formatMessage, visibleItemType, visibleCount), }) }} @@ -82,7 +82,7 @@ const messages = defineMessages({ }, }) -withDefaults( +const props = withDefaults( defineProps<{ count: number itemType: string @@ -102,8 +102,12 @@ const emit = defineEmits<{ const modal = ref>() const backupCreator = ref>() const buttonsDisabled = ref(false) +const visibleCount = ref(props.count) +const visibleItemType = ref(props.itemType) function show() { + visibleCount.value = props.count + visibleItemType.value = props.itemType modal.value?.show() } diff --git a/packages/ui/src/layouts/shared/content-tab/components/modals/ContentInstallModal.vue b/packages/ui/src/layouts/shared/content-tab/components/modals/ContentInstallModal.vue index 9bd7eb65f7..c2d6d6be3c 100644 --- a/packages/ui/src/layouts/shared/content-tab/components/modals/ContentInstallModal.vue +++ b/packages/ui/src/layouts/shared/content-tab/components/modals/ContentInstallModal.vue @@ -304,7 +304,8 @@ const messages = defineMessages({ }, incompatibleTooltip: { id: 'instances.content-install.incompatible-tooltip', - defaultMessage: 'This instance is not compatible with this project', + defaultMessage: + 'This instance uses a different loader or game version than this project supports.', }, selectIcon: { id: 'instances.content-install.select-icon', @@ -462,7 +463,7 @@ function removeIcon() { function resetState() { tab.value = props.defaultTab ?? 'existing' searchFilter.value = '' - hideUninstallable.value = true + hideUninstallable.value = false instanceName.value = `New instance (${props.instances.length + 1})` iconPath.value = null iconPreviewUrl.value = null @@ -481,7 +482,6 @@ function resetState() { } function handleHide() { - resetState() emit('cancel') } diff --git a/packages/ui/src/layouts/shared/content-tab/components/modals/ContentUpdaterModal.vue b/packages/ui/src/layouts/shared/content-tab/components/modals/ContentUpdaterModal.vue index 5097a4630f..556736e7b3 100644 --- a/packages/ui/src/layouts/shared/content-tab/components/modals/ContentUpdaterModal.vue +++ b/packages/ui/src/layouts/shared/content-tab/components/modals/ContentUpdaterModal.vue @@ -3,19 +3,13 @@ ref="modal" :max-width="'min(928px, calc(95vw - 10rem))'" :width="'min(928px, calc(95vw - 10rem))'" + :on-hide="handleModalHide" no-padding >
@@ -204,7 +198,14 @@ > {{ - formatMessage(isApp ? messages.updateWarningApp : messages.updateWarningWeb) + warning ?? + formatMessage( + incompatibilityWarningMode + ? messages.incompatibilityWarning + : isApp + ? messages.updateWarningApp + : messages.updateWarningWeb, + ) }}
@@ -214,23 +215,32 @@ {{ formatMessage(commonMessages.cancelButton) }} - + @@ -267,7 +277,12 @@ import { TriangleAlertIcon, XIcon, } from '@modrinth/assets' -import { capitalizeString, renderHighlightedString } from '@modrinth/utils' +import { + capitalizeString, + formatVersionsForDisplay, + renderHighlightedString, + type GameVersionTag, +} from '@modrinth/utils' import { useTimeoutFn } from '@vueuse/core' import { computed, ref, watch } from 'vue' @@ -279,6 +294,7 @@ import NewModal from '#ui/components/modal/NewModal.vue' import VersionChannelIndicator from '#ui/components/version/VersionChannelIndicator.vue' import { useDebugLogger } from '#ui/composables/debug-logger' import { defineMessages, useVIntl } from '#ui/composables/i18n' +import { injectTags } from '#ui/providers' import { commonMessages } from '#ui/utils/common-messages' import { versionChangesGameVersion, @@ -287,12 +303,17 @@ import { const { formatMessage } = useVIntl() const debug = useDebugLogger('ContentUpdaterModal') +const tags = injectTags(null) const messages = defineMessages({ updateVersionHeader: { id: 'instances.updater-modal.header', defaultMessage: 'Update version', }, + incompatibilityWarningHeader: { + id: 'instances.updater-modal.incompatibility-warning-header', + defaultMessage: 'Choose version', + }, switchModpackVersionHeader: { id: 'instances.updater-modal.header-modpack', defaultMessage: 'Switch modpack version', @@ -330,6 +351,11 @@ const messages = defineMessages({ id: 'instances.updater-modal.warning-web', defaultMessage: 'Updating can break your world. Review version changelogs and back up first.', }, + incompatibilityWarning: { + id: 'instances.updater-modal.incompatibility-warning', + defaultMessage: + 'This version is not marked as compatible with this instance. Dependencies will not be installed automatically.', + }, downgradeToVersion: { id: 'instances.updater-modal.downgrade-to', defaultMessage: 'Downgrade to {version}', @@ -375,6 +401,10 @@ const messages = defineMessages({ id: 'instances.updater-modal.incompatible-update.proceed', defaultMessage: 'Update anyway', }, + installAnywayButton: { + id: 'instances.updater-modal.install-anyway', + defaultMessage: 'Install anyway', + }, }) const props = withDefaults( @@ -389,6 +419,9 @@ const props = withDefaults( projectIconUrl?: string projectName?: string header?: string + mode?: 'version' | 'incompatibility-warning' + warning?: string + actionLoading?: boolean /** Whether versions are currently being loaded */ loading?: boolean /** Whether changelog is being loaded for the selected version */ @@ -399,12 +432,29 @@ const props = withDefaults( projectIconUrl: undefined, projectName: undefined, header: undefined, + mode: 'version', + warning: undefined, + actionLoading: false, loading: false, loadingChangelog: false, }, ) const isModpack = computed(() => props.projectType === 'modpack') +const incompatibilityWarningMode = computed(() => props.mode === 'incompatibility-warning') +const defaultHeader = computed(() => { + if (incompatibilityWarningMode.value) { + return formatMessage(messages.incompatibilityWarningHeader) + } + + return formatMessage( + isModpack.value + ? messages.switchModpackVersionHeader + : switchMode.value + ? messages.switchVersionHeader + : messages.updateVersionHeader, + ) +}) const emit = defineEmits<{ update: [version: Labrinth.Versions.v2.Version, event: MouseEvent] @@ -424,6 +474,7 @@ const pendingIncompatibleUpdate = ref<{ version: Labrinth.Versions.v2.Version event: MouseEvent } | null>(null) +const suppressCancelOnHide = ref(false) // Store the initial version ID to select when versions become available const pendingInitialVersionId = ref(undefined) @@ -501,12 +552,18 @@ const filteredVersions = computed(() => { if (searchQuery.value) { const query = searchQuery.value.toLowerCase() versions = versions.filter( - (v) => v.name.toLowerCase().includes(query) || v.version_number.toLowerCase().includes(query), + (v) => + v.name.toLowerCase().includes(query) || + v.version_number.toLowerCase().includes(query) || + (incompatibilityWarningMode.value && + [...v.loaders, ...v.game_versions].some((value) => + value.toLowerCase().includes(query), + )), ) } const beforeFilterCount = versions.length - if (!isModpack.value && hideIncompatibleState.value) { + if (!incompatibilityWarningMode.value && !isModpack.value && hideIncompatibleState.value) { versions = versions.filter( (version) => version.id === props.currentVersionId || @@ -528,6 +585,7 @@ const filteredVersions = computed(() => { }) function shouldShowBadge(version: Labrinth.Versions.v2.Version): boolean { + if (incompatibilityWarningMode.value) return false return version.id === props.currentVersionId || shouldShowIncompatibleBadge(version) } @@ -587,8 +645,20 @@ function formatLongDate(dateString: string): string { function formatLoaderGameVersion(version: Labrinth.Versions.v2.Version): string { const loader = capitalizeString(version.loaders[0] || '') - const gameVersion = version.game_versions[0] || '' - return `${loader} ${gameVersion}` + const gameVersions = formatGameVersions(version) + return [loader, gameVersions].filter(Boolean).join(' ') +} + +function formatGameVersions(version: Labrinth.Versions.v2.Version): string { + if (!incompatibilityWarningMode.value) { + return version.game_versions[0] || '' + } + + const gameVersions = tags?.gameVersions.value?.length + ? formatVersionsForDisplay(version.game_versions, tags.gameVersions.value as GameVersionTag[]) + : version.game_versions + + return gameVersions.join(', ') } let prefetchTimeout: ReturnType | null = null @@ -615,6 +685,11 @@ function handleVersionSelect(version: Labrinth.Versions.v2.Version) { function handleUpdate(event: MouseEvent) { if (selectedVersion.value) { + if (incompatibilityWarningMode.value) { + emitUpdate(selectedVersion.value, event, { hide: false }) + return + } + const changesGameVersion = versionChangesGameVersion( selectedVersion.value, props.currentGameVersion, @@ -679,9 +754,18 @@ function handleCancel() { hide() } +function handleModalHide() { + if (suppressCancelOnHide.value) { + suppressCancelOnHide.value = false + return + } + + emit('cancel') +} + function show(initialVersionId?: string, options?: { switchMode?: boolean }) { searchQuery.value = '' - hideIncompatibleState.value = !isModpack.value + hideIncompatibleState.value = incompatibilityWarningMode.value ? false : !isModpack.value pendingIncompatibleUpdate.value = null switchMode.value = options?.switchMode ?? false @@ -724,6 +808,7 @@ function show(initialVersionId?: string, options?: { switchMode?: boolean }) { } function hide() { + suppressCancelOnHide.value = true modal.value?.hide() } From 9fce7acbc63e8d5963c43bd5e5e7f0d4a039e06a Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Mon, 1 Jun 2026 13:55:21 +0100 Subject: [PATCH 3/3] fix: lint --- apps/app-frontend/src/App.vue | 3 +-- apps/app-frontend/src/providers/content-install.ts | 6 +----- .../components/modals/ContentInstallModal.vue | 2 +- .../components/modals/ContentUpdaterModal.vue | 10 +++------- packages/ui/src/locales/en-US/index.json | 12 ++++++++++++ 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 34a5ae1419..a7c73f2e3f 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -600,8 +600,7 @@ const { handleModpackDuplicateGoToInstance: handleContentInstallModpackDuplicateGoToInstance, setIncompatibilityWarningModal: setContentIncompatibilityWarningModal, incompatibilityWarningVersions: contentInstallIncompatibilityWarningVersions, - incompatibilityWarningCurrentGameVersion: - contentInstallIncompatibilityWarningCurrentGameVersion, + incompatibilityWarningCurrentGameVersion: contentInstallIncompatibilityWarningCurrentGameVersion, incompatibilityWarningCurrentLoader: contentInstallIncompatibilityWarningCurrentLoader, incompatibilityWarningProjectType: contentInstallIncompatibilityWarningProjectType, incompatibilityWarningProjectIconUrl: contentInstallIncompatibilityWarningProjectIconUrl, diff --git a/apps/app-frontend/src/providers/content-install.ts b/apps/app-frontend/src/providers/content-install.ts index 4552c9268c..30067ec4ca 100644 --- a/apps/app-frontend/src/providers/content-install.ts +++ b/apps/app-frontend/src/providers/content-install.ts @@ -525,11 +525,7 @@ export function createContentInstall(opts: { incompatibilityWarningInstalling.value = true try { - await add_project_from_version( - incompatibilityWarningInstance.path, - version.id, - 'standalone', - ) + await add_project_from_version(incompatibilityWarningInstance.path, version.id, 'standalone') } catch (err) { opts.handleError(err) incompatibilityWarningInstalling.value = false diff --git a/packages/ui/src/layouts/shared/content-tab/components/modals/ContentInstallModal.vue b/packages/ui/src/layouts/shared/content-tab/components/modals/ContentInstallModal.vue index c2d6d6be3c..920280dba3 100644 --- a/packages/ui/src/layouts/shared/content-tab/components/modals/ContentInstallModal.vue +++ b/packages/ui/src/layouts/shared/content-tab/components/modals/ContentInstallModal.vue @@ -122,8 +122,8 @@ :color="inst.compatible ? 'standard' : 'orange'" >