musealpha / src-tauri /src /browser /tab_manager.rs
asdf98's picture
Upload 112 files
3d7d9b5 verified
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tauri::{AppHandle, Emitter, Manager};
use crate::state::AppState;
#[allow(dead_code)]
pub const TAB_SLEEP_THRESHOLD_SECS: i64 = 30 * 60;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserTab {
pub id: String,
pub label: String,
pub url: String,
pub title: String,
pub favicon: Option<String>,
pub loading: bool,
pub pinned: bool,
pub sleeping: bool,
pub zoom: f64,
pub can_go_back: bool,
pub can_go_forward: bool,
pub last_active: i64,
}
#[derive(Default)]
pub struct TabManager {
pub tabs: HashMap<String, BrowserTab>,
pub order: Vec<String>,
pub active: Option<String>,
pub closed_stack: Vec<BrowserTab>,
pub zoom_memory: HashMap<String, f64>,
}
impl TabManager {
pub fn push_closed(&mut self, tab: BrowserTab) {
if self.closed_stack.len() >= 25 { self.closed_stack.remove(0); }
self.closed_stack.push(tab);
}
pub fn pop_closed(&mut self) -> Option<BrowserTab> { self.closed_stack.pop() }
pub fn remember_zoom(&mut self, domain: &str, zoom: f64) { self.zoom_memory.insert(domain.to_string(), zoom); }
pub fn get_zoom_for_domain(&self, domain: &str) -> Option<f64> { self.zoom_memory.get(domain).copied() }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ViewportLayout {
#[serde(default)]
pub x: f64,
#[serde(default)]
pub y: f64,
pub width: f64,
pub height: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct BrowserSnapshot {
pub tabs: Vec<BrowserTab>,
pub active: Option<String>,
pub can_restore: bool,
}
pub fn snapshot(app: &AppHandle) -> Result<BrowserSnapshot, String> {
let state = app.state::<AppState>();
let tabs = state.tabs.lock().map_err(|_| "lock")?;
let ordered = tabs.order.iter().filter_map(|id| tabs.tabs.get(id).cloned()).collect();
let can_restore = !tabs.closed_stack.is_empty();
Ok(BrowserSnapshot { tabs: ordered, active: tabs.active.clone(), can_restore })
}
pub fn emit_snapshot(app: &AppHandle) -> Result<(), String> {
let snap = snapshot(app)?;
app.emit("browser://tabs", snap).map_err(|e| e.to_string())
}
pub fn update_tab_field(app: &AppHandle, id: &str, f: impl FnOnce(&mut BrowserTab)) {
let state = app.state::<AppState>();
if let Ok(mut tabs) = state.tabs.lock() {
if let Some(tab) = tabs.tabs.get_mut(id) { f(tab); }
}
let _ = emit_snapshot(app);
}
pub fn tab_label(app: &AppHandle, tab_id: &str) -> Result<String, String> {
let state = app.state::<AppState>();
let tabs = state.tabs.lock().map_err(|_| "lock")?;
tabs.tabs.get(tab_id).map(|t| t.label.clone()).ok_or_else(|| format!("tab not found: {tab_id}"))
}
pub fn eval_on_tab(app: &AppHandle, tab_id: &str, js: &str) -> Result<(), String> {
let label = tab_label(app, tab_id)?;
app.get_webview(&label).ok_or("webview not found")?.eval(js).map_err(|e| e.to_string())
}
pub fn extract_domain(url: &str) -> String {
url.split("//").nth(1)
.and_then(|s| s.split('/').next())
.map(|s| s.split(':').next().unwrap_or(s))
.unwrap_or("")
.trim_start_matches("www.")
.to_lowercase()
}