mirror of
https://github.com/modrinth/code.git
synced 2026-09-05 06:19:11 +00:00
Generated
-1
@@ -11172,7 +11172,6 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"uuid 1.23.3",
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -35,7 +35,6 @@ theseus = { workspace = true, features = ["tauri"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-error = { workspace = true }
|
||||
url = { workspace = true }
|
||||
urlencoding = { workspace = true }
|
||||
uuid = { workspace = true, features = ["serde", "v4"] }
|
||||
|
||||
@@ -83,11 +83,7 @@ macro_rules! impl_serialize {
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
// For the Theseus variant, we add a special display for the error,
|
||||
// to view the spans if subscribed to them (which is information that is lost when serializing)
|
||||
TheseusSerializableError::Theseus(theseus_error) => {
|
||||
$crate::error::display_tracing_error(theseus_error);
|
||||
|
||||
let unavailable_reason = match theseus_error.raw.as_ref() {
|
||||
theseus::ErrorKind::SharedInstanceUnavailable(reason) => Some(reason),
|
||||
_ => None,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
use tracing_error::ExtractSpanTrace;
|
||||
|
||||
pub fn display_tracing_error(err: &theseus::Error) {
|
||||
match get_span_trace(err) {
|
||||
Some(span_trace) => {
|
||||
tracing::error!(error = %err, span_trace = %span_trace);
|
||||
}
|
||||
None => {
|
||||
tracing::error!(error = %err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_span_trace<'a>(
|
||||
error: &'a (dyn std::error::Error + 'static),
|
||||
) -> Option<&'a tracing_error::SpanTrace> {
|
||||
error.source().and_then(|e| e.span_trace())
|
||||
}
|
||||
@@ -12,7 +12,6 @@ use tauri_plugin_fs::FsExt;
|
||||
use theseus::prelude::*;
|
||||
|
||||
mod api;
|
||||
mod error;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
@@ -56,7 +56,9 @@ async fn open_app_db_pool(db_path: &Path) -> crate::Result<Pool<Sqlite>> {
|
||||
.create_if_missing(true);
|
||||
|
||||
Ok(SqlitePoolOptions::new()
|
||||
.max_connections(100)
|
||||
.max_connections(10)
|
||||
.idle_timeout(None)
|
||||
.max_lifetime(None)
|
||||
.connect_with(conn_options)
|
||||
.await?)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user