feat(v2): implement session persistence, file watcher, and markdown viewer
Phase 4 complete (MVP ship): - SessionDb (rusqlite, WAL mode): sessions + layout_state tables, CRUD - FileWatcherManager (notify v6): watch files, emit Tauri change events - MarkdownPane: marked.js rendering with Catppuccin styles, live reload - Layout store wired to persistence (addPane/removePane/setPreset persist) - restoreFromDb() on startup restores panes in layout order - Sidebar "M" button opens file picker for markdown files - New adapters: session-bridge.ts, file-bridge.ts - Deps: rusqlite (bundled), dirs 5, notify 6, marked
This commit is contained in:
parent
5ca035d438
commit
bdb87978a9
14 changed files with 1075 additions and 17 deletions
|
|
@ -1,2 +1,97 @@
|
|||
// File watcher for markdown viewer
|
||||
// Phase 4: notify crate, debounce, Tauri events
|
||||
// Uses notify crate to watch files and emit Tauri events on change
|
||||
|
||||
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use tauri::Emitter;
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
struct FileChangedPayload {
|
||||
pane_id: String,
|
||||
path: String,
|
||||
content: String,
|
||||
}
|
||||
|
||||
struct WatchEntry {
|
||||
_watcher: RecommendedWatcher,
|
||||
_path: PathBuf,
|
||||
}
|
||||
|
||||
pub struct FileWatcherManager {
|
||||
watchers: Mutex<HashMap<String, WatchEntry>>,
|
||||
}
|
||||
|
||||
impl FileWatcherManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
watchers: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn watch(
|
||||
&self,
|
||||
app: &tauri::AppHandle,
|
||||
pane_id: &str,
|
||||
path: &str,
|
||||
) -> Result<String, String> {
|
||||
let file_path = PathBuf::from(path);
|
||||
if !file_path.exists() {
|
||||
return Err(format!("File not found: {path}"));
|
||||
}
|
||||
|
||||
// Read initial content
|
||||
let content = std::fs::read_to_string(&file_path)
|
||||
.map_err(|e| format!("Failed to read file: {e}"))?;
|
||||
|
||||
// Set up watcher
|
||||
let app_handle = app.clone();
|
||||
let pane_id_owned = pane_id.to_string();
|
||||
let watch_path = file_path.clone();
|
||||
|
||||
let mut watcher = RecommendedWatcher::new(
|
||||
move |res: Result<Event, notify::Error>| {
|
||||
if let Ok(event) = res {
|
||||
if event.kind.is_modify() {
|
||||
if let Ok(new_content) = std::fs::read_to_string(&watch_path) {
|
||||
let _ = app_handle.emit(
|
||||
"file-changed",
|
||||
FileChangedPayload {
|
||||
pane_id: pane_id_owned.clone(),
|
||||
path: watch_path.to_string_lossy().to_string(),
|
||||
content: new_content,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Config::default(),
|
||||
)
|
||||
.map_err(|e| format!("Failed to create watcher: {e}"))?;
|
||||
|
||||
watcher
|
||||
.watch(file_path.parent().unwrap_or(&file_path), RecursiveMode::NonRecursive)
|
||||
.map_err(|e| format!("Failed to watch path: {e}"))?;
|
||||
|
||||
let mut watchers = self.watchers.lock().unwrap();
|
||||
watchers.insert(pane_id.to_string(), WatchEntry {
|
||||
_watcher: watcher,
|
||||
_path: file_path,
|
||||
});
|
||||
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
pub fn unwatch(&self, pane_id: &str) {
|
||||
let mut watchers = self.watchers.lock().unwrap();
|
||||
watchers.remove(pane_id);
|
||||
}
|
||||
|
||||
pub fn read_file(&self, path: &str) -> Result<String, String> {
|
||||
std::fs::read_to_string(path)
|
||||
.map_err(|e| format!("Failed to read file: {e}"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue