Add tab group sidebar support
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use ely_domain::{
|
||||
BookmarkId, DomainError, DownloadId, NoteId, PluginId, ProfileId, ReadingListId, SpaceId,
|
||||
SplitId, TabId,
|
||||
SplitId, TabGroupId, TabId,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -21,6 +21,9 @@ pub enum CoreError {
|
||||
#[error("split not found: {id}")]
|
||||
SplitNotFound { id: SplitId },
|
||||
|
||||
#[error("tab group not found: {id}")]
|
||||
TabGroupNotFound { id: TabGroupId },
|
||||
|
||||
#[error("profile not found: {id}")]
|
||||
ProfileNotFound { id: ProfileId },
|
||||
|
||||
|
||||
@@ -100,6 +100,10 @@ pub(crate) fn tab_note_body(command: &str) -> Option<&str> {
|
||||
command_argument(command, &["tab-note ", "tab note ", "note-tab ", "note tab "])
|
||||
}
|
||||
|
||||
pub(crate) fn tab_group_name(command: &str) -> Option<&str> {
|
||||
command_argument(command, &["group-tab ", "group tab ", "tab-group ", "tab group "])
|
||||
}
|
||||
|
||||
pub(crate) fn new_profile_name(command: &str) -> Option<&str> {
|
||||
command_argument(command, &["new-profile ", "new profile "])
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ use ely_domain::{
|
||||
ArchivePolicy, ArchivedTab, BookmarkEntry, BrowserTab, DomainError, DownloadEntry,
|
||||
DownloadPolicy, FavoriteLimit, HistoryEntry, HistoryRecordingPolicy, NewTabDestination,
|
||||
NoteEntry, Profile, ProfileId, ProfileKind, ReadingListEntry, SearchEngine,
|
||||
SitePermissionAuditEvent, SitePermissionEntry, Space, SpaceId, SplitLayout, SyncStatus, TabId,
|
||||
UrlText,
|
||||
SitePermissionAuditEvent, SitePermissionEntry, Space, SpaceId, SplitLayout, SyncStatus,
|
||||
TabGroup, TabId, UrlText,
|
||||
};
|
||||
|
||||
use crate::{CoreError, navigation::tab_title};
|
||||
@@ -22,6 +22,7 @@ mod reading_list;
|
||||
mod site_permissions;
|
||||
mod splits;
|
||||
mod sync;
|
||||
mod tab_groups;
|
||||
mod tab_order;
|
||||
mod tabs;
|
||||
|
||||
@@ -60,6 +61,7 @@ pub struct BrowserSnapshot {
|
||||
pub download_entries: Vec<DownloadEntry>,
|
||||
pub history_entries: Vec<HistoryEntry>,
|
||||
pub active_profile_history_entry_count: usize,
|
||||
pub tab_groups: Vec<TabGroup>,
|
||||
pub split_layouts: Vec<SplitLayout>,
|
||||
pub installed_plugins: Vec<InstalledPlugin>,
|
||||
pub plugin_audit_events: Vec<PluginAuditEvent>,
|
||||
@@ -92,6 +94,7 @@ pub struct BrowserCore {
|
||||
site_permission_audit_events: Vec<SitePermissionAuditEvent>,
|
||||
download_entries: Vec<DownloadEntry>,
|
||||
history_entries: Vec<HistoryEntry>,
|
||||
tab_groups: Vec<TabGroup>,
|
||||
split_layouts: Vec<SplitLayout>,
|
||||
archived_split_layouts: Vec<SplitLayout>,
|
||||
installed_plugins: Vec<InstalledPlugin>,
|
||||
@@ -161,6 +164,7 @@ impl BrowserCore {
|
||||
site_permission_audit_events: Vec::new(),
|
||||
download_entries: Vec::new(),
|
||||
history_entries: Vec::new(),
|
||||
tab_groups: Vec::new(),
|
||||
split_layouts: Vec::new(),
|
||||
archived_split_layouts: Vec::new(),
|
||||
installed_plugins: Vec::new(),
|
||||
@@ -354,6 +358,7 @@ impl BrowserCore {
|
||||
download_entries: self.visible_downloads(),
|
||||
history_entries: self.visible_history(),
|
||||
active_profile_history_entry_count: self.active_profile_history_count(),
|
||||
tab_groups: self.visible_tab_groups(),
|
||||
split_layouts: self.visible_split_layouts(),
|
||||
installed_plugins: self.installed_plugins.clone(),
|
||||
plugin_audit_events: self.plugin_audit_events.clone(),
|
||||
|
||||
@@ -9,7 +9,7 @@ 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_note_body, task_manager_url,
|
||||
switch_profile_name, sync_status_url, tab_group_name, tab_note_body, task_manager_url,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -133,6 +133,10 @@ impl BrowserCore {
|
||||
self.set_active_tab_reading_progress(percent)?;
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(name) = tab_group_name(command) {
|
||||
self.group_active_tab(name)?;
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(body) = tab_note_body(command) {
|
||||
self.save_active_tab_note(body)?;
|
||||
return Ok(true);
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use ely_domain::{TabGroup, TabGroupId, TabId};
|
||||
|
||||
use crate::CoreError;
|
||||
|
||||
use super::BrowserCore;
|
||||
|
||||
impl BrowserCore {
|
||||
pub fn group_active_tab(&mut self, name: impl Into<String>) -> Result<TabGroupId, CoreError> {
|
||||
let group_id = self.find_or_create_active_space_tab_group(name)?;
|
||||
let active_tab_id = self.active_tab_id.clone();
|
||||
self.assign_tab_to_group(&active_tab_id, &group_id)?;
|
||||
Ok(group_id)
|
||||
}
|
||||
|
||||
pub fn assign_tab_to_group(
|
||||
&mut self,
|
||||
tab_id: &TabId,
|
||||
group_id: &TabGroupId,
|
||||
) -> Result<(), CoreError> {
|
||||
let group_space_id = self
|
||||
.tab_groups
|
||||
.iter()
|
||||
.find(|group| group.id() == group_id)
|
||||
.map(|group| group.space_id().clone())
|
||||
.ok_or_else(|| CoreError::TabGroupNotFound { id: group_id.clone() })?;
|
||||
let tab = self
|
||||
.tabs
|
||||
.iter_mut()
|
||||
.find(|tab| tab.id() == tab_id)
|
||||
.ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?;
|
||||
|
||||
if tab.space_id() != &group_space_id {
|
||||
return Err(CoreError::TabGroupNotFound { id: group_id.clone() });
|
||||
}
|
||||
|
||||
tab.set_group_id(group_id.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear_tab_group(&mut self, tab_id: &TabId) -> Result<(), CoreError> {
|
||||
let tab = self
|
||||
.tabs
|
||||
.iter_mut()
|
||||
.find(|tab| tab.id() == tab_id)
|
||||
.ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?;
|
||||
tab.clear_group_id();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn visible_tab_groups(&self) -> Vec<TabGroup> {
|
||||
let mut groups = self
|
||||
.tab_groups
|
||||
.iter()
|
||||
.filter(|group| group.space_id() == &self.active_space_id)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
groups.sort_by(|left, right| {
|
||||
left.sort_key().cmp(&right.sort_key()).then_with(|| left.id().cmp(right.id()))
|
||||
});
|
||||
groups
|
||||
}
|
||||
|
||||
fn find_or_create_active_space_tab_group(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
) -> Result<TabGroupId, CoreError> {
|
||||
let name = name.into();
|
||||
if let Some(group_id) = self.find_active_space_group_id(&name) {
|
||||
return Ok(group_id);
|
||||
}
|
||||
|
||||
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 group = TabGroup::new(active_space_id, name, color_hex, sort_key)?;
|
||||
let group_id = group.id().clone();
|
||||
self.tab_groups.push(group);
|
||||
Ok(group_id)
|
||||
}
|
||||
|
||||
fn find_active_space_group_id(&self, name: &str) -> Option<TabGroupId> {
|
||||
let normalized_name = name.trim().to_lowercase();
|
||||
self.tab_groups
|
||||
.iter()
|
||||
.find(|group| {
|
||||
group.space_id() == &self.active_space_id
|
||||
&& group.name().to_lowercase() == normalized_name
|
||||
})
|
||||
.map(|group| group.id().clone())
|
||||
}
|
||||
|
||||
fn next_tab_group_sort_key(&self) -> u64 {
|
||||
self.tab_groups
|
||||
.iter()
|
||||
.filter(|group| group.space_id() == &self.active_space_id)
|
||||
.map(TabGroup::sort_key)
|
||||
.max()
|
||||
.map_or(0, |sort_key| sort_key.saturating_add(1))
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,12 @@ use ely_domain::{
|
||||
ArchivePolicy, ArchiveSource, ArchivedTab, BrowserTab, ProfileId, SpaceId, TabId, UrlText,
|
||||
};
|
||||
|
||||
use super::BrowserCore;
|
||||
use crate::{
|
||||
CoreError,
|
||||
navigation::{tab_matches_query, tab_title},
|
||||
};
|
||||
|
||||
use super::BrowserCore;
|
||||
|
||||
impl BrowserCore {
|
||||
pub fn open_new_tab(&mut self) -> Result<TabId, CoreError> {
|
||||
let url = self.new_tab_url()?;
|
||||
@@ -53,6 +52,7 @@ impl BrowserCore {
|
||||
let tab_index = self.active_tab_index()?;
|
||||
let target_sort_key = self.next_tab_sort_key(space_id);
|
||||
self.tabs[tab_index].move_to_space(space_id.clone());
|
||||
self.tabs[tab_index].clear_group_id();
|
||||
self.tabs[tab_index].set_sort_key(target_sort_key);
|
||||
self.sort_tabs_within_space(space_id);
|
||||
self.active_tabs_by_space.insert(space_id.clone(), tab_id.clone());
|
||||
|
||||
Reference in New Issue
Block a user