aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorHsiangNianian <44714368+HsiangNianian@users.noreply.github.com>2026-01-16 01:35:23 +0000
committergithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>2026-01-16 01:35:23 +0000
commit963b4b8567ac1bd8b23c41e1bfbd6a99d202d1ed (patch)
tree6df911b964d0d32facf55844c55e61ba3878ae3f
parente21e288275806df7785d869667c90062e1890bf0 (diff)
downloadDropOut-963b4b8567ac1bd8b23c41e1bfbd6a99d202d1ed.tar.gz
DropOut-963b4b8567ac1bd8b23c41e1bfbd6a99d202d1ed.zip
style: auto format and lint fix [skip ci]
-rw-r--r--ui/README.md4
-rw-r--r--ui/package.json20
-rw-r--r--ui/src/app.css6
-rw-r--r--ui/src/lib/effects/ConstellationEffect.ts129
-rw-r--r--ui/src/lib/effects/SaturnEffect.ts233
-rw-r--r--ui/src/lib/modLoaderApi.ts12
-rw-r--r--ui/src/main.ts12
-rw-r--r--ui/src/stores/auth.svelte.ts27
-rw-r--r--ui/src/stores/logs.svelte.ts46
-rw-r--r--ui/src/stores/settings.svelte.ts89
-rw-r--r--ui/src/stores/ui.svelte.ts4
-rw-r--r--ui/src/types/index.ts1
-rw-r--r--ui/svelte.config.js4
-rw-r--r--ui/tsconfig.json5
-rw-r--r--ui/vite.config.ts18
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_"],
+});