File size: 3,377 Bytes
3d7d9b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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()
}