Files
Starlight_Lancher/apps/app-frontend/src/components/home/HomeLaunchProgress.vue
T
Disy bc904065c3
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
feat: complete hosted mod sync and launcher interface updates
Add pack sync markers, tagged mod updates, parallel progress, JWT downloads and retry recovery. Include pending onboarding, about scene, compatibility data pack and download fixes.
2026-09-15 19:06:56 +08:00

123 lines
3.7 KiB
Vue

<script setup lang="ts">
import { DownloadIcon } from '@modrinth/assets'
import { defineMessages, ProgressBar, useVIntl } from '@modrinth/ui'
import { computed, onUnmounted, ref } from 'vue'
import { loading_listener } from '@/helpers/events'
import type { LoadingBar } from '@/helpers/state'
import { progress_bars_list } from '@/helpers/state'
const { formatMessage } = useVIntl()
const messages = defineMessages({
title: { id: 'app.home.launch-progress.title', defaultMessage: '启动进度' },
empty: { id: 'app.home.launch-progress.empty', defaultMessage: '启动游戏后,这里会显示进度。' },
})
interface LoadingEventPayload {
total?: number | null
event: LoadingBar['bar_type']
loader_uuid: string
fraction: number | null
message: string
}
interface LaunchProgressItem {
waiting: boolean
key: string
message: string
fraction: number | null
}
// 启动相关(以及准备)阶段会发的 loading 类型;安装/下载也一并显示,便于用户看到进度
const LAUNCH_BAR_TYPES = new Set([
'hosted_pack_sync',
'minecraft_download',
'instance_update',
'zip_extract',
'pack_download',
'pack_file_download',
])
const progressItems = ref<LaunchProgressItem[]>([])
const activeMap = new Map<string, LaunchProgressItem>()
function isVisible(barType: LoadingBar['bar_type']): boolean {
const type = barType?.type ?? ''
return LAUNCH_BAR_TYPES.has(type)
}
function applyEvent(payload: LoadingEventPayload) {
// fraction 为 null 约定为「完成」,移除该进度条
if (payload.fraction === null) {
activeMap.delete(payload.loader_uuid)
progressItems.value = Array.from(activeMap.values())
return
}
if (!isVisible(payload.event)) return
activeMap.set(payload.loader_uuid, {
waiting: payload.total === 0,
key: payload.loader_uuid,
message: payload.message,
fraction: payload.fraction,
})
progressItems.value = Array.from(activeMap.values())
}
const hasProgress = computed(() => progressItems.value.length > 0)
function percent(item: LaunchProgressItem): string {
if (item.waiting || item.fraction == null || !Number.isFinite(item.fraction)) return ''
return `${Math.round(Math.max(0, Math.min(1, item.fraction)) * 100)}%`
}
let initializing = true
const buffered: LoadingEventPayload[] = []
const unlistenLoading = await loading_listener((payload: LoadingEventPayload) => {
if (initializing) buffered.push(payload)
else applyEvent(payload)
})
const bars = await progress_bars_list().catch(() => ({}))
for (const bar of Object.values(bars)) {
applyEvent({
event: bar.bar_type,
loader_uuid: String(bar.loading_bar_uuid),
fraction: bar.total ? (bar.current ?? 0) / bar.total : 0,
total: bar.total,
message: bar.message ?? '',
})
}
initializing = false
for (const payload of buffered) applyEvent(payload)
onUnmounted(() => {
unlistenLoading?.()
})
</script>
<template>
<section
v-if="hasProgress"
class="flex min-w-0 flex-col gap-3 border-0 border-b-[1px] border-solid border-[--brand-gradient-border] p-4"
>
<div class="flex items-center gap-2">
<DownloadIcon class="size-4 shrink-0 text-secondary" aria-hidden="true" />
<h2 class="m-0 truncate text-lg">
{{ formatMessage(messages.title) }}
</h2>
</div>
<ul class="m-0 flex list-none flex-col gap-2 p-0">
<li v-for="item in progressItems" :key="item.key" class="flex min-w-0 flex-col gap-1">
<div class="flex min-w-0 items-center justify-between gap-2 text-sm text-primary">
<span class="min-w-0 truncate">{{ item.message }}</span>
<span v-if="percent(item)" class="shrink-0 tabular-nums text-secondary">
{{ percent(item) }}
</span>
</div>
<ProgressBar :progress="item.fraction ?? 0" :waiting="item.waiting" full-width />
</li>
</ul>
</section>
</template>