feat(stage-tamagotchi): resource island & loading progress

This commit is contained in:
Neko Ayaka
2025-06-28 17:36:35 +08:00
parent 8a7d75f2cc
commit 2c93bf1621
6 changed files with 139 additions and 0 deletions
+1
View File
@@ -78,6 +78,7 @@
"@iconify-json/eos-icons": "^1.2.2",
"@iconify-json/lucide": "^1.2.52",
"@iconify-json/mingcute": "^1.2.3",
"@iconify-json/ph": "^1.2.2",
"@iconify-json/simple-icons": "^1.2.40",
"@iconify-json/solar": "^1.2.2",
"@iconify-json/svg-spinners": "^1.2.2",
@@ -0,0 +1,118 @@
<script setup lang="ts">
import type { Ref } from 'vue'
import { onMounted, ref } from 'vue'
interface ProgressInfoItem {
filename: string
progress: number
currentSize: number
totalSize: number
}
interface ProgressInfo {
files: Ref<ProgressInfoItem>[]
totalProgress: number
}
const progressInfo = ref<ProgressInfo>({
files: [],
totalProgress: 0,
})
const simulatingProgressFiles = [
{ filename: 'model.safetensors', totalSize: 1000000 },
{ filename: 'config.json', totalSize: 500000 },
{ filename: 'metadata.json', totalSize: 200000 },
]
function simulateProgressForItem(totalSize: number, item: Ref<ProgressInfoItem>) {
const interval = setInterval(() => {
if (item.value.progress >= 100) {
item.value.progress = 100
clearInterval(interval)
return
}
item.value.currentSize += 8192
item.value.progress = (item.value.currentSize / totalSize) * 100
}, 100)
}
function simulateProgress() {
for (const file of simulatingProgressFiles) {
const item = ref<ProgressInfoItem>({
filename: file.filename,
progress: 0,
currentSize: 0,
totalSize: file.totalSize,
})
progressInfo.value.files.push(item)
simulateProgressForItem(file.totalSize, item)
const interval = setInterval(() => {
const totalProgress = progressInfo.value.files.reduce((acc, file) => acc + file.value.progress, 0) / progressInfo.value.files.length
progressInfo.value.totalProgress = totalProgress
if (totalProgress >= 100) {
clearInterval(interval)
}
}, 100)
}
}
onMounted(() => {
// Simulate progress for demonstration purposes
simulateProgress()
})
</script>
<template>
<div
left="0" fixed top-0 m-3 rounded-xl p-3
shadow-md bg="white/80 dark:neutral-900/80"
backdrop-blur="md"
w="[calc(100%-1.5rem)]"
>
<div mb-3 flex items-center gap-2>
<div v-if="progressInfo.totalProgress < 100" i-svg-spinners:pulse-ring />
<div v-else i-solar:check-circle-bold />
<div>
Preparing external resources...
</div>
</div>
<div w-full flex flex-col gap-2>
<div
v-for="file in progressInfo.files"
:key="file.value.filename"
max-w-full flex flex-col gap="1 sm:2"
>
<div flex justify-between text="xs sm:sm neutral-500 dark:neutral-400">
<div flex items-center gap-1>
<div v-if="file.value.progress < 100" i-svg-spinners:pulse-ring />
<div v-else i-solar:check-circle-bold text="green-600 dark:green-400" />
<div>
{{ file.value.filename }}
</div>
<div>
<div :style="{ width: `${file.value.progress}%` }" />
</div>
</div>
<div>{{ file.value.progress.toFixed(2) }}%</div>
</div>
<div relative>
<div
bg="primary-300 dark:primary-400"
absolute h-4 rounded-md will-change-width
:style="{ width: `${file.value.progress}%` }"
transition="width duration-500 ease-in-out"
/>
<div
bg="neutral-100 dark:neutral-900" h-4 w-full rounded-md
/>
</div>
</div>
</div>
</div>
</template>
@@ -0,0 +1,7 @@
<script setup lang="ts">
import LoadingProgress from './LoadingProgress.vue'
</script>
<template>
<LoadingProgress />
</template>
@@ -7,6 +7,7 @@ import { listen } from '@tauri-apps/api/event'
import { storeToRefs } from 'pinia'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import ResourceStatusIsland from '../components/Widgets/ResourceStatusIsland/index.vue'
import { useAppRuntime } from '../composables/runtime'
import { useWindowShortcuts } from '../composables/window-shortcuts'
import { useWindowControlStore } from '../stores/window-controls'
@@ -146,6 +147,7 @@ if (import.meta.hot) { // For better DX
>
<div relative h-full w-full items-end gap-2 class="view">
<WidgetStage h-full w-full flex-1 :focus-at="live2dFocusAt" mb="<md:18" />
<ResourceStatusIsland />
<div
absolute bottom-4 left-4 flex gap-1 op-0 transition="opacity duration-500"
:class="{
+1
View File
@@ -160,6 +160,7 @@ words:
- rushstack
- Saccade
- saccades
- safetensors
- SAVEPOINT
- sensenova
- serde
+10
View File
@@ -445,6 +445,9 @@ importers:
'@iconify-json/mingcute':
specifier: ^1.2.3
version: 1.2.3
'@iconify-json/ph':
specifier: ^1.2.2
version: 1.2.2
'@iconify-json/simple-icons':
specifier: ^1.2.40
version: 1.2.40
@@ -3084,6 +3087,9 @@ packages:
'@iconify-json/mingcute@1.2.3':
resolution: {integrity: sha512-yiEQfLBF5iwyOdxuY0kEU06+8Mp6Mrp14KVXTb+5jjSVuD71C9EQrzM/mm1Efd8Nu2amJalTPisl3loC8pHBqQ==}
'@iconify-json/ph@1.2.2':
resolution: {integrity: sha512-PgkEZNtqa8hBGjHXQa4pMwZa93hmfu8FUSjs/nv4oUU6yLsgv+gh9nu28Kqi8Fz9CCVu4hj1MZs9/60J57IzFw==}
'@iconify-json/simple-icons@1.2.40':
resolution: {integrity: sha512-sr2fbrS8rRhJNap41ucTStctxTcWQ3lcsHkY3loc4Yt1KNOne6D+l1JTOQCDj9f/VrUktVIEdaRQoYTvqfuSSw==}
@@ -13707,6 +13713,10 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/ph@1.2.2':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/simple-icons@1.2.40':
dependencies:
'@iconify/types': 2.0.0