Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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)
Expand Down
60 changes: 23 additions & 37 deletions apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -17,25 +17,42 @@ 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([])

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)
Expand Down Expand Up @@ -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(() => {
Expand Down
60 changes: 60 additions & 0 deletions apps/app-frontend/src/composables/use-instance-metadata-refresh.ts
Original file line number Diff line number Diff line change
@@ -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)
}
36 changes: 8 additions & 28 deletions apps/app-frontend/src/pages/Index.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
<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'

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>>()
Expand Down Expand Up @@ -50,36 +48,18 @@ 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
.slice()
.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) ||
Expand Down
11 changes: 2 additions & 9 deletions apps/app-frontend/src/pages/instance/layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -867,15 +867,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) => {
Expand Down
1 change: 0 additions & 1 deletion apps/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
4 changes: 0 additions & 4 deletions apps/app/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 0 additions & 18 deletions apps/app/src/error.rs

This file was deleted.

1 change: 0 additions & 1 deletion apps/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use tauri_plugin_fs::FsExt;
use theseus::prelude::*;

mod api;
mod error;

#[cfg(target_os = "macos")]
mod macos;
Expand Down
4 changes: 3 additions & 1 deletion packages/app-lib/src/state/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?)
}
Expand Down
Loading