Track tab sort keys
This commit is contained in:
@@ -20,6 +20,7 @@ mod reading_list;
|
|||||||
mod site_permissions;
|
mod site_permissions;
|
||||||
mod splits;
|
mod splits;
|
||||||
mod sync;
|
mod sync;
|
||||||
|
mod tab_order;
|
||||||
mod tabs;
|
mod tabs;
|
||||||
|
|
||||||
pub use plugins::{InstalledPlugin, PluginAuditAction, PluginAuditEvent};
|
pub use plugins::{InstalledPlugin, PluginAuditAction, PluginAuditEvent};
|
||||||
@@ -125,7 +126,8 @@ impl BrowserCore {
|
|||||||
active_profile_id.clone(),
|
active_profile_id.clone(),
|
||||||
new_tab_title,
|
new_tab_title,
|
||||||
new_tab_url.clone(),
|
new_tab_url.clone(),
|
||||||
);
|
)
|
||||||
|
.with_sort_key(0);
|
||||||
let active_tab_id = tab.id().clone();
|
let active_tab_id = tab.id().clone();
|
||||||
let mut active_tabs_by_space = BTreeMap::new();
|
let mut active_tabs_by_space = BTreeMap::new();
|
||||||
let mut active_tabs_by_space_profile = BTreeMap::new();
|
let mut active_tabs_by_space_profile = BTreeMap::new();
|
||||||
@@ -406,16 +408,18 @@ impl BrowserCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn pinned_tabs(&self) -> Vec<BrowserTab> {
|
fn pinned_tabs(&self) -> Vec<BrowserTab> {
|
||||||
|
tab_order::sorted_tabs(
|
||||||
self.tabs
|
self.tabs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|tab| tab.space_id() == &self.active_space_id)
|
.filter(|tab| tab.space_id() == &self.active_space_id)
|
||||||
.filter(|tab| tab.flags().pinned && !tab.flags().favorite)
|
.filter(|tab| tab.flags().pinned && !tab.flags().favorite),
|
||||||
.cloned()
|
)
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visible_tabs(&self) -> Vec<BrowserTab> {
|
fn visible_tabs(&self) -> Vec<BrowserTab> {
|
||||||
self.tabs.iter().filter(|tab| tab.space_id() == &self.active_space_id).cloned().collect()
|
tab_order::sorted_tabs(
|
||||||
|
self.tabs.iter().filter(|tab| tab.space_id() == &self.active_space_id),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_tab_activity(&mut self, tab_id: &TabId, active_at: SystemTime) {
|
fn record_tab_activity(&mut self, tab_id: &TabId, active_at: SystemTime) {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
use ely_domain::{BrowserTab, SpaceId};
|
||||||
|
|
||||||
|
use super::BrowserCore;
|
||||||
|
|
||||||
|
impl BrowserCore {
|
||||||
|
pub(super) fn next_tab_sort_key(&self, space_id: &SpaceId) -> u64 {
|
||||||
|
self.tabs
|
||||||
|
.iter()
|
||||||
|
.filter(|tab| tab.space_id() == space_id)
|
||||||
|
.map(BrowserTab::sort_key)
|
||||||
|
.max()
|
||||||
|
.map_or(0, |sort_key| sort_key.saturating_add(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn normalize_tab_sort_keys(&mut self, space_id: &SpaceId) {
|
||||||
|
let mut sort_key = 0;
|
||||||
|
for tab in self.tabs.iter_mut().filter(|tab| tab.space_id() == space_id) {
|
||||||
|
tab.set_sort_key(sort_key);
|
||||||
|
sort_key = sort_key.saturating_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn sort_tabs_within_space(&mut self, space_id: &SpaceId) {
|
||||||
|
let indices = self
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(index, tab)| (tab.space_id() == space_id).then_some(index))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut tabs = indices.iter().map(|index| self.tabs[*index].clone()).collect::<Vec<_>>();
|
||||||
|
tabs.sort_by(compare_tabs);
|
||||||
|
|
||||||
|
for (index, tab) in indices.into_iter().zip(tabs) {
|
||||||
|
self.tabs[index] = tab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn sorted_tabs<'a>(tabs: impl Iterator<Item = &'a BrowserTab>) -> Vec<BrowserTab> {
|
||||||
|
let mut tabs = tabs.cloned().collect::<Vec<_>>();
|
||||||
|
tabs.sort_by(compare_tabs);
|
||||||
|
tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_tabs(left: &BrowserTab, right: &BrowserTab) -> std::cmp::Ordering {
|
||||||
|
left.sort_key().cmp(&right.sort_key()).then_with(|| left.id().cmp(right.id()))
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ impl BrowserCore {
|
|||||||
pub fn open_tab(&mut self, url: UrlText) -> TabId {
|
pub fn open_tab(&mut self, url: UrlText) -> TabId {
|
||||||
let tab = self.build_tab(url);
|
let tab = self.build_tab(url);
|
||||||
let tab_id = tab.id().clone();
|
let tab_id = tab.id().clone();
|
||||||
|
let space_id = tab.space_id().clone();
|
||||||
let insert_index = self
|
let insert_index = self
|
||||||
.tabs
|
.tabs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -27,6 +28,7 @@ impl BrowserCore {
|
|||||||
.map_or(self.tabs.len(), |index| index + 1);
|
.map_or(self.tabs.len(), |index| index + 1);
|
||||||
self.record_history_entry(&tab);
|
self.record_history_entry(&tab);
|
||||||
self.tabs.insert(insert_index, tab);
|
self.tabs.insert(insert_index, tab);
|
||||||
|
self.normalize_tab_sort_keys(&space_id);
|
||||||
self.active_tab_id = tab_id.clone();
|
self.active_tab_id = tab_id.clone();
|
||||||
self.active_tabs_by_space.insert(self.active_space_id.clone(), tab_id.clone());
|
self.active_tabs_by_space.insert(self.active_space_id.clone(), tab_id.clone());
|
||||||
self.active_tabs_by_space_profile
|
self.active_tabs_by_space_profile
|
||||||
@@ -49,7 +51,10 @@ impl BrowserCore {
|
|||||||
|
|
||||||
self.detach_tab_from_split(&tab_id);
|
self.detach_tab_from_split(&tab_id);
|
||||||
let tab_index = self.active_tab_index()?;
|
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].move_to_space(space_id.clone());
|
||||||
|
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());
|
self.active_tabs_by_space.insert(space_id.clone(), tab_id.clone());
|
||||||
self.active_tabs_by_space_profile.remove(&(source_space_id.clone(), profile_id.clone()));
|
self.active_tabs_by_space_profile.remove(&(source_space_id.clone(), profile_id.clone()));
|
||||||
|
|
||||||
@@ -257,6 +262,18 @@ impl BrowserCore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_tab_sort_key(&mut self, tab_id: &TabId, sort_key: u64) -> Result<(), CoreError> {
|
||||||
|
let tab = self
|
||||||
|
.tabs
|
||||||
|
.iter_mut()
|
||||||
|
.find(|tab| tab.id() == tab_id)
|
||||||
|
.ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?;
|
||||||
|
let space_id = tab.space_id().clone();
|
||||||
|
tab.set_sort_key(sort_key);
|
||||||
|
self.sort_tabs_within_space(&space_id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn active_tab(&self) -> Result<&BrowserTab, CoreError> {
|
pub fn active_tab(&self) -> Result<&BrowserTab, CoreError> {
|
||||||
self.tabs
|
self.tabs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -279,7 +296,8 @@ impl BrowserCore {
|
|||||||
url: UrlText,
|
url: UrlText,
|
||||||
) -> BrowserTab {
|
) -> BrowserTab {
|
||||||
let title = tab_title(&url);
|
let title = tab_title(&url);
|
||||||
BrowserTab::new(TabId::new(), space_id, profile_id, title, url)
|
let sort_key = self.next_tab_sort_key(&space_id);
|
||||||
|
BrowserTab::new(TabId::new(), space_id, profile_id, title, url).with_sort_key(sort_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn tab_belongs_to_space(&self, tab_id: &TabId, space_id: &SpaceId) -> bool {
|
pub(super) fn tab_belongs_to_space(&self, tab_id: &TabId, space_id: &SpaceId) -> bool {
|
||||||
@@ -294,7 +312,9 @@ impl BrowserCore {
|
|||||||
.position(|existing| existing.id() == &self.active_tab_id)
|
.position(|existing| existing.id() == &self.active_tab_id)
|
||||||
.map_or(self.tabs.len(), |index| index + 1);
|
.map_or(self.tabs.len(), |index| index + 1);
|
||||||
|
|
||||||
|
let space_id = tab.space_id().clone();
|
||||||
self.tabs.insert(insert_index, tab);
|
self.tabs.insert(insert_index, tab);
|
||||||
|
self.normalize_tab_sort_keys(&space_id);
|
||||||
self.select_tab(&tab_id)?;
|
self.select_tab(&tab_id)?;
|
||||||
Ok(tab_id)
|
Ok(tab_id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,39 @@ fn opens_new_tab_below_active_tab() -> Result<(), Box<dyn Error>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn opened_tabs_receive_visible_sort_keys() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let first_tab_id = core.active_tab()?.id().clone();
|
||||||
|
let second_tab_id = core.open_tab(UrlText::parse("https://example.com")?);
|
||||||
|
|
||||||
|
core.select_tab(&first_tab_id)?;
|
||||||
|
let third_tab_id = core.open_tab(UrlText::parse("https://servo.org")?);
|
||||||
|
let snapshot = core.snapshot()?;
|
||||||
|
let ordered =
|
||||||
|
snapshot.tabs.iter().map(|tab| (tab.id().clone(), tab.sort_key())).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(ordered, vec![(first_tab_id, 0), (third_tab_id, 1), (second_tab_id, 2)]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn snapshot_orders_tabs_by_sort_key() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let first_tab_id = core.active_tab()?.id().clone();
|
||||||
|
let second_tab_id = core.open_tab(UrlText::parse("https://example.com")?);
|
||||||
|
let third_tab_id = core.open_tab(UrlText::parse("https://servo.org")?);
|
||||||
|
|
||||||
|
core.set_tab_sort_key(&second_tab_id, 30)?;
|
||||||
|
core.set_tab_sort_key(&third_tab_id, 10)?;
|
||||||
|
core.set_tab_sort_key(&first_tab_id, 20)?;
|
||||||
|
let snapshot = core.snapshot()?;
|
||||||
|
let ordered_ids = snapshot.tabs.iter().map(|tab| tab.id().clone()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(ordered_ids, vec![third_tab_id, first_tab_id, second_tab_id]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn opened_tabs_record_active_tab_as_parent() -> Result<(), Box<dyn Error>> {
|
fn opened_tabs_record_active_tab_as_parent() -> Result<(), Box<dyn Error>> {
|
||||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub struct BrowserTab {
|
|||||||
state: TabState,
|
state: TabState,
|
||||||
flags: TabFlags,
|
flags: TabFlags,
|
||||||
split_id: Option<SplitId>,
|
split_id: Option<SplitId>,
|
||||||
|
sort_key: u64,
|
||||||
sync_enabled: bool,
|
sync_enabled: bool,
|
||||||
created_at: SystemTime,
|
created_at: SystemTime,
|
||||||
last_active_at: SystemTime,
|
last_active_at: SystemTime,
|
||||||
@@ -55,6 +56,7 @@ impl BrowserTab {
|
|||||||
state: TabState::Ready,
|
state: TabState::Ready,
|
||||||
flags: TabFlags::default(),
|
flags: TabFlags::default(),
|
||||||
split_id: None,
|
split_id: None,
|
||||||
|
sort_key: 0,
|
||||||
sync_enabled: true,
|
sync_enabled: true,
|
||||||
created_at,
|
created_at,
|
||||||
last_active_at: created_at,
|
last_active_at: created_at,
|
||||||
@@ -67,6 +69,12 @@ impl BrowserTab {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_sort_key(mut self, sort_key: u64) -> Self {
|
||||||
|
self.sort_key = sort_key;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn id(&self) -> &TabId {
|
pub fn id(&self) -> &TabId {
|
||||||
&self.id
|
&self.id
|
||||||
@@ -164,6 +172,15 @@ impl BrowserTab {
|
|||||||
self.split_id = None;
|
self.split_id = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn sort_key(&self) -> u64 {
|
||||||
|
self.sort_key
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sort_key(&mut self, sort_key: u64) {
|
||||||
|
self.sort_key = sort_key;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_sync_enabled(&mut self, sync_enabled: bool) {
|
pub fn set_sync_enabled(&mut self, sync_enabled: bool) {
|
||||||
self.sync_enabled = sync_enabled;
|
self.sync_enabled = sync_enabled;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user