aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/src
diff options
context:
space:
mode:
author简律纯 <i@jyunko.cn>2026-01-14 14:24:25 +0800
committerGitHub <noreply@github.com>2026-01-14 14:24:25 +0800
commit80bd692e633002e1920c9ddae7cabc7ec2eadb6a (patch)
tree36d59d2fcd87d118c6db443c9208bf34b1025eff /ui/src
parente8e139c07d05e2f29f04906019dff5f3c520f8cc (diff)
parent561469b5a895d7c99fe6c9e73266b49ebe4237b8 (diff)
downloadDropOut-80bd692e633002e1920c9ddae7cabc7ec2eadb6a.tar.gz
DropOut-80bd692e633002e1920c9ddae7cabc7ec2eadb6a.zip
Merge pull request #20 from HsiangNianian/dev
Diffstat (limited to 'ui/src')
-rw-r--r--ui/src/components/SettingsView.svelte24
-rw-r--r--ui/src/components/StatusToast.svelte42
-rw-r--r--ui/src/components/VersionsView.svelte23
-rw-r--r--ui/src/lib/DownloadMonitor.svelte107
-rw-r--r--ui/src/stores/settings.svelte.ts1
-rw-r--r--ui/src/types/index.ts1
6 files changed, 175 insertions, 23 deletions
diff --git a/ui/src/components/SettingsView.svelte b/ui/src/components/SettingsView.svelte
index 9f260c1..801970b 100644
--- a/ui/src/components/SettingsView.svelte
+++ b/ui/src/components/SettingsView.svelte
@@ -117,6 +117,30 @@
</div>
</div>
+ <!-- Download Settings -->
+ <div class="bg-zinc-800/50 p-6 rounded-lg border border-zinc-700">
+ <h3
+ class="block text-sm font-bold text-zinc-400 mb-4 uppercase tracking-wide"
+ >Download Settings</h3>
+ <div>
+ <label for="download-threads" class="block text-xs text-zinc-500 mb-1"
+ >Concurrent Download Threads</label
+ >
+ <input
+ id="download-threads"
+ bind:value={settingsState.settings.download_threads}
+ type="number"
+ min="1"
+ max="128"
+ step="1"
+ class="bg-zinc-950 text-white w-full p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none"
+ />
+ <p class="text-xs text-zinc-500 mt-2">
+ Number of concurrent download threads (1-128). Higher values increase download speed but use more bandwidth and system resources. Default: 32
+ </p>
+ </div>
+ </div>
+
<div class="pt-4">
<button
onclick={() => settingsState.saveSettings()}
diff --git a/ui/src/components/StatusToast.svelte b/ui/src/components/StatusToast.svelte
index b1feffc..0d68778 100644
--- a/ui/src/components/StatusToast.svelte
+++ b/ui/src/components/StatusToast.svelte
@@ -3,28 +3,34 @@
</script>
{#if uiState.status !== "Ready"}
- <div
- class="absolute top-12 right-12 bg-zinc-800/90 backdrop-blur border border-zinc-600 p-4 rounded-lg shadow-2xl max-w-sm animate-in fade-in slide-in-from-top-4 duration-300 z-50 group"
- >
- <div class="flex justify-between items-start mb-1">
- <div class="text-xs text-zinc-400 uppercase font-bold">Status</div>
- <button
- onclick={() => uiState.setStatus("Ready")}
- class="text-zinc-500 hover:text-white transition -mt-1 -mr-1 p-1"
- >
- ✕
- </button>
+ {#key uiState.status}
+ <div
+ class="absolute top-12 right-12 bg-zinc-800/90 backdrop-blur border border-zinc-600 p-4 rounded-lg shadow-2xl max-w-sm animate-in fade-in slide-in-from-top-4 duration-300 z-50 group"
+ >
+ <div class="flex justify-between items-start mb-1">
+ <div class="text-xs text-zinc-400 uppercase font-bold">Status</div>
+ <button
+ onclick={() => uiState.setStatus("Ready")}
+ class="text-zinc-500 hover:text-white transition -mt-1 -mr-1 p-1"
+ >
+ ✕
+ </button>
+ </div>
+ <div class="font-mono text-sm whitespace-pre-wrap mb-2">{uiState.status}</div>
+ <div class="w-full bg-zinc-700/50 h-1 rounded-full overflow-hidden">
+ <div
+ class="h-full bg-indigo-500 origin-left w-full progress-bar"
+ ></div>
+ </div>
</div>
- <div class="font-mono text-sm whitespace-pre-wrap mb-2">{uiState.status}</div>
- <div class="w-full bg-zinc-700/50 h-1 rounded-full overflow-hidden">
- <div
- class="h-full bg-indigo-500 animate-[progress_5s_linear_forwards] origin-left w-full"
- ></div>
- </div>
- </div>
+ {/key}
{/if}
<style>
+ .progress-bar {
+ animation: progress 5s linear forwards;
+ }
+
@keyframes progress {
from {
transform: scaleX(1);
diff --git a/ui/src/components/VersionsView.svelte b/ui/src/components/VersionsView.svelte
index 8c0ddfe..98261b8 100644
--- a/ui/src/components/VersionsView.svelte
+++ b/ui/src/components/VersionsView.svelte
@@ -1,14 +1,35 @@
<script lang="ts">
import { gameState } from "../stores/game.svelte";
+
+ let searchQuery = $state("");
+ let normalizedQuery = $derived(
+ searchQuery.trim().toLowerCase().replace(/。/g, ".")
+ );
+
+ let filteredVersions = $derived(
+ gameState.versions.filter((v) =>
+ v.id.toLowerCase().includes(normalizedQuery)
+ )
+ );
</script>
<div class="p-8 h-full overflow-y-auto bg-zinc-900">
<h2 class="text-3xl font-bold mb-6">Versions</h2>
+
+ <input
+ type="text"
+ placeholder="Search versions..."
+ class="w-full p-3 mb-4 bg-zinc-800 border border-zinc-700 rounded text-white focus:outline-none focus:border-green-500 transition-colors"
+ bind:value={searchQuery}
+ />
+
<div class="grid gap-2">
{#if gameState.versions.length === 0}
<div class="text-zinc-500">Loading versions...</div>
+ {:else if filteredVersions.length === 0 && normalizedQuery.length > 0}
+ <div class="text-zinc-500">No versions found matching "{searchQuery}"</div>
{:else}
- {#each gameState.versions as version}
+ {#each filteredVersions as version}
<button
class="flex items-center justify-between p-4 bg-zinc-800 rounded hover:bg-zinc-700 transition text-left border border-zinc-700 {gameState.selectedVersion ===
version.id
diff --git a/ui/src/lib/DownloadMonitor.svelte b/ui/src/lib/DownloadMonitor.svelte
index b796591..52c935c 100644
--- a/ui/src/lib/DownloadMonitor.svelte
+++ b/ui/src/lib/DownloadMonitor.svelte
@@ -9,11 +9,16 @@
downloaded: number; // in bytes
total: number; // in bytes
status: string;
+ completed_files: number;
+ total_files: number;
+ total_downloaded_bytes: number;
}
let currentFile = "";
- let progress = 0; // percentage 0-100
+ let progress = 0; // percentage 0-100 (current file)
+ let totalProgress = 0; // percentage 0-100 (all files)
let totalFiles = 0;
+ let completedFiles = 0;
let statusText = "Preparing...";
let unlistenProgress: () => void;
let unlistenStart: () => void;
@@ -21,13 +26,30 @@
let downloadedBytes = 0;
let totalBytes = 0;
+ // Speed and ETA tracking
+ let downloadSpeed = 0; // bytes per second
+ let etaSeconds = 0;
+ let startTime = 0;
+ let totalDownloadedBytes = 0;
+ let lastUpdateTime = 0;
+ let lastTotalBytes = 0;
+
onMount(async () => {
unlistenStart = await listen<number>("download-start", (event) => {
visible = true;
totalFiles = event.payload;
+ completedFiles = 0;
progress = 0;
+ totalProgress = 0;
statusText = "Starting download...";
currentFile = "";
+ // Reset speed tracking
+ startTime = Date.now();
+ totalDownloadedBytes = 0;
+ downloadSpeed = 0;
+ etaSeconds = 0;
+ lastUpdateTime = Date.now();
+ lastTotalBytes = 0;
});
unlistenProgress = await listen<DownloadEvent>(
@@ -36,8 +58,7 @@
const payload = event.payload;
currentFile = payload.file;
- // Simple file progress for now. Global progress would require tracking all files.
- // For single file (Client jar), this is accurate.
+ // Current file progress
downloadedBytes = payload.downloaded;
totalBytes = payload.total;
@@ -46,12 +67,54 @@
if (payload.total > 0) {
progress = (payload.downloaded / payload.total) * 100;
}
+
+ // Total progress (all files)
+ completedFiles = payload.completed_files;
+ totalFiles = payload.total_files;
+ if (totalFiles > 0) {
+ const currentFileFraction =
+ payload.total > 0 ? payload.downloaded / payload.total : 0;
+ totalProgress = ((completedFiles + currentFileFraction) / totalFiles) * 100;
+ }
+
+ // Calculate download speed (using moving average)
+ totalDownloadedBytes = payload.total_downloaded_bytes;
+ const now = Date.now();
+ const timeDiff = (now - lastUpdateTime) / 1000; // seconds
+
+ if (timeDiff >= 0.5) { // Update speed every 0.5 seconds
+ const bytesDiff = totalDownloadedBytes - lastTotalBytes;
+ const instantSpeed = bytesDiff / timeDiff;
+ // Smooth the speed with exponential moving average
+ downloadSpeed = downloadSpeed === 0 ? instantSpeed : downloadSpeed * 0.7 + instantSpeed * 0.3;
+ lastUpdateTime = now;
+ lastTotalBytes = totalDownloadedBytes;
+ }
+
+ // Estimate remaining time
+ if (downloadSpeed > 0 && completedFiles < totalFiles) {
+ const remainingFiles = totalFiles - completedFiles;
+ let estimatedRemainingBytes: number;
+
+ if (completedFiles > 0) {
+ // Use average size of completed files to estimate remaining files
+ const avgBytesPerCompletedFile = totalDownloadedBytes / completedFiles;
+ estimatedRemainingBytes = avgBytesPerCompletedFile * remainingFiles;
+ } else {
+ // No completed files yet: estimate based only on current file's remaining bytes
+ estimatedRemainingBytes = Math.max(totalBytes - downloadedBytes, 0);
+ }
+ etaSeconds = estimatedRemainingBytes / downloadSpeed;
+ } else {
+ etaSeconds = 0;
+ }
}
);
unlistenComplete = await listen("download-complete", () => {
statusText = "Done!";
progress = 100;
+ totalProgress = 100;
setTimeout(() => {
visible = false;
}, 2000);
@@ -71,6 +134,24 @@
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
}
+
+ function formatSpeed(bytesPerSecond: number) {
+ if (bytesPerSecond === 0) return "-- /s";
+ return formatBytes(bytesPerSecond) + "/s";
+ }
+
+ function formatTime(seconds: number) {
+ if (seconds <= 0 || !isFinite(seconds)) return "--";
+ if (seconds < 60) return `${Math.round(seconds)}s`;
+ if (seconds < 3600) {
+ const mins = Math.floor(seconds / 60);
+ const secs = Math.round(seconds % 60);
+ return `${mins}m ${secs}s`;
+ }
+ const hours = Math.floor(seconds / 3600);
+ const mins = Math.floor((seconds % 3600) / 60);
+ return `${hours}h ${mins}m`;
+ }
</script>
{#if visible}
@@ -82,11 +163,29 @@
<span class="text-xs text-zinc-400">{statusText}</span>
</div>
+ <!-- Total Progress Bar -->
+ <div class="mb-3">
+ <div class="flex justify-between text-[10px] text-zinc-400 mb-1">
+ <span>Total Progress</span>
+ <span>{completedFiles} / {totalFiles} files</span>
+ </div>
+ <div class="w-full bg-zinc-800 rounded-full h-2.5 overflow-hidden">
+ <div
+ class="bg-gradient-to-r from-blue-500 to-cyan-400 h-2.5 rounded-full transition-all duration-200"
+ style="width: {totalProgress}%"
+ ></div>
+ </div>
+ <div class="flex justify-between text-[10px] text-zinc-500 font-mono mt-0.5">
+ <span>{formatSpeed(downloadSpeed)} · ETA: {formatTime(etaSeconds)}</span>
+ <span>{completedFiles < totalFiles ? Math.floor(totalProgress) : 100}%</span>
+ </div>
+ </div>
+
<div class="text-xs text-zinc-300 truncate mb-1" title={currentFile}>
{currentFile || "Waiting..."}
</div>
- <!-- Progress Bar -->
+ <!-- Current File Progress Bar -->
<div class="w-full bg-zinc-800 rounded-full h-2 mb-2 overflow-hidden">
<div
class="bg-gradient-to-r from-green-500 to-emerald-400 h-2 rounded-full transition-all duration-200"
diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts
index 989172c..397b9a6 100644
--- a/ui/src/stores/settings.svelte.ts
+++ b/ui/src/stores/settings.svelte.ts
@@ -9,6 +9,7 @@ export class SettingsState {
java_path: "java",
width: 854,
height: 480,
+ download_threads: 32,
});
javaInstallations = $state<JavaInstallation[]>([]);
isDetectingJava = $state(false);
diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts
index b7ff0a0..1f83585 100644
--- a/ui/src/types/index.ts
+++ b/ui/src/types/index.ts
@@ -29,6 +29,7 @@ export interface LauncherConfig {
java_path: string;
width: number;
height: number;
+ download_threads: number;
}
export interface JavaInstallation {