feat(tauri): add move, rename, grouping commands and file watcher
Add Tauri commands: move_task, rename_list, set/get_group_by_due_date, watch_workspace. Implement file watcher using notify crate with 500ms debounce and self-change suppression via mute_watcher(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c4c03679ae
commit
5faf285d28
213
apps/tauri/src-tauri/Cargo.lock
generated
213
apps/tauri/src-tauri/Cargo.lock
generated
|
|
@ -94,39 +94,6 @@ version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "onyx-core"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"directories",
|
|
||||||
"keyring",
|
|
||||||
"quick-xml 0.36.2",
|
|
||||||
"reqwest 0.12.28",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_yaml",
|
|
||||||
"sha2",
|
|
||||||
"tokio",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "onyx-tauri"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"onyx-core",
|
|
||||||
"chrono",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tauri",
|
|
||||||
"tauri-build",
|
|
||||||
"tauri-plugin-dialog",
|
|
||||||
"tauri-plugin-os",
|
|
||||||
"tokio",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
@ -842,6 +809,17 @@ dependencies = [
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"libredox",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
|
|
@ -912,6 +890,15 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent-sys"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futf"
|
name = "futf"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
|
@ -1661,6 +1648,35 @@ dependencies = [
|
||||||
"cfb",
|
"cfb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"inotify-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify-sys"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instant"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.12.0"
|
version = "2.12.0"
|
||||||
|
|
@ -1786,6 +1802,26 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
|
||||||
|
dependencies = [
|
||||||
|
"kqueue-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue-sys"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kuchikiki"
|
name = "kuchikiki"
|
||||||
version = "0.8.8-speedreader"
|
version = "0.8.8-speedreader"
|
||||||
|
|
@ -1859,7 +1895,10 @@ version = "0.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
|
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
"libc",
|
"libc",
|
||||||
|
"plain",
|
||||||
|
"redox_syscall 0.7.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1981,6 +2020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"log",
|
||||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
@ -2060,6 +2100,46 @@ version = "0.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify"
|
||||||
|
version = "7.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"filetime",
|
||||||
|
"fsevent-sys",
|
||||||
|
"inotify",
|
||||||
|
"kqueue",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"mio",
|
||||||
|
"notify-types",
|
||||||
|
"walkdir",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-debouncer-mini"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aaa5a66d07ed97dce782be94dcf5ab4d1b457f4243f7566c7557f15cabc8c799"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"notify",
|
||||||
|
"notify-types",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-types"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174"
|
||||||
|
dependencies = [
|
||||||
|
"instant",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -2299,6 +2379,41 @@ version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "onyx-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"directories",
|
||||||
|
"keyring",
|
||||||
|
"quick-xml 0.36.2",
|
||||||
|
"reqwest 0.12.28",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
|
"sha2",
|
||||||
|
"tokio",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "onyx-tauri"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"notify",
|
||||||
|
"notify-debouncer-mini",
|
||||||
|
"onyx-core",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-build",
|
||||||
|
"tauri-plugin-dialog",
|
||||||
|
"tauri-plugin-os",
|
||||||
|
"tokio",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -2364,7 +2479,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall 0.5.18",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
@ -2580,6 +2695,12 @@ version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plain"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plist"
|
name = "plist"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
|
@ -2933,6 +3054,15 @@ dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
|
@ -3612,7 +3742,7 @@ dependencies = [
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-quartz-core",
|
"objc2-quartz-core",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"redox_syscall",
|
"redox_syscall 0.5.18",
|
||||||
"tracing",
|
"tracing",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
|
@ -4131,6 +4261,19 @@ dependencies = [
|
||||||
"toml 0.9.12+spec-1.1.0",
|
"toml 0.9.12+spec-1.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom 0.4.2",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tendril"
|
name = "tendril"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ onyx-core = { path = "../../../crates/onyx-core" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
uuid = { version = "1", features = ["serde", "v4"] }
|
uuid = { version = "1", features = ["serde", "v4"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
notify = "7"
|
||||||
|
notify-debouncer-mini = "0.5"
|
||||||
|
|
||||||
[package.metadata.tauri]
|
[package.metadata.tauri]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use notify_debouncer_mini::{new_debouncer, DebouncedEventKind};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::State;
|
use tauri::{Emitter, Manager, State};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use onyx_core::{
|
use onyx_core::{
|
||||||
|
|
@ -13,6 +15,13 @@ use onyx_core::{
|
||||||
webdav,
|
webdav,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Active file watcher stored globally so it lives for the app lifetime.
|
||||||
|
static WATCHER: Mutex<Option<notify_debouncer_mini::Debouncer<notify::RecommendedWatcher>>> =
|
||||||
|
Mutex::new(None);
|
||||||
|
|
||||||
|
/// Shared mute timestamp — set before writes, checked by the watcher.
|
||||||
|
static LAST_WRITE: Mutex<Option<Instant>> = Mutex::new(None);
|
||||||
|
|
||||||
/// Shared application state behind a mutex.
|
/// Shared application state behind a mutex.
|
||||||
struct AppState {
|
struct AppState {
|
||||||
config: AppConfig,
|
config: AppConfig,
|
||||||
|
|
@ -43,6 +52,11 @@ impl From<CoreSyncResult> for SyncResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Suppress file watcher events for the next second (call before writes).
|
||||||
|
fn mute_watcher(_state: &mut AppState) {
|
||||||
|
*LAST_WRITE.lock().unwrap() = Some(Instant::now());
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper: get or open a TaskRepository for the current workspace.
|
/// Helper: get or open a TaskRepository for the current workspace.
|
||||||
fn ensure_repo(state: &mut AppState) -> Result<(), String> {
|
fn ensure_repo(state: &mut AppState) -> Result<(), String> {
|
||||||
if state.repo.is_some() {
|
if state.repo.is_some() {
|
||||||
|
|
@ -151,6 +165,7 @@ fn create_list(
|
||||||
) -> Result<TaskList, String> {
|
) -> Result<TaskList, String> {
|
||||||
let mut s = state.lock().unwrap();
|
let mut s = state.lock().unwrap();
|
||||||
ensure_repo(&mut s)?;
|
ensure_repo(&mut s)?;
|
||||||
|
mute_watcher(&mut s);
|
||||||
s.repo
|
s.repo
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -165,6 +180,7 @@ fn delete_list(
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut s = state.lock().unwrap();
|
let mut s = state.lock().unwrap();
|
||||||
ensure_repo(&mut s)?;
|
ensure_repo(&mut s)?;
|
||||||
|
mute_watcher(&mut s);
|
||||||
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
||||||
s.repo
|
s.repo
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
|
@ -199,6 +215,7 @@ fn create_task(
|
||||||
) -> Result<Task, String> {
|
) -> Result<Task, String> {
|
||||||
let mut s = state.lock().unwrap();
|
let mut s = state.lock().unwrap();
|
||||||
ensure_repo(&mut s)?;
|
ensure_repo(&mut s)?;
|
||||||
|
mute_watcher(&mut s);
|
||||||
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
||||||
let mut task = Task::new(title);
|
let mut task = Task::new(title);
|
||||||
if let Some(desc) = description.filter(|d| !d.is_empty()) {
|
if let Some(desc) = description.filter(|d| !d.is_empty()) {
|
||||||
|
|
@ -219,6 +236,7 @@ fn update_task(
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut s = state.lock().unwrap();
|
let mut s = state.lock().unwrap();
|
||||||
ensure_repo(&mut s)?;
|
ensure_repo(&mut s)?;
|
||||||
|
mute_watcher(&mut s);
|
||||||
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
||||||
s.repo
|
s.repo
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
|
@ -235,6 +253,7 @@ fn delete_task(
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut s = state.lock().unwrap();
|
let mut s = state.lock().unwrap();
|
||||||
ensure_repo(&mut s)?;
|
ensure_repo(&mut s)?;
|
||||||
|
mute_watcher(&mut s);
|
||||||
let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
||||||
let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?;
|
let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?;
|
||||||
s.repo
|
s.repo
|
||||||
|
|
@ -252,6 +271,7 @@ fn toggle_task(
|
||||||
) -> Result<Task, String> {
|
) -> Result<Task, String> {
|
||||||
let mut s = state.lock().unwrap();
|
let mut s = state.lock().unwrap();
|
||||||
ensure_repo(&mut s)?;
|
ensure_repo(&mut s)?;
|
||||||
|
mute_watcher(&mut s);
|
||||||
let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
||||||
let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?;
|
let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?;
|
||||||
let repo = s.repo.as_mut().unwrap();
|
let repo = s.repo.as_mut().unwrap();
|
||||||
|
|
@ -274,6 +294,7 @@ fn reorder_task(
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut s = state.lock().unwrap();
|
let mut s = state.lock().unwrap();
|
||||||
ensure_repo(&mut s)?;
|
ensure_repo(&mut s)?;
|
||||||
|
mute_watcher(&mut s);
|
||||||
let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
||||||
let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?;
|
let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?;
|
||||||
s.repo
|
s.repo
|
||||||
|
|
@ -283,6 +304,77 @@ fn reorder_task(
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Move / rename / grouping ────────────────────────────────────────
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn move_task(
|
||||||
|
from_list_id: String,
|
||||||
|
to_list_id: String,
|
||||||
|
task_id: String,
|
||||||
|
state: State<'_, Mutex<AppState>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let mut s = state.lock().unwrap();
|
||||||
|
ensure_repo(&mut s)?;
|
||||||
|
mute_watcher(&mut s);
|
||||||
|
let from = Uuid::parse_str(&from_list_id).map_err(|e| e.to_string())?;
|
||||||
|
let to = Uuid::parse_str(&to_list_id).map_err(|e| e.to_string())?;
|
||||||
|
let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?;
|
||||||
|
s.repo
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.move_task(from, to, tid)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn rename_list(
|
||||||
|
list_id: String,
|
||||||
|
new_name: String,
|
||||||
|
state: State<'_, Mutex<AppState>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let mut s = state.lock().unwrap();
|
||||||
|
ensure_repo(&mut s)?;
|
||||||
|
mute_watcher(&mut s);
|
||||||
|
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
||||||
|
s.repo
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.rename_list(id, new_name)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn set_group_by_due_date(
|
||||||
|
list_id: String,
|
||||||
|
enabled: bool,
|
||||||
|
state: State<'_, Mutex<AppState>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let mut s = state.lock().unwrap();
|
||||||
|
ensure_repo(&mut s)?;
|
||||||
|
mute_watcher(&mut s);
|
||||||
|
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
||||||
|
s.repo
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.set_group_by_due_date(id, enabled)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn get_group_by_due_date(
|
||||||
|
list_id: String,
|
||||||
|
state: State<'_, Mutex<AppState>>,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
let mut s = state.lock().unwrap();
|
||||||
|
ensure_repo(&mut s)?;
|
||||||
|
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
|
||||||
|
s.repo
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get_group_by_due_date(id)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
// ── Sync commands ────────────────────────────────────────────────────
|
// ── Sync commands ────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|
@ -348,6 +440,43 @@ async fn sync_workspace(
|
||||||
Ok(result.into())
|
Ok(result.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── File watcher ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn start_watcher(handle: tauri::AppHandle, path: PathBuf) {
|
||||||
|
let handle = handle.clone();
|
||||||
|
let debouncer = new_debouncer(
|
||||||
|
std::time::Duration::from_millis(500),
|
||||||
|
move |events: Result<Vec<notify_debouncer_mini::DebouncedEvent>, notify::Error>| {
|
||||||
|
let Ok(events) = events else { return };
|
||||||
|
// Only care about data file changes
|
||||||
|
let has_data_change = events.iter().any(|e| {
|
||||||
|
if e.kind != DebouncedEventKind::Any { return false; }
|
||||||
|
let p = e.path.to_string_lossy();
|
||||||
|
p.ends_with(".md") || p.ends_with(".json")
|
||||||
|
});
|
||||||
|
if !has_data_change { return; }
|
||||||
|
// Skip if we wrote recently (self-change suppression)
|
||||||
|
if let Some(t) = *LAST_WRITE.lock().unwrap() {
|
||||||
|
if t.elapsed() < std::time::Duration::from_secs(1) { return; }
|
||||||
|
}
|
||||||
|
let _ = handle.emit("fs-changed", ());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
match debouncer {
|
||||||
|
Ok(mut d) => {
|
||||||
|
let _ = d.watcher().watch(&path, notify::RecursiveMode::Recursive);
|
||||||
|
*WATCHER.lock().unwrap() = Some(d);
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("Failed to start file watcher: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn watch_workspace(path: String, app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||||
|
start_watcher(app_handle, PathBuf::from(path));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// ── App entry ────────────────────────────────────────────────────────
|
// ── App entry ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
|
@ -360,6 +489,18 @@ pub fn run() {
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.manage(Mutex::new(AppState { config, repo: None }))
|
.manage(Mutex::new(AppState { config, repo: None }))
|
||||||
|
.setup(|app| {
|
||||||
|
let handle = app.handle().clone();
|
||||||
|
let state: State<'_, Mutex<AppState>> = app.state();
|
||||||
|
let workspace_path = {
|
||||||
|
let s = state.lock().unwrap();
|
||||||
|
s.config.get_current_workspace().ok().map(|(_, ws)| ws.path.clone())
|
||||||
|
};
|
||||||
|
if let Some(path) = workspace_path {
|
||||||
|
start_watcher(handle, path);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
get_config,
|
get_config,
|
||||||
save_config,
|
save_config,
|
||||||
|
|
@ -376,11 +517,16 @@ pub fn run() {
|
||||||
delete_task,
|
delete_task,
|
||||||
toggle_task,
|
toggle_task,
|
||||||
reorder_task,
|
reorder_task,
|
||||||
|
move_task,
|
||||||
|
rename_list,
|
||||||
|
set_group_by_due_date,
|
||||||
|
get_group_by_due_date,
|
||||||
set_webdav_config,
|
set_webdav_config,
|
||||||
store_credentials,
|
store_credentials,
|
||||||
load_credentials,
|
load_credentials,
|
||||||
test_webdav_connection,
|
test_webdav_connection,
|
||||||
sync_workspace,
|
sync_workspace,
|
||||||
|
watch_workspace,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue