diff --git a/apps/app-frontend/src/pages/Browse.vue b/apps/app-frontend/src/pages/Browse.vue index e256d5b02..8ce11f96b 100644 --- a/apps/app-frontend/src/pages/Browse.vue +++ b/apps/app-frontend/src/pages/Browse.vue @@ -29,7 +29,7 @@ import { import { useQueryClient } from '@tanstack/vue-query' import { convertFileSrc } from '@tauri-apps/api/core' import type { Ref } from 'vue' -import { computed, ref, watch } from 'vue' +import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import type { LocationQuery } from 'vue-router' import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router' @@ -41,6 +41,7 @@ import { get_search_results_v3, get_version_many, } from '@/helpers/cache.js' +import { profile_listener } from '@/helpers/events.js' import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata' import { get as getInstance, @@ -181,6 +182,28 @@ watchServerContextChanges() await initInstanceContext() +async function refreshInstalledProjectIds() { + if (!route.query.i) return + + if (route.query.from === 'worlds') { + const worlds = await get_profile_worlds(route.query.i as string).catch(handleError) + if (!worlds) return + + const serverProjectIds = worlds + .filter((w) => w.type === 'server' && 'project_id' in w && w.project_id) + .map((w) => (w as { project_id: string }).project_id) + debugLog('installedServerProjectIds loaded', { count: serverProjectIds.length }) + installedProjectIds.value = serverProjectIds + return + } + + const ids = await getInstalledProjectIds(route.query.i as string).catch(handleError) + if (!ids) return + + debugLog('installedProjectIds loaded', { count: ids.length }) + installedProjectIds.value = ids +} + async function initInstanceContext() { debugLog('initInstanceContext', { queryI: route.query.i, @@ -199,24 +222,7 @@ async function initInstanceContext() { gameVersion: instance.value?.game_version, }) - if (route.query.from === 'worlds') { - get_profile_worlds(route.query.i as string) - .then((worlds) => { - const serverProjectIds = worlds - .filter((w) => w.type === 'server' && 'project_id' in w && w.project_id) - .map((w) => (w as { project_id: string }).project_id) - debugLog('installedServerProjectIds loaded', { count: serverProjectIds.length }) - installedProjectIds.value = serverProjectIds - }) - .catch(handleError) - } else { - getInstalledProjectIds(route.query.i as string) - .then((ids) => { - debugLog('installedProjectIds loaded', { count: ids?.length }) - installedProjectIds.value = ids - }) - .catch(handleError) - } + await refreshInstalledProjectIds() if (instance.value?.linked_data?.project_id) { debugLog('checking linked project for server status', instance.value.linked_data.project_id) @@ -805,10 +811,10 @@ function getCardActions( selectedInstall.versionId, instance.value ? instance.value.path : null, 'SearchCard', - (versionId) => { + (versionId, installedProjectIds) => { setProjectInstalling(projectResult.project_id, false) if (versionId) { - onSearchResultInstalled(projectResult.project_id) + onSearchResultsInstalled(installedProjectIds ?? [projectResult.project_id]) } }, (profile) => { @@ -834,7 +840,19 @@ function onSearchResultInstalled(id: string) { markServerProjectInstalled(id) return } - newlyInstalled.value.push(id) + if (!newlyInstalled.value.includes(id)) { + newlyInstalled.value = [...newlyInstalled.value, id] + } +} + +function onSearchResultsInstalled(ids: string[]) { + if (isServerContext.value) { + for (const id of ids) { + markServerProjectInstalled(id) + } + return + } + newlyInstalled.value = Array.from(new Set([...newlyInstalled.value, ...ids])) } async function search(requestParams: string) { @@ -966,6 +984,38 @@ if (instance.value?.game_version) { await searchState.refreshSearch() +type UnlistenFn = () => void + +let isUnmounted = false +let unlistenProfiles: UnlistenFn | null = null + +onMounted(() => { + profile_listener(async (event: { event: string; profile_path_id: string }) => { + if ( + instance.value && + event.profile_path_id === instance.value.path && + event.event === 'synced' + ) { + await refreshInstalledProjectIds() + await searchState.refreshSearch() + } + }) + .then((unlisten) => { + if (isUnmounted) { + unlisten() + return + } + + unlistenProfiles = unlisten + }) + .catch(handleError) +}) + +onUnmounted(() => { + isUnmounted = true + unlistenProfiles?.() +}) + function getProjectBrowseQuery() { if (!installContext.value) return undefined return { diff --git a/apps/app-frontend/src/pages/project/Index.vue b/apps/app-frontend/src/pages/project/Index.vue index 035d12899..7d5b24dd3 100644 --- a/apps/app-frontend/src/pages/project/Index.vue +++ b/apps/app-frontend/src/pages/project/Index.vue @@ -406,6 +406,20 @@ function buildProjectHref(path, extraQuery = {}) { return qs ? `${path}?${qs}` : path } +function buildBrowseHref(path) { + const params = new URLSearchParams() + for (const [key, val] of Object.entries(route.query)) { + if (key === 'b') continue + if (Array.isArray(val)) { + for (const v of val) params.append(key, v) + } else if (val) { + params.append(key, String(val)) + } + } + const qs = params.toString() + return qs ? `${path}?${qs}` : path +} + const projectDescriptionHref = computed(() => buildProjectHref(`/project/${route.params.id}`)) const versionsHref = computed(() => buildProjectHref(`/project/${route.params.id}/versions`, instanceFilters.value), @@ -416,7 +430,7 @@ const projectBrowseBackUrl = computed(() => { const browsePath = route.query.b if (typeof browsePath === 'string' && browsePath.startsWith('/browse/')) return browsePath const type = data.value?.project_type ? `${data.value.project_type}` : 'mod' - return `/browse/${type}` + return buildBrowseHref(`/browse/${type}`) }) const projectInstallContext = computed(() => { @@ -725,10 +739,11 @@ async function install(version) { version, instance.value ? instance.value.path : null, 'ProjectPage', - (version) => { + (version, installedProjectIds) => { installing.value = false - if (instance.value && version) { + const installedIds = installedProjectIds ?? [data.value.id] + if (instance.value && version && installedIds.includes(data.value.id)) { installed.value = true installedVersion.value = version } diff --git a/apps/app-frontend/src/pages/project/Version.vue b/apps/app-frontend/src/pages/project/Version.vue index 655e88cb0..8a37f765e 100644 --- a/apps/app-frontend/src/pages/project/Version.vue +++ b/apps/app-frontend/src/pages/project/Version.vue @@ -5,7 +5,7 @@ :current-title="version.name" :link-stack="[ { - href: `/project/${route.params.id}/versions`, + href: buildProjectHref(`/project/${route.params.id}/versions`), label: 'Versions', }, ]" @@ -249,6 +249,19 @@ const author = computed(() => const displayDependencies = ref({}) +function buildProjectHref(path) { + const params = new URLSearchParams() + for (const [key, val] of Object.entries(route.query)) { + if (Array.isArray(val)) { + for (const v of val) params.append(key, v) + } else if (val) { + params.append(key, String(val)) + } + } + const qs = params.toString() + return qs ? `${path}?${qs}` : path +} + async function refreshDisplayDependencies() { const projectIds = new Set() const versionIds = new Set() @@ -282,7 +295,7 @@ async function refreshDisplayDependencies() { icon: project?.icon_url, title: project?.title || project?.name, subtitle: `Version ${version.version_number} is ${dependency.dependency_type}`, - link: `/project/${project.slug}/version/${version.id}`, + link: buildProjectHref(`/project/${project.slug}/version/${version.id}`), } } else { const project = dependencies.projects.find((obj) => obj.id === dependency.project_id) @@ -292,7 +305,7 @@ async function refreshDisplayDependencies() { icon: project?.icon_url, title: project?.title || project?.name, subtitle: `${dependency.dependency_type}`, - link: `/project/${project.slug}`, + link: buildProjectHref(`/project/${project.slug}`), } } else { return { diff --git a/apps/app-frontend/src/pages/project/Versions.vue b/apps/app-frontend/src/pages/project/Versions.vue index 0b11e6895..f7aa3d0e1 100644 --- a/apps/app-frontend/src/pages/project/Versions.vue +++ b/apps/app-frontend/src/pages/project/Versions.vue @@ -5,7 +5,7 @@ :game-versions="gameVersions" :versions="versions" :project="project" - :version-link="(version) => `/project/${project.id}/version/${version.id}`" + :version-link="(version) => buildProjectHref(`/project/${project.id}/version/${version.id}`)" >