You've already forked AstralRinth
forked from didirus/AstralRinth
381ea51cce
* fix: files.vue bugs before styling changes * feat: move files tab to shared layout structure * fix: qa * fix: qa * fix: bugs * fix: lint * fix: admonition cleanup with progress + actions * fix: cleanup * fix: modals * fix: admon title * fix: i18n standard * fix: lint + i18n pass * fix: remove transition * fix: type errors * feat: files tab in app * fix: qa * fix: backup item minmax * fix: use ContentPageHeader for server panel * fix: lint * fix: lint * fix: lint * feat: page leave safety * fix: lint * fix: cargo fmt fix * fix: blank in prod * fix: content card table stuff * Revert "fix: blank in prod" This reverts commit 74758fe185cf85a4a20355857f889cb091b97ace. * fix: import * feat: browse worlds/servers flow * fix: worlds tab parity with content tab * fix: perf bug + shader filter pill copy * feat: singleplayer filter * fix: ordering * fix: breadcrumbs * fix: lint * fix: qa * feat: store server proj id when adding to a non-linked instance * fix: lint * fix: i18n + qa * fix: conflict * qa: already installed modal + placeholders not server-specific * fix: qa * fix: add + edit server modals * fix: qa * fix: security * fix: devin flags * fix: lint * chore: change file to break build cache * fix: admon * fix: import path stuff * feat: qa * fix: fmt fmt idiot --------- Signed-off-by: Calum H. <calum@modrinth.com>
179 lines
5.7 KiB
Vue
179 lines
5.7 KiB
Vue
<template>
|
|
<Transition
|
|
enter-active-class="transition-all duration-300 ease-out overflow-hidden"
|
|
enter-from-class="opacity-0 max-h-0"
|
|
enter-to-class="opacity-100 max-h-40"
|
|
leave-active-class="transition-all duration-200 ease-in overflow-hidden"
|
|
leave-from-class="opacity-100 max-h-40"
|
|
leave-to-class="opacity-0 max-h-0"
|
|
>
|
|
<Admonition v-if="ctx.uploadState?.value?.isUploading" type="info" class="mb-4">
|
|
<template #icon>
|
|
<UploadIcon class="h-6 w-6 flex-none text-brand-blue" />
|
|
</template>
|
|
<template #header>
|
|
{{
|
|
ctx.uploadingLabel
|
|
? ctx.uploadingLabel(
|
|
ctx.uploadState.value.completedFiles,
|
|
ctx.uploadState.value.totalFiles,
|
|
)
|
|
: formatMessage(messages.uploadingFiles, {
|
|
completed: ctx.uploadState.value.completedFiles,
|
|
total: ctx.uploadState.value.totalFiles,
|
|
})
|
|
}}
|
|
<span v-if="ctx.uploadState.value.currentFileName" class="font-normal text-secondary">
|
|
— {{ ctx.uploadState.value.currentFileName }}
|
|
</span>
|
|
</template>
|
|
<span class="text-secondary">
|
|
{{
|
|
formatMessage(messages.uploadProgress, {
|
|
uploaded: formatBytes(ctx.uploadState.value.uploadedBytes),
|
|
total: formatBytes(ctx.uploadState.value.totalBytes),
|
|
percent: Math.round(uploadOverallProgress * 100),
|
|
})
|
|
}}
|
|
</span>
|
|
<template v-if="ctx.cancelUpload" #top-right-actions>
|
|
<ButtonStyled type="outlined" color="blue">
|
|
<button class="!border" @click="ctx.cancelUpload?.()">
|
|
{{ formatMessage(commonMessages.cancelButton) }}
|
|
</button>
|
|
</ButtonStyled>
|
|
</template>
|
|
<template #progress>
|
|
<ProgressBar :progress="uploadOverallProgress" :max="1" color="blue" full-width />
|
|
</template>
|
|
</Admonition>
|
|
</Transition>
|
|
<TransitionGroup
|
|
name="fs-op"
|
|
enter-active-class="transition-all duration-300 ease-out overflow-hidden"
|
|
enter-from-class="opacity-0 max-h-0"
|
|
enter-to-class="opacity-100 max-h-40"
|
|
leave-active-class="transition-all duration-200 ease-in overflow-hidden"
|
|
leave-from-class="opacity-100 max-h-40"
|
|
leave-to-class="opacity-0 max-h-0"
|
|
>
|
|
<Admonition
|
|
v-for="op in activeOperations"
|
|
:key="`fs-op-${op.op}-${op.src}`"
|
|
:type="op.state === 'done' ? 'success' : op.state?.startsWith('fail') ? 'critical' : 'info'"
|
|
class="mb-4"
|
|
>
|
|
<template #icon="{ iconClass }">
|
|
<PackageOpenIcon :class="iconClass" />
|
|
</template>
|
|
<template #header>
|
|
{{
|
|
formatMessage(messages.extracting, {
|
|
source: op.src.includes('https://') ? formatMessage(messages.modpackFromUrl) : op.src,
|
|
})
|
|
}}
|
|
<span v-if="op.state === 'done'" class="font-normal text-green">
|
|
— {{ formatMessage(commonMessages.doneLabel) }}</span
|
|
>
|
|
<span v-else-if="op.state?.startsWith('fail')" class="font-normal text-red">
|
|
— {{ formatMessage(messages.failed) }}</span
|
|
>
|
|
</template>
|
|
<span class="text-secondary">
|
|
{{
|
|
formatMessage(messages.extracted, {
|
|
size: 'bytes_processed' in op ? formatBytes(op.bytes_processed ?? 0) : '0 B',
|
|
})
|
|
}}
|
|
<template v-if="'current_file' in op && op.current_file">
|
|
— {{ op.current_file?.split('/')?.pop() }}
|
|
</template>
|
|
</span>
|
|
<template v-if="op.id && ctx.dismissOperation" #top-right-actions>
|
|
<ButtonStyled
|
|
v-if="op.state !== 'done' && !op.state?.startsWith('fail')"
|
|
type="outlined"
|
|
color="blue"
|
|
>
|
|
<button class="!border" @click="ctx.dismissOperation?.(op.id!, 'cancel')">
|
|
{{ formatMessage(commonMessages.cancelButton) }}
|
|
</button>
|
|
</ButtonStyled>
|
|
<ButtonStyled
|
|
v-if="op.state === 'done' || op.state?.startsWith('fail')"
|
|
circular
|
|
type="transparent"
|
|
hover-color-fill="background"
|
|
:color="op.state === 'done' ? 'green' : 'red'"
|
|
>
|
|
<button @click="ctx.dismissOperation?.(op.id!, 'dismiss')">
|
|
<XIcon />
|
|
</button>
|
|
</ButtonStyled>
|
|
</template>
|
|
<template #progress>
|
|
<ProgressBar
|
|
:progress="'progress' in op ? (op.progress ?? 0) : 0"
|
|
:max="1"
|
|
:color="op.state === 'done' ? 'green' : op.state?.startsWith('fail') ? 'red' : 'blue'"
|
|
:waiting="op.state === 'queued' || !op.progress || op.progress === 0"
|
|
full-width
|
|
/>
|
|
</template>
|
|
</Admonition>
|
|
</TransitionGroup>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { PackageOpenIcon, UploadIcon, XIcon } from '@modrinth/assets'
|
|
import { formatBytes } from '@modrinth/utils'
|
|
import { computed } from 'vue'
|
|
|
|
import Admonition from '#ui/components/base/Admonition.vue'
|
|
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
|
import ProgressBar from '#ui/components/base/ProgressBar.vue'
|
|
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
|
import { commonMessages } from '#ui/utils/common-messages'
|
|
|
|
import { injectFileManager } from '../providers/file-manager'
|
|
|
|
const { formatMessage } = useVIntl()
|
|
|
|
const messages = defineMessages({
|
|
uploadingFiles: {
|
|
id: 'files.operations.uploading-files',
|
|
defaultMessage: 'Uploading files ({completed}/{total})',
|
|
},
|
|
uploadProgress: {
|
|
id: 'files.operations.upload-progress',
|
|
defaultMessage: '{uploaded} / {total} ({percent}%)',
|
|
},
|
|
extracting: {
|
|
id: 'files.operations.extracting',
|
|
defaultMessage: 'Extracting {source}',
|
|
},
|
|
modpackFromUrl: {
|
|
id: 'files.operations.modpack-from-url',
|
|
defaultMessage: 'modpack from URL',
|
|
},
|
|
failed: {
|
|
id: 'files.operations.failed',
|
|
defaultMessage: 'Failed',
|
|
},
|
|
extracted: {
|
|
id: 'files.operations.extracted',
|
|
defaultMessage: '{size} extracted',
|
|
},
|
|
})
|
|
|
|
const ctx = injectFileManager()
|
|
|
|
const activeOperations = computed(() => ctx.activeOperations?.value ?? [])
|
|
|
|
const uploadOverallProgress = computed(() => {
|
|
const state = ctx.uploadState?.value
|
|
if (!state || !state.isUploading || state.totalFiles === 0) return 0
|
|
return Math.min((state.completedFiles + state.currentFileProgress) / state.totalFiles, 1)
|
|
})
|
|
</script>
|