Compare commits

..

No commits in common. "227160e04455888ab3b644f52f2eded4c21ca232" and "3265015284a9cde15b1d2cfbd3e4c0c68cdd0f45" have entirely different histories.

5 changed files with 89 additions and 182 deletions

View File

@ -1,7 +1,7 @@
GNU AFFERO GENERAL PUBLIC LICENSE GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007 Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
@ -643,7 +643,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
@ -658,4 +658,4 @@ specific requirements.
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.

View File

@ -3,34 +3,13 @@
windows_subsystem = "windows" windows_subsystem = "windows"
)] )]
use std::{ use std::time::{Duration, Instant};
collections::HashMap,
sync::{Arc, Mutex},
time::{Duration, Instant}, fmt, error::Error,
};
use chrono::{DateTime, FixedOffset, Local}; use chrono::{DateTime, FixedOffset, Local};
use tauri::{State, Window, async_runtime::spawn}; use tauri::{async_runtime::spawn, Window};
use tokio::time::interval; use tokio::time::interval;
use uuid::Uuid; 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)] #[derive(Clone, Default, serde::Serialize)]
struct Timer { struct Timer {
id: Uuid, id: Uuid,
@ -38,8 +17,6 @@ struct Timer {
duration: Duration, duration: Duration,
elapsed: Option<Duration>, elapsed: Option<Duration>,
message: String, message: String,
#[serde(skip)]
checked: Option<Instant>,
} }
impl Timer { impl Timer {
@ -56,119 +33,52 @@ impl Timer {
self.elapsed.map_or(false, |e| e >= self.duration) self.elapsed.map_or(false, |e| e >= self.duration)
} }
fn start(self) -> Self { async fn run(&mut self, window: Window) {
Timer { self.started = Some(Local::now().into());
started: Some(Local::now().into()), let mut elapsed = Duration::from_secs(0);
elapsed: Some(Duration::from_secs(0)), self.elapsed = Some(elapsed);
checked: Some(Instant::now()), let mut last_checked = 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); let mut interval = interval(Duration::from_secs(1) / 60);
loop { loop {
interval.tick().await; interval.tick().await;
match timers.tick_timer(timer_id) { let now = Instant::now();
Err(_) => break, let duration = now - last_checked;
Ok(timer) => {
if timer.complete() || window.emit("timer-tick", timer).is_err() { elapsed += duration;
break; self.elapsed = Some(elapsed);
}
} if self.complete() {
break;
} }
if window.emit("timer-tick", self.clone()).is_err() {
break;
}
last_checked = now;
} }
window.emit("timer-done", timer).ok(); window
.emit("timer-done", self.clone())
.expect("Our window went away?");
}
}
#[tauri::command]
fn start_timer(window: Window, duration: Duration, message: &str) -> Uuid {
let mut timer = Timer::new(message, duration);
let timer_id = timer.id;
spawn(async move {
timer.run(window).await;
}); });
Ok(res) timer_id
} }
fn main() { fn main() {
tauri::Builder::default() tauri::Builder::default()
.manage(Timers(Default::default())) .invoke_handler(tauri::generate_handler![start_timer])
.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");
} }

View File

@ -1,7 +1,3 @@
<!--
Copyright 2022 ModZero.
SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script type="ts"> <script type="ts">
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import type { UnlistenFn } from "@tauri-apps/api/event"; import type { UnlistenFn } from "@tauri-apps/api/event";
@ -9,8 +5,8 @@
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
let seconds = 5; let seconds = 5;
let timerTickUnlisten: Promise<UnlistenFn> | null = null; let timer_tick_unlisten: Promise<UnlistenFn> | null = null;
let timerDoneUnlisten: Promise<UnlistenFn> | null = null; let timer_done_unlisten: Promise<UnlistenFn> | null = null;
type Timer = { type Timer = {
id: string; id: string;
@ -21,58 +17,32 @@
}; };
onMount(() => { onMount(() => {
timerTickUnlisten = listen<Timer>("timer-tick", (event) => { timer_tick_unlisten = listen<Timer>("timer-tick", (event) => {
console.log("Tick!", event.payload.id, event.payload.elapsed); console.log("Tick!", event.payload.id, event.payload.elapsed);
}); });
timerDoneUnlisten = listen<Timer>("timer-done", (event) => { timer_done_unlisten = listen<Timer>("timer-done", (event) => {
console.log("Done!", event.payload.id); console.log("Done!", event.payload.id);
}); });
}); });
onDestroy(() => { onDestroy(() => {
timerTickUnlisten?.then((ttu) => ttu()); timer_tick_unlisten?.then((ttu) => ttu());
timerDoneUnlisten?.then((tdu) => tdu()); timer_done_unlisten?.then((tdu) => tdu());
}); });
async function startTimer() { function start_timer() {
let timer = await invoke<Timer>("make_timer", { invoke("start_timer", {
duration: { secs: seconds, nanos: 0 }, duration: { secs: seconds, nanos: 0 },
message: "Hi!", message: "Hi!",
}); });
invoke("start_timer", { timerId: timer.id });
} }
</script> </script>
<main> <main>
<div id="timer" /> <label>
<div id="controls"> Fire after
<label> <input type="number" bind:value={seconds} />
Fire after </label>
<input type="number" bind:value={seconds} /> <button on:click={start_timer}>Fire!</button>
</label>
<button on:click={startTimer}>Fire!</button>
</div>
</main> </main>
<style>
main {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
display: flex;
flex-direction: column;
}
#timer {
flex-grow: 1;
margin: 0.5em;
}
#controls {
margin: 0.5em;
padding: 0.5em;
}
</style>

View File

@ -1,5 +1,5 @@
:root { :root {
font-family: Helvetica, Arial, sans-serif; font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
font-weight: 400; font-weight: 400;
@ -26,6 +26,7 @@ a:hover {
body { body {
margin: 0; margin: 0;
display: flex;
place-items: center; place-items: center;
min-width: 320px; min-width: 320px;
min-height: 100vh; min-height: 100vh;
@ -36,15 +37,45 @@ h1 {
line-height: 1.1; line-height: 1.1;
} }
.card {
padding: 2em;
}
#app { #app {
position: absolute;
max-width: 1280px; max-width: 1280px;
margin: 0 0; margin: 0 auto;
padding: 2rem; padding: 2rem;
text-align: center; text-align: center;
top: 0; }
bottom: 0;
left: 0; button {
right: 0; 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;
}
} }

View File

@ -1,7 +1,3 @@
// Copyright 2022 ModZero.
// SPDX-License-Identifier: AGPL-3.0-or-later
import "./app.css"; import "./app.css";
import App from "./App.svelte"; import App from "./App.svelte";