diff --git a/apps/frontend/src/composables/servers/modrinth-servers.ts b/apps/frontend/src/composables/servers/modrinth-servers.ts index 2b02457a28..b7987ee3fa 100644 --- a/apps/frontend/src/composables/servers/modrinth-servers.ts +++ b/apps/frontend/src/composables/servers/modrinth-servers.ts @@ -2,7 +2,7 @@ import type { AbstractWebNotificationManager } from '@modrinth/ui' import type { JWTAuth, ModuleError, ModuleName } from '@modrinth/utils' import { ModrinthServerError } from '@modrinth/utils' -import { ContentModule, GeneralModule, NetworkModule, StartupModule } from './modules/index.ts' +import { GeneralModule, NetworkModule, StartupModule } from './modules/index.ts' import { useServersFetch } from './servers-fetch.ts' export function handleServersError(err: any, notifications: AbstractWebNotificationManager) { @@ -27,7 +27,6 @@ export class ModrinthServer { private errors: Partial> = {} readonly general: GeneralModule - readonly content: ContentModule readonly network: NetworkModule readonly startup: StartupModule @@ -35,7 +34,6 @@ export class ModrinthServer { this.serverId = serverId this.general = new GeneralModule(this) - this.content = new ContentModule(this) this.network = new NetworkModule(this) this.startup = new StartupModule(this) } @@ -209,7 +207,7 @@ export class ModrinthServer { }, ): Promise { const modulesToRefresh = - modules.length > 0 ? modules : (['general', 'content', 'network', 'startup'] as ModuleName[]) + modules.length > 0 ? modules : (['general', 'network', 'startup'] as ModuleName[]) for (const module of modulesToRefresh) { this.errors[module] = undefined @@ -238,9 +236,6 @@ export class ModrinthServer { } break } - case 'content': - await this.content.fetch() - break case 'network': await this.network.fetch() break @@ -250,11 +245,6 @@ export class ModrinthServer { } } catch (error) { if (error instanceof ModrinthServerError) { - if (error.statusCode === 404 && module === 'content') { - console.debug(`Optional ${module} resource not found:`, error.message) - continue - } - if (error.statusCode && error.statusCode >= 500) { console.debug(`Temporary ${module} unavailable:`, error.message) continue diff --git a/apps/frontend/src/composables/servers/modules/content.ts b/apps/frontend/src/composables/servers/modules/content.ts deleted file mode 100644 index 2db34b7691..0000000000 --- a/apps/frontend/src/composables/servers/modules/content.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { ContentType, Mod } from '@modrinth/utils' - -import { useServersFetch } from '../servers-fetch.ts' -import { ServerModule } from './base.ts' - -export class ContentModule extends ServerModule { - data: Mod[] = [] - - async fetch(): Promise { - const mods = await useServersFetch(`servers/${this.serverId}/mods`, {}, 'content') - this.data = mods.sort((a, b) => (a?.name ?? '').localeCompare(b?.name ?? '')) - } - - async install(contentType: ContentType, projectId: string, versionId: string): Promise { - await useServersFetch(`servers/${this.serverId}/mods`, { - method: 'POST', - body: { - rinth_ids: { project_id: projectId, version_id: versionId }, - install_as: contentType, - }, - }) - } - - async remove(path: string): Promise { - await useServersFetch(`servers/${this.serverId}/deleteMod`, { - method: 'POST', - body: { path }, - }) - } - - async reinstall(replace: string, projectId: string, versionId: string): Promise { - await useServersFetch(`servers/${this.serverId}/mods/update`, { - method: 'POST', - body: { replace, project_id: projectId, version_id: versionId }, - }) - } -} diff --git a/apps/frontend/src/composables/servers/modules/index.ts b/apps/frontend/src/composables/servers/modules/index.ts index 62fe2c45f8..ea54fce52d 100644 --- a/apps/frontend/src/composables/servers/modules/index.ts +++ b/apps/frontend/src/composables/servers/modules/index.ts @@ -1,6 +1,5 @@ export * from './backups.ts' export * from './base.ts' -export * from './content.ts' export * from './general.ts' export * from './network.ts' export * from './startup.ts' diff --git a/apps/frontend/src/pages/discover/[type]/index.vue b/apps/frontend/src/pages/discover/[type]/index.vue index 777025dd24..a9a5f3c51d 100644 --- a/apps/frontend/src/pages/discover/[type]/index.vue +++ b/apps/frontend/src/pages/discover/[type]/index.vue @@ -12,13 +12,14 @@ import { SearchIcon, XIcon, } from '@modrinth/assets' -import { defineMessages, useVIntl } from '@modrinth/ui' import { Avatar, Button, ButtonStyled, Checkbox, + defineMessages, DropdownSelect, + injectModrinthClient, injectNotificationManager, NewProjectCard, Pagination, @@ -26,8 +27,10 @@ import { SearchSidebarFilter, type SortType, useSearch, + useVIntl, } from '@modrinth/ui' -import { capitalizeString, cycleValue, type Mod as InstallableMod } from '@modrinth/utils' +import { capitalizeString, cycleValue } from '@modrinth/utils' +import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query' import { useThrottleFn } from '@vueuse/core' import { computed, type Reactive, watch } from 'vue' @@ -40,10 +43,13 @@ import type { DisplayLocation, DisplayMode } from '~/plugins/cosmetics.ts' const { formatMessage } = useVIntl() +const client = injectModrinthClient() +const queryClient = useQueryClient() + const filtersMenuOpen = ref(false) -const route = useNativeRoute() -const router = useNativeRouter() +const route = useRoute() +const router = useRouter() const cosmetics = useCosmetics() const tags = useGeneratedState() @@ -72,6 +78,48 @@ const server = ref>() const serverHideInstalled = ref(false) const eraseDataOnInstall = ref(false) +const currentServerId = computed(() => queryAsString(route.query.sid) || null) + +// TanStack Query for server content list +const contentQueryKey = computed(() => ['content', 'list', currentServerId.value ?? ''] as const) +const { data: serverContentData, error: serverContentError } = useQuery({ + queryKey: contentQueryKey, + queryFn: () => client.archon.content_v0.list(currentServerId.value!), + enabled: computed(() => !!currentServerId.value), +}) + +// Watch for errors and notify user +watch(serverContentError, (error) => { + if (error) { + console.error('Failed to load server content:', error) + handleError(error) + } +}) + +// Install content mutation +const installContentMutation = useMutation({ + mutationFn: ({ + serverId, + type, + projectId, + versionId, + }: { + serverId: string + type: 'mod' | 'plugin' + projectId: string + versionId: string + }) => + client.archon.content_v0.install(serverId, { + rinth_ids: { project_id: projectId, version_id: versionId }, + install_as: type, + }), + onSuccess: () => { + if (currentServerId.value) { + queryClient.invalidateQueries({ queryKey: ['content', 'list', currentServerId.value] }) + } + }, +}) + const PERSISTENT_QUERY_PARAMS = ['sid', 'shi'] async function updateServerContext() { @@ -89,7 +137,7 @@ async function updateServerContext() { } if (!server.value || server.value.serverId !== serverId) { - server.value = await useModrinthServers(serverId, ['general', 'content']) + server.value = await useModrinthServers(serverId, ['general']) } if (route.query.shi && projectType.value?.id !== 'modpack' && server.value) { @@ -147,10 +195,10 @@ const serverFilters = computed(() => { }) } - if (serverHideInstalled.value) { - const installedMods = server.value.content?.data - .filter((x: InstallableMod) => x.project_id) - .map((x: InstallableMod) => x.project_id) + if (serverHideInstalled.value && serverContentData.value) { + const installedMods = serverContentData.value + .filter((x) => x.project_id) + .map((x) => x.project_id) .filter((id): id is string => id !== undefined) installedMods @@ -251,12 +299,20 @@ async function serverInstall(project: InstallableSearchResult) { project.installed = true navigateTo(`/hosting/manage/${server.value.serverId}/options/loader`) } else if (projectType.value?.id === 'mod') { - await server.value.content.install('mod', version.project_id, version.id) - await server.value.refresh(['content']) + await installContentMutation.mutateAsync({ + serverId: server.value.serverId, + type: 'mod', + projectId: version.project_id, + versionId: version.id, + }) project.installed = true } else if (projectType.value?.id === 'plugin') { - await server.value.content.install('plugin', version.project_id, version.id) - await server.value.refresh(['content']) + await installContentMutation.mutateAsync({ + serverId: server.value.serverId, + type: 'plugin', + projectId: version.project_id, + versionId: version.id, + }) project.installed = true } } catch (e) { @@ -643,10 +699,8 @@ useSeoMeta({