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>

View File

@ -21,7 +21,7 @@
"name": "CodeZhangBorui",
"avatarUrl": "https://avatars.githubusercontent.com/u/61909157?v=4&s=96",
"url": "https://github.com/CodeZhangBorui",
"contributions": 444
"contributions": 446
},
{
"name": "IMB11",

View File

@ -30,6 +30,7 @@ import HomeDashboard from '@/components/home/HomeDashboard.vue'
import HomeInstancePickerModal from '@/components/home/HomeInstancePickerModal.vue'
import HomeMinecraftNews from '@/components/home/HomeMinecraftNews.vue'
import HomeMinimal from '@/components/home/HomeMinimal.vue'
import HomeLaunchProgress from '@/components/home/HomeLaunchProgress.vue'
import HomePlayInsights from '@/components/home/HomePlayInsights.vue'
import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { get_default_user, users } from '@/helpers/auth'
@ -378,6 +379,7 @@ onUnmounted(() => {
class="flex min-w-0 flex-col slide-enter-active"
:class="{ 'slide-enter-from': !animateSidebarShow }"
>
<HomeLaunchProgress />
<HomePlayInsights />
<HomeDailyChallenge />
<HomeMinecraftNews />

View File

@ -0,0 +1,8 @@
p = r"D:\Project\Starlight_Lancher\target\release\Starlight Launcher.exe"
data = open(p, "rb").read()
print("exe 大小:", len(data))
print("内嵌 PNG 数量(粗估):", data.count(b"\x89PNG\r\n\x1a\n"))
ico = open(r"D:\Project\Starlight_Lancher\apps\app\icons\icon.ico", "rb").read()
print("icon.ico 大小:", len(ico))
print("icon.ico 原样出现在 exe:", ico in data)

View File

@ -0,0 +1,42 @@
import struct
p = r"D:\Project\Starlight_Lancher\target\release\Starlight Launcher.exe"
data = open(p, "rb").read()
# 粗解析 PE找 .rsrc 段的 RT_GROUP_ICON 和 RT_ICON
# 简化:直接找所有 PNG现代 ico 用 png 存大图)和 BMP(ico用)
# 提取第一个 PNG 看尺寸
import io
PNG_SIG = b"\x89PNG\r\n\x1a\n"
results = []
idx = 0
while True:
i = data.find(PNG_SIG, idx)
if i < 0:
break
# PNG IHDR: 宽高在 sig 后 16 字节起 (8 sig + 4 len + 4 'IHDR' = 16)
try:
w = struct.unpack(">I", data[i+16:i+20])[0]
h = struct.unpack(">I", data[i+20:i+24])[0]
if 8 <= w <= 1024 and 8 <= h <= 1024:
results.append((i, w, h))
except Exception:
pass
idx = i + 8
print("找到疑似图标 PNG (offset,w,h) 前 30 个:")
for r in results[:30]:
print(r)
# 对比icon.ico 里各图像尺寸
ico = open(r"D:\Project\Starlight_Lancher\apps\app\icons\icon.ico", "rb").read()
n = struct.unpack("<H", ico[4:6])[0]
print(f"\nicon.ico 含 {n} 个图像:")
off = 6
for k in range(n):
w = ico[off] or 256
h = ico[off+1] or 256
size = struct.unpack("<I", ico[off+8:off+12])[0]
print(f" 图像{k}: {w}x{h}, {size} bytes")
off += 16

26
tools/icon_work/fix_lp.py Normal file
View File

@ -0,0 +1,26 @@
p = r"D:\Project\Starlight_Lancher\apps\app-frontend\src\components\home\HomeLaunchProgress.vue"
t = open(p, encoding="utf-8").read()
# 1) 给最外层 section 加 v-if
t = t.replace(
'\t<section\n\t\tclass="flex min-w-0 flex-col gap-3 border-0 border-b-[1px] border-solid border-[--brand-gradient-border] p-4"\n\t>',
'\t<section\n\t\tv-if="hasProgress"\n\t\tclass="flex min-w-0 flex-col gap-3 border-0 border-b-[1px] border-solid border-[--brand-gradient-border] p-4"\n\t>',
)
# 2) 删空态 <p v-if="!hasProgress">...</p>
import re
t = re.sub(
r'\t\t<p v-if="!hasProgress"[^>]*>\n\t\t\t\{\{ formatMessage\(messages\.empty\) \}\}\n\t\t</p>\n',
"",
t,
)
# 3) <ul v-else ...> 改 <ul ...>
t = t.replace('<ul v-else class="m-0 flex list-none flex-col gap-2 p-0">', '<ul class="m-0 flex list-none flex-col gap-2 p-0">')
open(p, "w", encoding="utf-8", newline="\n").write(t)
print("done")
print("--- 78-95 ---")
for i, line in enumerate(t.split("\n")[77:95], 78):
print(f"{i}: {line}")

Binary file not shown.

View File

@ -0,0 +1,6 @@
import glob
for f in glob.glob("packages/app-lib/src/event/*.rs"):
print("=== " + f + " ===")
print(open(f, encoding="utf-8").read()[:4000])
print()

View File

@ -0,0 +1,5 @@
l = open("packages/app-lib/src/launcher/mod.rs", encoding="utf-8").read().split("\n")
for i, line in enumerate(l):
if "init_loading" in line or "emit_loading" in line or "MinecraftDownload" in line or "InstanceUpdate" in line:
print(f"{i+1}: {line.strip()}")

View File

@ -0,0 +1,29 @@
p = r"D:\Project\Starlight_Lancher\apps\app-frontend\src\pages\Index.vue"
lines = open(p, encoding="utf-8").read().split("\n")
# 1) import在 HomePlayInsights import 行后加一行
imp_i = None
for i, ln in enumerate(lines):
if "import HomePlayInsights from '@/components/home/HomePlayInsights.vue'" in ln:
imp_i = i
break
assert imp_i is not None, "找不到 HomePlayInsights import"
lines.insert(imp_i, "import HomeLaunchProgress from '@/components/home/HomeLaunchProgress.vue'")
# 2) 模板:在 <HomePlayInsights /> 前插入 <HomeLaunchProgress />
tpl_i = None
for i, ln in enumerate(lines):
if ln.strip() == "<HomePlayInsights />":
tpl_i = i
break
assert tpl_i is not None, "找不到 <HomePlayInsights />"
lines.insert(tpl_i, "\t\t\t<HomeLaunchProgress />")
open(p, "w", encoding="utf-8", newline="\n").write("\n".join(lines))
print("done")
for i in range(imp_i - 1, imp_i + 2):
print(f"{i+1}: {lines[i]}")
print("...")
for i in range(tpl_i - 2, tpl_i + 3):
print(f"{i+1}: {lines[i]}")