Convert split views to tab groups
This commit is contained in:
@@ -21,6 +21,9 @@ pub enum CoreError {
|
||||
#[error("split not found: {id}")]
|
||||
SplitNotFound { id: SplitId },
|
||||
|
||||
#[error("split pane belongs to a different space: {id}")]
|
||||
SplitPaneSpaceMismatch { id: SplitId },
|
||||
|
||||
#[error("tab group not found: {id}")]
|
||||
TabGroupNotFound { id: TabGroupId },
|
||||
|
||||
|
||||
@@ -104,6 +104,13 @@ pub(crate) fn tab_group_name(command: &str) -> Option<&str> {
|
||||
command_argument(command, &["group-tab ", "group tab ", "tab-group ", "tab group "])
|
||||
}
|
||||
|
||||
pub(crate) fn split_group_name(command: &str) -> Option<&str> {
|
||||
command_argument(
|
||||
command,
|
||||
&["group-split-view ", "group split view ", "split-view-to-group ", "split view to group "],
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn new_profile_name(command: &str) -> Option<&str> {
|
||||
command_argument(command, &["new-profile ", "new profile "])
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ use crate::{
|
||||
move_tab_space_name, new_private_profile_name, new_profile_name, new_space_name, note_body,
|
||||
notes_url, plugin_detail_url, plugins_url, reading_list_url, reading_progress_percent,
|
||||
search_url, settings_page_url, settings_url, shortcut_settings_url, space_icon,
|
||||
switch_profile_name, sync_status_url, tab_group_name, tab_note_body, task_manager_url,
|
||||
split_group_name, switch_profile_name, sync_status_url, tab_group_name, tab_note_body,
|
||||
task_manager_url,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -133,6 +134,12 @@ impl BrowserCore {
|
||||
self.set_active_tab_reading_progress(percent)?;
|
||||
return Ok(true);
|
||||
}
|
||||
if command.eq_ignore_ascii_case("tab group to split") {
|
||||
return Ok(self.split_active_tab_group()?.is_some());
|
||||
}
|
||||
if let Some(name) = split_group_name(command) {
|
||||
return Ok(self.group_active_split_view(Some(name))?.is_some());
|
||||
}
|
||||
if let Some(name) = tab_group_name(command) {
|
||||
self.group_active_tab(name)?;
|
||||
return Ok(true);
|
||||
@@ -159,6 +166,10 @@ impl BrowserCore {
|
||||
"split-tab-group" | "split tab group" | "tab-group-to-split" | "tab group to split" => {
|
||||
Ok(self.split_active_tab_group()?.is_some())
|
||||
}
|
||||
"group-split-view"
|
||||
| "group split view"
|
||||
| "split-view-to-group"
|
||||
| "split view to group" => Ok(self.group_active_split_view(None)?.is_some()),
|
||||
"toggle-tab-group" | "toggle tab group" => {
|
||||
Ok(self.toggle_active_tab_group_collapsed()?.is_some())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use ely_domain::{
|
||||
MAX_SPLIT_PANES, SplitAxis, SplitId, SplitLayout, SplitPane, TabGroup, TabGroupId, TabId,
|
||||
BrowserTab, MAX_SPLIT_PANES, SpaceId, SplitAxis, SplitId, SplitLayout, SplitPane, TabGroup,
|
||||
TabGroupId, TabId,
|
||||
};
|
||||
|
||||
use crate::CoreError;
|
||||
@@ -89,6 +90,49 @@ impl BrowserCore {
|
||||
Ok(Some(split_id))
|
||||
}
|
||||
|
||||
pub fn group_active_split_view(
|
||||
&mut self,
|
||||
name: Option<&str>,
|
||||
) -> Result<Option<TabGroupId>, CoreError> {
|
||||
let Some(split_id) = self.active_tab()?.split_id().cloned() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let layout_index = self
|
||||
.split_layouts
|
||||
.iter()
|
||||
.position(|layout| layout.id() == &split_id)
|
||||
.ok_or_else(|| CoreError::SplitNotFound { id: split_id.clone() })?;
|
||||
let layout = self.split_layouts[layout_index].clone();
|
||||
if layout.pane_count() < 2 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let pane_ids = layout.panes().iter().map(|pane| pane.tab_id().clone()).collect::<Vec<_>>();
|
||||
let space_id = self.split_pane_space_id(&split_id, &pane_ids)?;
|
||||
let group_name = name
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map_or_else(|| layout.title().to_string(), ToString::to_string);
|
||||
let color_hex = self
|
||||
.spaces
|
||||
.iter()
|
||||
.find(|space| space.id() == &space_id)
|
||||
.map(|space| space.accent_hex())
|
||||
.ok_or_else(|| CoreError::SpaceNotFound { id: space_id.clone() })?;
|
||||
let sort_key = self.next_tab_group_sort_key_for_space(&space_id);
|
||||
let group = TabGroup::new(space_id, group_name, color_hex, sort_key)?;
|
||||
let group_id = group.id().clone();
|
||||
|
||||
for tab in self.tabs.iter_mut().filter(|tab| pane_ids.contains(tab.id())) {
|
||||
tab.clear_split_id();
|
||||
tab.set_group_id(group_id.clone());
|
||||
}
|
||||
|
||||
self.split_layouts.remove(layout_index);
|
||||
self.tab_groups.push(group);
|
||||
Ok(Some(group_id))
|
||||
}
|
||||
|
||||
pub fn toggle_active_tab_group_collapsed(&mut self) -> Result<Option<bool>, CoreError> {
|
||||
let Some(group_id) = self.active_tab_group_id()? else {
|
||||
return Ok(None);
|
||||
@@ -148,7 +192,7 @@ impl BrowserCore {
|
||||
|
||||
let active_space_id = self.active_space_id.clone();
|
||||
let color_hex = self.active_space()?.accent_hex();
|
||||
let sort_key = self.next_tab_group_sort_key();
|
||||
let sort_key = self.next_tab_group_sort_key_for_space(&active_space_id);
|
||||
let group = TabGroup::new(active_space_id, name, color_hex, sort_key)?;
|
||||
let group_id = group.id().clone();
|
||||
self.tab_groups.push(group);
|
||||
@@ -166,16 +210,16 @@ impl BrowserCore {
|
||||
.map(|group| group.id().clone())
|
||||
}
|
||||
|
||||
fn next_tab_group_sort_key(&self) -> u64 {
|
||||
fn next_tab_group_sort_key_for_space(&self, space_id: &SpaceId) -> u64 {
|
||||
self.tab_groups
|
||||
.iter()
|
||||
.filter(|group| group.space_id() == &self.active_space_id)
|
||||
.filter(|group| group.space_id() == space_id)
|
||||
.map(TabGroup::sort_key)
|
||||
.max()
|
||||
.map_or(0, |sort_key| sort_key.saturating_add(1))
|
||||
}
|
||||
|
||||
fn active_space_group_tabs(&self, group_id: &TabGroupId) -> Vec<ely_domain::BrowserTab> {
|
||||
fn active_space_group_tabs(&self, group_id: &TabGroupId) -> Vec<BrowserTab> {
|
||||
tab_order::sorted_tabs(
|
||||
self.tabs
|
||||
.iter()
|
||||
@@ -184,6 +228,34 @@ impl BrowserCore {
|
||||
)
|
||||
}
|
||||
|
||||
fn split_pane_space_id(
|
||||
&self,
|
||||
split_id: &SplitId,
|
||||
pane_ids: &[TabId],
|
||||
) -> Result<SpaceId, CoreError> {
|
||||
let first_pane_id =
|
||||
pane_ids.first().ok_or_else(|| CoreError::SplitNotFound { id: split_id.clone() })?;
|
||||
let first_tab = self
|
||||
.tabs
|
||||
.iter()
|
||||
.find(|tab| tab.id() == first_pane_id)
|
||||
.ok_or_else(|| CoreError::TabNotFound { id: first_pane_id.clone() })?;
|
||||
let space_id = first_tab.space_id().clone();
|
||||
|
||||
for pane_id in pane_ids {
|
||||
let tab = self
|
||||
.tabs
|
||||
.iter()
|
||||
.find(|tab| tab.id() == pane_id)
|
||||
.ok_or_else(|| CoreError::TabNotFound { id: pane_id.clone() })?;
|
||||
if tab.space_id() != &space_id {
|
||||
return Err(CoreError::SplitPaneSpaceMismatch { id: split_id.clone() });
|
||||
}
|
||||
}
|
||||
|
||||
Ok(space_id)
|
||||
}
|
||||
|
||||
fn active_tab_group_id(&self) -> Result<Option<TabGroupId>, CoreError> {
|
||||
let tab = self.active_tab()?;
|
||||
let Some(group_id) = tab.group_id().cloned() else {
|
||||
|
||||
Reference in New Issue
Block a user