feat:添加了启动进度的显示
Some checks failed
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

This commit is contained in:
2026-09-13 13:07:45 +08:00
parent 62d704dec0
commit 9b704919cf
10 changed files with 227 additions and 1 deletions

View File

@ -0,0 +1,108 @@
<script setup lang="ts">
import { DownloadIcon } from '@modrinth/assets'
import { defineMessages, useVIntl } from '@modrinth/ui'
import { computed, onUnmounted, ref } from 'vue'
import { loading_listener } from '@/helpers/events'
import type { LoadingBar } 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 {
event: LoadingBar['bar_type']
loader_uuid: string
fraction: number | null
message: string
}
interface LaunchProgressItem {
key: string
message: string
fraction: number | null
}
// 启动相关(以及准备)阶段会发的 loading 类型;安装/下载也一并显示,便于用户看到进度
const LAUNCH_BAR_TYPES = new Set([
'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, {
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.fraction == null || !Number.isFinite(item.fraction)) return ''
return `${Math.round(Math.max(0, Math.min(1, item.fraction)) * 100)}%`
}
const unlistenLoading = await loading_listener((payload: LoadingEventPayload) => {
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>
<div class="h-1 w-full overflow-hidden rounded-full bg-surface-4">
<div
class="h-full rounded-full bg-brand transition-[width] duration-200"
:style="{ width: percent(item) || '100%' }"
/>
</div>
</li>
</ul>
</section>
</template>