mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 09:04:55 +00:00
* feat: implement improved flow for server panel edit installation * feat: installation form finalized * feat: error state for InstallingBanner * feat: action button refactor + save banner text fix * fix: lint * fix: content card alignment * feat: better copy * fix: lint * fix: hide shift click + fix NeoForge chip * fix: lint
176 lines
4.2 KiB
Vue
176 lines
4.2 KiB
Vue
<template>
|
|
<Admonition :type="contentError ? 'critical' : 'info'" :show-actions-underneath="!contentError">
|
|
<template #icon>
|
|
<slot v-if="!contentError" name="icon">
|
|
<SpinnerIcon class="h-6 w-6 flex-none animate-spin text-brand-blue" />
|
|
</slot>
|
|
</template>
|
|
<template #header>
|
|
{{ contentError ? 'Installation error' : "We're preparing your server!" }}
|
|
</template>
|
|
<template v-if="contentError">
|
|
{{ errorLabel }}
|
|
</template>
|
|
<template v-else-if="progress">{{ 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>
|
|
<ButtonStyled color="red" type="outlined">
|
|
<button class="!border" @click="emit('retry')">
|
|
<RotateCounterClockwiseIcon class="size-5" />
|
|
Retry
|
|
</button>
|
|
</ButtonStyled>
|
|
</template>
|
|
<template v-if="!contentError" #actions>
|
|
<ProgressBar
|
|
v-if="progress"
|
|
:progress="progress.percent"
|
|
:max="100"
|
|
color="blue"
|
|
full-width
|
|
/>
|
|
<ProgressBar v-else :progress="0" :max="1" color="blue" full-width waiting />
|
|
</template>
|
|
</Admonition>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { RotateCounterClockwiseIcon } from '@modrinth/assets'
|
|
import SpinnerIcon from '@modrinth/assets/icons/spinner.svg'
|
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
import Admonition from '../base/Admonition.vue'
|
|
import ButtonStyled from '../base/ButtonStyled.vue'
|
|
import ProgressBar from '../base/ProgressBar.vue'
|
|
|
|
export interface SyncProgress {
|
|
phase: 'Analyzing' | 'InstallingPack' | 'InstallingLoader' | 'Addons'
|
|
percent: number
|
|
}
|
|
|
|
export interface ContentError {
|
|
step: string
|
|
description: string
|
|
}
|
|
|
|
const props = defineProps<{
|
|
progress?: SyncProgress | null
|
|
contentError?: ContentError | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
retry: []
|
|
}>()
|
|
|
|
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 'The specified loader or Minecraft version could not be installed. It may be invalid or unsupported.'
|
|
}
|
|
if (desc === 'this version is not yet supported') {
|
|
return 'This version of Minecraft or loader is not yet supported by Modrinth Hosting.'
|
|
}
|
|
if (desc === 'internal error') {
|
|
return 'An internal error occurred while installing the platform. Please try again.'
|
|
}
|
|
}
|
|
|
|
if (step === 'modpack') {
|
|
if (desc?.includes('no primary file')) {
|
|
return 'The modpack version has no downloadable file. It may have been packaged incorrectly.'
|
|
}
|
|
if (desc?.includes('failed to install')) {
|
|
return 'Failed to install the modpack. It may be corrupted or incompatible.'
|
|
}
|
|
}
|
|
|
|
return props.contentError?.description ?? 'An unexpected error occurred during installation.'
|
|
})
|
|
|
|
const phaseLabel = computed(() => {
|
|
switch (props.progress?.phase) {
|
|
case 'InstallingLoader':
|
|
return 'Installing platform...'
|
|
case 'InstallingPack':
|
|
return 'Installing modpack...'
|
|
case 'Addons':
|
|
return 'Installing addons...'
|
|
default:
|
|
return 'Installing...'
|
|
}
|
|
})
|
|
|
|
const tickerMessages = [
|
|
'Organizing files...',
|
|
'Downloading mods...',
|
|
'Configuring server...',
|
|
'Setting up environment...',
|
|
'Adding Java...',
|
|
]
|
|
|
|
const currentIndex = ref(0)
|
|
|
|
let intervalId: ReturnType<typeof setInterval> | null = null
|
|
|
|
onMounted(() => {
|
|
intervalId = setInterval(() => {
|
|
currentIndex.value = (currentIndex.value + 1) % tickerMessages.length
|
|
}, 3000)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
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>
|