diff --git a/Cargo.lock b/Cargo.lock index 459048aa57..4c94019842 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11172,7 +11172,6 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tracing", - "tracing-error", "url", "urlencoding", "uuid 1.23.3", diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 668c557a86..35770abfc9 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -105,6 +105,7 @@ import { useCheckDisableMouseover } from '@/composables/macCssFix.js' import { useAppEvent } from '@/composables/use-app-event' import { useAppSettings } from '@/composables/use-app-settings.ts' import { useError } from '@/composables/use-error.js' +import { useInstanceMetadataRefresh } from '@/composables/use-instance-metadata-refresh' import { isDarkTheme, useTheme } from '@/composables/use-theme.ts' import { config } from '@/config' import { getAccountAppearance, rememberAccountAppearance } from '@/helpers/account-appearance.ts' @@ -190,6 +191,7 @@ const appTheme = useTheme() const router = useRouter() const route = useRoute() const { channel: appEventChannel, events: appEvents } = setupAppEventsProvider() +useInstanceMetadataRefresh(appEvents) const breadcrumbManager = createBreadcrumbManager() provideBreadcrumbManager(breadcrumbManager) const canNavigateBack = ref(false) diff --git a/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue b/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue index d926b34069..99cc39a147 100644 --- a/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue +++ b/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue @@ -8,7 +8,7 @@ import { injectNotificationManager, useVIntl, } from '@modrinth/ui' -import { useQueryClient } from '@tanstack/vue-query' +import { useQuery } from '@tanstack/vue-query' import dayjs from 'dayjs' import { computed, onMounted, onUnmounted, ref } from 'vue' import { useRouter } from 'vue-router' @@ -17,17 +17,17 @@ import NavButton from '@/components/ui/NavButton.vue' import { useAppEvent } from '@/composables/use-app-event' import { handleSevereError } from '@/composables/use-error.js' import { trackEvent } from '@/helpers/analytics' -import { getInstanceIconUrl, kill, list, run } from '@/helpers/instance' +import { getInstanceIconUrl, kill, run } from '@/helpers/instance' import { get_all } from '@/helpers/process' import { showInstanceInFolder } from '@/helpers/utils' -import { instanceKeys } from '@/pages/instance/query-options' +import { instanceListQueryOptions } from '@/pages/instance/query-options' const ITEM_SIZE = 52 const APPROX_USED_VERTICAL_SPACE = 475 // doesn't need to be exact lol just close enough so there's a little gap and no overflow const STORAGE_KEY = 'modrinth-quick-instance-count' const { handleError } = injectNotificationManager() -const queryClient = useQueryClient() +const instancesQuery = useQuery(instanceListQueryOptions()) const router = useRouter() const instanceOptions = ref() const runningInstances = ref([]) @@ -35,7 +35,24 @@ const runningInstances = ref([]) const { formatMessage } = useVIntl() const maxAuto = ref(0) -const allInstances = ref([]) +const allInstances = computed(() => + (instancesQuery.data.value ?? []).slice().sort((a, b) => { + const dateACreated = dayjs(a.created) + const dateAPlayed = a.last_played ? dayjs(a.last_played) : dayjs(0) + + const dateBCreated = dayjs(b.created) + const dateBPlayed = b.last_played ? dayjs(b.last_played) : dayjs(0) + + const dateA = dateACreated.isAfter(dateAPlayed) ? dateACreated : dateAPlayed + const dateB = dateBCreated.isAfter(dateBPlayed) ? dateBCreated : dateBPlayed + + if (dateA.isSame(dateB)) { + return a.name.localeCompare(b.name) + } + + return dateB - dateA + }), +) const dragging = ref(false) const stored = localStorage.getItem(STORAGE_KEY) @@ -134,40 +151,9 @@ const onDividerPointerUp = (event) => { endDrag(event) } -const getInstances = async () => { - const instances = await list().catch(handleError) - - for (const instance of instances) { - queryClient.setQueryData(instanceKeys.detail(instance.id), instance) - } - - allInstances.value = instances.sort((a, b) => { - const dateACreated = dayjs(a.created) - const dateAPlayed = a.last_played ? dayjs(a.last_played) : dayjs(0) - - const dateBCreated = dayjs(b.created) - const dateBPlayed = b.last_played ? dayjs(b.last_played) : dayjs(0) - - const dateA = dateACreated.isAfter(dateAPlayed) ? dateACreated : dateAPlayed - const dateB = dateBCreated.isAfter(dateBPlayed) ? dateBCreated : dateBPlayed - - if (dateA.isSame(dateB)) { - return a.name.localeCompare(b.name) - } - - return dateB - dateA - }) -} - -await getInstances() +await instancesQuery.suspense().catch(handleError) updateMaxAuto() -useAppEvent('instance', async (event) => { - if (event.event !== 'synced') { - await getInstances() - } -}) - useAppEvent('process', checkProcesses) onMounted(() => { diff --git a/apps/app-frontend/src/composables/use-instance-metadata-refresh.ts b/apps/app-frontend/src/composables/use-instance-metadata-refresh.ts new file mode 100644 index 0000000000..34bbbd836b --- /dev/null +++ b/apps/app-frontend/src/composables/use-instance-metadata-refresh.ts @@ -0,0 +1,60 @@ +import { useQueryClient } from '@tanstack/vue-query' + +import type { InstancePayload } from '@/generated/app-events/InstancePayload' +import { instanceKeys, instanceListQueryOptions } from '@/pages/instance/query-options' +import type { AppEvents } from '@/providers/app-events' + +import { useAppEvent } from './use-app-event' + +const INSTANCE_METADATA_EVENTS = new Set([ + 'created', + 'synced', + 'edited', + 'removed', +]) + +export function useInstanceMetadataRefresh(events: AppEvents) { + const queryClient = useQueryClient() + let refreshQueued = false + let refreshPromise: Promise | undefined + + function queueRefresh() { + refreshQueued = true + if (!refreshPromise) { + refreshPromise = Promise.resolve().then(async () => { + try { + do { + refreshQueued = false + const joinedExistingRequest = + queryClient.isFetching({ queryKey: instanceKeys.list(), exact: true }) > 0 + const instances = await queryClient.fetchQuery({ + ...instanceListQueryOptions(), + staleTime: 0, + }) + + for (const instance of instances) { + queryClient.setQueryData(instanceKeys.detail(instance.id), instance) + } + + if (joinedExistingRequest) { + refreshQueued = true + } + } while (refreshQueued) + } finally { + refreshPromise = undefined + } + }) + } + + return refreshPromise + } + + useAppEvent( + 'instance', + (event) => { + if (INSTANCE_METADATA_EVENTS.has(event.event)) return queueRefresh() + }, + events, + ) + useAppEvent('instance_groups_changed', queueRefresh, events) +} diff --git a/apps/app-frontend/src/pages/Index.vue b/apps/app-frontend/src/pages/Index.vue index 62003cf6cb..e1200bd3c2 100644 --- a/apps/app-frontend/src/pages/Index.vue +++ b/apps/app-frontend/src/pages/Index.vue @@ -1,17 +1,15 @@