mirror of
https://github.com/modrinth/code.git
synced 2026-09-05 06:19:11 +00:00
feat: sync individual content installation states on panel
This commit is contained in:
@@ -1150,6 +1150,50 @@ export namespace Archon {
|
|||||||
version_id: string
|
version_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InstallProgressFileKey = {
|
||||||
|
type: 'file'
|
||||||
|
parent_directory: string
|
||||||
|
filename: string
|
||||||
|
install_type: 'install' | 'update'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InstallProgressModrinthModpackKey = {
|
||||||
|
type: 'modrinth_modpack'
|
||||||
|
project_id: string
|
||||||
|
version_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InstallProgressLocalModpackKey = {
|
||||||
|
type: 'local_modpack'
|
||||||
|
filename: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InstallProgressPlatformKey = {
|
||||||
|
type: 'platform'
|
||||||
|
platform: 'forge' | 'neoforge' | 'fabric' | 'quilt' | 'paper' | 'purpur' | 'vanilla'
|
||||||
|
platform_version: string
|
||||||
|
game_version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InstallProgressKey =
|
||||||
|
| InstallProgressFileKey
|
||||||
|
| InstallProgressModrinthModpackKey
|
||||||
|
| InstallProgressLocalModpackKey
|
||||||
|
| InstallProgressPlatformKey
|
||||||
|
|
||||||
|
export type InstallProgressItem = {
|
||||||
|
world_id: string
|
||||||
|
key: InstallProgressKey
|
||||||
|
id: string
|
||||||
|
progress: number | null
|
||||||
|
error: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WSInstallProgressEvent = {
|
||||||
|
event: 'install-progress'
|
||||||
|
items: InstallProgressItem[]
|
||||||
|
}
|
||||||
|
|
||||||
export type FilesystemOpKind = 'unarchive'
|
export type FilesystemOpKind = 'unarchive'
|
||||||
|
|
||||||
export type FilesystemOpState =
|
export type FilesystemOpState =
|
||||||
@@ -1247,6 +1291,7 @@ export namespace Archon {
|
|||||||
| WSInstallationResultEvent
|
| WSInstallationResultEvent
|
||||||
| WSUptimeEvent
|
| WSUptimeEvent
|
||||||
| WSNewModEvent
|
| WSNewModEvent
|
||||||
|
| WSInstallProgressEvent
|
||||||
| WSFilesystemOpsEvent
|
| WSFilesystemOpsEvent
|
||||||
|
|
||||||
export type WSEventType = WSEvent['event']
|
export type WSEventType = WSEvent['event']
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
|
|||||||
const fsAuth = ref<{ url: string; token: string } | null>(null)
|
const fsAuth = ref<{ url: string; token: string } | null>(null)
|
||||||
const fsOps = ref<Archon.Websocket.v0.FilesystemOperation[]>([])
|
const fsOps = ref<Archon.Websocket.v0.FilesystemOperation[]>([])
|
||||||
const fsQueuedOps = ref<Archon.Websocket.v0.QueuedFilesystemOp[]>([])
|
const fsQueuedOps = ref<Archon.Websocket.v0.QueuedFilesystemOp[]>([])
|
||||||
|
const installProgressItems = ref<Archon.Websocket.v0.InstallProgressItem[]>([])
|
||||||
const connectedSocketServerId = ref<string | null>(null)
|
const connectedSocketServerId = ref<string | null>(null)
|
||||||
const socketUnsubscribers = ref<SocketUnsubscriber[]>([])
|
const socketUnsubscribers = ref<SocketUnsubscriber[]>([])
|
||||||
const cpuData = ref<number[]>([])
|
const cpuData = ref<number[]>([])
|
||||||
@@ -265,6 +266,11 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
|
|||||||
startUptimeTicker()
|
startUptimeTicker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleInstallProgress = (data: Archon.Websocket.v0.WSInstallProgressEvent) => {
|
||||||
|
if (!shouldProcessEvent()) return
|
||||||
|
installProgressItems.value = data.items
|
||||||
|
}
|
||||||
|
|
||||||
const handleAuthIncorrect = () => {
|
const handleAuthIncorrect = () => {
|
||||||
if (!shouldProcessEvent()) return
|
if (!shouldProcessEvent()) return
|
||||||
isWsAuthIncorrect.value = true
|
isWsAuthIncorrect.value = true
|
||||||
@@ -301,6 +307,7 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
|
|||||||
serverPowerState.value = 'stopped'
|
serverPowerState.value = 'stopped'
|
||||||
powerStateDetails.value = undefined
|
powerStateDetails.value = undefined
|
||||||
uptimeSeconds.value = 0
|
uptimeSeconds.value = 0
|
||||||
|
installProgressItems.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectSocket = async (
|
const connectSocket = async (
|
||||||
@@ -317,6 +324,20 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
|
|||||||
disconnectSocket(connectedSocketServerId.value ?? undefined)
|
disconnectSocket(connectedSocketServerId.value ?? undefined)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const baseSubscriptions: SocketUnsubscriber[] = [
|
||||||
|
client.archon.sockets.on(targetServerId, 'log', handleLog),
|
||||||
|
client.archon.sockets.on(targetServerId, 'log4j', handleLog4j),
|
||||||
|
client.archon.sockets.on(targetServerId, 'stats', handleStats),
|
||||||
|
client.archon.sockets.on(targetServerId, 'state', handleState),
|
||||||
|
client.archon.sockets.on(targetServerId, 'power-state', handlePowerState),
|
||||||
|
client.archon.sockets.on(targetServerId, 'uptime', handleUptime),
|
||||||
|
client.archon.sockets.on(targetServerId, 'install-progress', handleInstallProgress),
|
||||||
|
client.archon.sockets.on(targetServerId, 'auth-incorrect', handleAuthIncorrect),
|
||||||
|
client.archon.sockets.on(targetServerId, 'auth-ok', handleAuthOk),
|
||||||
|
]
|
||||||
|
const extraSubscriptions = connectOptions.extraSubscriptions?.(targetServerId) ?? []
|
||||||
|
socketUnsubscribers.value = [...baseSubscriptions, ...extraSubscriptions]
|
||||||
|
|
||||||
const safeConnectOptions = connectOptions.force ? { force: true } : undefined
|
const safeConnectOptions = connectOptions.force ? { force: true } : undefined
|
||||||
await client.archon.sockets.safeConnect(targetServerId, safeConnectOptions)
|
await client.archon.sockets.safeConnect(targetServerId, safeConnectOptions)
|
||||||
connectedSocketServerId.value = targetServerId
|
connectedSocketServerId.value = targetServerId
|
||||||
@@ -326,21 +347,10 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
|
|||||||
modrinthServersConsole.clear()
|
modrinthServersConsole.clear()
|
||||||
modrinthServersConsole.beginInitialLogHydration()
|
modrinthServersConsole.beginInitialLogHydration()
|
||||||
|
|
||||||
const baseSubscriptions: SocketUnsubscriber[] = [
|
|
||||||
client.archon.sockets.on(targetServerId, 'log', handleLog),
|
|
||||||
client.archon.sockets.on(targetServerId, 'log4j', handleLog4j),
|
|
||||||
client.archon.sockets.on(targetServerId, 'stats', handleStats),
|
|
||||||
client.archon.sockets.on(targetServerId, 'state', handleState),
|
|
||||||
client.archon.sockets.on(targetServerId, 'power-state', handlePowerState),
|
|
||||||
client.archon.sockets.on(targetServerId, 'uptime', handleUptime),
|
|
||||||
client.archon.sockets.on(targetServerId, 'auth-incorrect', handleAuthIncorrect),
|
|
||||||
client.archon.sockets.on(targetServerId, 'auth-ok', handleAuthOk),
|
|
||||||
]
|
|
||||||
const extraSubscriptions = connectOptions.extraSubscriptions?.(targetServerId) ?? []
|
|
||||||
socketUnsubscribers.value = [...baseSubscriptions, ...extraSubscriptions]
|
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[hosting/manage] Failed to connect server socket:', error)
|
console.error('[hosting/manage] Failed to connect server socket:', error)
|
||||||
|
clearSocketListeners()
|
||||||
isConnected.value = false
|
isConnected.value = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -402,6 +412,7 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
|
|||||||
isServerRunning,
|
isServerRunning,
|
||||||
stats,
|
stats,
|
||||||
uptimeSeconds,
|
uptimeSeconds,
|
||||||
|
installProgressItems,
|
||||||
isSyncingContent: options.isSyncingContent as Ref<boolean>,
|
isSyncingContent: options.isSyncingContent as Ref<boolean>,
|
||||||
busyReasons,
|
busyReasons,
|
||||||
fsAuth,
|
fsAuth,
|
||||||
@@ -437,6 +448,7 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
|
|||||||
isConnected,
|
isConnected,
|
||||||
isServerRunning,
|
isServerRunning,
|
||||||
isWsAuthIncorrect,
|
isWsAuthIncorrect,
|
||||||
|
installProgressItems,
|
||||||
powerStateDetails,
|
powerStateDetails,
|
||||||
ramData,
|
ramData,
|
||||||
refreshFsAuth,
|
refreshFsAuth,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Avatar from '#ui/components/base/Avatar.vue'
|
|||||||
import BulletDivider from '#ui/components/base/BulletDivider.vue'
|
import BulletDivider from '#ui/components/base/BulletDivider.vue'
|
||||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||||
import Checkbox from '#ui/components/base/Checkbox.vue'
|
import Checkbox from '#ui/components/base/Checkbox.vue'
|
||||||
|
import ProgressSpinner from '#ui/components/base/ProgressSpinner.vue'
|
||||||
import type { Option as OverflowMenuOption } from '#ui/components/base/OverflowMenu.vue'
|
import type { Option as OverflowMenuOption } from '#ui/components/base/OverflowMenu.vue'
|
||||||
import TeleportOverflowMenu from '#ui/components/base/TeleportOverflowMenu.vue'
|
import TeleportOverflowMenu from '#ui/components/base/TeleportOverflowMenu.vue'
|
||||||
import Toggle from '#ui/components/base/Toggle.vue'
|
import Toggle from '#ui/components/base/Toggle.vue'
|
||||||
@@ -50,6 +51,7 @@ interface Props {
|
|||||||
source?: ContentSource
|
source?: ContentSource
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
installing?: boolean
|
installing?: boolean
|
||||||
|
installProgress?: number | null
|
||||||
hasUpdate?: boolean
|
hasUpdate?: boolean
|
||||||
isClientOnly?: boolean
|
isClientOnly?: boolean
|
||||||
clientWarning?: ClientWarningType | null
|
clientWarning?: ClientWarningType | null
|
||||||
@@ -73,6 +75,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
source: undefined,
|
source: undefined,
|
||||||
enabled: undefined,
|
enabled: undefined,
|
||||||
installing: false,
|
installing: false,
|
||||||
|
installProgress: undefined,
|
||||||
hasUpdate: false,
|
hasUpdate: false,
|
||||||
isClientOnly: false,
|
isClientOnly: false,
|
||||||
clientWarning: null,
|
clientWarning: null,
|
||||||
@@ -124,6 +127,11 @@ const clientWarningMessage = computed(() => {
|
|||||||
|
|
||||||
const { shift: shiftHeld } = useMagicKeys()
|
const { shift: shiftHeld } = useMagicKeys()
|
||||||
const deleteHovered = ref(false)
|
const deleteHovered = ref(false)
|
||||||
|
const installTooltip = computed(() => {
|
||||||
|
if (!props.installing) return undefined
|
||||||
|
if (props.installProgress == null) return formatMessage(commonMessages.installingLabel)
|
||||||
|
return `${formatMessage(commonMessages.installingLabel)} (${Math.round(props.installProgress)}%)`
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -147,6 +155,7 @@ const deleteHovered = ref(false)
|
|||||||
v-if="showCheckbox"
|
v-if="showCheckbox"
|
||||||
:model-value="selected ?? false"
|
:model-value="selected ?? false"
|
||||||
:aria-label="formatMessage(messages.selectProject, { project: project.title })"
|
:aria-label="formatMessage(messages.selectProject, { project: project.title })"
|
||||||
|
:disabled="isDisabled"
|
||||||
class="shrink-0"
|
class="shrink-0"
|
||||||
@update:model-value="(value, event) => emit('select', value, event)"
|
@update:model-value="(value, event) => emit('select', value, event)"
|
||||||
/>
|
/>
|
||||||
@@ -156,7 +165,7 @@ const deleteHovered = ref(false)
|
|||||||
:class="enabled === false && !disabled ? 'grayscale opacity-50' : ''"
|
:class="enabled === false && !disabled ? 'grayscale opacity-50' : ''"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-tooltip="installing ? formatMessage(commonMessages.installingLabel) : undefined"
|
v-tooltip="installTooltip"
|
||||||
class="relative flex shrink-0 items-center"
|
class="relative flex shrink-0 items-center"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
@@ -170,7 +179,13 @@ const deleteHovered = ref(false)
|
|||||||
v-if="installing"
|
v-if="installing"
|
||||||
class="absolute inset-0 flex items-center justify-center rounded-2xl bg-black/20"
|
class="absolute inset-0 flex items-center justify-center rounded-2xl bg-black/20"
|
||||||
>
|
>
|
||||||
<SpinnerIcon class="size-5 animate-spin text-white" />
|
<ProgressSpinner
|
||||||
|
v-if="installProgress != null && installProgress > 0"
|
||||||
|
:progress="installProgress"
|
||||||
|
:max="100"
|
||||||
|
class="size-5 text-white"
|
||||||
|
/>
|
||||||
|
<SpinnerIcon v-else class="size-5 animate-spin text-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex min-w-0 flex-col gap-0.5">
|
<div class="flex min-w-0 flex-col gap-0.5">
|
||||||
|
|||||||
@@ -104,20 +104,24 @@ defineExpose({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Selection logic
|
// Selection logic
|
||||||
|
const selectableItems = computed(() => props.items.filter((item) => !item.disabled))
|
||||||
|
|
||||||
const allSelected = computed(() => {
|
const allSelected = computed(() => {
|
||||||
if (props.items.length === 0) return false
|
if (selectableItems.value.length === 0) return false
|
||||||
return props.items.every((item) => selectedIds.value.includes(item.id))
|
return selectableItems.value.every((item) => selectedIds.value.includes(item.id))
|
||||||
})
|
})
|
||||||
|
|
||||||
const someSelected = computed(() => {
|
const someSelected = computed(() => {
|
||||||
return props.items.some((item) => selectedIds.value.includes(item.id)) && !allSelected.value
|
return (
|
||||||
|
selectableItems.value.some((item) => selectedIds.value.includes(item.id)) && !allSelected.value
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
function toggleSelectAll() {
|
function toggleSelectAll() {
|
||||||
if (allSelected.value || someSelected.value) {
|
if (allSelected.value || someSelected.value) {
|
||||||
selectedIds.value = []
|
selectedIds.value = []
|
||||||
} else {
|
} else {
|
||||||
selectedIds.value = props.items.map((item) => item.id)
|
selectedIds.value = selectableItems.value.map((item) => item.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +136,10 @@ function toggleItemSelection(
|
|||||||
if (selected && event?.shiftKey && lastSelectedIndex.value !== null && index !== undefined) {
|
if (selected && event?.shiftKey && lastSelectedIndex.value !== null && index !== undefined) {
|
||||||
const start = Math.min(lastSelectedIndex.value, index)
|
const start = Math.min(lastSelectedIndex.value, index)
|
||||||
const end = Math.max(lastSelectedIndex.value, index)
|
const end = Math.max(lastSelectedIndex.value, index)
|
||||||
const rangeIds = props.items.slice(start, end + 1).map((item) => item.id)
|
const rangeIds = props.items
|
||||||
|
.slice(start, end + 1)
|
||||||
|
.filter((item) => !item.disabled)
|
||||||
|
.map((item) => item.id)
|
||||||
const merged = new Set([...selectedIds.value, ...rangeIds])
|
const merged = new Set([...selectedIds.value, ...rangeIds])
|
||||||
selectedIds.value = [...merged]
|
selectedIds.value = [...merged]
|
||||||
} else if (selected) {
|
} else if (selected) {
|
||||||
@@ -192,6 +199,7 @@ function handleSort(column: ContentCardTableSortColumn) {
|
|||||||
:model-value="allSelected"
|
:model-value="allSelected"
|
||||||
:indeterminate="someSelected"
|
:indeterminate="someSelected"
|
||||||
:aria-label="formatMessage(commonMessages.selectAllLabel)"
|
:aria-label="formatMessage(commonMessages.selectAllLabel)"
|
||||||
|
:disabled="selectableItems.length === 0"
|
||||||
class="shrink-0"
|
class="shrink-0"
|
||||||
@update:model-value="toggleSelectAll"
|
@update:model-value="toggleSelectAll"
|
||||||
/>
|
/>
|
||||||
@@ -267,6 +275,7 @@ function handleSort(column: ContentCardTableSortColumn) {
|
|||||||
:source="item.source"
|
:source="item.source"
|
||||||
:enabled="item.enabled"
|
:enabled="item.enabled"
|
||||||
:installing="item.installing"
|
:installing="item.installing"
|
||||||
|
:install-progress="item.installProgress"
|
||||||
:has-update="item.hasUpdate"
|
:has-update="item.hasUpdate"
|
||||||
:is-client-only="item.isClientOnly"
|
:is-client-only="item.isClientOnly"
|
||||||
:client-warning="item.clientWarning"
|
:client-warning="item.clientWarning"
|
||||||
@@ -331,6 +340,7 @@ function handleSort(column: ContentCardTableSortColumn) {
|
|||||||
:source="item.source"
|
:source="item.source"
|
||||||
:enabled="item.enabled"
|
:enabled="item.enabled"
|
||||||
:installing="item.installing"
|
:installing="item.installing"
|
||||||
|
:install-progress="item.installProgress"
|
||||||
:has-update="item.hasUpdate"
|
:has-update="item.hasUpdate"
|
||||||
:is-client-only="item.isClientOnly"
|
:is-client-only="item.isClientOnly"
|
||||||
:client-warning="item.clientWarning"
|
:client-warning="item.clientWarning"
|
||||||
|
|||||||
@@ -286,6 +286,7 @@ const tableItems = computed<ContentCardTableItem[]>(() => {
|
|||||||
toggleDisabled: ctx.isBusy.value,
|
toggleDisabled: ctx.isBusy.value,
|
||||||
toggleDisabledTooltip: ctx.isBusy.value ? (ctx.busyMessage?.value ?? null) : null,
|
toggleDisabledTooltip: ctx.isBusy.value ? (ctx.busyMessage?.value ?? null) : null,
|
||||||
installing: item.installing === true,
|
installing: item.installing === true,
|
||||||
|
installProgress: item.installProgress,
|
||||||
hasUpdate: base.hasUpdate ?? item.has_update,
|
hasUpdate: base.hasUpdate ?? item.has_update,
|
||||||
isClientOnly:
|
isClientOnly:
|
||||||
isClientOnlyEnvironment(item.environment) ||
|
isClientOnlyEnvironment(item.environment) ||
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export interface ContentCardTableItem {
|
|||||||
toggleDisabled?: boolean
|
toggleDisabled?: boolean
|
||||||
toggleDisabledTooltip?: string | null
|
toggleDisabledTooltip?: string | null
|
||||||
installing?: boolean
|
installing?: boolean
|
||||||
|
installProgress?: number | null
|
||||||
hasUpdate?: boolean
|
hasUpdate?: boolean
|
||||||
isClientOnly?: boolean
|
isClientOnly?: boolean
|
||||||
clientWarning?: ClientWarningType | null
|
clientWarning?: ClientWarningType | null
|
||||||
@@ -91,6 +92,7 @@ export interface ContentItem extends Omit<
|
|||||||
pack_client_retained?: boolean
|
pack_client_retained?: boolean
|
||||||
pack_client_depends?: boolean
|
pack_client_depends?: boolean
|
||||||
installing?: boolean
|
installing?: boolean
|
||||||
|
installProgress?: number | null
|
||||||
source_kind?: ContentSourceKind | null
|
source_kind?: ContentSourceKind | null
|
||||||
external?: boolean
|
external?: boolean
|
||||||
external_url?: string
|
external_url?: string
|
||||||
|
|||||||
+243
-33
@@ -115,8 +115,16 @@ const messages = defineMessages({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const client = injectModrinthClient()
|
const client = injectModrinthClient()
|
||||||
const { server, serverId, worldId, busyReasons, isSyncingContent, uploadState, cancelUpload } =
|
const {
|
||||||
injectModrinthServerContext()
|
server,
|
||||||
|
serverId,
|
||||||
|
worldId,
|
||||||
|
busyReasons,
|
||||||
|
isSyncingContent,
|
||||||
|
installProgressItems,
|
||||||
|
uploadState,
|
||||||
|
cancelUpload,
|
||||||
|
} = injectModrinthServerContext()
|
||||||
const contentUploadSession = useUploadSessionUpload({
|
const contentUploadSession = useUploadSessionUpload({
|
||||||
client,
|
client,
|
||||||
scope: 'content',
|
scope: 'content',
|
||||||
@@ -202,6 +210,45 @@ const setupActionBusyMessage = computed(() => {
|
|||||||
return filteredReasons.length > 0 ? formatMessage(filteredReasons[0].reason) : null
|
return filteredReasons.length > 0 ? formatMessage(filteredReasons[0].reason) : null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const currentWorldInstallProgressItems = computed(() =>
|
||||||
|
installProgressItems.value.filter((item) => item.world_id === worldId.value),
|
||||||
|
)
|
||||||
|
const isIndividualContentSync = ref(false)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[currentWorldInstallProgressItems, isSyncingContent],
|
||||||
|
([items, syncing]) => {
|
||||||
|
if (!syncing) {
|
||||||
|
isIndividualContentSync.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (items.some((item) => item.key.type !== 'file')) {
|
||||||
|
isIndividualContentSync.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (items.length > 0) {
|
||||||
|
isIndividualContentSync.value = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
const contentBusyReasons = computed(() => {
|
||||||
|
if (!isIndividualContentSync.value) return busyReasons.value
|
||||||
|
return busyReasons.value.filter(
|
||||||
|
(reason) =>
|
||||||
|
reason.reason.id !== 'servers.busy.installing' &&
|
||||||
|
reason.reason.id !== 'servers.busy.syncing-content',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const contentActionDisabled = computed(() => !canSetup.value || contentBusyReasons.value.length > 0)
|
||||||
|
const contentActionBusyMessage = computed(() => {
|
||||||
|
if (!canSetup.value) return permissionDeniedMessage.value
|
||||||
|
return contentBusyReasons.value.length > 0
|
||||||
|
? formatMessage(contentBusyReasons.value[0].reason)
|
||||||
|
: null
|
||||||
|
})
|
||||||
|
|
||||||
const modpackProjectId = computed(() => {
|
const modpackProjectId = computed(() => {
|
||||||
const spec = contentQuery.data.value?.modpack?.spec
|
const spec = contentQuery.data.value?.modpack?.spec
|
||||||
return spec?.platform === 'modrinth' ? spec.project_id : null
|
return spec?.platform === 'modrinth' ? spec.project_id : null
|
||||||
@@ -308,6 +355,9 @@ const modpack = computed<ContentModpackData | null>(() => {
|
|||||||
header: 'categories',
|
header: 'categories',
|
||||||
})) as ContentModpackCardCategory[],
|
})) as ContentModpackCardCategory[],
|
||||||
hasUpdate: !!mp.has_update || !!newestModpackUpdateVersion.value,
|
hasUpdate: !!mp.has_update || !!newestModpackUpdateVersion.value,
|
||||||
|
disabled: setupActionDisabled.value,
|
||||||
|
disabledText:
|
||||||
|
setupActionBusyMessage.value ?? formatMessage(commonMessages.installingLabel),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -332,6 +382,94 @@ const addonLookup = computed(() => {
|
|||||||
return map
|
return map
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function normalizeInstallFilename(filename: string) {
|
||||||
|
return filename.endsWith('.disabled') ? filename.slice(0, -'.disabled'.length) : filename
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileInstallProgressByFilename = computed(() => {
|
||||||
|
const progressByFilename = new Map<string, Archon.Websocket.v0.InstallProgressItem>()
|
||||||
|
for (const item of currentWorldInstallProgressItems.value) {
|
||||||
|
if (item.key.type === 'file') {
|
||||||
|
progressByFilename.set(normalizeInstallFilename(item.key.filename), item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return progressByFilename
|
||||||
|
})
|
||||||
|
const completedInstallFilenames = ref<Set<string>>(new Set())
|
||||||
|
|
||||||
|
watch(
|
||||||
|
fileInstallProgressByFilename,
|
||||||
|
(progressByFilename, previousProgressByFilename) => {
|
||||||
|
const completed = new Set(completedInstallFilenames.value)
|
||||||
|
let shouldRefreshContent = false
|
||||||
|
if (previousProgressByFilename) {
|
||||||
|
for (const filename of previousProgressByFilename.keys()) {
|
||||||
|
if (!progressByFilename.has(filename)) {
|
||||||
|
if (!completed.has(filename)) shouldRefreshContent = true
|
||||||
|
completed.add(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [filename, item] of progressByFilename) {
|
||||||
|
if (item.error != null || item.progress === 100) {
|
||||||
|
if (item.error == null && !completed.has(filename)) shouldRefreshContent = true
|
||||||
|
completed.add(filename)
|
||||||
|
} else {
|
||||||
|
completed.delete(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completedInstallFilenames.value = completed
|
||||||
|
if (shouldRefreshContent && !contentQuery.isFetching.value) {
|
||||||
|
void contentQuery.refetch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
function getContentItemInstallFilename(item: ContentItem) {
|
||||||
|
const filename = item.version?.file_name || item.file_name
|
||||||
|
return normalizeInstallFilename(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContentItemInstallProgress(item: ContentItem) {
|
||||||
|
return fileInstallProgressByFilename.value.get(getContentItemInstallFilename(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileInstallProgressToContentItem(
|
||||||
|
item: Archon.Websocket.v0.InstallProgressItem,
|
||||||
|
key: Archon.Websocket.v0.InstallProgressFileKey,
|
||||||
|
): ContentItem {
|
||||||
|
const filename = key.filename
|
||||||
|
const extensionIndex = filename.lastIndexOf('.')
|
||||||
|
const title = extensionIndex > 0 ? filename.slice(0, extensionIndex) : filename
|
||||||
|
const projectType =
|
||||||
|
key.parent_directory === 'plugins'
|
||||||
|
? 'plugin'
|
||||||
|
: key.parent_directory === 'datapacks'
|
||||||
|
? 'datapack'
|
||||||
|
: 'mod'
|
||||||
|
return {
|
||||||
|
id: `installing:${item.id}`,
|
||||||
|
file_name: filename,
|
||||||
|
project: {
|
||||||
|
id: item.id,
|
||||||
|
slug: filename,
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
id: item.id,
|
||||||
|
version_number: formatMessage(commonMessages.installingLabel),
|
||||||
|
file_name: filename,
|
||||||
|
},
|
||||||
|
enabled: true,
|
||||||
|
project_type: projectType,
|
||||||
|
has_update: false,
|
||||||
|
update_version_id: null,
|
||||||
|
installing: true,
|
||||||
|
installProgress: item.progress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const pendingServerContentInstalls = ref<PendingServerContentInstall[]>([])
|
const pendingServerContentInstalls = ref<PendingServerContentInstall[]>([])
|
||||||
const lastStableContentKeys = ref<Set<string>>(new Set())
|
const lastStableContentKeys = ref<Set<string>>(new Set())
|
||||||
const contentInstallBaselineKeys = ref<Set<string> | null>(null)
|
const contentInstallBaselineKeys = ref<Set<string> | null>(null)
|
||||||
@@ -347,7 +485,19 @@ const { pause: pausePendingInstallPoll, resume: resumePendingInstallPoll } = use
|
|||||||
)
|
)
|
||||||
|
|
||||||
function syncPendingServerContentInstalls() {
|
function syncPendingServerContentInstalls() {
|
||||||
pendingServerContentInstalls.value = readPendingServerContentInstalls(serverId, worldId.value)
|
const pendingInstalls = readPendingServerContentInstalls(serverId, worldId.value)
|
||||||
|
pendingServerContentInstalls.value = pendingInstalls
|
||||||
|
|
||||||
|
const completed = new Set(completedInstallFilenames.value)
|
||||||
|
let changed = false
|
||||||
|
for (const item of pendingInstalls) {
|
||||||
|
if (item.fileName) {
|
||||||
|
changed = completed.delete(normalizeInstallFilename(item.fileName)) || changed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
completedInstallFilenames.value = completed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePendingServerContentInstallsChanged(event: Event) {
|
function handlePendingServerContentInstallsChanged(event: Event) {
|
||||||
@@ -580,12 +730,19 @@ const rawContentItems = computed<ContentItem[]>(() => {
|
|||||||
(addon.version?.id ? pendingInstallByVersionId.get(addon.version.id) : null) ??
|
(addon.version?.id ? pendingInstallByVersionId.get(addon.version.id) : null) ??
|
||||||
pendingInstallByFileName.get(addon.filename) ??
|
pendingInstallByFileName.get(addon.filename) ??
|
||||||
null
|
null
|
||||||
const installing = !!pendingItem || installingContentKeys.has(getAddonInstallKey(addon))
|
const installProgress = getContentItemInstallProgress(contentItem)
|
||||||
|
const installFilename = getContentItemInstallFilename(contentItem)
|
||||||
|
const installing =
|
||||||
|
installProgress != null
|
||||||
|
? installProgress.error == null && installProgress.progress !== 100
|
||||||
|
: !completedInstallFilenames.value.has(installFilename) &&
|
||||||
|
(!!pendingItem || installingContentKeys.has(getAddonInstallKey(addon)))
|
||||||
|
|
||||||
if (!installing || !pendingItem) {
|
if (!installing || !pendingItem) {
|
||||||
return {
|
return {
|
||||||
...contentItem,
|
...contentItem,
|
||||||
installing,
|
installing,
|
||||||
|
installProgress: installing ? installProgress?.progress : undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,10 +768,38 @@ const rawContentItems = computed<ContentItem[]>(() => {
|
|||||||
},
|
},
|
||||||
owner: pendingContentItem.owner ?? contentItem.owner,
|
owner: pendingContentItem.owner ?? contentItem.owner,
|
||||||
installing,
|
installing,
|
||||||
|
installProgress: installProgress?.progress,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return [...addonItems, ...pendingItems]
|
const pendingDisplayItems = pendingItems.map((item) => {
|
||||||
|
const installProgress = getContentItemInstallProgress(item)
|
||||||
|
const installing =
|
||||||
|
installProgress != null
|
||||||
|
? installProgress.error == null && installProgress.progress !== 100
|
||||||
|
: !completedInstallFilenames.value.has(getContentItemInstallFilename(item))
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
installing,
|
||||||
|
installProgress: installing ? installProgress?.progress : undefined,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const displayedInstallFilenames = new Set(
|
||||||
|
[...addonItems, ...pendingDisplayItems].map(getContentItemInstallFilename),
|
||||||
|
)
|
||||||
|
const progressOnlyItems = currentWorldInstallProgressItems.value.flatMap((item) => {
|
||||||
|
if (
|
||||||
|
item.key.type !== 'file' ||
|
||||||
|
item.error != null ||
|
||||||
|
item.progress === 100 ||
|
||||||
|
displayedInstallFilenames.has(normalizeInstallFilename(item.key.filename))
|
||||||
|
) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return [fileInstallProgressToContentItem(item, item.key)]
|
||||||
|
})
|
||||||
|
|
||||||
|
return [...addonItems, ...pendingDisplayItems, ...progressOnlyItems]
|
||||||
})
|
})
|
||||||
|
|
||||||
const displayedContentItems = ref<ContentItem[]>([])
|
const displayedContentItems = ref<ContentItem[]>([])
|
||||||
@@ -637,13 +822,31 @@ function getContentItemId(item: ContentItem) {
|
|||||||
|
|
||||||
function mergeFragileContentItems(items: ContentItem[]) {
|
function mergeFragileContentItems(items: ContentItem[]) {
|
||||||
const nextItems = new Map(items.map((item) => [getContentItemDisplayKey(item), item]))
|
const nextItems = new Map(items.map((item) => [getContentItemDisplayKey(item), item]))
|
||||||
const mergedItems = displayedContentItems.value.map((item) => {
|
const mergedItems = displayedContentItems.value.flatMap((item) => {
|
||||||
const key = getContentItemDisplayKey(item)
|
let nextKey = getContentItemDisplayKey(item)
|
||||||
const nextItem = nextItems.get(key)
|
let nextItem = nextItems.get(nextKey)
|
||||||
if (!nextItem) return item
|
if (!nextItem && item.installing) {
|
||||||
|
const installFilename = getContentItemInstallFilename(item)
|
||||||
|
const matchingEntry = Array.from(nextItems.entries()).find(
|
||||||
|
([, candidate]) => getContentItemInstallFilename(candidate) === installFilename,
|
||||||
|
)
|
||||||
|
if (matchingEntry) {
|
||||||
|
nextKey = matchingEntry[0]
|
||||||
|
nextItem = matchingEntry[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!nextItem) {
|
||||||
|
if (
|
||||||
|
item.installing &&
|
||||||
|
completedInstallFilenames.value.has(getContentItemInstallFilename(item))
|
||||||
|
) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return [item]
|
||||||
|
}
|
||||||
|
|
||||||
nextItems.delete(key)
|
nextItems.delete(nextKey)
|
||||||
return nextItem
|
return [nextItem]
|
||||||
})
|
})
|
||||||
|
|
||||||
return [...mergedItems, ...nextItems.values()]
|
return [...mergedItems, ...nextItems.values()]
|
||||||
@@ -658,9 +861,7 @@ watch(
|
|||||||
],
|
],
|
||||||
([items, syncing, isFetching, isLoading]) => {
|
([items, syncing, isFetching, isLoading]) => {
|
||||||
if (syncing) {
|
if (syncing) {
|
||||||
if (items.length > 0) {
|
displayedContentItems.value = mergeFragileContentItems(items)
|
||||||
displayedContentItems.value = mergeFragileContentItems(items)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,6 +903,7 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
worldId,
|
worldId,
|
||||||
() => {
|
() => {
|
||||||
|
completedInstallFilenames.value = new Set()
|
||||||
syncPendingServerContentInstalls()
|
syncPendingServerContentInstalls()
|
||||||
syncContentInstallKeys()
|
syncContentInstallKeys()
|
||||||
void flushStoredServerInstalls()
|
void flushStoredServerInstalls()
|
||||||
@@ -793,14 +995,14 @@ const toggleMutation = useMutation({
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function handleToggleEnabled(item: ContentItem) {
|
async function handleToggleEnabled(item: ContentItem) {
|
||||||
if (setupActionDisabled.value) return
|
if (contentActionDisabled.value) return
|
||||||
const addon = addonLookup.value.get(item.file_name)
|
const addon = addonLookup.value.get(item.file_name)
|
||||||
if (!addon) return
|
if (!addon) return
|
||||||
await toggleMutation.mutateAsync({ addon })
|
await toggleMutation.mutateAsync({ addon })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDeleteItem(item: ContentItem) {
|
async function handleDeleteItem(item: ContentItem) {
|
||||||
if (setupActionDisabled.value) return
|
if (contentActionDisabled.value) return
|
||||||
const addon = addonLookup.value.get(item.file_name)
|
const addon = addonLookup.value.get(item.file_name)
|
||||||
if (!addon) return
|
if (!addon) return
|
||||||
await deleteMutation.mutateAsync({ addon })
|
await deleteMutation.mutateAsync({ addon })
|
||||||
@@ -808,6 +1010,7 @@ async function handleDeleteItem(item: ContentItem) {
|
|||||||
|
|
||||||
function itemsToAddonRequests(items: ContentItem[]): Archon.Content.v1.RemoveAddonRequest[] {
|
function itemsToAddonRequests(items: ContentItem[]): Archon.Content.v1.RemoveAddonRequest[] {
|
||||||
return items.flatMap((item) => {
|
return items.flatMap((item) => {
|
||||||
|
if (item.installing) return []
|
||||||
const addon = addonLookup.value.get(item.file_name)
|
const addon = addonLookup.value.get(item.file_name)
|
||||||
if (!addon) return []
|
if (!addon) return []
|
||||||
return [{ filename: addon.filename, kind: addon.kind }]
|
return [{ filename: addon.filename, kind: addon.kind }]
|
||||||
@@ -815,7 +1018,7 @@ function itemsToAddonRequests(items: ContentItem[]): Archon.Content.v1.RemoveAdd
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleBulkDelete(items: ContentItem[]) {
|
async function handleBulkDelete(items: ContentItem[]) {
|
||||||
if (setupActionDisabled.value) return
|
if (contentActionDisabled.value) return
|
||||||
const requests = itemsToAddonRequests(items)
|
const requests = itemsToAddonRequests(items)
|
||||||
if (requests.length === 0) return
|
if (requests.length === 0) return
|
||||||
try {
|
try {
|
||||||
@@ -831,7 +1034,7 @@ async function handleBulkDelete(items: ContentItem[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleBulkEnable(items: ContentItem[]) {
|
async function handleBulkEnable(items: ContentItem[]) {
|
||||||
if (setupActionDisabled.value) return
|
if (contentActionDisabled.value) return
|
||||||
const requests = itemsToAddonRequests(items)
|
const requests = itemsToAddonRequests(items)
|
||||||
if (requests.length === 0) return
|
if (requests.length === 0) return
|
||||||
try {
|
try {
|
||||||
@@ -847,7 +1050,7 @@ async function handleBulkEnable(items: ContentItem[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleBulkDisable(items: ContentItem[]) {
|
async function handleBulkDisable(items: ContentItem[]) {
|
||||||
if (setupActionDisabled.value) return
|
if (contentActionDisabled.value) return
|
||||||
const requests = itemsToAddonRequests(items)
|
const requests = itemsToAddonRequests(items)
|
||||||
if (requests.length === 0) return
|
if (requests.length === 0) return
|
||||||
try {
|
try {
|
||||||
@@ -916,7 +1119,7 @@ const currentLoader = computed(
|
|||||||
)
|
)
|
||||||
|
|
||||||
function handleBrowseContent() {
|
function handleBrowseContent() {
|
||||||
if (setupActionDisabled.value) return
|
if (contentActionDisabled.value) return
|
||||||
const contentType = type.value
|
const contentType = type.value
|
||||||
if (browseServerContent && ['mod', 'plugin', 'datapack'].includes(contentType)) {
|
if (browseServerContent && ['mod', 'plugin', 'datapack'].includes(contentType)) {
|
||||||
browseServerContent({
|
browseServerContent({
|
||||||
@@ -934,7 +1137,7 @@ function handleBrowseContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleUploadFiles() {
|
function handleUploadFiles() {
|
||||||
if (setupActionDisabled.value) return
|
if (contentActionDisabled.value) return
|
||||||
const input = document.createElement('input')
|
const input = document.createElement('input')
|
||||||
input.type = 'file'
|
input.type = 'file'
|
||||||
input.multiple = true
|
input.multiple = true
|
||||||
@@ -1074,7 +1277,7 @@ async function handleViewModpackContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleModpackContentToggle(item: ContentItem) {
|
async function handleModpackContentToggle(item: ContentItem) {
|
||||||
if (setupActionDisabled.value) return
|
if (contentActionDisabled.value) return
|
||||||
const addon = addonLookup.value.get(item.file_name)
|
const addon = addonLookup.value.get(item.file_name)
|
||||||
if (!addon) return
|
if (!addon) return
|
||||||
modpackContentModal.value?.updateItem(item.file_name, { disabled: true })
|
modpackContentModal.value?.updateItem(item.file_name, { disabled: true })
|
||||||
@@ -1105,7 +1308,7 @@ async function handleModpackContentToggle(item: ContentItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleModpackBulkToggle(items: ContentItem[], enable: boolean) {
|
async function handleModpackBulkToggle(items: ContentItem[], enable: boolean) {
|
||||||
if (setupActionDisabled.value) return
|
if (contentActionDisabled.value) return
|
||||||
const requests = itemsToAddonRequests(items)
|
const requests = itemsToAddonRequests(items)
|
||||||
if (requests.length === 0) return
|
if (requests.length === 0) return
|
||||||
|
|
||||||
@@ -1172,9 +1375,9 @@ async function handleModpackUnlinkConfirm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleBulkUpdate(items: ContentItem[]) {
|
async function handleBulkUpdate(items: ContentItem[]) {
|
||||||
if (setupActionDisabled.value) return
|
if (contentActionDisabled.value) return
|
||||||
const addons = items
|
const addons = items
|
||||||
.filter((item) => item.has_update)
|
.filter((item) => item.has_update && !item.installing)
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
filename: item.file_name,
|
filename: item.file_name,
|
||||||
version_id: item.update_version_id ?? undefined,
|
version_id: item.update_version_id ?? undefined,
|
||||||
@@ -1218,6 +1421,7 @@ async function handleSwitchVersion(item: ContentItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleModpackUpdate() {
|
async function handleModpackUpdate() {
|
||||||
|
if (setupActionDisabled.value) return
|
||||||
const mp = contentQuery.data.value?.modpack
|
const mp = contentQuery.data.value?.modpack
|
||||||
if (!mp || mp.spec.platform !== 'modrinth') return
|
if (!mp || mp.spec.platform !== 'modrinth') return
|
||||||
|
|
||||||
@@ -1272,8 +1476,8 @@ function resetUpdateState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleModalUpdate(selectedVersion: Labrinth.Versions.v2.Version, event?: MouseEvent) {
|
function handleModalUpdate(selectedVersion: Labrinth.Versions.v2.Version, event?: MouseEvent) {
|
||||||
if (setupActionDisabled.value) return
|
|
||||||
if (updatingModpack.value) {
|
if (updatingModpack.value) {
|
||||||
|
if (setupActionDisabled.value) return
|
||||||
pendingModpackUpdateVersion.value = selectedVersion
|
pendingModpackUpdateVersion.value = selectedVersion
|
||||||
|
|
||||||
const mpSpec = contentQuery.data.value?.modpack?.spec
|
const mpSpec = contentQuery.data.value?.modpack?.spec
|
||||||
@@ -1294,6 +1498,7 @@ function handleModalUpdate(selectedVersion: Labrinth.Versions.v2.Version, event?
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (contentActionDisabled.value) return
|
||||||
performUpdate(selectedVersion)
|
performUpdate(selectedVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1310,7 +1515,10 @@ function setAddonInstalling(filename: string, installing: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function performUpdate(selectedVersion: Labrinth.Versions.v2.Version) {
|
async function performUpdate(selectedVersion: Labrinth.Versions.v2.Version) {
|
||||||
if (setupActionDisabled.value) return
|
if (
|
||||||
|
(updatingModpack.value && setupActionDisabled.value) ||
|
||||||
|
(!updatingModpack.value && contentActionDisabled.value)
|
||||||
|
) return
|
||||||
const item = updatingProject.value
|
const item = updatingProject.value
|
||||||
if (item) {
|
if (item) {
|
||||||
setAddonInstalling(item.file_name, true)
|
setAddonInstalling(item.file_name, true)
|
||||||
@@ -1389,8 +1597,8 @@ provideContentManager({
|
|||||||
error: computed(() => contentQuery.error.value ?? null),
|
error: computed(() => contentQuery.error.value ?? null),
|
||||||
modpack,
|
modpack,
|
||||||
isPackLocked: ref(false),
|
isPackLocked: ref(false),
|
||||||
isBusy: setupActionDisabled,
|
isBusy: contentActionDisabled,
|
||||||
busyMessage: setupActionBusyMessage,
|
busyMessage: contentActionBusyMessage,
|
||||||
disableAddContent: computed(() => !canSetup.value),
|
disableAddContent: computed(() => !canSetup.value),
|
||||||
disableAddContentTooltip: permissionDeniedMessage.value,
|
disableAddContentTooltip: permissionDeniedMessage.value,
|
||||||
contentTypeLabel: type,
|
contentTypeLabel: type,
|
||||||
@@ -1462,8 +1670,8 @@ provideContentManager({
|
|||||||
:modpack-name="modpack?.project.title"
|
:modpack-name="modpack?.project.title"
|
||||||
:modpack-icon-url="modpack?.project.icon_url"
|
:modpack-icon-url="modpack?.project.icon_url"
|
||||||
enable-toggle
|
enable-toggle
|
||||||
:action-disabled="setupActionDisabled"
|
:action-disabled="contentActionDisabled"
|
||||||
:action-disabled-tooltip="setupActionBusyMessage ?? undefined"
|
:action-disabled-tooltip="contentActionBusyMessage ?? undefined"
|
||||||
@update:enabled="handleModpackContentToggle"
|
@update:enabled="handleModpackContentToggle"
|
||||||
@bulk:enable="handleModpackBulkToggle($event, true)"
|
@bulk:enable="handleModpackBulkToggle($event, true)"
|
||||||
@bulk:disable="handleModpackBulkToggle($event, false)"
|
@bulk:disable="handleModpackBulkToggle($event, false)"
|
||||||
@@ -1494,8 +1702,10 @@ provideContentManager({
|
|||||||
"
|
"
|
||||||
:loading="loadingVersions"
|
:loading="loadingVersions"
|
||||||
:loading-changelog="loadingChangelog"
|
:loading-changelog="loadingChangelog"
|
||||||
:action-disabled="setupActionDisabled"
|
:action-disabled="updatingModpack ? setupActionDisabled : contentActionDisabled"
|
||||||
:action-disabled-tooltip="setupActionBusyMessage ?? undefined"
|
:action-disabled-tooltip="
|
||||||
|
(updatingModpack ? setupActionBusyMessage : contentActionBusyMessage) ?? undefined
|
||||||
|
"
|
||||||
target-type="instance"
|
target-type="instance"
|
||||||
@update="handleModalUpdate"
|
@update="handleModalUpdate"
|
||||||
@cancel="resetUpdateState"
|
@cancel="resetUpdateState"
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export interface ModrinthServerContext {
|
|||||||
readonly isServerRunning: ComputedRef<boolean>
|
readonly isServerRunning: ComputedRef<boolean>
|
||||||
readonly stats: Ref<ServerStats>
|
readonly stats: Ref<ServerStats>
|
||||||
readonly uptimeSeconds: Ref<number>
|
readonly uptimeSeconds: Ref<number>
|
||||||
|
readonly installProgressItems: Ref<Archon.Websocket.v0.InstallProgressItem[]>
|
||||||
|
|
||||||
// Content sync state
|
// Content sync state
|
||||||
readonly isSyncingContent: Ref<boolean>
|
readonly isSyncingContent: Ref<boolean>
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ const meta = {
|
|||||||
isServerRunning: computed(() => true),
|
isServerRunning: computed(() => true),
|
||||||
stats,
|
stats,
|
||||||
uptimeSeconds: ref(0),
|
uptimeSeconds: ref(0),
|
||||||
|
installProgressItems: ref<Archon.Websocket.v0.InstallProgressItem[]>([]),
|
||||||
isSyncingContent: ref(false),
|
isSyncingContent: ref(false),
|
||||||
busyReasons: computed(() => []),
|
busyReasons: computed(() => []),
|
||||||
fsAuth: ref(null),
|
fsAuth: ref(null),
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ const meta = {
|
|||||||
isServerRunning: computed(() => true),
|
isServerRunning: computed(() => true),
|
||||||
stats,
|
stats,
|
||||||
uptimeSeconds: ref(0),
|
uptimeSeconds: ref(0),
|
||||||
|
installProgressItems: ref<Archon.Websocket.v0.InstallProgressItem[]>([]),
|
||||||
isSyncingContent: ref(false),
|
isSyncingContent: ref(false),
|
||||||
busyReasons: computed(() => [
|
busyReasons: computed(() => [
|
||||||
{ reason: defineMessage({ id: 's.bg', defaultMessage: 'Background task running' }) },
|
{ reason: defineMessage({ id: 's.bg', defaultMessage: 'Background task running' }) },
|
||||||
|
|||||||
Reference in New Issue
Block a user