mirror of
https://github.com/modrinth/code.git
synced 2026-08-27 10:04:52 +00:00
feat: sync individual content installation states on panel (#6909)
* feat: sync individual content installation states on panel * feat: improve handling * fix: lint * fix: ws connection duplication + disconnecting during browse * fix: sse feats * fix: qa * fix: qa * fix: prepr * fix: bug * fix: lint
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<Admonition
|
||||
:type="contentError ? 'critical' : 'info'"
|
||||
:dismissible="dismissible"
|
||||
v-if="installation"
|
||||
:type="installation.status === 'failed' ? 'critical' : 'info'"
|
||||
:dismissible="installation.status === 'failed'"
|
||||
:progress="progressValue"
|
||||
progress-color="blue"
|
||||
:waiting="isWaiting"
|
||||
@@ -10,23 +11,8 @@
|
||||
<template #header>
|
||||
{{ headerLabel }}
|
||||
</template>
|
||||
<template v-if="contentError">
|
||||
{{ errorLabel }}
|
||||
</template>
|
||||
<template v-else-if="effectivePhase">{{ phaseLabel }}</template>
|
||||
<div v-else class="ticker-container">
|
||||
<div class="ticker-content">
|
||||
<div
|
||||
v-for="(message, index) in tickerMessages"
|
||||
:key="message"
|
||||
class="ticker-item"
|
||||
:class="{ active: index === currentIndex % tickerMessages.length }"
|
||||
>
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="contentError" #top-right-actions>
|
||||
{{ installation.status === 'failed' ? errorLabel : descriptionLabel }}
|
||||
<template v-if="installation.status === 'failed'" #top-right-actions>
|
||||
<Button
|
||||
v-tooltip="retryDisabled ? retryDisabledTooltip : undefined"
|
||||
type="outlined"
|
||||
@@ -44,29 +30,17 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RotateCounterClockwiseIcon } from '@modrinth/assets'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
import { Button } from '#ui/components/base/buttons'
|
||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
import { injectModrinthServerContext } from '#ui/providers'
|
||||
import { commonMessages } from '#ui/utils/common-messages'
|
||||
import { formatLoaderLabel } from '#ui/utils/loaders'
|
||||
|
||||
import Admonition from '../base/Admonition.vue'
|
||||
|
||||
export interface SyncProgress {
|
||||
phase: 'Analyzing' | 'InstallingPack' | 'InstallingLoader' | 'Addons'
|
||||
percent: number
|
||||
}
|
||||
|
||||
export interface ContentError {
|
||||
step: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
progress?: SyncProgress | null
|
||||
fallbackPhase?: SyncProgress['phase'] | null
|
||||
contentError?: ContentError | null
|
||||
dismissible?: boolean
|
||||
defineProps<{
|
||||
retryDisabled?: boolean
|
||||
retryDisabledTooltip?: string
|
||||
}>()
|
||||
@@ -77,191 +51,182 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { installation } = injectModrinthServerContext()
|
||||
|
||||
const messages = defineMessages({
|
||||
errorHeader: {
|
||||
id: 'servers.installing-banner.error.header',
|
||||
defaultMessage: 'Installation failed',
|
||||
},
|
||||
preparingHeader: {
|
||||
id: 'servers.installing-banner.preparing.header',
|
||||
defaultMessage: "We're preparing your server",
|
||||
platformErrorHeader: {
|
||||
id: 'servers.installing-banner.error.header.platform',
|
||||
defaultMessage: 'Failed to install {loader} {loaderVersion} for Minecraft {gameVersion}',
|
||||
},
|
||||
invalidLoaderVersionError: {
|
||||
id: 'servers.installing-banner.error.invalid-loader-version',
|
||||
defaultMessage:
|
||||
'The specified loader or Minecraft version could not be installed. It may be invalid or unsupported.',
|
||||
platformWithoutVersionErrorHeader: {
|
||||
id: 'servers.installing-banner.error.header.platform-without-version',
|
||||
defaultMessage: 'Failed to install {loader} for Minecraft {gameVersion}',
|
||||
},
|
||||
unsupportedLoaderVersionError: {
|
||||
id: 'servers.installing-banner.error.unsupported-loader-version',
|
||||
defaultMessage: 'This version of Minecraft or loader is not yet supported by Modrinth Hosting.',
|
||||
minecraftErrorHeader: {
|
||||
id: 'servers.installing-banner.error.header.minecraft',
|
||||
defaultMessage: 'Failed to install Minecraft {version}',
|
||||
},
|
||||
internalPlatformError: {
|
||||
id: 'servers.installing-banner.error.internal-platform',
|
||||
defaultMessage: 'An internal error occurred while installing the platform. Please try again.',
|
||||
modpackErrorHeader: {
|
||||
id: 'servers.installing-banner.error.header.modpack',
|
||||
defaultMessage: 'Failed to install modpack',
|
||||
},
|
||||
noPrimaryFileError: {
|
||||
id: 'servers.installing-banner.error.no-primary-file',
|
||||
defaultMessage:
|
||||
'This modpack version does not include a downloadable file. It may have been packaged incorrectly.',
|
||||
},
|
||||
modpackInstallFailedError: {
|
||||
id: 'servers.installing-banner.error.modpack-install-failed',
|
||||
defaultMessage: 'The modpack could not be installed. It may be corrupted or incompatible.',
|
||||
localModpackErrorHeader: {
|
||||
id: 'servers.installing-banner.error.header.local-modpack',
|
||||
defaultMessage: 'Failed to install {filename}',
|
||||
},
|
||||
unknownError: {
|
||||
id: 'servers.installing-banner.error.unknown',
|
||||
defaultMessage: 'An unexpected error occurred during installation.',
|
||||
},
|
||||
preparingHeader: {
|
||||
id: 'servers.installing-banner.preparing.header',
|
||||
defaultMessage: 'Preparing your server',
|
||||
},
|
||||
installingPlatform: {
|
||||
id: 'servers.installing-banner.phase.installing-platform',
|
||||
defaultMessage: 'Installing platform...',
|
||||
id: 'servers.installing-banner.installing-platform',
|
||||
defaultMessage: 'Installing {loader} for Minecraft {version}',
|
||||
},
|
||||
installingMinecraft: {
|
||||
id: 'servers.installing-banner.installing-minecraft',
|
||||
defaultMessage: 'Installing Minecraft {version}',
|
||||
},
|
||||
installingModpack: {
|
||||
id: 'servers.installing-banner.phase.installing-modpack',
|
||||
defaultMessage: 'Installing modpack...',
|
||||
id: 'servers.installing-banner.installing-modpack',
|
||||
defaultMessage: 'Installing modpack',
|
||||
},
|
||||
installingAddons: {
|
||||
id: 'servers.installing-banner.phase.installing-addons',
|
||||
defaultMessage: 'Installing addons...',
|
||||
installingLocalModpack: {
|
||||
id: 'servers.installing-banner.installing-local-modpack',
|
||||
defaultMessage: 'Installing {filename}',
|
||||
},
|
||||
tickerOrganizingFiles: {
|
||||
id: 'servers.installing-banner.ticker.organizing-files',
|
||||
defaultMessage: 'Organizing files...',
|
||||
preparingDescription: {
|
||||
id: 'servers.installing-banner.description.preparing',
|
||||
defaultMessage: 'Preparing your server...',
|
||||
},
|
||||
tickerDownloadingMods: {
|
||||
id: 'servers.installing-banner.ticker.downloading-mods',
|
||||
defaultMessage: 'Downloading mods...',
|
||||
applyingDescription: {
|
||||
id: 'servers.installing-banner.description.applying',
|
||||
defaultMessage: 'Applying your installation changes...',
|
||||
},
|
||||
tickerConfiguringServer: {
|
||||
id: 'servers.installing-banner.ticker.configuring-server',
|
||||
defaultMessage: 'Configuring server...',
|
||||
durationDescription: {
|
||||
id: 'servers.installing-banner.description.duration',
|
||||
defaultMessage: 'This installation may take several minutes...',
|
||||
},
|
||||
tickerSettingUpEnvironment: {
|
||||
id: 'servers.installing-banner.ticker.setting-up-environment',
|
||||
defaultMessage: 'Setting up environment...',
|
||||
controlsDescription: {
|
||||
id: 'servers.installing-banner.description.controls',
|
||||
defaultMessage: 'Server controls will unlock when installation finishes.',
|
||||
},
|
||||
tickerAddingJava: {
|
||||
id: 'servers.installing-banner.ticker.adding-java',
|
||||
defaultMessage: 'Adding Java...',
|
||||
stillWorkingDescription: {
|
||||
id: 'servers.installing-banner.description.still-working',
|
||||
defaultMessage: 'Still working—your installation is in progress...',
|
||||
},
|
||||
})
|
||||
|
||||
const errorLabel = computed(() => {
|
||||
const desc = props.contentError?.description?.toLowerCase()
|
||||
const step = props.contentError?.step
|
||||
|
||||
if (step === 'modloader') {
|
||||
if (desc === 'the specified version may be incorrect') {
|
||||
return formatMessage(messages.invalidLoaderVersionError)
|
||||
}
|
||||
if (desc === 'this version is not yet supported') {
|
||||
return formatMessage(messages.unsupportedLoaderVersionError)
|
||||
}
|
||||
if (desc === 'internal error') {
|
||||
return formatMessage(messages.internalPlatformError)
|
||||
}
|
||||
}
|
||||
|
||||
if (step === 'modpack') {
|
||||
if (desc?.includes('no primary file')) {
|
||||
return formatMessage(messages.noPrimaryFileError)
|
||||
}
|
||||
if (desc?.includes('failed to install')) {
|
||||
return formatMessage(messages.modpackInstallFailedError)
|
||||
}
|
||||
}
|
||||
|
||||
return props.contentError?.description ?? formatMessage(messages.unknownError)
|
||||
})
|
||||
|
||||
const effectivePhase = computed(() => props.progress?.phase ?? props.fallbackPhase ?? null)
|
||||
|
||||
const headerLabel = computed(() => {
|
||||
if (props.contentError) return formatMessage(messages.errorHeader)
|
||||
if (effectivePhase.value === 'Addons') return formatMessage(commonMessages.installingContentLabel)
|
||||
const current = installation.value
|
||||
if (!current) return ''
|
||||
|
||||
switch (current.key.type) {
|
||||
case 'platform': {
|
||||
if (current.key.platform === 'vanilla') {
|
||||
if (current.status === 'failed') {
|
||||
return formatMessage(messages.minecraftErrorHeader, {
|
||||
version: current.key.game_version,
|
||||
})
|
||||
}
|
||||
return formatMessage(messages.installingMinecraft, {
|
||||
version: current.key.game_version,
|
||||
})
|
||||
}
|
||||
if (current.status === 'failed') {
|
||||
if (!current.key.platform_version) {
|
||||
return formatMessage(messages.platformWithoutVersionErrorHeader, {
|
||||
loader: formatLoaderLabel(current.key.platform),
|
||||
gameVersion: current.key.game_version,
|
||||
})
|
||||
}
|
||||
return formatMessage(messages.platformErrorHeader, {
|
||||
loader: formatLoaderLabel(current.key.platform),
|
||||
loaderVersion: current.key.platform_version,
|
||||
gameVersion: current.key.game_version,
|
||||
})
|
||||
}
|
||||
return formatMessage(messages.installingPlatform, {
|
||||
loader: formatLoaderLabel(current.key.platform),
|
||||
version: current.key.game_version,
|
||||
})
|
||||
}
|
||||
case 'modrinth_modpack':
|
||||
return formatMessage(
|
||||
current.status === 'failed' ? messages.modpackErrorHeader : messages.installingModpack,
|
||||
)
|
||||
case 'local_modpack':
|
||||
return formatMessage(
|
||||
current.status === 'failed'
|
||||
? messages.localModpackErrorHeader
|
||||
: messages.installingLocalModpack,
|
||||
{
|
||||
filename: current.key.filename,
|
||||
},
|
||||
)
|
||||
case 'unknown':
|
||||
return formatMessage(
|
||||
current.status === 'failed' ? messages.errorHeader : messages.preparingHeader,
|
||||
)
|
||||
}
|
||||
|
||||
return formatMessage(messages.preparingHeader)
|
||||
})
|
||||
|
||||
const phaseLabel = computed(() => {
|
||||
switch (effectivePhase.value) {
|
||||
case 'InstallingLoader':
|
||||
return formatMessage(messages.installingPlatform)
|
||||
case 'InstallingPack':
|
||||
return formatMessage(messages.installingModpack)
|
||||
case 'Addons':
|
||||
return formatMessage(messages.installingAddons)
|
||||
const descriptionIndex = ref(0)
|
||||
const installationId = computed(() => installation.value?.id ?? null)
|
||||
|
||||
watch(installationId, () => {
|
||||
descriptionIndex.value = 0
|
||||
})
|
||||
|
||||
const descriptionLabel = computed(() => {
|
||||
switch (descriptionIndex.value) {
|
||||
case 0:
|
||||
return formatMessage(messages.preparingDescription)
|
||||
case 1:
|
||||
return formatMessage(messages.applyingDescription)
|
||||
case 2:
|
||||
return formatMessage(messages.durationDescription)
|
||||
default:
|
||||
return formatMessage(commonMessages.installingLabel)
|
||||
return descriptionIndex.value % 2 === 1
|
||||
? formatMessage(messages.controlsDescription)
|
||||
: formatMessage(messages.stillWorkingDescription)
|
||||
}
|
||||
})
|
||||
|
||||
const errorLabel = computed(() => installation.value?.error ?? formatMessage(messages.unknownError))
|
||||
|
||||
const progressValue = computed(() => {
|
||||
if (props.contentError) return undefined
|
||||
return props.progress ? props.progress.percent / 100 : 0
|
||||
const current = installation.value
|
||||
if (!current || current.status === 'failed') return undefined
|
||||
return current.progress == null ? 0 : current.progress / 100
|
||||
})
|
||||
|
||||
const isWaiting = computed(() => {
|
||||
if (props.contentError) return false
|
||||
return !props.progress || props.progress.percent <= 0
|
||||
const current = installation.value
|
||||
if (!current || current.status === 'failed') return false
|
||||
return current.progress == null || current.progress <= 0
|
||||
})
|
||||
|
||||
const tickerMessages = computed(() => [
|
||||
formatMessage(messages.tickerOrganizingFiles),
|
||||
formatMessage(messages.tickerDownloadingMods),
|
||||
formatMessage(messages.tickerConfiguringServer),
|
||||
formatMessage(messages.tickerSettingUpEnvironment),
|
||||
formatMessage(messages.tickerAddingJava),
|
||||
])
|
||||
|
||||
const currentIndex = ref(0)
|
||||
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
onMounted(() => {
|
||||
intervalId = setInterval(() => {
|
||||
currentIndex.value = (currentIndex.value + 1) % tickerMessages.value.length
|
||||
}, 3000)
|
||||
if (installation.value?.status === 'pending' || installation.value?.status === 'installing') {
|
||||
descriptionIndex.value += 1
|
||||
}
|
||||
}, 15_000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
if (intervalId) clearInterval(intervalId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ticker-container {
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ticker-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ticker-item {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
color: var(--color-secondary-text);
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
filter: blur(4px);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.ticker-item.active {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
filter: blur(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -131,6 +131,11 @@ async function onFlowComplete(ctx: CreationFlowContextValue) {
|
||||
await handleMrpackUpload(ctx.modpackFile.value, ctx.buildProperties())
|
||||
} else if (ctx.setupType.value === 'modpack' && ctx.modpackSelection.value) {
|
||||
debug('onFlowComplete: modpack selection path, calling installContent')
|
||||
serverContext.beginInstallation({
|
||||
type: 'modrinth_modpack',
|
||||
project_id: ctx.modpackSelection.value.projectId,
|
||||
version_id: ctx.modpackSelection.value.versionId,
|
||||
})
|
||||
await client.archon.content_v1.installContent(
|
||||
serverContext.serverId,
|
||||
serverContext.worldId.value!,
|
||||
@@ -159,6 +164,15 @@ async function onFlowComplete(ctx: CreationFlowContextValue) {
|
||||
apiLoader: toApiLoader(loader ?? 'vanilla'),
|
||||
})
|
||||
|
||||
serverContext.beginInstallation({
|
||||
type: 'platform',
|
||||
platform: (loader ?? 'vanilla') as Extract<
|
||||
Archon.Websocket.v0.InstallProgressKey,
|
||||
{ type: 'platform' }
|
||||
>['platform'],
|
||||
platform_version: loaderVersion,
|
||||
game_version: ctx.selectedGameVersion.value ?? '',
|
||||
})
|
||||
await client.archon.content_v1.installContent(
|
||||
serverContext.serverId,
|
||||
serverContext.worldId.value!,
|
||||
@@ -183,6 +197,7 @@ async function onFlowComplete(ctx: CreationFlowContextValue) {
|
||||
creationFlowRef.value?.hide()
|
||||
} catch (error) {
|
||||
debug('onFlowComplete: ERROR', error)
|
||||
serverContext.cancelOptimisticInstallation()
|
||||
if ((error as ModrinthApiError).statusCode === 429) {
|
||||
addNotification({
|
||||
title: formatMessage(messages.rateLimitTitle),
|
||||
@@ -210,6 +225,10 @@ async function handleMrpackUpload(file: File, properties: Archon.Content.v1.Prop
|
||||
{ softOverride: false },
|
||||
)
|
||||
await uploadProgressModal.value!.track(handle)
|
||||
serverContext.beginInstallation({
|
||||
type: 'local_modpack',
|
||||
filename: file.name,
|
||||
})
|
||||
emitReinstall()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import Admonition from '#ui/components/base/Admonition.vue'
|
||||
import StackedAdmonitions, {
|
||||
type StackedAdmonitionItem,
|
||||
} from '#ui/components/base/StackedAdmonitions.vue'
|
||||
import InstallingBanner, {
|
||||
type ContentError,
|
||||
type SyncProgress,
|
||||
} from '#ui/components/servers/InstallingBanner.vue'
|
||||
import InstallingBanner from '#ui/components/servers/InstallingBanner.vue'
|
||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
import { useServerBackupsQueue } from '#ui/composables/server-backups-queue'
|
||||
import { useServerPermissions } from '#ui/composables/server-permissions'
|
||||
@@ -20,13 +17,8 @@ import BackupAdmonition, { type BackupAdmonitionEntry } from './BackupAdmonition
|
||||
import FileOperationAdmonition from './FileOperationAdmonition.vue'
|
||||
import UploadAdmonition from './UploadAdmonition.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
syncProgress?: SyncProgress | null
|
||||
contentError?: ContentError | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'content-retry': []
|
||||
'installation-retry': []
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
@@ -58,28 +50,18 @@ const messages = defineMessages({
|
||||
const isOnContentTab = computed(() => route.path.includes('/content'))
|
||||
const isOnFilesTab = computed(() => route.path.includes('/files'))
|
||||
|
||||
const bannerCoversInstalling = computed(
|
||||
() =>
|
||||
ctx.server.value?.status === 'installing' ||
|
||||
ctx.isSyncingContent.value ||
|
||||
ctx.busyReasons.value.some(
|
||||
(r) =>
|
||||
r.reason.id === 'servers.busy.installing' || r.reason.id === 'servers.busy.syncing-content',
|
||||
),
|
||||
)
|
||||
|
||||
function isBackupReason(id: string) {
|
||||
return id === 'servers.busy.backup-creating' || id === 'servers.busy.backup-restoring'
|
||||
}
|
||||
|
||||
function isInstallingReason(id: string) {
|
||||
return id === 'servers.busy.installing' || id === 'servers.busy.syncing-content'
|
||||
return id === 'servers.busy.installing'
|
||||
}
|
||||
|
||||
const filteredBusyReasons = computed(() =>
|
||||
ctx.busyReasons.value.filter((r) => {
|
||||
if (isBackupReason(r.reason.id)) return false
|
||||
if (bannerCoversInstalling.value && isInstallingReason(r.reason.id)) return false
|
||||
if (isInstallingReason(r.reason.id)) return false
|
||||
return true
|
||||
}),
|
||||
)
|
||||
@@ -95,17 +77,6 @@ const filesBusyHeader = computed(() =>
|
||||
const dismissedIds = reactive(new Set<string>())
|
||||
const cancellingIds = reactive(new Set<string>())
|
||||
const uploadCancelling = ref(false)
|
||||
const dismissedContentErrorKey = ref<string | null>(null)
|
||||
|
||||
const contentErrorKey = computed(() =>
|
||||
props.contentError ? `${props.contentError.step}:${props.contentError.description}` : null,
|
||||
)
|
||||
|
||||
watch(contentErrorKey, (key) => {
|
||||
if (!key) {
|
||||
dismissedContentErrorKey.value = null
|
||||
}
|
||||
})
|
||||
|
||||
const backupAdmonitionEntries = computed<BackupAdmonitionEntry[]>(() => {
|
||||
const result: BackupAdmonitionEntry[] = []
|
||||
@@ -171,12 +142,7 @@ type ServerAdmonitionItem = StackedAdmonitionItem & {
|
||||
)
|
||||
|
||||
const showInstallingBanner = computed(() => {
|
||||
if (!ctx.server.value) return false
|
||||
const installing = bannerCoversInstalling.value || !!props.contentError
|
||||
if (!installing) return false
|
||||
if (contentErrorKey.value && dismissedContentErrorKey.value === contentErrorKey.value)
|
||||
return false
|
||||
return props.syncProgress?.phase !== 'Analyzing'
|
||||
return !!ctx.installation.value && ctx.installation.value.status !== 'complete'
|
||||
})
|
||||
|
||||
function fsOpType(op: FileOperation): StackedAdmonitionItem['type'] {
|
||||
@@ -210,10 +176,11 @@ const stackItems = computed<ServerAdmonitionItem[]>(() => {
|
||||
let sortIndex = 0
|
||||
|
||||
if (showInstallingBanner.value) {
|
||||
const failed = ctx.installation.value?.status === 'failed'
|
||||
out.push({
|
||||
id: 'installing',
|
||||
type: props.contentError ? 'critical' : 'info',
|
||||
dismissible: !!props.contentError,
|
||||
type: failed ? 'critical' : 'info',
|
||||
dismissible: failed,
|
||||
kind: 'installing',
|
||||
priority: 0,
|
||||
sortIndex: sortIndex++,
|
||||
@@ -365,8 +332,8 @@ async function onDismissAll() {
|
||||
const tasks: Promise<unknown>[] = []
|
||||
for (const it of stackItems.value) {
|
||||
if (!it.dismissible) continue
|
||||
if (it.kind === 'installing' && props.contentError) {
|
||||
onContentErrorDismiss()
|
||||
if (it.kind === 'installing') {
|
||||
onInstallationDismiss()
|
||||
} else if (it.kind === 'fs-op' && it.op.id) {
|
||||
const { op } = it
|
||||
if (op.state === 'done' || op.state?.startsWith('fail')) {
|
||||
@@ -385,9 +352,9 @@ function onFileOpDismiss(item: ServerAdmonitionItem) {
|
||||
}
|
||||
}
|
||||
|
||||
function onContentErrorDismiss() {
|
||||
if (contentErrorKey.value) {
|
||||
dismissedContentErrorKey.value = contentErrorKey.value
|
||||
function onInstallationDismiss() {
|
||||
if (ctx.installation.value) {
|
||||
ctx.dismissInstallation(ctx.installation.value.id)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -402,14 +369,10 @@ function onContentErrorDismiss() {
|
||||
<template #item="{ item, dismissible }">
|
||||
<InstallingBanner
|
||||
v-if="item.kind === 'installing'"
|
||||
:progress="syncProgress"
|
||||
:fallback-phase="isOnContentTab && !syncProgress ? 'Addons' : null"
|
||||
:content-error="contentError"
|
||||
:dismissible="dismissible && !!contentError"
|
||||
:retry-disabled="!canSetup"
|
||||
:retry-disabled-tooltip="permissionDeniedMessage"
|
||||
@dismiss="onContentErrorDismiss"
|
||||
@retry="emit('content-retry')"
|
||||
@dismiss="onInstallationDismiss"
|
||||
@retry="emit('installation-retry')"
|
||||
/>
|
||||
<UploadAdmonition
|
||||
v-else-if="item.kind === 'upload'"
|
||||
|
||||
@@ -13,20 +13,12 @@ export type PowerAction = 'Start' | 'Stop' | 'Restart' | 'Kill'
|
||||
export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
|
||||
const { formatMessage } = useVIntl()
|
||||
const client = injectModrinthClient()
|
||||
const { serverId, server, powerState, isSyncingContent, busyReasons } =
|
||||
injectModrinthServerContext()
|
||||
const { serverId, powerState, busyReasons } = injectModrinthServerContext()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { canUsePowerActions, permissionDeniedMessage } = useServerPermissions()
|
||||
|
||||
const isInstalling = computed(
|
||||
() =>
|
||||
server.value.status === 'installing' ||
|
||||
isSyncingContent.value ||
|
||||
busyReasons.value.some(
|
||||
(r) =>
|
||||
r.reason.id === 'servers.busy.installing' ||
|
||||
r.reason.id === 'servers.busy.syncing-content',
|
||||
),
|
||||
const isInstalling = computed(() =>
|
||||
busyReasons.value.some((reason) => reason.reason.id === 'servers.busy.installing'),
|
||||
)
|
||||
const isRunning = computed(() => powerState.value === 'running')
|
||||
const isStopping = computed(() => powerState.value === 'stopping')
|
||||
|
||||
Reference in New Issue
Block a user