Theme service: 7 built-in themes seeded from Rust, ThemePicker loads from backend. Workspace service: save/load snapshots, crash recovery detection. SettingsModal: full port with Tauri invoke. CommandPalette, HostKeyDialog, ConnectionEditDialog all ported. CodeMirror editor: inline above terminal, reads/writes via SFTP. Full keyboard shortcuts: Ctrl+K/W/Tab/Shift+Tab/1-9/B/F. Terminal search bar via Ctrl+F (SearchAddon). Tab badges: protocol dots, ROOT warning, environment pills. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
83 lines
3.1 KiB
Rust
83 lines
3.1 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::settings::SettingsService;
|
|
|
|
// ── domain types ─────────────────────────────────────────────────────────────
|
|
|
|
/// A single open tab that should be restored on next launch.
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct WorkspaceTab {
|
|
pub connection_id: i64,
|
|
pub protocol: String,
|
|
pub position: i32,
|
|
}
|
|
|
|
/// The full workspace state persisted between sessions.
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct WorkspaceSnapshot {
|
|
pub tabs: Vec<WorkspaceTab>,
|
|
}
|
|
|
|
// ── service ───────────────────────────────────────────────────────────────────
|
|
|
|
const SNAPSHOT_KEY: &str = "workspace_snapshot";
|
|
const CLEAN_SHUTDOWN_KEY: &str = "clean_shutdown";
|
|
|
|
pub struct WorkspaceService {
|
|
settings: SettingsService,
|
|
}
|
|
|
|
impl WorkspaceService {
|
|
pub fn new(settings: SettingsService) -> Self {
|
|
Self { settings }
|
|
}
|
|
|
|
/// Serialize `snapshot` to JSON and persist it via the settings store.
|
|
pub fn save(&self, snapshot: &WorkspaceSnapshot) -> Result<(), String> {
|
|
let json = serde_json::to_string(snapshot)
|
|
.map_err(|e| format!("workspace::save: failed to serialize snapshot: {e}"))?;
|
|
self.settings.set(SNAPSHOT_KEY, &json)
|
|
}
|
|
|
|
/// Load and deserialize the last saved workspace snapshot.
|
|
///
|
|
/// Returns `None` if no snapshot has been saved yet or if deserialization
|
|
/// fails (treated as a fresh start rather than a hard error).
|
|
pub fn load(&self) -> Option<WorkspaceSnapshot> {
|
|
let json = self.settings.get(SNAPSHOT_KEY)?;
|
|
serde_json::from_str(&json)
|
|
.map_err(|e| eprintln!("workspace::load: failed to deserialize snapshot: {e}"))
|
|
.ok()
|
|
}
|
|
|
|
/// Record that the application is shutting down cleanly.
|
|
///
|
|
/// Call this just before the Tauri window closes normally. On the next
|
|
/// startup, `was_clean_shutdown()` will return `true` and the crash-
|
|
/// recovery path can be skipped.
|
|
pub fn mark_clean_shutdown(&self) -> Result<(), String> {
|
|
self.settings.set(CLEAN_SHUTDOWN_KEY, "true")
|
|
}
|
|
|
|
/// Returns `true` if the last session ended with a clean shutdown.
|
|
///
|
|
/// Returns `false` (crash / dirty shutdown) when the key is absent, empty,
|
|
/// or set to any value other than `"true"`.
|
|
pub fn was_clean_shutdown(&self) -> bool {
|
|
self.settings
|
|
.get(CLEAN_SHUTDOWN_KEY)
|
|
.map(|v| v == "true")
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Remove the clean-shutdown flag.
|
|
///
|
|
/// Call this at startup — immediately after reading `was_clean_shutdown()`.
|
|
/// The flag is only valid for the single launch following the shutdown that
|
|
/// wrote it; clearing it ensures a subsequent crash is detected correctly.
|
|
pub fn clear_clean_shutdown(&self) -> Result<(), String> {
|
|
self.settings.delete(CLEAN_SHUTDOWN_KEY)
|
|
}
|
|
}
|