[refactor] copied the existing types into the Svelte App

This commit is contained in:
2026-02-27 21:05:29 +01:00
parent 7027c97dd3
commit 1b82ef653e
11 changed files with 249 additions and 82 deletions

View File

@@ -1,79 +0,0 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

3
src/app/app.css Normal file
View File

@@ -0,0 +1,3 @@
@import "@picocss/pico/css/pico.classless.jade.min.css";
@import "./styles/base.css";
@import "./styles/countdown.css";

16
src/app/styles/base.css Normal file
View File

@@ -0,0 +1,16 @@
:root {
--pico-border-radius: 0.5rem;
--pico-typography-spacing-vertical: 1.5rem;
--pico-form-element-spacing-vertical: 1rem;
--pico-form-element-spacing-horizontal: 1.25rem;
}
h1 {
--pico-font-family: Pacifico, cursive;
--pico-font-weight: 400;
--pico-typography-spacing-vertical: 0.5rem;
}
button {
--pico-font-weight: 700;
}

View File

@@ -0,0 +1,10 @@
.countdown-actions {
display: flex;
gap: 0.5rem;
}
.countdown-error {
min-height: 1.25rem;
color: #b00020;
margin: 0.25rem 0;
}

View File

@@ -0,0 +1,45 @@
import {invokeCommand} from "../../shared/tauri/invoke";
import {CountdownCommand, CountdownSnapshot, CountdownState, Duration} from "./types";
import {millisToDuration} from "./helper.ts";
type CountdownSnapshotDto = {
id: number;
label: string;
duration: number;
state: CountdownState;
start_epoch_ms: number | null;
target_epoch_ms: number | null;
};
export async function fetchCountdownSnapshot(): Promise<CountdownSnapshot> {
let temp = await invokeCommand<CountdownSnapshotDto>("countdown_snapshot");
const duration: Duration = millisToDuration(temp.duration);
return {
id: temp.id,
label: temp.label,
duration: duration,
state: temp.state,
start_epoch: temp.start_epoch_ms !== null ? new Date(temp.start_epoch_ms) : null,
target_epoch: temp.target_epoch_ms !== null ? new Date(temp.target_epoch_ms) : null,
};
}
async function invokeCountdownCommand(command: CountdownCommand): Promise<void> {
await invokeCommand<void>(command);
}
export async function startCountdown(): Promise<void> {
await invokeCountdownCommand("countdown_start");
}
export async function pauseCountdown(): Promise<void> {
await invokeCountdownCommand("countdown_pause");
}
export async function resumeCountdown(): Promise<void> {
await invokeCountdownCommand("countdown_resume");
}
export async function resetCountdown(): Promise<void> {
await invokeCountdownCommand("countdown_reset");
}

View File

@@ -0,0 +1,48 @@
import {
fetchCountdownSnapshot,
pauseCountdown,
resetCountdown,
resumeCountdown,
startCountdown,
} from "./api";
import { createCountdownView } from "./view";
export async function initCountdownController(container: HTMLElement): Promise<void> {
const view = createCountdownView(container);
const refreshSnapshot = async (): Promise<void> => {
try {
const snapshot = await fetchCountdownSnapshot();
view.setSnapshot(snapshot);
} catch (error) {
view.setError(`snapshot error: ${String(error)}`);
}
};
const runAction = async (action: () => Promise<void>): Promise<void> => {
try {
await action();
await refreshSnapshot();
} catch (error) {
view.setError(`command error: ${String(error)}`);
}
};
view.onStart(() => {
void runAction(startCountdown);
});
view.onPause(() => {
void runAction(pauseCountdown);
});
view.onResume(() => {
void runAction(resumeCountdown);
});
view.onReset(() => {
void runAction(resetCountdown);
});
view.onRefresh(() => {
void refreshSnapshot();
});
await refreshSnapshot();
}

View File

