mirror of
https://github.com/modrinth/code.git
synced 2026-09-05 06:19:11 +00:00
@@ -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'
|
||||
@@ -191,6 +192,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)
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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<InstancePayload['event']>([
|
||||
'created',
|
||||
'synced',
|
||||
'edited',
|
||||
'removed',
|
||||
])
|
||||
|
||||
export function useInstanceMetadataRefresh(events: AppEvents) {
|
||||
const queryClient = useQueryClient()
|
||||
let refreshQueued = false
|
||||
let refreshPromise: Promise<void> | 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)
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { PlayIcon, PlusIcon } from '@modrinth/assets'
|
||||
import { ContextMenu, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, inject, onActivated, ref } from 'vue'
|
||||
|
||||
import LibrarySection from '@/components/ui/library/index.vue'
|
||||
import WelcomeScreen from '@/components/ui/WelcomeScreen.vue'
|
||||
import RecentWorldsList from '@/components/ui/world/RecentWorldsList.vue'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { toError } from '@/helpers/errors'
|
||||
import { list } from '@/helpers/instance'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { instanceListQueryOptions } from '@/pages/instance/query-options'
|
||||
import { useRootBreadcrumb } from '@/providers/breadcrumbs'
|
||||
import { injectOnboardingChecklist } from '@/providers/onboarding-checklist'
|
||||
|
||||
@@ -19,8 +17,8 @@ defineOptions({
|
||||
name: 'LibraryPage',
|
||||
})
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { hasCreatedInstance, isReady } = injectOnboardingChecklist()
|
||||
const showCreationModal = inject<() => void>('showCreationModal')
|
||||
const pageOptions = ref<InstanceType<typeof ContextMenu>>()
|
||||
@@ -50,8 +48,11 @@ const homeBreadcrumb = useRootBreadcrumb({
|
||||
})
|
||||
onActivated(homeBreadcrumb.reset)
|
||||
|
||||
const instances = ref<GameInstance[]>([])
|
||||
let latestInstanceFetch = 0
|
||||
const instancesQuery = useQuery(instanceListQueryOptions())
|
||||
const instances = computed(() => instancesQuery.data.value ?? [])
|
||||
if (hasCreatedInstance.value) {
|
||||
await instancesQuery.suspense().catch(handleError)
|
||||
}
|
||||
|
||||
const recentInstances = computed(() =>
|
||||
instances.value
|
||||
@@ -59,27 +60,6 @@ const recentInstances = computed(() =>
|
||||
.sort((a, b) => dayjs(b.last_played ?? b.created).diff(dayjs(a.last_played ?? a.created))),
|
||||
)
|
||||
|
||||
async function fetchInstances() {
|
||||
const fetchId = ++latestInstanceFetch
|
||||
try {
|
||||
const nextInstances = await list()
|
||||
if (fetchId === latestInstanceFetch) {
|
||||
instances.value = nextInstances
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (fetchId === latestInstanceFetch) {
|
||||
handleError(toError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCreatedInstance.value) {
|
||||
await fetchInstances()
|
||||
}
|
||||
|
||||
useAppEvent('instance', fetchInstances)
|
||||
useAppEvent('instance_groups_changed', fetchInstances)
|
||||
|
||||
function openPageContextMenu(event: MouseEvent) {
|
||||
if (
|
||||
!(event.target instanceof HTMLElement) ||
|
||||
|
||||
@@ -872,15 +872,8 @@ watch(instanceId, (currentInstanceId, previousInstanceId) => {
|
||||
})
|
||||
|
||||
useAppEvent('instance', async (event) => {
|
||||
if (event.instance_id !== instanceId.value) return
|
||||
if (event.event === 'removed' || route.path === '/') {
|
||||
if (route.path !== '/') await router.push({ path: '/' })
|
||||
return
|
||||
}
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: instanceKeys.detail(event.instance_id),
|
||||
exact: true,
|
||||
})
|
||||
if (event.instance_id !== instanceId.value || event.event !== 'removed') return
|
||||
if (route.path !== '/') await router.push({ path: '/' })
|
||||
})
|
||||
|
||||
useAppEvent('process', (event) => {
|
||||
|
||||
Reference in New Issue
Block a user