Files
Modrinth-plus/apps/frontend/src/pages/hosting/manage/[id]/options/loader.vue
Calum H. 7d92e4ec7f feat: content tab rewrite for worlds (#5136)
* 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>
2026-03-12 13:24:32 -07:00

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>