support events

This commit is contained in:
Julian Freeman
2026-03-22 17:35:44 -04:00
parent 4016ed0d53
commit 6aff740207
6 changed files with 494 additions and 296 deletions

81
src-tauri/Cargo.lock generated
View File

@@ -673,6 +673,7 @@ dependencies = [
"base64 0.22.1",
"chrono",
"image",
"rusqlite",
"serde",
"serde_json",
"tauri",
@@ -1078,7 +1079,7 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a"
dependencies = [
"libloading 0.7.4",
"libloading 0.8.9",
]
[[package]]
@@ -1337,6 +1338,18 @@ dependencies = [
"zune-inflate",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "2.3.0"
@@ -1944,6 +1957,18 @@ name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"foldhash 0.2.0",
]
[[package]]
name = "hashlink"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230"
dependencies = [
"hashbrown 0.16.1",
]
[[package]]
name = "heck"
@@ -2627,6 +2652,17 @@ dependencies = [
"system-deps 7.0.7",
]
[[package]]
name = "libsqlite3-sys"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "libwayshot-xcap"
version = "0.3.2"
@@ -4234,6 +4270,31 @@ version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
[[package]]
name = "rsqlite-vfs"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d"
dependencies = [
"hashbrown 0.16.1",
"thiserror 2.0.18",
]
[[package]]
name = "rusqlite"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e"
dependencies = [
"bitflags 2.11.0",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
"sqlite-wasm-rs",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
@@ -4691,6 +4752,18 @@ dependencies = [
"system-deps 6.2.2",
]
[[package]]
name = "sqlite-wasm-rs"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b"
dependencies = [
"cc",
"js-sys",
"rsqlite-vfs",
"wasm-bindgen",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@@ -5741,6 +5814,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version-compare"
version = "0.2.1"

View File

@@ -31,6 +31,7 @@ chrono = "0.4.44"
tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "time"] }
anyhow = "1.0.102"
base64 = "0.22.1"
rusqlite = { version = "0.39.0", features = ["bundled"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2"

135
src-tauri/src/db.rs Normal file
View File

@@ -0,0 +1,135 @@
use rusqlite::{params, Connection};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Serialize, Deserialize, Clone)]
pub struct Tag {
pub id: i64,
pub name: String,
pub parent_id: Option<i64>,
pub color: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Event {
pub id: i64,
pub date: String,
pub start_minute: i32,
pub end_minute: i32,
pub main_tag_id: i64,
pub sub_tag_id: Option<i64>,
pub content: String,
}
pub fn init_db(path: &str) -> anyhow::Result<()> {
let conn = Connection::open(path)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
parent_id INTEGER,
color TEXT NOT NULL,
FOREIGN KEY(parent_id) REFERENCES tags(id)
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
start_minute INTEGER NOT NULL,
end_minute INTEGER NOT NULL,
main_tag_id INTEGER NOT NULL,
sub_tag_id INTEGER,
content TEXT,
FOREIGN KEY(main_tag_id) REFERENCES tags(id),
FOREIGN KEY(sub_tag_id) REFERENCES tags(id)
)",
[],
)?;
Ok(())
}
pub fn get_tags(path: &str) -> anyhow::Result<Vec<Tag>> {
let conn = Connection::open(path)?;
let mut stmt = conn.prepare("SELECT id, name, parent_id, color FROM tags")?;
let tag_iter = stmt.query_map([], |row| {
Ok(Tag {
id: row.get(0)?,
name: row.get(1)?,
parent_id: row.get(2)?,
color: row.get(3)?,
})
})?;
let mut results = Vec::new();
for tag in tag_iter {
results.push(tag?);
}
Ok(results)
}
pub fn add_tag(path: &str, name: &str, parent_id: Option<i64>, color: &str) -> anyhow::Result<i64> {
let conn = Connection::open(path)?;
conn.execute(
"INSERT INTO tags (name, parent_id, color) VALUES (?1, ?2, ?3)",
params![name, parent_id, color],
)?;
Ok(conn.last_insert_rowid())
}
pub fn delete_tag(path: &str, id: i64) -> anyhow::Result<()> {
let conn = Connection::open(path)?;
// Also delete child tags
conn.execute("DELETE FROM tags WHERE parent_id = ?1", params![id])?;
conn.execute("DELETE FROM tags WHERE id = ?1", params![id])?;
Ok(())
}
pub fn get_events(path: &str, date: &str) -> anyhow::Result<Vec<Event>> {
let conn = Connection::open(path)?;
let mut stmt = conn.prepare("SELECT id, date, start_minute, end_minute, main_tag_id, sub_tag_id, content FROM events WHERE date = ?1")?;
let event_iter = stmt.query_map(params![date], |row| {
Ok(Event {
id: row.get(0)?,
date: row.get(1)?,
start_minute: row.get(2)?,
end_minute: row.get(3)?,
main_tag_id: row.get(4)?,
sub_tag_id: row.get(5)?,
content: row.get(6)?,
})
})?;
let mut results = Vec::new();
for event in event_iter {
results.push(event?);
}
Ok(results)
}
pub fn save_event(path: &str, event: Event) -> anyhow::Result<i64> {
let conn = Connection::open(path)?;
if event.id > 0 {
conn.execute(
"UPDATE events SET date=?1, start_minute=?2, end_minute=?3, main_tag_id=?4, sub_tag_id=?5, content=?6 WHERE id=?7",
params![event.date, event.start_minute, event.end_minute, event.main_tag_id, event.sub_tag_id, event.content, event.id],
)?;
Ok(event.id)
} else {
conn.execute(
"INSERT INTO events (date, start_minute, end_minute, main_tag_id, sub_tag_id, content) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
params![event.date, event.start_minute, event.end_minute, event.main_tag_id, event.sub_tag_id, event.content],
)?;
Ok(conn.last_insert_rowid())
}
}
pub fn delete_event(path: &str, id: i64) -> anyhow::Result<()> {
let conn = Connection::open(path)?;
conn.execute("DELETE FROM events WHERE id = ?1", params![id])?;
Ok(())
}

