diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 0aa4ab1cbb..91ff2a64ff 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -260,6 +260,9 @@ "app.browse.hide-added-servers": { "message": "Hide servers already added" }, + "app.browse.hide-installed-modpacks": { + "message": "Hide already installed" + }, "app.browse.project-type.modpacks": { "message": "Modpacks" }, diff --git a/apps/app-frontend/src/pages/Browse.vue b/apps/app-frontend/src/pages/Browse.vue index 592ab88b87..c062796416 100644 --- a/apps/app-frontend/src/pages/Browse.vue +++ b/apps/app-frontend/src/pages/Browse.vue @@ -50,6 +50,7 @@ import { instance_listener } from '@/helpers/events.js' import { get as getInstance, get_installed_project_ids as getInstalledProjectIds, + list as listInstances, } from '@/helpers/instance' import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata' import { get as getSettings, set as setSettings } from '@/helpers/settings.ts' @@ -171,7 +172,7 @@ const instance: Ref = ref( queryClient.getQueryData(['instances', 'summary', initialInstanceId]) ?? null, ) const installedProjectIds: Ref = ref(null) -const instanceHideInstalled = ref(false) +const instanceHideInstalled = ref(route.query.ai === 'true') const newlyInstalled = ref([]) const hiddenInstanceProjectIds = ref>(new Set()) const hiddenInstanceProjectIdsInitialized = ref(false) @@ -288,7 +289,17 @@ watchServerContextChanges() await initInstanceContext() async function refreshInstalledProjectIds() { - if (!route.query.i) return + if (!route.query.i) { + const instances = await listInstances().catch(handleError) + if (!instances) return + + const ids = instances + .map((gameInstance) => gameInstance.link?.project_id) + .filter((id): id is string => !!id) + debugLog('installedInstanceProjectIds loaded', { count: ids.length }) + installedProjectIds.value = ids + return + } if (route.query.from === 'worlds') { const worlds = await get_instance_worlds(route.query.i as string).catch(handleError) @@ -318,6 +329,7 @@ async function initInstanceContext() { queryFrom: route.query.from, }) await initServerContext() + await refreshInstalledProjectIds() if (route.query.i) { instance.value = (await getInstance(route.query.i as string).catch(handleError)) ?? null @@ -327,8 +339,6 @@ async function initInstanceContext() { gameVersion: instance.value?.game_version, }) - await refreshInstalledProjectIds() - if (instance.value?.link?.project_id) { debugLog('checking linked project for server status', instance.value.link.project_id) const projectV3 = await get_project_v3( @@ -341,13 +351,23 @@ async function initInstanceContext() { } } } +} - if (route.query.ai && !(route.params.projectType === 'modpack')) { - debugLog('setting instanceHideInstalled from query', route.query.ai) - instanceHideInstalled.value = route.query.ai === 'true' - } +function setBrowseHideInstalledFlag(flag: 'hide_installed_modpacks', value: boolean) { + themeStore.featureFlags[flag] = value + getSettings() + .then((settings) => { + settings.feature_flags[flag] = value + return setSettings(settings) + }) + .catch(handleError) } +const hideInstalledModpacks = computed({ + get: () => themeStore.getFeatureFlag('hide_installed_modpacks'), + set: (value: boolean) => setBrowseHideInstalledFlag('hide_installed_modpacks', value), +}) + const instanceFilters = computed(() => { const filters = [] @@ -367,11 +387,15 @@ const instanceFilters = computed(() => { if (isServerInstance.value) { filters.push({ type: 'environment', option: 'client' }) } + } - if (instanceHideInstalled.value && hiddenInstanceProjectIds.value.size > 0) { - for (const id of hiddenInstanceProjectIds.value) { - filters.push({ type: 'project_id', option: `project_id:${id}`, negative: true }) - } + if ( + (instance.value || projectType.value === 'modpack') && + (projectType.value === 'modpack' ? hideInstalledModpacks.value : instanceHideInstalled.value) && + hiddenInstanceProjectIds.value.size > 0 + ) { + for (const id of hiddenInstanceProjectIds.value) { + filters.push({ type: 'project_id', option: `project_id:${id}`, negative: true }) } } @@ -430,6 +454,12 @@ const serverContextFilters = computed(() => { { type: 'environment', option: 'client' }, { type: 'environment', option: 'server' }, ) + + if (hideInstalledModpacks.value && hiddenInstanceProjectIds.value.size > 0) { + for (const id of hiddenInstanceProjectIds.value) { + filters.push({ type: 'project_id', option: `project_id:${id}`, negative: true }) + } + } } if (serverHideInstalled.value && hiddenServerContentProjectIds.value.size > 0) { @@ -500,6 +530,10 @@ const messages = defineMessages({ id: 'app.browse.hide-added-servers', defaultMessage: 'Hide servers already added', }, + hideInstalledModpacks: { + id: 'app.browse.hide-installed-modpacks', + defaultMessage: 'Hide already installed', + }, installingToServer: { id: 'app.browse.server.installing', defaultMessage: 'Installing', @@ -553,6 +587,7 @@ function resetInstanceContext() { hiddenInstanceProjectIdsInitialized.value = false isServerInstance.value = false browseBreadcrumb.reset() + void refreshInstalledProjectIds() } watch( @@ -781,6 +816,7 @@ function getCardActions( serverContentProjectIds.value.has(projectResult.project_id || '') || serverContextServerData.value?.upstream?.project_id === projectResult.project_id const isInstalling = installingProjectIds.value.has(projectResult.project_id) + const showAsInstalled = isInstalled && currentProjectType !== 'modpack' if ( isServerContext.value && @@ -790,7 +826,7 @@ function getCardActions( const isInstallingSelection = isInstallingQueuedServerInstalls.value const validatingInstall = isInstalling && currentProjectType !== 'modpack' && !isInstallingSelection - const installLabel = isInstalled + const installLabel = showAsInstalled ? commonMessages.installedLabel : isQueued ? isInstalling || isInstallingSelection @@ -810,11 +846,11 @@ function getCardActions( icon: isInstalling || isInstallingSelection ? SpinnerIcon - : isQueued || isInstalled + : isQueued || showAsInstalled ? CheckIcon : PlusIcon, iconClass: isInstalling || isInstallingSelection ? 'animate-spin' : undefined, - disabled: isInstalled || isInstalling || isInstallingSelection, + disabled: showAsInstalled || isInstalling || isInstallingSelection, color: isQueued && !isInstalling && !isInstallingSelection ? 'green' : 'brand', type: 'outlined', onClick: async () => { @@ -875,15 +911,15 @@ function getCardActions( label: formatMessage( isInstalling ? messages.installingToServer - : isInstalled + : showAsInstalled ? commonMessages.installedLabel : shouldUseInstallIcon ? commonMessages.installButton : messages.addToAnInstance, ), - icon: isInstalling ? SpinnerIcon : isInstalled ? CheckIcon : PlusIcon, + icon: isInstalling ? SpinnerIcon : showAsInstalled ? CheckIcon : PlusIcon, iconClass: isInstalling ? 'animate-spin' : undefined, - disabled: isInstalled || isInstalling, + disabled: showAsInstalled || isInstalling, color: 'brand', type: 'outlined', onClick: async () => { @@ -994,10 +1030,11 @@ async function search(requestParams: string) { ...hit, } - if (instance.value || isServerContext.value) { - const installedIds = instance.value - ? new Set([...newlyInstalled.value, ...(installedProjectIds.value ?? [])]) - : serverContentProjectIds.value + if (instance.value || isServerContext.value || projectType.value === 'modpack') { + const installedIds = + isServerContext.value && projectType.value !== 'modpack' + ? serverContentProjectIds.value + : new Set([...newlyInstalled.value, ...(installedProjectIds.value ?? [])]) mapped.installed = installedIds.has(hit.project_id) } @@ -1057,9 +1094,9 @@ watch( () => projectType.value, ], () => { - if (isServerContext.value) { + if (isServerContext.value && projectType.value !== 'modpack') { syncHiddenServerContentProjectIds() - } else if (instance.value) { + } else if (instance.value || projectType.value === 'modpack') { syncHiddenInstanceProjectIds() } }, @@ -1091,6 +1128,18 @@ let unlistenInstances: UnlistenFn | null = null onMounted(() => { instance_listener(async (event: { event: string; instance_id: string }) => { + if (event.event === 'added' || event.event === 'created' || event.event === 'removed') { + if (!route.query.i) { + await refreshInstalledProjectIds() + if (projectType.value === 'modpack') { + if (event.event === 'removed') { + syncHiddenInstanceProjectIds() + } + await searchState.refreshSearch() + } + } + } + if (instance.value && event.instance_id === instance.value.id && event.event === 'synced') { await refreshInstalledProjectIds() await searchState.refreshSearch() @@ -1156,8 +1205,17 @@ provideBrowseManager({ installContext, providedFilters: combinedProvidedFilters, hideInstalled: computed({ - get: () => (isServerContext.value ? serverHideInstalled.value : instanceHideInstalled.value), + get: () => { + if (projectType.value === 'modpack') return hideInstalledModpacks.value + if (isServerContext.value) return serverHideInstalled.value + return instanceHideInstalled.value + }, set: (val: boolean) => { + if (projectType.value === 'modpack') { + hideInstalledModpacks.value = val + if (val) syncHiddenInstanceProjectIds() + return + } if (isServerContext.value) { serverHideInstalled.value = val if (val) syncHiddenServerContentProjectIds() @@ -1168,11 +1226,18 @@ provideBrowseManager({ }, }), showHideInstalled: computed( - () => (isServerContext.value && projectType.value !== 'modpack') || !!instance.value, + () => + projectType.value === 'modpack' || + (isServerContext.value && projectType.value !== 'modpack') || + !!instance.value, ), hideInstalledLabel: computed(() => formatMessage( - isFromWorlds.value ? messages.hideAddedServers : commonMessages.hideInstalledContentLabel, + isFromWorlds.value + ? messages.hideAddedServers + : projectType.value === 'modpack' + ? messages.hideInstalledModpacks + : commonMessages.hideInstalledContentLabel, ), ), hideSelected: hideSelectedServerInstalls, diff --git a/apps/app-frontend/src/store/theme.ts b/apps/app-frontend/src/store/theme.ts index 38bac76762..4b61ebff78 100644 --- a/apps/app-frontend/src/store/theme.ts +++ b/apps/app-frontend/src/store/theme.ts @@ -17,6 +17,7 @@ export const DEFAULT_FEATURE_FLAGS = { show_instance_play_time: true, advanced_filters_collapsed: true, always_show_copy_details: false, + hide_installed_modpacks: false, } export const THEME_OPTIONS = ['dark', 'light', 'oled', 'retro', 'system'] as const diff --git a/packages/app-lib/src/state/settings.rs b/packages/app-lib/src/state/settings.rs index e8769cfcfe..304cc43e4d 100644 --- a/packages/app-lib/src/state/settings.rs +++ b/packages/app-lib/src/state/settings.rs @@ -65,6 +65,7 @@ pub enum FeatureFlag { SkipNonEssentialWarnings, AdvancedFiltersCollapsed, AlwaysShowCopyDetails, + HideInstalledModpacks, } impl Settings {