@@ -0,0 +1,18 @@
import type {Duration} from "../../shared/time/duration";
export type CountdownState = "Idle" | "Running" | "Paused" | "Finished";
export type CountdownSnapshot = {
id: number;
label: string;
duration: Duration;
state: CountdownState;
start_epoch: Date | null;
target_epoch: Date | null;
}
export type CountdownCommand =
| "countdown_start"
| "countdown_pause"
| "countdown_resume"
| "countdown_reset";

View File

@@ -0,0 +1,79 @@
import {formatDuration} from "./helper.ts";
import {CountdownSnapshot} from "./types.ts";
export type CountdownView = {
onStart: (handler: () => void) => void;
onResume: (handler: () => void) => void;
onPause: (handler: () => void) => void;
onReset: (handler: () => void) => void;
onRefresh: (handler: () => void) => void;
setSnapshot: (snapshot: CountdownSnapshot) => void;
setError: (message: string) => void;
};
function makeButton(label: string): HTMLButtonElement {
const button = document.createElement("button");
button.id = "cd-btn-" + label.toLowerCase();
button.className = "countdown-btn"
button.type = "button";
button.textContent = label;
return button;
}
export function createCountdownView(container: HTMLElement): CountdownView {
const panel = document.createElement("section");
panel.className = "countdown-panel";
const title = document.createElement("h2");
title.textContent = "Countdown Controls";
const actions = document.createElement("div");
actions.className = "countdown-actions";
const startButton = makeButton("Start");
const resumeButton = makeButton("Resume");
const pauseButton = makeButton("Pause");
const resetButton = makeButton("Reset");
const refreshButton = makeButton("Refresh");
actions.append(startButton, resumeButton, pauseButton, resetButton, refreshButton);
const summary = document.createElement("p");
summary.className = "countdown-summary";
summary.textContent = "Waiting for snapshot...";
const error = document.createElement("p");
error.className = "countdown-error";
const snapshot = document.createElement("pre");
snapshot.className = "countdown-snapshot";
panel.append(title, actions, summary, error, snapshot);
container.appendChild(panel);
return {
onStart(handler) {
startButton.addEventListener("click", handler);
},
onResume(handler) {
resumeButton.addEventListener("click", handler);
},
onPause(handler) {
pauseButton.addEventListener("click", handler);
},
onReset(handler) {
resetButton.addEventListener("click", handler);
},
onRefresh(handler) {
refreshButton.addEventListener("click", handler);
},
setSnapshot(value) {
error.textContent = "";
summary.textContent = `State: ${value.state} | Remaining: ${formatDuration(value.duration)}`;
snapshot.textContent = JSON.stringify(value, null, 2);
},
setError(message) {
error.textContent = message;
},
};
}

View File

@@ -1,9 +1,9 @@
import { mount } from 'svelte'
import './app.css'
import {mount} from 'svelte'
import './app/app.css'
import App from './App.svelte'
const app = mount(App, {
target: document.getElementById('app')!,
target: document.getElementById('app')!,
})
export default app

View File

@@ -0,0 +1,8 @@
import { invoke } from "@tauri-apps/api/core";
export async function invokeCommand<T>(
command: string,
payload?: Record<string, unknown>
): Promise<T> {
return invoke<T>(command, payload);
}

View File

@@ -0,0 +1,19 @@
export type Duration = {
hours: number;
minutes: number;
seconds: number;
millis: number;
}
export function formatDuration(duration: Duration): string {
return `${duration.hours.toString().padStart(2, '0')}:${duration.minutes.toString().padStart(2, '0')}:${duration.seconds.toString().padStart(2, '0')}.${duration.millis.toString().padStart(3, '0')}`;
}
export function millisToDuration(millis: number): Duration {
return {
hours: Math.floor(millis / 3600000),
minutes: Math.floor((millis % 3600000) / 60000),
seconds: Math.floor((millis % 60000) / 1000),
millis: millis % 1000,
}
}