From ef32d86a0e259a1b1fcfed6d77f168340a2c03fa Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Sat, 30 May 2026 16:16:11 +0100 Subject: [PATCH] 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" >