This commit is contained in:
2025-06-20 15:45:40 -04:00
parent 3dbbf34249
commit 07da14a943
121 changed files with 24041 additions and 1608 deletions

7
src-tauri/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

5117
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

28
src-tauri/Cargo.toml Normal file
View File

@@ -0,0 +1,28 @@
[package]
name = "basketball-scoreboard"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "basketball_scoreboard_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["time"] }
uuid = { version = "1", features = ["v4"] }
tauri-plugin-store = "2"
tauri-plugin-os = "2"

3
src-tauri/build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,16 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for windows",
"windows": [
"scoreboard",
"control-panel"
],
"permissions": [
"core:default",
"opener:default",
"store:default",
"core:window:allow-close",
"os:default"
]
}

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

38
src-tauri/src/lib.rs Normal file
View File

@@ -0,0 +1,38 @@
mod timer;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use tauri::{Manager, WindowEvent};
use timer::*;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_store::Builder::new().build())
.manage(Arc::new(Mutex::new(HashMap::<String, Timer>::new())))
.invoke_handler(tauri::generate_handler![
start_timer,
pause_timer,
reset_timer,
create_timer,
list_timers,
delete_timer,
set_timer_remaining,
set_timer_duration,
get_timer,
])
.on_window_event(|window, event| {
if let WindowEvent::CloseRequested { .. } = event {
if window.label() == "scoreboard" {
if let Some(ctrl) = window.get_webview_window("control-panel") {
let _ = ctrl.close();
}
}
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

5
src-tauri/src/main.rs Normal file
View File

@@ -0,0 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
basketball_scoreboard_lib::run()
}

178
src-tauri/src/timer.rs Normal file
View File

@@ -0,0 +1,178 @@
use serde::Serialize;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
time::{Duration, Instant},
};
use tauri::{AppHandle, Emitter, State};
use tokio::time::sleep;
use uuid::Uuid;
#[derive(Serialize, Clone)]
pub struct Timer {
pub id: String,
pub duration: u64,
pub remaining: u64,
pub running: bool,
#[serde(skip)]
pub last_tick: Instant,
}
impl Timer {
pub fn new(id: Option<String>, duration: u64) -> Self {
Self {
id: id.unwrap_or_else(|| Uuid::new_v4().to_string()),
duration,
remaining: duration,
running: false,
last_tick: Instant::now(),
}
}
pub fn reset(&mut self) {
self.remaining = self.duration;
self.running = false;
self.last_tick = Instant::now();
}
pub fn set_remaining(&mut self, remaining: u64) {
self.remaining = remaining;
if self.remaining == 0 {
self.running = false;
}
self.last_tick = Instant::now();
}
pub fn set_duration(&mut self, duration: u64) {
self.duration = duration;
self.remaining = duration;
self.running = false;
self.last_tick = Instant::now();
}
pub fn tick(&mut self, handle: AppHandle) {
if self.running {
let now = Instant::now();
let elapsed = now.duration_since(self.last_tick).as_millis() as u64;
if elapsed > 0 {
self.last_tick = now;
self.remaining = self.remaining.saturating_sub(elapsed);
}
if self.remaining == 0 {
self.running = false;
}
let _ = handle.emit("timer-tick", self.to_owned());
}
}
}
pub type Timers = Arc<Mutex<HashMap<String, Timer>>>;
#[tauri::command]
pub fn create_timer(
id: Option<String>,
duration: u64,
app: AppHandle,
timers: State<'_, Timers>,
) -> Timer {
let mut map = timers.lock().unwrap();
let timer = Timer::new(id, duration);
let ret = timer.clone();
map.insert(ret.id.clone(), timer);
let _ = app.emit("timers-list-updated", map.clone());
ret
}
#[tauri::command]
pub fn delete_timer(id: String, app: AppHandle, timers: State<'_, Timers>) {
let mut map = timers.lock().unwrap();
map.remove(&id);
let _ = app.emit("timers-list-updated", map.clone());
}
#[tauri::command]
pub fn list_timers(timers: State<'_, Timers>) -> HashMap<String, Timer> {
let map = timers.lock().unwrap();
map.clone()
}
#[tauri::command]
pub fn get_timer(id: String, timers: State<'_, Timers>) -> Option<Timer> {
let map = timers.lock().unwrap();
map.get(&id).cloned()
}
#[tauri::command]
pub fn start_timer(id: String, app: AppHandle, timers: State<'_, Timers>) {
{
let mut map = timers.lock().unwrap();
let t = match map.get_mut(&id) {
Some(t) => t,
None => return,
};
if t.running {
return;
}
t.running = true;
t.last_tick = Instant::now();
}
let timers = timers.inner().clone();
let app_handle = app.clone();
tauri::async_runtime::spawn(async move {
loop {
{
let mut map = timers.lock().unwrap();
let t = map.get_mut(&id).unwrap();
if !t.running {
break;
}
t.tick(app_handle.clone());
}
sleep(Duration::from_millis(1)).await;
}
});
}
#[tauri::command]
pub fn pause_timer(id: String, app: AppHandle, timers: State<'_, Timers>) {
if let Some(t) = timers.lock().unwrap().get_mut(&id) {
t.running = false;
t.last_tick = Instant::now();
app.emit("timer-tick", t.to_owned()).unwrap();
}
}
#[tauri::command]
pub fn reset_timer(id: String, app: AppHandle, timers: State<'_, Timers>) {
let mut map = timers.lock().unwrap();
let t = match map.get_mut(&id) {
Some(t) => t,
None => return,
};
t.reset();
let _ = app.emit("timer-tick", t.to_owned());
}
#[tauri::command]
pub fn set_timer_remaining(id: String, remaining: u64, app: AppHandle, timers: State<'_, Timers>) {
let mut map = timers.lock().unwrap();
if let Some(t) = map.get_mut(&id) {
t.set_remaining(remaining);
let _ = app.emit("timer-tick", t.to_owned());
}
}
#[tauri::command]
pub fn set_timer_duration(id: String, duration: u64, app: AppHandle, timers: State<'_, Timers>) {
let mut map = timers.lock().unwrap();
if let Some(t) = map.get_mut(&id) {
t.set_duration(duration);
let _ = app.emit("timer-tick", t.to_owned());
}
}

47
src-tauri/tauri.conf.json Normal file
View File

@@ -0,0 +1,47 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "basketball-scoreboard",
"version": "0.1.0",
"identifier": "dev.altaiar.bss2",
"build": {
"beforeDevCommand": "yarn dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "yarn build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "Scoreboard",
"label": "scoreboard",
"url": "/scoreboard",
"fullscreen": true
},
{
"title": "Control Panel",
"label": "control-panel",
"url": "/control-panel",
"width": 640,
"height": 240,
"closable": false,
"resizable": true,
"center": true,
"parent": "scoreboard"
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}