* feat: base content card component * fix: tooltips + colors * feat: fix orgs * feat: base content tab internals rewrite * feat: fix invalidmodal * feat: add ContentModpackCard * fix: extract types * draft: layout * feat: unlink modal * feat: impl content tab * fix: lint * fix: toggling * temp: disable updating stuff * feat: selection v-model * feat: bulk selection * feat: mods tab rough draft * feat: use fuse.js * feat: add project combobox * clean up project combobox * feat: start install to play modal * fix: events * feat: use v-on * feat: bulk actions + fix floating action bar width * feat: figma alignments * feat: migrate toggle to tailwind * fix: row borders * feat: disabled state * feat: virtual list impl for card table based on window scroll * fix: lint * feat: virtualization + smaller contentcard items * feat: use ContentCardTable + ContentCardItems * feat: fix gap + border issues on last elm * feat: cleanup + use proper searching * fix: use TeleportOverflowMenu * fix: fallback to svg if src is invalid on avatar component * fix: storybook * feat: start on updater modal * feat: finish content updater modal * feat: i18n pass * feat: impl modal * feat(app): backend changes for content tab refactor (#5237) * feat: include_changelog=false for updater modal * fix: hash overrides * feat: update checking for modpack * feat: qa * feat: modpack content modal * fix: padding in table to match modals + tightness * fix: lint * feat: delete modal * feat: fix toggle bugs * fix: prepr * fix: duplicate messages * qa: full width search * qa: use bg-surface-1.5 * qa: animation for filter pills * qa: standardize hover colors * fix: border-[1px] is border * qa: mass de-select actually mass selecting * qa: match figma designs for floating action bar * qa: modal fixes * q: modal fixes x2 * fix: table border * qa: confirm modals * qa: modal alignment * qa: re-add stuck heading + dedupe logic * qa: dedupe virtual scrolling + remove dead components * qa: responsiveness for content table + link fixes * qa: version column link, tooltips + lint fixes * qa: instance busy protections * fix: installation freeze bug * chore: remove old mods page * refactor: deduplicate layout * chore: delete old content page(s) * qa * qa * qa * feat: sort btn - to iterate * fix: ml * feat: date added * fix: lint * fix: formatting.ts removal * feat: get_dependencies_as_content_items * qa: final QA changes * refactor: deduplicate + polish content.rs * feat: hook up content.vue with v1 * feat: hide v1 content api behind frontend feature flag * fix: query keys + copy on empty state * chore: i18n pass * feat: reimpl unlink + upload endpoint * feat: use bulk endpoints v1 * fix: lint * fix: flags * fix: responsiveness via container queries * fix: lint * qa: 1 * qa: fixes * qa: fix ssr issues with browse content * qa: header page divider * qa: modals * fix: prepr * fix: issues * fix: lint * fix: toggle v1 ff * qa: 5 * qa: delete modal copy * feat: creation flow modals (#5383) * refactor: delete content v0 usages + impl * feat: qa + fixes * feat: installing banner using state event * feat: fix modpack card bugs + filtering issues * refactor: delete backups v0 api module * feat: v1 servers GET endpoint * fix: backups * feat: swap to kyros upload v1 addon * fix: use tanstack for loader.vue * feat: finish install from discovery modal * qa: bug fixes * feat: set up installation settings * fix: lint * fix: typos * fix: bugs * fix: disable inline content * feat: content tab improvements — upload UX, installation settings, and client-only indicators Upload cancellation and navigation guard: - Add ConfirmLeaveModal that prompts when navigating away during upload - Cancel in-flight XHR uploads when user confirms leaving the page - Add beforeunload handler to warn on browser/tab close during upload - Track uploadedBytes/totalBytes in UploadState for progress display - Replace Collapsible with Transition for upload progress admonition - Show byte progress and percentage in upload banner - Clamp upload progress to prevent exceeding 100% Installation settings (server.properties): - Add KnownPropertiesFields and PropertiesFields types to Archon types - Add buildProperties() to creation flow context to collect gamemode, difficulty, seed, world type, structures, and generator settings - Pass properties through installContent on onboarding, discovery, and ServerSetupModal flows Server setup and discovery flow improvements: - Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent - Replace loaderApiNames lookup with toApiLoader() helper - Remove eraseDataOnInstall toggle — always use soft_override: false - Simplify modpack install on discovery page to use first available version and route through creation flow modal for both onboarding and non-onboarding - Differentiate post-install navigation: content page for onboarding, loader options for existing servers Modpack update flow: - Replace updateModpack() call with installContent() using soft_override: true to support version selection in the content updater modal Client-only mod indicators: - Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment) - Add environment to ContentItem and isClientOnly to ContentCardTableItem - Show orange TriangleAlertIcon with tooltip on client-only mods in content table - Add "Client-only" filter pill to content filtering (controlled via showClientOnlyFilter on ContentManagerContext) - Apply client-only indicators in both ContentPageLayout and ModpackContentModal Misc: - Add CLAUDE.md note about using prepr commands for lint checks - Export ConfirmLeaveModal from instances barrel * fix: piping * fix: switch content disable for linked server instances * feat: client only filter * fix: prepr * feat: hasUpdate shape update * feat: bulk update endpoint impl for content in panel * feat: websocket state impl again with new phases * fix: ws * fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug * fix: qa bugs * fix: lint, a11y and i18n * refactor: set up layouts folder properly * fix: linked data cache stuff + lint * feat: move installationsettings to shared layout * fix: lint * fix: issues * feat: temp fuck staging up * fix: lockfile * fix: data sync issues on loader.vue * fix: lint * Hide shader configuration files from content list (#5499) * feat: workaround search problem + split out reset * fix: qa * fix: changelog not showing on first open * fix: qa + optimistic updating improvements * fix: prepr+lint * fix: qa * feat: qa * fix: lint * fix: lint * fix: build * fix: build * fix: type errors * fix: fade and JAVA_HOME passthrough * feat: qa * feat: impl diff shit * fix: qa * fix: app qa * feat: update diff modal * fix: endpoint * fix: qa * fix: qa * fix: use bulk in modpack modal * feat: abort signal impl + fix issues * fix: diff modal trunc * feat: qa * fix: qa * feat: tooltip content tab * fix: prepr * fix: dismiss on settings btn * feat: qa * feat: dont clear handlers on disconnect * fix: lint * fix: wrangler + introduce staging-archon env file --------- Signed-off-by: Calum H. <calum@modrinth.com> Co-authored-by: tdgao <mr.trumgao@gmail.com> Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
594 lines
19 KiB
Vue
594 lines
19 KiB
Vue
<template>
|
|
<div class="flex flex-col gap-6 rounded-2xl bg-surface-3 p-6">
|
|
<InstallationSettingsLayout ref="installationSettingsLayout">
|
|
<template #extra>
|
|
<div class="flex flex-col gap-2.5">
|
|
<span class="text-lg font-semibold text-contrast">{{
|
|
formatMessage(messages.resetServerTitle)
|
|
}}</span>
|
|
<span class="text-primary">
|
|
{{ formatMessage(messages.resetServerDescription) }}
|
|
</span>
|
|
<div>
|
|
<ButtonStyled color="red">
|
|
<button class="!shadow-none" :disabled="isInstalling" @click="setupModal?.show()">
|
|
<RotateCounterClockwiseIcon class="size-5" />
|
|
{{ formatMessage(commonMessages.resetServerButton) }}
|
|
</button>
|
|
</ButtonStyled>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template #extra-modals>
|
|
<ServerSetupModal
|
|
ref="setupModal"
|
|
@reinstall="onReinstall"
|
|
@browse-modpacks="onBrowseModpacks"
|
|
/>
|
|
</template>
|
|
</InstallationSettingsLayout>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Archon, LauncherMeta } from '@modrinth/api-client'
|
|
import { RotateCounterClockwiseIcon } from '@modrinth/assets'
|
|
import {
|
|
ButtonStyled,
|
|
commonMessages,
|
|
defineMessages,
|
|
formatLoaderLabel,
|
|
injectModrinthClient,
|
|
injectModrinthServerContext,
|
|
injectNotificationManager,
|
|
injectTags,
|
|
InstallationSettingsLayout,
|
|
provideInstallationSettings,
|
|
ServerSetupModal,
|
|
useDebugLogger,
|
|
useVIntl,
|
|
} from '@modrinth/ui'
|
|
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
|
import { computed, ref, watch } from 'vue'
|
|
|
|
const debug = useDebugLogger('LoaderPage')
|
|
const client = injectModrinthClient()
|
|
const { server, serverId, worldId, isSyncingContent, busyReasons } = injectModrinthServerContext()
|
|
const { addNotification } = injectNotificationManager()
|
|
const queryClient = useQueryClient()
|
|
const tags = injectTags()
|
|
const { formatMessage } = useVIntl()
|
|
|
|
const messages = defineMessages({
|
|
resetServerTitle: {
|
|
id: 'hosting.loader.reset-server',
|
|
defaultMessage: 'Reset server',
|
|
},
|
|
resetServerDescription: {
|
|
id: 'hosting.loader.reset-server-description',
|
|
defaultMessage:
|
|
'Removes all data on your server, including your worlds, mods, and configuration files. Backups will remain and can be restored.',
|
|
},
|
|
loaderVersionLabel: {
|
|
id: 'hosting.loader.loader-version',
|
|
defaultMessage: '{loader, select, null {Loader} other {{loader}}} version',
|
|
},
|
|
failedToLoadVersions: {
|
|
id: 'hosting.loader.failed-to-load-versions',
|
|
defaultMessage: 'Failed to load versions',
|
|
},
|
|
failedToChangeVersion: {
|
|
id: 'hosting.loader.failed-to-change-version',
|
|
defaultMessage: 'Failed to change modpack version',
|
|
},
|
|
failedToSaveSettings: {
|
|
id: 'hosting.loader.failed-to-save-settings',
|
|
defaultMessage: 'Failed to save installation settings',
|
|
},
|
|
repairStartedTitle: {
|
|
id: 'hosting.loader.repair-started-title',
|
|
defaultMessage: 'Repair completed',
|
|
},
|
|
repairStartedText: {
|
|
id: 'hosting.loader.repair-started-text',
|
|
defaultMessage: 'Your server installation has been repaired.',
|
|
},
|
|
failedToRepair: {
|
|
id: 'hosting.loader.failed-to-repair',
|
|
defaultMessage: 'Failed to repair server',
|
|
},
|
|
failedToReinstall: {
|
|
id: 'hosting.loader.failed-to-reinstall',
|
|
defaultMessage: 'Failed to reinstall modpack',
|
|
},
|
|
failedToUnlink: {
|
|
id: 'hosting.loader.failed-to-unlink',
|
|
defaultMessage: 'Failed to unlink modpack',
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
reinstall: [any?]
|
|
'reinstall-failed': []
|
|
}>()
|
|
|
|
const isInstalling = computed(() => {
|
|
const val =
|
|
server.value?.status === 'installing' || isSyncingContent.value || busyReasons.value.length > 0
|
|
debug(
|
|
'isInstalling:',
|
|
val,
|
|
'server.status:',
|
|
server.value?.status,
|
|
'isSyncingContent:',
|
|
isSyncingContent.value,
|
|
)
|
|
return val
|
|
})
|
|
const installationSettingsLayout = ref<InstanceType<typeof InstallationSettingsLayout>>()
|
|
const setupModal = ref<InstanceType<typeof ServerSetupModal>>()
|
|
|
|
async function invalidateServerState() {
|
|
debug('invalidateServerState: starting')
|
|
await Promise.all([
|
|
queryClient.invalidateQueries({ queryKey: ['servers', 'detail', serverId] }),
|
|
queryClient.invalidateQueries({ queryKey: ['content', 'list', 'v1', serverId] }),
|
|
])
|
|
debug('invalidateServerState: complete')
|
|
}
|
|
|
|
const addonsQuery = useQuery({
|
|
queryKey: computed(() => ['content', 'list', 'v1', serverId]),
|
|
queryFn: () =>
|
|
client.archon.content_v1.getAddons(serverId, worldId.value!, { from_modpack: false }),
|
|
enabled: computed(() => worldId.value !== null),
|
|
})
|
|
|
|
const modpack = computed(() => addonsQuery.data.value?.modpack ?? null)
|
|
|
|
const modpackVersionsQuery = useQuery({
|
|
queryKey: computed(() => ['labrinth', 'versions', 'v2', modpack.value?.spec.project_id]),
|
|
queryFn: () =>
|
|
client.labrinth.versions_v2.getProjectVersions(modpack.value!.spec.project_id, {
|
|
include_changelog: false,
|
|
}),
|
|
enabled: computed(() => !!modpack.value?.spec.project_id),
|
|
})
|
|
|
|
const editingPlatform = ref(server.value?.loader?.toLowerCase() ?? 'vanilla')
|
|
const editingGameVersion = ref(server.value?.mc_version ?? '')
|
|
|
|
const modLoaders = ['fabric', 'forge', 'quilt', 'neoforge']
|
|
|
|
function toApiLoaderName(loader: string): string {
|
|
return loader === 'neoforge' ? 'neo' : loader
|
|
}
|
|
|
|
const apiLoaderName = computed(() =>
|
|
modLoaders.includes(editingPlatform.value) ? toApiLoaderName(editingPlatform.value) : null,
|
|
)
|
|
|
|
const manifestQuery = useQuery({
|
|
queryKey: computed(() => ['loader-manifest', apiLoaderName.value] as const),
|
|
queryFn: () => client.launchermeta.manifest_v0.getManifest(apiLoaderName.value!),
|
|
enabled: computed(() => !!apiLoaderName.value),
|
|
staleTime: 5 * 60 * 1000,
|
|
})
|
|
|
|
const paperBuildsQuery = useQuery({
|
|
queryKey: computed(() => ['paper-builds', editingGameVersion.value] as const),
|
|
queryFn: () => client.paper.versions_v3.getBuilds(editingGameVersion.value),
|
|
enabled: computed(() => editingPlatform.value === 'paper' && !!editingGameVersion.value),
|
|
staleTime: 5 * 60 * 1000,
|
|
})
|
|
|
|
const purpurBuildsQuery = useQuery({
|
|
queryKey: computed(() => ['purpur-builds', editingGameVersion.value] as const),
|
|
queryFn: () => client.purpur.versions_v2.getBuilds(editingGameVersion.value),
|
|
enabled: computed(() => editingPlatform.value === 'purpur' && !!editingGameVersion.value),
|
|
staleTime: 5 * 60 * 1000,
|
|
})
|
|
|
|
type LoaderVersionEntry = LauncherMeta.Manifest.v0.LoaderVersion
|
|
|
|
function getLoaderVersionsForGameVersion(
|
|
loader: string,
|
|
gameVersion: string,
|
|
): LoaderVersionEntry[] {
|
|
if (loader === 'paper') {
|
|
return (paperBuildsQuery.data.value?.builds ?? [])
|
|
.toSorted((a, b) => b - a)
|
|
.map((b) => ({ id: String(b), stable: true }))
|
|
}
|
|
if (loader === 'purpur') {
|
|
return (purpurBuildsQuery.data.value?.builds.all ?? [])
|
|
.toSorted((a, b) => parseInt(b) - parseInt(a))
|
|
.map((b) => ({ id: b, stable: true }))
|
|
}
|
|
|
|
const manifest = manifestQuery.data.value?.gameVersions
|
|
if (!manifest) return []
|
|
|
|
const placeholder = manifest.find((x) => x.id === '${modrinth.gameVersion}')
|
|
if (placeholder) return placeholder.loaders
|
|
|
|
const entry = manifest.find((x) => x.id === gameVersion)
|
|
return entry?.loaders ?? []
|
|
}
|
|
|
|
function toApiLoader(loader: string): Archon.Content.v1.Modloader {
|
|
if (loader === 'neoforge') return 'neo_forge'
|
|
return loader as Archon.Content.v1.Modloader
|
|
}
|
|
|
|
provideInstallationSettings({
|
|
loading: computed(() => !server.value || addonsQuery.isLoading.value),
|
|
installationInfo: computed(() => {
|
|
const addons = addonsQuery.data.value
|
|
const rawLoader = addons?.modloader ?? server.value?.loader ?? null
|
|
const loader = rawLoader ? formatLoaderLabel(rawLoader) : null
|
|
const gameVersion = addons?.game_version ?? server.value?.mc_version ?? null
|
|
const loaderVersion = addons?.modloader_version ?? server.value?.loader_version ?? null
|
|
|
|
debug('installationInfo computed:', {
|
|
'addons?.modloader': addons?.modloader,
|
|
'server.loader': server.value?.loader,
|
|
rawLoader,
|
|
loader,
|
|
'addons?.game_version': addons?.game_version,
|
|
'server.mc_version': server.value?.mc_version,
|
|
gameVersion,
|
|
'addons?.modloader_version': addons?.modloader_version,
|
|
'server.loader_version': server.value?.loader_version,
|
|
loaderVersion,
|
|
'addonsQuery.isLoading': addonsQuery.isLoading.value,
|
|
'addonsQuery.isFetching': addonsQuery.isFetching.value,
|
|
})
|
|
|
|
const rows = [
|
|
{ label: formatMessage(commonMessages.platformLabel), value: loader },
|
|
{ label: formatMessage(commonMessages.gameVersionLabel), value: gameVersion },
|
|
]
|
|
if (loader !== 'Vanilla') {
|
|
rows.push({
|
|
label: formatMessage(messages.loaderVersionLabel, { loader: loader ?? 'null' }),
|
|
value: loaderVersion,
|
|
})
|
|
}
|
|
return rows
|
|
}),
|
|
isLinked: computed(() => {
|
|
const val = !!modpack.value
|
|
debug('isLinked:', val, 'modpack:', modpack.value?.spec?.project_id)
|
|
return val
|
|
}),
|
|
isBusy: isInstalling,
|
|
modpack: computed(() => {
|
|
if (!modpack.value) return null
|
|
return {
|
|
iconUrl: modpack.value.icon_url,
|
|
title: modpack.value.title ?? modpack.value.spec.project_id,
|
|
link: `/project/${modpack.value.spec.project_id}`,
|
|
versionNumber: modpack.value.version_number,
|
|
owner: modpack.value.owner
|
|
? {
|
|
id: modpack.value.owner.id,
|
|
name: modpack.value.owner.name,
|
|
iconUrl: modpack.value.owner.icon_url,
|
|
type: modpack.value.owner.type as 'user' | 'organization',
|
|
}
|
|
: undefined,
|
|
}
|
|
}),
|
|
currentPlatform: computed(() => server.value?.loader?.toLowerCase() ?? 'vanilla'),
|
|
currentGameVersion: computed(() => server.value?.mc_version ?? ''),
|
|
currentLoaderVersion: computed(() => server.value?.loader_version ?? ''),
|
|
availablePlatforms: ['vanilla', 'fabric', 'neoforge', 'forge', 'quilt', 'paper', 'purpur'],
|
|
|
|
editingPlatformRef: editingPlatform,
|
|
editingGameVersionRef: editingGameVersion,
|
|
|
|
resolveGameVersions(loader, showSnapshots) {
|
|
const versions = showSnapshots
|
|
? tags.gameVersions.value
|
|
: tags.gameVersions.value.filter((v) => v.version_type === 'release')
|
|
|
|
if (loader && loader !== 'vanilla' && !['paper', 'purpur'].includes(loader)) {
|
|
const manifest = manifestQuery.data.value?.gameVersions
|
|
if (manifest) {
|
|
const hasPlaceholder = manifest.some((x) => x.id === '${modrinth.gameVersion}')
|
|
if (!hasPlaceholder) {
|
|
const supportedVersions = new Set(
|
|
manifest.filter((x) => x.loaders.length > 0).map((x) => x.id),
|
|
)
|
|
return versions
|
|
.filter((v) => supportedVersions.has(v.version))
|
|
.map((v) => ({ value: v.version, label: v.version }))
|
|
}
|
|
}
|
|
}
|
|
|
|
return versions.map((v) => ({ value: v.version, label: v.version }))
|
|
},
|
|
|
|
resolveLoaderVersions(loader, gameVersion) {
|
|
if (loader === 'vanilla' || !gameVersion) return []
|
|
return getLoaderVersionsForGameVersion(loader, gameVersion)
|
|
},
|
|
|
|
resolveHasSnapshots(loader) {
|
|
if (loader === 'vanilla' || ['paper', 'purpur'].includes(loader)) {
|
|
return tags.gameVersions.value.some((v) => v.version_type !== 'release')
|
|
}
|
|
const manifest = manifestQuery.data.value?.gameVersions
|
|
if (!manifest) return false
|
|
const hasPlaceholder = manifest.some((x) => x.id === '${modrinth.gameVersion}')
|
|
if (hasPlaceholder) {
|
|
return tags.gameVersions.value.some((v) => v.version_type !== 'release')
|
|
}
|
|
const supportedVersions = new Set(manifest.filter((x) => x.loaders.length > 0).map((x) => x.id))
|
|
const supported = tags.gameVersions.value.filter((v) => supportedVersions.has(v.version))
|
|
return supported.some((v) => v.version_type !== 'release')
|
|
},
|
|
|
|
async save(platform, gameVersion, loaderVersionId) {
|
|
debug('save: called with', { platform, gameVersion, loaderVersionId })
|
|
const currentPlatform = server.value?.loader?.toLowerCase() ?? 'vanilla'
|
|
const platformChanged = platform !== currentPlatform
|
|
|
|
debug('save: emitting reinstall before API call')
|
|
emit(
|
|
'reinstall',
|
|
platformChanged
|
|
? { loader: platform, lVersion: loaderVersionId, mVersion: gameVersion }
|
|
: { mVersion: gameVersion },
|
|
)
|
|
try {
|
|
if (platformChanged) {
|
|
const request: Archon.Content.v1.InstallWorldContent = {
|
|
content_variant: 'bare',
|
|
loader: toApiLoader(platform),
|
|
version: loaderVersionId ?? '',
|
|
game_version: gameVersion || undefined,
|
|
soft_override: true,
|
|
}
|
|
debug('save: platform changed, calling installContent', request)
|
|
await client.archon.content_v1.installContent(serverId, worldId.value!, request)
|
|
} else {
|
|
debug('save: game version only, calling applyGameVersionUpdate', gameVersion)
|
|
await client.archon.content_v1.applyGameVersionUpdate(serverId, worldId.value!, gameVersion)
|
|
}
|
|
debug('save: succeeded, invalidating')
|
|
invalidateServerState()
|
|
} catch (err) {
|
|
debug('save: failed, emitting reinstall-failed', err)
|
|
emit('reinstall-failed')
|
|
addNotification({
|
|
type: 'error',
|
|
text: err instanceof Error ? err.message : formatMessage(messages.failedToSaveSettings),
|
|
})
|
|
throw err
|
|
}
|
|
},
|
|
|
|
async repair() {
|
|
debug('repair: called')
|
|
try {
|
|
await client.archon.content_v1.repair(serverId, worldId.value!)
|
|
debug('repair: API succeeded, invalidating')
|
|
await invalidateServerState()
|
|
addNotification({
|
|
type: 'success',
|
|
title: formatMessage(messages.repairStartedTitle),
|
|
text: formatMessage(messages.repairStartedText),
|
|
})
|
|
} catch (err) {
|
|
debug('repair: failed', err)
|
|
addNotification({
|
|
type: 'error',
|
|
text: err instanceof Error ? err.message : formatMessage(messages.failedToRepair),
|
|
})
|
|
}
|
|
},
|
|
|
|
async reinstallModpack() {
|
|
if (!modpack.value) return
|
|
debug(
|
|
'reinstallModpack: called, project:',
|
|
modpack.value.spec.project_id,
|
|
'version:',
|
|
modpack.value.spec.version_id,
|
|
)
|
|
debug('reinstallModpack: emitting reinstall before API call')
|
|
emit('reinstall')
|
|
try {
|
|
await client.archon.content_v1.installContent(serverId, worldId.value!, {
|
|
content_variant: 'modpack',
|
|
spec: {
|
|
platform: 'modrinth',
|
|
project_id: modpack.value.spec.project_id,
|
|
version_id: modpack.value.spec.version_id,
|
|
},
|
|
soft_override: false,
|
|
})
|
|
debug('reinstallModpack: installContent succeeded, invalidating')
|
|
invalidateServerState()
|
|
} catch (err) {
|
|
debug('reinstallModpack: failed, emitting reinstall-failed', err)
|
|
emit('reinstall-failed')
|
|
addNotification({
|
|
type: 'error',
|
|
text: err instanceof Error ? err.message : formatMessage(messages.failedToReinstall),
|
|
})
|
|
}
|
|
},
|
|
|
|
async unlinkModpack() {
|
|
debug('unlinkModpack: called')
|
|
const previousData = addonsQuery.data.value
|
|
if (previousData) {
|
|
debug('unlinkModpack: optimistically removing modpack from cache')
|
|
queryClient.setQueryData(['content', 'list', 'v1', serverId], {
|
|
...previousData,
|
|
modpack: null,
|
|
})
|
|
}
|
|
|
|
try {
|
|
await client.archon.content_v1.unlinkModpack(serverId, worldId.value!)
|
|
debug('unlinkModpack: API succeeded')
|
|
} catch (err) {
|
|
debug('unlinkModpack: failed, reverting cache', err)
|
|
if (previousData) {
|
|
queryClient.setQueryData(['content', 'list', 'v1', serverId], previousData)
|
|
}
|
|
addNotification({
|
|
type: 'error',
|
|
text: err instanceof Error ? err.message : formatMessage(messages.failedToUnlink),
|
|
})
|
|
} finally {
|
|
debug('unlinkModpack: invalidating queries')
|
|
await Promise.all([
|
|
queryClient.invalidateQueries({
|
|
queryKey: ['servers', 'detail', serverId],
|
|
}),
|
|
queryClient.invalidateQueries({
|
|
queryKey: ['content', 'list', 'v1', serverId],
|
|
}),
|
|
])
|
|
debug('unlinkModpack: invalidation complete')
|
|
}
|
|
},
|
|
|
|
getCachedModpackVersions: () => modpackVersionsQuery.data.value ?? null,
|
|
|
|
async fetchModpackVersions() {
|
|
debug('fetchModpackVersions: called, project:', modpack.value?.spec.project_id)
|
|
try {
|
|
const versions = await client.labrinth.versions_v2.getProjectVersions(
|
|
modpack.value!.spec.project_id,
|
|
{
|
|
include_changelog: false,
|
|
},
|
|
)
|
|
debug('fetchModpackVersions: got', versions.length, 'versions')
|
|
return versions
|
|
} catch (err) {
|
|
debug('fetchModpackVersions: failed', err)
|
|
addNotification({
|
|
type: 'error',
|
|
text: err instanceof Error ? err.message : formatMessage(messages.failedToLoadVersions),
|
|
})
|
|
throw err
|
|
}
|
|
},
|
|
|
|
async getVersionChangelog(versionId) {
|
|
debug('getVersionChangelog: called, versionId:', versionId)
|
|
try {
|
|
return await client.labrinth.versions_v2.getVersion(versionId)
|
|
} catch {
|
|
debug('getVersionChangelog: failed for', versionId)
|
|
return null
|
|
}
|
|
},
|
|
|
|
async onModpackVersionConfirm(version) {
|
|
if (!modpack.value) return
|
|
debug('onModpackVersionConfirm: called, version:', version.id)
|
|
debug('onModpackVersionConfirm: emitting reinstall before API call')
|
|
emit('reinstall')
|
|
try {
|
|
await client.archon.content_v1.installContent(serverId, worldId.value!, {
|
|
content_variant: 'modpack',
|
|
spec: {
|
|
platform: 'modrinth',
|
|
project_id: modpack.value.spec.project_id,
|
|
version_id: version.id,
|
|
},
|
|
soft_override: true,
|
|
})
|
|
debug('onModpackVersionConfirm: installContent succeeded, invalidating')
|
|
invalidateServerState()
|
|
} catch (err) {
|
|
debug('onModpackVersionConfirm: failed, emitting reinstall-failed', err)
|
|
emit('reinstall-failed')
|
|
addNotification({
|
|
type: 'error',
|
|
text: err instanceof Error ? err.message : formatMessage(messages.failedToChangeVersion),
|
|
})
|
|
}
|
|
},
|
|
|
|
updaterModalProps: computed(() => ({
|
|
isApp: false,
|
|
currentVersionId: modpack.value?.spec.version_id ?? '',
|
|
projectIconUrl: modpack.value?.icon_url ?? undefined,
|
|
projectName:
|
|
modpack.value?.title ??
|
|
modpack.value?.spec.project_id ??
|
|
formatMessage(commonMessages.modpackLabel),
|
|
currentGameVersion: addonsQuery.data.value?.game_version ?? server.value?.mc_version ?? '',
|
|
currentLoader: addonsQuery.data.value?.modloader ?? server.value?.loader ?? '',
|
|
})),
|
|
|
|
isServer: true,
|
|
isApp: false,
|
|
|
|
lockPlatform: true,
|
|
hideLoaderVersion: true,
|
|
|
|
async previewSave(_platform, gameVersion, _loaderVersionId, signal) {
|
|
const result = await client.archon.content_v1.getUpdateGameVersionPreview(
|
|
serverId,
|
|
worldId.value!,
|
|
gameVersion,
|
|
signal,
|
|
)
|
|
if (result.addon_changes.length === 0 && !result.has_unknown_content) return null
|
|
return {
|
|
diffs: result.addon_changes.map((diff) => ({
|
|
type: diff.type,
|
|
projectName: diff.project?.title ?? undefined,
|
|
fileName: diff.file_name ?? undefined,
|
|
currentVersionName: diff.current_version?.version_number ?? undefined,
|
|
newVersionName: diff.new_version?.version_number ?? undefined,
|
|
})),
|
|
newGameVersion: result.new_game_version,
|
|
newLoaderVersion: result.new_loader_version,
|
|
hasUnknownContent: result.has_unknown_content,
|
|
}
|
|
},
|
|
})
|
|
|
|
watch(
|
|
() => server.value?.status,
|
|
(newStatus, oldStatus) => {
|
|
debug('status watcher:', oldStatus, '->', newStatus, {
|
|
'server.loader': server.value?.loader,
|
|
'server.mc_version': server.value?.mc_version,
|
|
'server.loader_version': server.value?.loader_version,
|
|
})
|
|
if (oldStatus === 'installing' && newStatus === 'available') {
|
|
debug('status installing->available, resetting editing refs')
|
|
editingPlatform.value = server.value?.loader?.toLowerCase() ?? 'vanilla'
|
|
editingGameVersion.value = server.value?.mc_version ?? ''
|
|
}
|
|
},
|
|
)
|
|
|
|
function onReinstall(event?: any) {
|
|
installationSettingsLayout.value?.cancelEditing()
|
|
emit('reinstall', event)
|
|
}
|
|
|
|
function onBrowseModpacks() {
|
|
debug('onBrowseModpacks: navigating to modpack discovery')
|
|
navigateTo({
|
|
path: '/discover/modpacks',
|
|
query: { sid: serverId, from: 'reset-server', wid: worldId.value },
|
|
})
|
|
}
|
|
</script>
|