diff options
| author | 2026-01-16 01:35:23 +0000 | |
|---|---|---|
| committer | 2026-01-16 01:35:23 +0000 | |
| commit | 963b4b8567ac1bd8b23c41e1bfbd6a99d202d1ed (patch) | |
| tree | 6df911b964d0d32facf55844c55e61ba3878ae3f | |
| parent | e21e288275806df7785d869667c90062e1890bf0 (diff) | |
| download | DropOut-963b4b8567ac1bd8b23c41e1bfbd6a99d202d1ed.tar.gz DropOut-963b4b8567ac1bd8b23c41e1bfbd6a99d202d1ed.zip | |
style: auto format and lint fix [skip ci]
| -rw-r--r-- | ui/README.md | 4 | ||||
| -rw-r--r-- | ui/package.json | 20 | ||||
| -rw-r--r-- | ui/src/app.css | 6 | ||||
| -rw-r--r-- | ui/src/lib/effects/ConstellationEffect.ts | 129 | ||||
| -rw-r--r-- | ui/src/lib/effects/SaturnEffect.ts | 233 | ||||
| -rw-r--r-- | ui/src/lib/modLoaderApi.ts | 12 | ||||
| -rw-r--r-- | ui/src/main.ts | 12 | ||||
| -rw-r--r-- | ui/src/stores/auth.svelte.ts | 27 | ||||
| -rw-r--r-- | ui/src/stores/logs.svelte.ts | 46 | ||||
| -rw-r--r-- | ui/src/stores/settings.svelte.ts | 89 | ||||
| -rw-r--r-- | ui/src/stores/ui.svelte.ts | 4 | ||||
| -rw-r--r-- | ui/src/types/index.ts | 1 | ||||
| -rw-r--r-- | ui/svelte.config.js | 4 | ||||
| -rw-r--r-- | ui/tsconfig.json | 5 | ||||
| -rw-r--r-- | ui/vite.config.ts | 18 |
15 files changed, 311 insertions, 299 deletions
diff --git a/ui/README.md b/ui/README.md index e6cd94f..a45e2a0 100644 --- a/ui/README.md +++ b/ui/README.md @@ -42,6 +42,6 @@ If you have state that's important to retain within a component, consider creati ```ts // store.ts // An extremely simple external store -import { writable } from 'svelte/store' -export default writable(0) +import { writable } from "svelte/store"; +export default writable(0); ``` diff --git a/ui/package.json b/ui/package.json index 05dd2b2..82f8db3 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,7 @@ { "name": "@dropout/ui", - "private": true, "version": "0.0.0", + "private": true, "type": "module", "scripts": { "dev": "vite", @@ -12,6 +12,15 @@ "lint:fix": "oxlint . --fix", "format": "oxfmt . --write" }, + "dependencies": { + "@tauri-apps/api": "^2.9.1", + "@tauri-apps/plugin-dialog": "^2.5.0", + "@tauri-apps/plugin-fs": "^2.4.5", + "@tauri-apps/plugin-shell": "^2.3.4", + "lucide-svelte": "^0.562.0", + "marked": "^17.0.1", + "node-emoji": "^2.2.0" + }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.1.18", @@ -31,14 +40,5 @@ "overrides": { "vite": "npm:rolldown-vite@7.2.5" } - }, - "dependencies": { - "@tauri-apps/api": "^2.9.1", - "@tauri-apps/plugin-dialog": "^2.5.0", - "@tauri-apps/plugin-fs": "^2.4.5", - "@tauri-apps/plugin-shell": "^2.3.4", - "lucide-svelte": "^0.562.0", - "marked": "^17.0.1", - "node-emoji": "^2.2.0" } } diff --git a/ui/src/app.css b/ui/src/app.css index 82aa72f..63449b7 100644 --- a/ui/src/app.css +++ b/ui/src/app.css @@ -100,7 +100,9 @@ input[type="email"], textarea { background-color: rgba(0, 0, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.1); - transition: border-color 0.2s ease, box-shadow 0.2s ease; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; } input[type="text"]:focus, @@ -148,7 +150,7 @@ input[type="checkbox"]:checked { } input[type="checkbox"]:checked::after { - content: ''; + content: ""; position: absolute; left: 5px; top: 2px; diff --git a/ui/src/lib/effects/ConstellationEffect.ts b/ui/src/lib/effects/ConstellationEffect.ts index 2cc702e..d2db529 100644 --- a/ui/src/lib/effects/ConstellationEffect.ts +++ b/ui/src/lib/effects/ConstellationEffect.ts @@ -1,4 +1,3 @@ - export class ConstellationEffect { private canvas: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; @@ -17,7 +16,7 @@ export class ConstellationEffect { constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; this.ctx = canvas.getContext("2d", { alpha: true })!; - + // Bind methods this.animate = this.animate.bind(this); this.handleMouseMove = this.handleMouseMove.bind(this); @@ -25,7 +24,7 @@ export class ConstellationEffect { // Initial setup this.resize(window.innerWidth, window.innerHeight); this.initParticles(); - + // Mouse interaction window.addEventListener("mousemove", this.handleMouseMove); @@ -44,10 +43,10 @@ export class ConstellationEffect { this.canvas.style.height = `${height}px`; this.ctx.scale(dpr, dpr); - + // Re-initialize if screen size changes significantly to maintain density if (this.particles.length === 0) { - this.initParticles(); + this.initParticles(); } } @@ -71,9 +70,9 @@ export class ConstellationEffect { animate() { this.ctx.clearRect(0, 0, this.width, this.height); - + // Update and draw particles - this.particles.forEach(p => { + this.particles.forEach((p) => { p.update(this.width, this.height); p.draw(this.ctx); }); @@ -86,41 +85,41 @@ export class ConstellationEffect { private drawConnections() { this.ctx.lineWidth = 1; - + for (let i = 0; i < this.particles.length; i++) { - const p1 = this.particles[i]; - - // Connect to mouse if close - const distMouse = Math.hypot(p1.x - this.mouseX, p1.y - this.mouseY); - if (distMouse < this.connectionDistance + 50) { - const alpha = 1 - (distMouse / (this.connectionDistance + 50)); - this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.4})`; // Brighter near mouse - this.ctx.beginPath(); - this.ctx.moveTo(p1.x, p1.y); - this.ctx.lineTo(this.mouseX, this.mouseY); - this.ctx.stroke(); - - // Gently attract to mouse - if (distMouse > 10) { - p1.x += (this.mouseX - p1.x) * 0.005; - p1.y += (this.mouseY - p1.y) * 0.005; - } + const p1 = this.particles[i]; + + // Connect to mouse if close + const distMouse = Math.hypot(p1.x - this.mouseX, p1.y - this.mouseY); + if (distMouse < this.connectionDistance + 50) { + const alpha = 1 - distMouse / (this.connectionDistance + 50); + this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.4})`; // Brighter near mouse + this.ctx.beginPath(); + this.ctx.moveTo(p1.x, p1.y); + this.ctx.lineTo(this.mouseX, this.mouseY); + this.ctx.stroke(); + + // Gently attract to mouse + if (distMouse > 10) { + p1.x += (this.mouseX - p1.x) * 0.005; + p1.y += (this.mouseY - p1.y) * 0.005; } - - // Connect to other particles - for (let j = i + 1; j < this.particles.length; j++) { - const p2 = this.particles[j]; - const dist = Math.hypot(p1.x - p2.x, p1.y - p2.y); - - if (dist < this.connectionDistance) { - const alpha = 1 - (dist / this.connectionDistance); - this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.15})`; - this.ctx.beginPath(); - this.ctx.moveTo(p1.x, p1.y); - this.ctx.lineTo(p2.x, p2.y); - this.ctx.stroke(); - } + } + + // Connect to other particles + for (let j = i + 1; j < this.particles.length; j++) { + const p2 = this.particles[j]; + const dist = Math.hypot(p1.x - p2.x, p1.y - p2.y); + + if (dist < this.connectionDistance) { + const alpha = 1 - dist / this.connectionDistance; + this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.15})`; + this.ctx.beginPath(); + this.ctx.moveTo(p1.x, p1.y); + this.ctx.lineTo(p2.x, p2.y); + this.ctx.stroke(); } + } } } @@ -131,33 +130,33 @@ export class ConstellationEffect { } class Particle { - x: number; - y: number; - vx: number; - vy: number; - size: number; - - constructor(w: number, h: number, speed: number) { - this.x = Math.random() * w; - this.y = Math.random() * h; - this.vx = (Math.random() - 0.5) * speed; - this.vy = (Math.random() - 0.5) * speed; - this.size = Math.random() * 2 + 1; - } + x: number; + y: number; + vx: number; + vy: number; + size: number; + + constructor(w: number, h: number, speed: number) { + this.x = Math.random() * w; + this.y = Math.random() * h; + this.vx = (Math.random() - 0.5) * speed; + this.vy = (Math.random() - 0.5) * speed; + this.size = Math.random() * 2 + 1; + } - update(w: number, h: number) { - this.x += this.vx; - this.y += this.vy; + update(w: number, h: number) { + this.x += this.vx; + this.y += this.vy; - // Bounce off walls - if (this.x < 0 || this.x > w) this.vx *= -1; - if (this.y < 0 || this.y > h) this.vy *= -1; - } + // Bounce off walls + if (this.x < 0 || this.x > w) this.vx *= -1; + if (this.y < 0 || this.y > h) this.vy *= -1; + } - draw(ctx: CanvasRenderingContext2D) { - ctx.fillStyle = "rgba(255, 255, 255, 0.4)"; - ctx.beginPath(); - ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); - ctx.fill(); - } + draw(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = "rgba(255, 255, 255, 0.4)"; + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + ctx.fill(); + } } diff --git a/ui/src/lib/effects/SaturnEffect.ts b/ui/src/lib/effects/SaturnEffect.ts index 42aee66..357da9d 100644 --- a/ui/src/lib/effects/SaturnEffect.ts +++ b/ui/src/lib/effects/SaturnEffect.ts @@ -6,7 +6,7 @@ export class SaturnEffect { private ctx: CanvasRenderingContext2D; private width: number = 0; private height: number = 0; - + // Data-oriented design for performance // xyz: Float32Array where [i*3, i*3+1, i*3+2] corresponds to x, y, z private xyz: Float32Array | null = null; @@ -23,7 +23,7 @@ export class SaturnEffect { private lastMouseX: number = 0; private lastMouseTime: number = 0; private mouseVelocities: number[] = []; // Store recent velocities for averaging - + // Rotation speed control private readonly baseSpeed: number = 0.005; // Original rotation speed private currentSpeed: number = 0.005; // Current rotation speed (can be modified by mouse) @@ -35,15 +35,15 @@ export class SaturnEffect { constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; - this.ctx = canvas.getContext('2d', { + this.ctx = canvas.getContext("2d", { alpha: true, - desynchronized: false // default is usually fine, 'desynchronized' can help latency but might flicker + desynchronized: false, // default is usually fine, 'desynchronized' can help latency but might flicker })!; - + // Initial resize will set up everything this.resize(window.innerWidth, window.innerHeight); this.initParticles(); - + this.animate = this.animate.bind(this); this.animate(); } @@ -60,24 +60,24 @@ export class SaturnEffect { handleMouseMove(clientX: number) { if (!this.isDragging) return; - + const currentTime = performance.now(); const deltaTime = currentTime - this.lastMouseTime; - + if (deltaTime > 0) { const deltaX = clientX - this.lastMouseX; const velocity = deltaX / deltaTime; // pixels per millisecond - + // Store recent velocities (keep last 5 for smoothing) this.mouseVelocities.push(velocity); if (this.mouseVelocities.length > 5) { this.mouseVelocities.shift(); } - + // Apply direct rotation while dragging this.angle += deltaX * 0.002; } - + this.lastMouseX = clientX; this.lastMouseTime = currentTime; } @@ -98,22 +98,22 @@ export class SaturnEffect { handleTouchMove(clientX: number) { if (!this.isDragging) return; - + const currentTime = performance.now(); const deltaTime = currentTime - this.lastMouseTime; - + if (deltaTime > 0) { const deltaX = clientX - this.lastMouseX; const velocity = deltaX / deltaTime; - + this.mouseVelocities.push(velocity); if (this.mouseVelocities.length > 5) { this.mouseVelocities.shift(); } - + this.angle += deltaX * 0.002; } - + this.lastMouseX = clientX; this.lastMouseTime = currentTime; } @@ -127,39 +127,40 @@ export class SaturnEffect { private applyFlingVelocity() { // Calculate average velocity from recent samples - const avgVelocity = this.mouseVelocities.reduce((a, b) => a + b, 0) / this.mouseVelocities.length; - + const avgVelocity = + this.mouseVelocities.reduce((a, b) => a + b, 0) / this.mouseVelocities.length; + // Threshold for considering it a "fling" (pixels per millisecond) const flingThreshold = 0.3; // Threshold for considering the rotation as "stopped" by user const stopThreshold = 0.1; - + if (Math.abs(avgVelocity) > flingThreshold) { // User flung it - start rotating again this.isStopped = false; - + // Determine new direction based on fling direction const newDirection = avgVelocity > 0 ? 1 : -1; - + // If direction changed, update it permanently if (newDirection !== this.rotationDirection) { this.rotationDirection = newDirection; } - + // Calculate speed boost based on fling strength // Map velocity to speed multiplier (stronger fling = faster rotation) const speedMultiplier = Math.min( this.maxSpeedMultiplier, - this.minSpeedMultiplier + Math.abs(avgVelocity) * 10 + this.minSpeedMultiplier + Math.abs(avgVelocity) * 10, ); - + this.currentSpeed = this.baseSpeed * speedMultiplier; } else if (Math.abs(avgVelocity) < stopThreshold) { // User gently released - keep it stopped this.isStopped = true; this.currentSpeed = 0; } - // If velocity is between stopThreshold and flingThreshold, + // If velocity is between stopThreshold and flingThreshold, // keep current state (don't change isStopped) } @@ -167,17 +168,17 @@ export class SaturnEffect { const dpr = window.devicePixelRatio || 1; this.width = width; this.height = height; - + this.canvas.width = width * dpr; this.canvas.height = height * dpr; this.canvas.style.width = `${width}px`; this.canvas.style.height = `${height}px`; - + this.ctx.scale(dpr, dpr); // Dynamic scaling based on screen size const minDim = Math.min(width, height); - this.scaleFactor = minDim * 0.45; + this.scaleFactor = minDim * 0.45; } initParticles() { @@ -197,65 +198,68 @@ export class SaturnEffect { // 1. Planet for (let i = 0; i < planetCount; i++) { - const theta = Math.random() * Math.PI * 2; - const phi = Math.acos((Math.random() * 2) - 1); - const r = 1.0; - - // x, y, z - this.xyz[idx * 3] = r * Math.sin(phi) * Math.cos(theta); - this.xyz[idx * 3 + 1] = r * Math.sin(phi) * Math.sin(theta); - this.xyz[idx * 3 + 2] = r * Math.cos(phi); - - this.types[idx] = 0; // 0 for planet - idx++; + const theta = Math.random() * Math.PI * 2; + const phi = Math.acos(Math.random() * 2 - 1); + const r = 1.0; + + // x, y, z + this.xyz[idx * 3] = r * Math.sin(phi) * Math.cos(theta); + this.xyz[idx * 3 + 1] = r * Math.sin(phi) * Math.sin(theta); + this.xyz[idx * 3 + 2] = r * Math.cos(phi); + + this.types[idx] = 0; // 0 for planet + idx++; } // 2. Rings - const ringInner = 1.4; + const ringInner = 1.4; const ringOuter = 2.3; - + for (let i = 0; i < ringCount; i++) { - const angle = Math.random() * Math.PI * 2; - const dist = Math.sqrt(Math.random() * (ringOuter*ringOuter - ringInner*ringInner) + ringInner*ringInner); - - // x, y, z - this.xyz[idx * 3] = dist * Math.cos(angle); - this.xyz[idx * 3 + 1] = (Math.random() - 0.5) * 0.05; - this.xyz[idx * 3 + 2] = dist * Math.sin(angle); - - this.types[idx] = 1; // 1 for ring - idx++; + const angle = Math.random() * Math.PI * 2; + const dist = Math.sqrt( + Math.random() * (ringOuter * ringOuter - ringInner * ringInner) + ringInner * ringInner, + ); + + // x, y, z + this.xyz[idx * 3] = dist * Math.cos(angle); + this.xyz[idx * 3 + 1] = (Math.random() - 0.5) * 0.05; + this.xyz[idx * 3 + 2] = dist * Math.sin(angle); + + this.types[idx] = 1; // 1 for ring + idx++; } } animate() { this.ctx.clearRect(0, 0, this.width, this.height); - + // Normal blending - this.ctx.globalCompositeOperation = 'source-over'; - + this.ctx.globalCompositeOperation = "source-over"; + // Update rotation speed - decay towards base speed while maintaining direction if (!this.isDragging && !this.isStopped) { if (this.currentSpeed > this.baseSpeed) { // Gradually decay speed back to base speed - this.currentSpeed = this.baseSpeed + (this.currentSpeed - this.baseSpeed) * this.speedDecayRate; - + this.currentSpeed = + this.baseSpeed + (this.currentSpeed - this.baseSpeed) * this.speedDecayRate; + // Snap to base speed when close enough if (this.currentSpeed - this.baseSpeed < 0.00001) { this.currentSpeed = this.baseSpeed; } } - + // Apply rotation with current speed and direction this.angle += this.currentSpeed * this.rotationDirection; } - const cx = this.width * 0.6; + const cx = this.width * 0.6; const cy = this.height * 0.5; - + // Pre-calculate rotation matrices const rotationY = this.angle; - const rotationX = 0.4; + const rotationX = 0.4; const rotationZ = 0.15; const sinY = Math.sin(rotationY); @@ -265,66 +269,66 @@ export class SaturnEffect { const sinZ = Math.sin(rotationZ); const cosZ = Math.cos(rotationZ); - const fov = 1500; + const fov = 1500; const scaleFactor = this.scaleFactor; if (!this.xyz || !this.types) return; for (let i = 0; i < this.count; i++) { - const x = this.xyz[i * 3]; - const y = this.xyz[i * 3 + 1]; - const z = this.xyz[i * 3 + 2]; - - // Apply Scale - const px = x * scaleFactor; - const py = y * scaleFactor; - const pz = z * scaleFactor; - - // 1. Rotate Y - const x1 = px * cosY - pz * sinY; - const z1 = pz * cosY + px * sinY; - // y1 = py - - // 2. Rotate X - const y2 = py * cosX - z1 * sinX; - const z2 = z1 * cosX + py * sinX; - // x2 = x1 - - // 3. Rotate Z - const x3 = x1 * cosZ - y2 * sinZ; - const y3 = y2 * cosZ + x1 * sinZ; - const z3 = z2; - - const scale = fov / (fov + z3); - - if (z3 > -fov) { - const x2d = cx + x3 * scale; - const y2d = cy + y3 * scale; - - // Size calculation - slightly larger dots to compensate for lower count - // Previously Planet 2.0 -> 2.4, Ring 1.3 -> 1.5 - const type = this.types[i]; - const sizeBase = type === 0 ? 2.4 : 1.5; - const size = sizeBase * scale; - - // Opacity - let alpha = (scale * scale * scale); - if (alpha > 1) alpha = 1; - if (alpha < 0.15) continue; // Skip very faint particles for performance - - // Optimization: Planet color vs Ring color - if (type === 0) { - // Planet: Warm White - this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`; - } else { - // Ring: Cool White - this.ctx.fillStyle = `rgba(220, 240, 255, ${alpha})`; - } - - // Render as squares (fillRect) instead of circles (arc) - // This is significantly faster for software rendering and reduces GPU usage. - this.ctx.fillRect(x2d, y2d, size, size); + const x = this.xyz[i * 3]; + const y = this.xyz[i * 3 + 1]; + const z = this.xyz[i * 3 + 2]; + + // Apply Scale + const px = x * scaleFactor; + const py = y * scaleFactor; + const pz = z * scaleFactor; + + // 1. Rotate Y + const x1 = px * cosY - pz * sinY; + const z1 = pz * cosY + px * sinY; + // y1 = py + + // 2. Rotate X + const y2 = py * cosX - z1 * sinX; + const z2 = z1 * cosX + py * sinX; + // x2 = x1 + + // 3. Rotate Z + const x3 = x1 * cosZ - y2 * sinZ; + const y3 = y2 * cosZ + x1 * sinZ; + const z3 = z2; + + const scale = fov / (fov + z3); + + if (z3 > -fov) { + const x2d = cx + x3 * scale; + const y2d = cy + y3 * scale; + + // Size calculation - slightly larger dots to compensate for lower count + // Previously Planet 2.0 -> 2.4, Ring 1.3 -> 1.5 + const type = this.types[i]; + const sizeBase = type === 0 ? 2.4 : 1.5; + const size = sizeBase * scale; + + // Opacity + let alpha = scale * scale * scale; + if (alpha > 1) alpha = 1; + if (alpha < 0.15) continue; // Skip very faint particles for performance + + // Optimization: Planet color vs Ring color + if (type === 0) { + // Planet: Warm White + this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`; + } else { + // Ring: Cool White + this.ctx.fillStyle = `rgba(220, 240, 255, ${alpha})`; } + + // Render as squares (fillRect) instead of circles (arc) + // This is significantly faster for software rendering and reduces GPU usage. + this.ctx.fillRect(x2d, y2d, size, size); + } } this.animationId = requestAnimationFrame(this.animate); @@ -334,4 +338,3 @@ export class SaturnEffect { cancelAnimationFrame(this.animationId); } } - diff --git a/ui/src/lib/modLoaderApi.ts b/ui/src/lib/modLoaderApi.ts index 9d0d09d..75f404a 100644 --- a/ui/src/lib/modLoaderApi.ts +++ b/ui/src/lib/modLoaderApi.ts @@ -34,7 +34,7 @@ export async function getFabricLoaderVersions(): Promise<FabricLoaderVersion[]> * Get Fabric loaders available for a specific Minecraft version. */ export async function getFabricLoadersForVersion( - gameVersion: string + gameVersion: string, ): Promise<FabricLoaderEntry[]> { return invoke<FabricLoaderEntry[]>("get_fabric_loaders_for_version", { gameVersion, @@ -46,7 +46,7 @@ export async function getFabricLoadersForVersion( */ export async function installFabric( gameVersion: string, - loaderVersion: string + loaderVersion: string, ): Promise<InstalledFabricVersion> { return invoke<InstalledFabricVersion>("install_fabric", { gameVersion, @@ -66,7 +66,7 @@ export async function listInstalledFabricVersions(): Promise<string[]> { */ export async function isFabricInstalled( gameVersion: string, - loaderVersion: string + loaderVersion: string, ): Promise<boolean> { return invoke<boolean>("is_fabric_installed", { gameVersion, @@ -86,9 +86,7 @@ export async function getForgeGameVersions(): Promise<string[]> { /** * Get Forge versions available for a specific Minecraft version. */ -export async function getForgeVersionsForGame( - gameVersion: string -): Promise<ForgeVersion[]> { +export async function getForgeVersionsForGame(gameVersion: string): Promise<ForgeVersion[]> { return invoke<ForgeVersion[]>("get_forge_versions_for_game", { gameVersion, }); @@ -99,7 +97,7 @@ export async function getForgeVersionsForGame( */ export async function installForge( gameVersion: string, - forgeVersion: string + forgeVersion: string, ): Promise<InstalledForgeVersion> { return invoke<InstalledForgeVersion>("install_forge", { gameVersion, diff --git a/ui/src/main.ts b/ui/src/main.ts index 664a057..d47b930 100644 --- a/ui/src/main.ts +++ b/ui/src/main.ts @@ -1,9 +1,9 @@ -import { mount } from 'svelte' -import './app.css' -import App from './App.svelte' +import { mount } from "svelte"; +import "./app.css"; +import App from "./App.svelte"; const app = mount(App, { - target: document.getElementById('app')!, -}) + target: document.getElementById("app")!, +}); -export default app +export default app; diff --git a/ui/src/stores/auth.svelte.ts b/ui/src/stores/auth.svelte.ts index eb9dccd..1b613a7 100644 --- a/ui/src/stores/auth.svelte.ts +++ b/ui/src/stores/auth.svelte.ts @@ -14,7 +14,7 @@ export class AuthState { deviceCodeData = $state<DeviceCodeResponse | null>(null); msLoginLoading = $state(false); msLoginStatus = $state("Waiting for authorization..."); - + private pollInterval: ReturnType<typeof setInterval> | null = null; private isPollingRequestActive = false; private authProgressUnlisten: UnlistenFn | null = null; @@ -87,9 +87,7 @@ export class AuthState { this.setupAuthProgressListener(); try { - this.deviceCodeData = (await invoke( - "start_microsoft_login" - )) as DeviceCodeResponse; + this.deviceCodeData = (await invoke("start_microsoft_login")) as DeviceCodeResponse; if (this.deviceCodeData) { try { @@ -99,13 +97,17 @@ export class AuthState { } open(this.deviceCodeData.verification_uri); - logsState.addLog("info", "Auth", "Microsoft login started, waiting for browser authorization..."); + logsState.addLog( + "info", + "Auth", + "Microsoft login started, waiting for browser authorization...", + ); console.log("Starting polling for token..."); const intervalMs = (this.deviceCodeData.interval || 5) * 1000; this.pollInterval = setInterval( () => this.checkLoginStatus(this.deviceCodeData!.device_code), - intervalMs + intervalMs, ); } } catch (e) { @@ -159,7 +161,11 @@ export class AuthState { this.stopPolling(); this.cleanupAuthListener(); this.isLoginModalOpen = false; - logsState.addLog("info", "Auth", `Login successful! Welcome, ${this.currentAccount.username}`); + logsState.addLog( + "info", + "Auth", + `Login successful! Welcome, ${this.currentAccount.username}`, + ); uiState.setStatus("Welcome back, " + this.currentAccount.username); } catch (e: any) { const errStr = e.toString(); @@ -169,11 +175,8 @@ export class AuthState { console.error("Polling Error:", errStr); this.msLoginStatus = "Error: " + errStr; logsState.addLog("error", "Auth", `Login error: ${errStr}`); - - if ( - errStr.includes("expired_token") || - errStr.includes("access_denied") - ) { + + if (errStr.includes("expired_token") || errStr.includes("access_denied")) { this.stopPolling(); this.cleanupAuthListener(); alert("Login failed: " + errStr); diff --git a/ui/src/stores/logs.svelte.ts b/ui/src/stores/logs.svelte.ts index 5491f70..5df9abc 100644 --- a/ui/src/stores/logs.svelte.ts +++ b/ui/src/stores/logs.svelte.ts @@ -17,7 +17,14 @@ function parseGameLogLevel(levelStr: string): LogEntry["level"] { if (upper === "INFO") return "info"; if (upper === "WARN" || upper === "WARNING") return "warn"; if (upper === "ERROR" || upper === "SEVERE") return "error"; - if (upper === "DEBUG" || upper === "TRACE" || upper === "FINE" || upper === "FINER" || upper === "FINEST") return "debug"; + if ( + upper === "DEBUG" || + upper === "TRACE" || + upper === "FINE" || + upper === "FINER" || + upper === "FINEST" + ) + return "debug"; if (upper === "FATAL") return "fatal"; return "info"; } @@ -37,8 +44,9 @@ export class LogsState { addLog(level: LogEntry["level"], source: string, message: string) { const now = new Date(); - const timestamp = now.toLocaleTimeString() + "." + now.getMilliseconds().toString().padStart(3, "0"); - + const timestamp = + now.toLocaleTimeString() + "." + now.getMilliseconds().toString().padStart(3, "0"); + this.logs.push({ id: this.nextId++, timestamp, @@ -60,7 +68,7 @@ export class LogsState { // Parse game output and extract level/source addGameLog(rawLine: string, isStderr: boolean) { const match = rawLine.match(GAME_LOG_REGEX); - + if (match) { const [, thread, levelStr, extraSource, message] = match; const level = parseGameLogLevel(levelStr); @@ -105,33 +113,33 @@ export class LogsState { // Download Events (Summarized) await listen("download-start", (e) => { - this.addLog("info", "Downloader", `Starting batch download of ${e.payload} files...`); + this.addLog("info", "Downloader", `Starting batch download of ${e.payload} files...`); }); await listen("download-complete", () => { - this.addLog("info", "Downloader", "All downloads completed."); + this.addLog("info", "Downloader", "All downloads completed."); }); // Listen to file download progress to log finished files await listen<any>("download-progress", (e) => { - const p = e.payload; - if (p.status === "Finished") { - if (p.file.endsWith(".jar")) { - this.addLog("info", "Downloader", `Downloaded ${p.file}`); - } + const p = e.payload; + if (p.status === "Finished") { + if (p.file.endsWith(".jar")) { + this.addLog("info", "Downloader", `Downloaded ${p.file}`); } + } }); // Java Download await listen<any>("java-download-progress", (e) => { - const p = e.payload; - if (p.status === "Downloading" && p.percentage === 0) { - this.addLog("info", "JavaInstaller", `Downloading Java: ${p.file_name}`); - } else if (p.status === "Completed") { - this.addLog("info", "JavaInstaller", `Java installed: ${p.file_name}`); - } else if (p.status === "Error") { - this.addLog("error", "JavaInstaller", `Java download error`); - } + const p = e.payload; + if (p.status === "Downloading" && p.percentage === 0) { + this.addLog("info", "JavaInstaller", `Downloading Java: ${p.file_name}`); + } else if (p.status === "Completed") { + this.addLog("info", "JavaInstaller", `Java installed: ${p.file_name}`); + } else if (p.status === "Error") { + this.addLog("error", "JavaInstaller", `Java download error`); + } }); } } diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts index b85e5fb..12e4a1c 100644 --- a/ui/src/stores/settings.svelte.ts +++ b/ui/src/stores/settings.svelte.ts @@ -28,7 +28,7 @@ export class SettingsState { log_upload_service: "paste.rs", pastebin_api_key: undefined, }); - + // Convert background path to proper asset URL get backgroundUrl(): string | undefined { if (this.settings.custom_background_path) { @@ -42,59 +42,59 @@ export class SettingsState { // Java download modal state showJavaDownloadModal = $state(false); selectedDownloadSource = $state<JavaDownloadSource>("adoptium"); - + // Java catalog state javaCatalog = $state<JavaCatalog | null>(null); isLoadingCatalog = $state(false); catalogError = $state(""); - + // Version selection state selectedMajorVersion = $state<number | null>(null); selectedImageType = $state<"jre" | "jdk">("jre"); showOnlyRecommended = $state(true); searchQuery = $state(""); - + // Download progress state isDownloadingJava = $state(false); downloadProgress = $state<JavaDownloadProgress | null>(null); javaDownloadStatus = $state(""); - + // Pending downloads pendingDownloads = $state<PendingJavaDownload[]>([]); - + // Event listener cleanup private progressUnlisten: UnlistenFn | null = null; // Computed: filtered releases based on selection get filteredReleases(): JavaReleaseInfo[] { if (!this.javaCatalog) return []; - + let releases = this.javaCatalog.releases; - + // Filter by major version if selected if (this.selectedMajorVersion !== null) { - releases = releases.filter(r => r.major_version === this.selectedMajorVersion); + releases = releases.filter((r) => r.major_version === this.selectedMajorVersion); } - + // Filter by image type - releases = releases.filter(r => r.image_type === this.selectedImageType); - + releases = releases.filter((r) => r.image_type === this.selectedImageType); + // Filter by recommended (LTS) versions if (this.showOnlyRecommended) { - releases = releases.filter(r => r.is_lts); + releases = releases.filter((r) => r.is_lts); } - + // Filter by search query if (this.searchQuery.trim()) { const query = this.searchQuery.toLowerCase(); releases = releases.filter( - r => + (r) => r.release_name.toLowerCase().includes(query) || r.version.toLowerCase().includes(query) || - r.major_version.toString().includes(query) + r.major_version.toString().includes(query), ); } - + return releases; } @@ -102,36 +102,39 @@ export class SettingsState { get availableMajorVersions(): number[] { if (!this.javaCatalog) return []; let versions = [...this.javaCatalog.available_major_versions]; - + // Filter by LTS if showOnlyRecommended is enabled if (this.showOnlyRecommended) { - versions = versions.filter(v => this.javaCatalog!.lts_versions.includes(v)); + versions = versions.filter((v) => this.javaCatalog!.lts_versions.includes(v)); } - + // Sort descending (newest first) return versions.sort((a, b) => b - a); } // Get installation status for a release: 'installed' | 'download' - getInstallStatus(release: JavaReleaseInfo): 'installed' | 'download' { + getInstallStatus(release: JavaReleaseInfo): "installed" | "download" { // Find installed Java that matches the major version and image type (by path pattern) - const matchingInstallations = this.javaInstallations.filter(inst => { + const matchingInstallations = this.javaInstallations.filter((inst) => { // Check if this is a DropOut-managed Java (path contains temurin-XX-jre/jdk pattern) const pathLower = inst.path.toLowerCase(); const pattern = `temurin-${release.major_version}-${release.image_type}`; return pathLower.includes(pattern); }); - + // If any matching installation exists, it's installed - return matchingInstallations.length > 0 ? 'installed' : 'download'; + return matchingInstallations.length > 0 ? "installed" : "download"; } // Computed: selected release details get selectedRelease(): JavaReleaseInfo | null { if (!this.javaCatalog || this.selectedMajorVersion === null) return null; - return this.javaCatalog.releases.find( - r => r.major_version === this.selectedMajorVersion && r.image_type === this.selectedImageType - ) || null; + return ( + this.javaCatalog.releases.find( + (r) => + r.major_version === this.selectedMajorVersion && r.image_type === this.selectedImageType, + ) || null + ); } async loadSettings() { @@ -145,7 +148,7 @@ export class SettingsState { } // Ensure custom_background_path is reactive if (!this.settings.custom_background_path) { - this.settings.custom_background_path = undefined; + this.settings.custom_background_path = undefined; } } catch (e) { console.error("Failed to load settings:", e); @@ -158,7 +161,7 @@ export class SettingsState { if (this.settings.custom_background_path === "") { this.settings.custom_background_path = undefined; } - + await invoke("save_settings", { config: this.settings }); uiState.setStatus("Settings saved!"); } catch (e) { @@ -193,13 +196,13 @@ export class SettingsState { this.javaDownloadStatus = ""; this.catalogError = ""; this.downloadProgress = null; - + // Setup progress event listener await this.setupProgressListener(); - + // Load catalog await this.loadJavaCatalog(false); - + // Check for pending downloads await this.loadPendingDownloads(); } @@ -219,13 +222,13 @@ export class SettingsState { if (this.progressUnlisten) { this.progressUnlisten(); } - + this.progressUnlisten = await listen<JavaDownloadProgress>( "java-download-progress", (event) => { this.downloadProgress = event.payload; this.javaDownloadStatus = event.payload.status; - + if (event.payload.status === "Completed") { this.isDownloadingJava = false; setTimeout(async () => { @@ -235,18 +238,18 @@ export class SettingsState { } else if (event.payload.status === "Error") { this.isDownloadingJava = false; } - } + }, ); } async loadJavaCatalog(forceRefresh: boolean) { this.isLoadingCatalog = true; this.catalogError = ""; - + try { const command = forceRefresh ? "refresh_java_catalog" : "fetch_java_catalog"; this.javaCatalog = await invoke<JavaCatalog>(command); - + // Auto-select first LTS version if (this.selectedMajorVersion === null && this.javaCatalog.lts_versions.length > 0) { // Select most recent LTS (21 or highest) @@ -283,21 +286,21 @@ export class SettingsState { uiState.setStatus("Selected Java version is not available for this platform"); return; } - + this.isDownloadingJava = true; this.javaDownloadStatus = "Starting download..."; this.downloadProgress = null; - + try { const result: JavaInstallation = await invoke("download_adoptium_java", { majorVersion: this.selectedMajorVersion, imageType: this.selectedImageType, customPath: null, }); - + this.settings.java_path = result.path; await this.detectJava(); - + setTimeout(() => { this.showJavaDownloadModal = false; uiState.setStatus(`Java ${this.selectedMajorVersion} is ready to use!`); @@ -324,10 +327,10 @@ export class SettingsState { async resumeDownloads() { if (this.pendingDownloads.length === 0) return; - + this.isDownloadingJava = true; this.javaDownloadStatus = "Resuming download..."; - + try { const installed = await invoke<JavaInstallation[]>("resume_java_downloads"); if (installed.length > 0) { diff --git a/ui/src/stores/ui.svelte.ts b/ui/src/stores/ui.svelte.ts index 9c29c25..e88f6b4 100644 --- a/ui/src/stores/ui.svelte.ts +++ b/ui/src/stores/ui.svelte.ts @@ -10,9 +10,9 @@ export class UIState { setStatus(msg: string) { if (this.statusTimeout) clearTimeout(this.statusTimeout); - + this.status = msg; - + if (msg !== "Ready") { this.statusTimeout = setTimeout(() => { this.status = "Ready"; diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 0f02d64..83e7f9e 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -159,4 +159,3 @@ export interface InstalledForgeVersion { // ==================== Mod Loader Type ==================== export type ModLoaderType = "vanilla" | "fabric" | "forge"; - diff --git a/ui/svelte.config.js b/ui/svelte.config.js index 96b3455..a710f1b 100644 --- a/ui/svelte.config.js +++ b/ui/svelte.config.js @@ -1,8 +1,8 @@ -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; /** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ export default { // Consult https://svelte.dev/docs#compile-time-svelte-preprocess // for more information about preprocessors preprocess: vitePreprocess(), -} +}; diff --git a/ui/tsconfig.json b/ui/tsconfig.json index 1ffef60..d32ff68 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -1,7 +1,4 @@ { "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] } diff --git a/ui/vite.config.ts b/ui/vite.config.ts index d5fcbc5..32610e2 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -1,26 +1,26 @@ -import { defineConfig } from 'vite' -import { svelte } from '@sveltejs/vite-plugin-svelte' -import tailwindcss from '@tailwindcss/vite' +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import tailwindcss from "@tailwindcss/vite"; // https://vite.dev/config/ export default defineConfig({ plugins: [tailwindcss(), svelte()], - + // Fix for Tauri + Vite HMR server: { host: true, strictPort: true, hmr: { - protocol: 'ws', - host: 'localhost', + protocol: "ws", + host: "localhost", port: 5173, }, watch: { usePolling: true, }, }, - + // Ensure compatibility with Tauri clearScreen: false, - envPrefix: ['VITE_', 'TAURI_'], -}) + envPrefix: ["VITE_", "TAURI_"], +}); |