Moved timers into a plugin
This commit is contained in:
parent
227160e044
commit
f67d5f6a2e
7
.vscode/tasks.json
vendored
7
.vscode/tasks.json
vendored
@ -28,6 +28,13 @@
|
|||||||
"command": "npm",
|
"command": "npm",
|
||||||
"problemMatcher": [],
|
"problemMatcher": [],
|
||||||
"args": ["run", "build"]
|
"args": ["run", "build"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "ui:format",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "npm",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"args": ["run", "format"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,174 +1,15 @@
|
|||||||
|
// Copyright 2022 ModZero.
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
all(not(debug_assertions), target_os = "windows"),
|
all(not(debug_assertions), target_os = "windows"),
|
||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use std::{
|
mod timers;
|
||||||
collections::HashMap,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
time::{Duration, Instant}, fmt, error::Error,
|
|
||||||
};
|
|
||||||
|
|
||||||
use chrono::{DateTime, FixedOffset, Local};
|
|
||||||
use tauri::{State, Window, async_runtime::spawn};
|
|
||||||
use tokio::time::interval;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(serde::Serialize, Debug)]
|
|
||||||
enum TimerError {
|
|
||||||
NotFound,
|
|
||||||
NotStarted,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for TimerError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
TimerError::NotFound => write!(f, "timer not found"),
|
|
||||||
TimerError::NotStarted => write!(f, "timer not started"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for TimerError {}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, serde::Serialize)]
|
|
||||||
struct Timer {
|
|
||||||
id: Uuid,
|
|
||||||
started: Option<DateTime<FixedOffset>>,
|
|
||||||
duration: Duration,
|
|
||||||
elapsed: Option<Duration>,
|
|
||||||
message: String,
|
|
||||||
#[serde(skip)]
|
|
||||||
checked: Option<Instant>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Timer {
|
|
||||||
fn new(message: &str, duration: Duration) -> Self {
|
|
||||||
Self {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
duration,
|
|
||||||
message: message.to_string(),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete(&self) -> bool {
|
|
||||||
self.elapsed.map_or(false, |e| e >= self.duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start(self) -> Self {
|
|
||||||
Timer {
|
|
||||||
started: Some(Local::now().into()),
|
|
||||||
elapsed: Some(Duration::from_secs(0)),
|
|
||||||
checked: Some(Instant::now()),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick(self) -> Result<Self, TimerError> {
|
|
||||||
let now = Instant::now();
|
|
||||||
let elapsed = now - match self.checked {
|
|
||||||
None => return Err(TimerError::NotStarted),
|
|
||||||
Some(checked) => checked,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Timer {
|
|
||||||
elapsed: self.elapsed.map(|e| e + elapsed),
|
|
||||||
checked: Some(now),
|
|
||||||
..self
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Timers(Arc<Mutex<HashMap<Uuid, Timer>>>);
|
|
||||||
|
|
||||||
impl Timers {
|
|
||||||
fn make_timer(&self, duration: Duration, message: &str) -> Timer {
|
|
||||||
let timer = Timer::new(message, duration);
|
|
||||||
|
|
||||||
self.0.lock().unwrap().insert(timer.id, timer.clone());
|
|
||||||
|
|
||||||
timer
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_timer(&self, timer_id: Uuid) -> Option<Timer> {
|
|
||||||
self.0.lock().unwrap().get(&timer_id).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_timer(&self, timer_id: Uuid) -> Result<Timer, TimerError> {
|
|
||||||
let mut timers = self.0.lock().unwrap();
|
|
||||||
match timers.get(&timer_id).cloned() {
|
|
||||||
None => Err(TimerError::NotFound),
|
|
||||||
Some(t) => {
|
|
||||||
let started = t.start();
|
|
||||||
timers.insert(started.id, started.clone());
|
|
||||||
|
|
||||||
Ok(started)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick_timer(&self, timer_id: Uuid) -> Result<Timer, TimerError> {
|
|
||||||
let mut timers = self.0.lock().unwrap();
|
|
||||||
match timers.get(&timer_id).cloned() {
|
|
||||||
None => Err(TimerError::NotFound),
|
|
||||||
Some(t) => {
|
|
||||||
let started = t.tick()?;
|
|
||||||
timers.insert(started.id, started.clone());
|
|
||||||
|
|
||||||
Ok(started)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn make_timer(timers: State<'_, Timers>, duration: Duration, message: &str) -> Timer {
|
|
||||||
timers.make_timer(duration, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn delete_timer(timers: State<'_, Timers>, timer_id: Uuid) -> Option<Timer> {
|
|
||||||
timers.delete_timer(timer_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn start_timer(window: Window, timers: State<'_, Timers>, timer_id: Uuid) -> Result<Timer, TimerError> {
|
|
||||||
let timers = Timers(timers.0.to_owned());
|
|
||||||
let timer = timers.start_timer(timer_id)?;
|
|
||||||
let res = timer.clone();
|
|
||||||
|
|
||||||
spawn(async move {
|
|
||||||
let mut interval = interval(Duration::from_secs(1) / 60);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
interval.tick().await;
|
|
||||||
match timers.tick_timer(timer_id) {
|
|
||||||
Err(_) => break,
|
|
||||||
Ok(timer) => {
|
|
||||||
if timer.complete() || window.emit("timer-tick", timer).is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.emit("timer-done", timer).ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.manage(Timers(Default::default()))
|
.plugin(timers::init())
|
||||||
.invoke_handler(tauri::generate_handler![
|
|
||||||
delete_timer,
|
|
||||||
make_timer,
|
|
||||||
start_timer
|
|
||||||
])
|
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
185
src-tauri/src/timers.rs
Normal file
185
src-tauri/src/timers.rs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
// Copyright 2022 ModZero.
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
error::Error,
|
||||||
|
fmt,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use chrono::{DateTime, FixedOffset, Local};
|
||||||
|
use tauri::{
|
||||||
|
async_runtime::spawn,
|
||||||
|
plugin::{Builder, TauriPlugin},
|
||||||
|
AppHandle, Runtime, State, Manager,
|
||||||
|
};
|
||||||
|
use tokio::time::interval;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, Debug)]
|
||||||
|
pub enum TimerError {
|
||||||
|
NotFound,
|
||||||
|
NotStarted,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TimerError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
TimerError::NotFound => write!(f, "timer not found"),
|
||||||
|
TimerError::NotStarted => write!(f, "timer not started"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for TimerError {}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, serde::Serialize)]
|
||||||
|
pub struct Timer {
|
||||||
|
id: Uuid,
|
||||||
|
started: Option<DateTime<FixedOffset>>,
|
||||||
|
duration: Duration,
|
||||||
|
elapsed: Option<Duration>,
|
||||||
|
message: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
checked: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timer {
|
||||||
|
fn new(message: &str, duration: Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
duration,
|
||||||
|
message: message.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_complete(&self) -> bool {
|
||||||
|
self.elapsed.map_or(false, |e| e >= self.duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(self) -> Self {
|
||||||
|
Timer {
|
||||||
|
started: Some(Local::now().into()),
|
||||||
|
elapsed: Some(Duration::from_secs(0)),
|
||||||
|
checked: Some(Instant::now()),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(self) -> Result<Self, TimerError> {
|
||||||
|
let now = Instant::now();
|
||||||
|
let elapsed = now
|
||||||
|
- match self.checked {
|
||||||
|
None => return Err(TimerError::NotStarted),
|
||||||
|
Some(checked) => checked,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Timer {
|
||||||
|
elapsed: self.elapsed.map(|e| e + elapsed),
|
||||||
|
checked: Some(now),
|
||||||
|
..self
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Timers(Arc<Mutex<HashMap<Uuid, Timer>>>);
|
||||||
|
|
||||||
|
impl Timers {
|
||||||
|
fn make(&self, duration: Duration, message: &str) -> Timer {
|
||||||
|
let timer = Timer::new(message, duration);
|
||||||
|
|
||||||
|
self.0.lock().unwrap().insert(timer.id, timer.clone());
|
||||||
|
|
||||||
|
timer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(&self, timer_id: Uuid) -> Option<Timer> {
|
||||||
|
self.0.lock().unwrap().get(&timer_id).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self, timer_id: Uuid) -> Result<Timer, TimerError> {
|
||||||
|
let mut timers = self.0.lock().unwrap();
|
||||||
|
match timers.get(&timer_id).cloned() {
|
||||||
|
None => Err(TimerError::NotFound),
|
||||||
|
Some(t) => {
|
||||||
|
let started = t.start();
|
||||||
|
timers.insert(started.id, started.clone());
|
||||||
|
|
||||||
|
Ok(started)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(&self, timer_id: Uuid) -> Result<Timer, TimerError> {
|
||||||
|
let mut timers = self.0.lock().unwrap();
|
||||||
|
match timers.get(&timer_id).cloned() {
|
||||||
|
None => Err(TimerError::NotFound),
|
||||||
|
Some(t) => {
|
||||||
|
let started = t.tick()?;
|
||||||
|
timers.insert(started.id, started.clone());
|
||||||
|
|
||||||
|
Ok(started)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn make(timers: State<'_, Timers>, duration: Duration, message: &str) -> Timer {
|
||||||
|
timers.make(duration, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn delete(timers: State<'_, Timers>, timer_id: Uuid) -> Option<Timer> {
|
||||||
|
timers.delete(timer_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn start<R: Runtime>(
|
||||||
|
_app: AppHandle<R>,
|
||||||
|
timers: State<'_, Timers>,
|
||||||
|
timer_id: Uuid,
|
||||||
|
) -> Result<Timer, TimerError> {
|
||||||
|
let timers = Timers(timers.0.to_owned());
|
||||||
|
let timer = timers.start(timer_id)?;
|
||||||
|
|
||||||
|
spawn(async move {
|
||||||
|
let mut interval = interval(Duration::from_secs(1) / 60);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
match timers.tick(timer_id) {
|
||||||
|
Err(_) => break,
|
||||||
|
Ok(timer) => {
|
||||||
|
if timer.is_complete() {
|
||||||
|
_app.emit_all("timer-done", timer).ok();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if _app.emit_all("timer-tick", timer).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||||
|
Builder::new("timers")
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
delete,
|
||||||
|
make,
|
||||||
|
start
|
||||||
|
])
|
||||||
|
.setup(|app_handle| {
|
||||||
|
app_handle.manage(Timers::default());
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
@ -28,7 +28,6 @@
|
|||||||
timerDoneUnlisten = listen<Timer>("timer-done", (event) => {
|
timerDoneUnlisten = listen<Timer>("timer-done", (event) => {
|
||||||
console.log("Done!", event.payload.id);
|
console.log("Done!", event.payload.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@ -37,11 +36,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function startTimer() {
|
async function startTimer() {
|
||||||
let timer = await invoke<Timer>("make_timer", {
|
let timer = await invoke<Timer>("plugin:timers|make", {
|
||||||
duration: { secs: seconds, nanos: 0 },
|
duration: { secs: seconds, nanos: 0 },
|
||||||
message: "Hi!",
|
message: "Hi!",
|
||||||
});
|
});
|
||||||
invoke("start_timer", { timerId: timer.id });
|
invoke("plugin:timers|start", { timerId: timer.id });
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ h1 {
|
|||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
// Copyright 2022 ModZero.
|
// Copyright 2022 ModZero.
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
import App from "./App.svelte";
|
import App from "./App.svelte";
|
||||||
|
|
||||||
|
|
||||||
const app = new App({
|
const app = new App({
|
||||||
target: document.getElementById("app"),
|
target: document.getElementById("app"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||||
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [svelte()],
|
plugins: [svelte()],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user