View File

@@ -11,9 +11,60 @@ use tauri_plugin_store::StoreExt;
pub struct AppState {
pub is_paused: Arc<AtomicBool>,
pub capture_interval_secs: std::sync::atomic::AtomicU64,
pub db_path: std::sync::Mutex<Option<String>>,
pub toggle_menu_item: std::sync::Mutex<Option<tauri::menu::MenuItem<tauri::Wry>>>,
}
#[tauri::command]
pub fn update_db_path(state: tauri::State<'_, AppState>, path: String) -> Result<(), String> {
crate::db::init_db(&path).map_err(|e| e.to_string())?;
let mut db_path = state.db_path.lock().unwrap();
*db_path = Some(path);
Ok(())
}
#[tauri::command]
pub fn get_tags(state: tauri::State<'_, AppState>) -> Result<Vec<crate::db::Tag>, String> {
let path = state.db_path.lock().unwrap();
let path = path.as_ref().ok_or("Database path not set")?;
crate::db::get_tags(path).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn add_tag(state: tauri::State<'_, AppState>, name: String, parent_id: Option<i64>, color: String) -> Result<i64, String> {
let path = state.db_path.lock().unwrap();
let path = path.as_ref().ok_or("Database path not set")?;
crate::db::add_tag(path, &name, parent_id, &color).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn delete_tag(state: tauri::State<'_, AppState>, id: i64) -> Result<(), String> {
let path = state.db_path.lock().unwrap();
let path = path.as_ref().ok_or("Database path not set")?;
crate::db::delete_tag(path, id).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn get_events(state: tauri::State<'_, AppState>, date: String) -> Result<Vec<crate::db::Event>, String> {
let path = state.db_path.lock().unwrap();
let path = path.as_ref().ok_or("Database path not set")?;
crate::db::get_events(path, &date).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn save_event(state: tauri::State<'_, AppState>, event: crate::db::Event) -> Result<i64, String> {
let path = state.db_path.lock().unwrap();
let path = path.as_ref().ok_or("Database path not set")?;
crate::db::save_event(path, event).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn delete_event(state: tauri::State<'_, AppState>, id: i64) -> Result<(), String> {
let path = state.db_path.lock().unwrap();
let path = path.as_ref().ok_or("Database path not set")?;
crate::db::delete_event(path, id).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn update_interval(state: tauri::State<'_, AppState>, seconds: u64) {
state.capture_interval_secs.store(seconds, Ordering::SeqCst);

View File

@@ -1,3 +1,4 @@
mod db;
mod engine;
mod tray;
@@ -21,12 +22,20 @@ pub fn run() {
engine::get_pause_state,
engine::get_timeline,
engine::get_image_base64,
engine::update_interval
engine::update_interval,
engine::update_db_path,
engine::get_tags,
engine::add_tag,
engine::delete_tag,
engine::get_events,
engine::save_event,
engine::delete_event
])
.setup(|app| {
app.manage(engine::AppState {
is_paused: Arc::new(AtomicBool::new(false)),
capture_interval_secs: std::sync::atomic::AtomicU64::new(30),
db_path: std::sync::Mutex::new(None),
toggle_menu_item: std::sync::Mutex::new(None),
});
tray::create_tray(app.handle())?;