agentmux_srv/backend/wcore/
tab.rs1use uuid::Uuid;
7
8use crate::backend::storage::store::Store;
9use crate::backend::storage::StoreError;
10use crate::backend::obj::*;
11
12pub fn create_tab(store: &Store, ws_id: &str) -> Result<Tab, StoreError> {
16 create_tab_with_opts(store, ws_id, "", false)
17}
18
19pub fn create_tab_with_opts(
21 store: &Store,
22 ws_id: &str,
23 tab_name: &str,
24 pinned: bool,
25) -> Result<Tab, StoreError> {
26 let mut ws = store.must_get::<Workspace>(ws_id)?;
27
28 let name = if tab_name.is_empty() {
34 format!("Tab {}", ws.tabids.len() + ws.pinnedtabids.len() + 1)
35 } else {
36 tab_name.to_string()
37 };
38
39 let mut layout = LayoutState {
41 oid: Uuid::new_v4().to_string(),
42 rootnode: None,
43 magnifiednodeid: String::new(),
44 focusednodeid: String::new(),
45 leaforder: None,
46 pendingbackendactions: None,
47 meta: None,
48 ..Default::default()
49 };
50 store.insert(&mut layout)?;
51
52 let mut tab = Tab {
53 oid: Uuid::new_v4().to_string(),
54 name,
55 layoutstate: layout.oid.clone(),
56 blockids: vec![],
57 meta: MetaMapType::new(),
58 ..Default::default()
59 };
60 store.insert(&mut tab)?;
61
62 if pinned {
64 ws.pinnedtabids.push(tab.oid.clone());
65 } else {
66 ws.tabids.push(tab.oid.clone());
67 }
68 if ws.activetabid.is_empty() {
69 ws.activetabid = tab.oid.clone();
70 }
71 store.update(&mut ws)?;
72
73 Ok(tab)
74}
75
76pub fn delete_tab(
78 store: &Store,
79 ws_id: &str,
80 tab_id: &str,
81) -> Result<(), StoreError> {
82 let mut ws = store.must_get::<Workspace>(ws_id)?;
83
84 ws.tabids.retain(|id| id != tab_id);
86 ws.pinnedtabids.retain(|id| id != tab_id);
87
88 if ws.activetabid == tab_id {
90 ws.activetabid = ws.tabids.first().cloned().unwrap_or_default();
91 }
92 store.update(&mut ws)?;
93
94 delete_tab_inner(store, tab_id)?;
95 Ok(())
96}
97
98pub(super) fn delete_tab_inner(store: &Store, tab_id: &str) -> Result<(), StoreError> {
101 if let Ok(tab) = store.must_get::<Tab>(tab_id) {
102 for block_id in &tab.blockids {
104 crate::sagas::agent_teardown::discard(block_id);
105 }
106 if !tab.layoutstate.is_empty() {
108 let _ = store.delete::<LayoutState>(&tab.layoutstate);
109 }
110 for block_id in &tab.blockids {
112 let _ = store.delete::<Block>(block_id);
113 }
114 }
115 let _ = store.delete::<Tab>(tab_id);
116 Ok(())
117}
118
119pub fn set_active_tab(
121 store: &Store,
122 ws_id: &str,
123 tab_id: &str,
124) -> Result<(), StoreError> {
125 let mut ws = store.must_get::<Workspace>(ws_id)?;
126 let tab_str = tab_id.to_string();
127 if !ws.tabids.contains(&tab_str) && !ws.pinnedtabids.contains(&tab_str) {
128 return Err(StoreError::NotFound);
129 }
130 ws.activetabid = tab_str;
131 store.update(&mut ws)?;
132 Ok(())
133}
134
135pub fn reorder_tab(
137 store: &Store,
138 ws_id: &str,
139 tab_id: &str,
140 new_index: usize,
141) -> Result<(), StoreError> {
142 tracing::info!(ws_id = %ws_id, tab_id = %tab_id, new_index = %new_index, "[dnd] reorder_tab");
143 let mut ws = store.must_get::<Workspace>(ws_id)?;
144
145 if let Some(pos) = ws.tabids.iter().position(|id| id == tab_id) {
147 ws.tabids.remove(pos);
148 let insert_at = new_index.min(ws.tabids.len());
149 ws.tabids.insert(insert_at, tab_id.to_string());
150 } else if let Some(pos) = ws.pinnedtabids.iter().position(|id| id == tab_id) {
151 ws.pinnedtabids.remove(pos);
152 let insert_at = new_index.min(ws.pinnedtabids.len());
153 ws.pinnedtabids.insert(insert_at, tab_id.to_string());
154 } else {
155 return Err(StoreError::NotFound);
156 }
157
158 store.update(&mut ws)?;
159 tracing::info!(tab_id = %tab_id, new_index = %new_index, "[dnd] reorder_tab complete");
160 Ok(())
161}