Restore archived tabs from sidebar
This commit is contained in:
@@ -142,6 +142,20 @@ impl ElyShell {
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_archived_tab(
|
||||
&mut self,
|
||||
tab_id: &TabId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let ShellState::Ready(core) = &mut self.state
|
||||
&& core.restore_archived_tab(tab_id).is_ok()
|
||||
{
|
||||
self.sync_address_input(window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_active_tab_favorite(&mut self, cx: &mut Context<Self>) {
|
||||
if let ShellState::Ready(core) = &mut self.state
|
||||
&& core.toggle_active_tab_favorite().is_ok()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ely_browser_core::BrowserSnapshot;
|
||||
use ely_design_system::{ELY_THEME, colors, spacing};
|
||||
use ely_domain::BrowserTab;
|
||||
use ely_domain::{ArchiveSource, ArchivedTab, BrowserTab};
|
||||
use gpui::{
|
||||
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Window, div, px, rgb,
|
||||
@@ -176,6 +176,14 @@ impl ElyShell {
|
||||
.filter(|tab| !tab.flags().pinned)
|
||||
.map(|tab| self.render_tab_row(tab, tab.id() == &snapshot.active_tab_id, cx)),
|
||||
)
|
||||
.child(section_label("Archive"))
|
||||
.children(
|
||||
snapshot
|
||||
.archived_tabs
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|archived_tab| self.render_archived_row(archived_tab, cx)),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(section_label("Profile"))
|
||||
.child(
|
||||
@@ -316,6 +324,54 @@ impl ElyShell {
|
||||
.child(div().text_xs().text_color(rgb(colors::MUTED)).child(tab.display_url()))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_archived_row(
|
||||
&mut self,
|
||||
archived_tab: &ArchivedTab,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let tab = archived_tab.tab();
|
||||
let tab_id = tab.id().clone();
|
||||
|
||||
div()
|
||||
.id(SharedString::from(format!("archived-{}", tab.id().as_str())))
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(rgb(colors::HAIRLINE))
|
||||
.bg(rgb(colors::CANVAS))
|
||||
.px_3()
|
||||
.py_2()
|
||||
.gap_2()
|
||||
.flex()
|
||||
.items_center()
|
||||
.cursor_pointer()
|
||||
.hover(|style| style.bg(rgb(colors::SURFACE_CARD)))
|
||||
.active(|style| style.opacity(0.82))
|
||||
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||
shell.restore_archived_tab(&tab_id, window, cx);
|
||||
}))
|
||||
.child(div().text_color(rgb(colors::MUTED)).child(IconName::Undo2))
|
||||
.child(
|
||||
div()
|
||||
.min_w_0()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_semibold()
|
||||
.text_color(rgb(colors::INK))
|
||||
.child(tab.title().to_string()),
|
||||
)
|
||||
.child(div().text_xs().text_color(rgb(colors::MUTED)).child(format!(
|
||||
"{} - {}",
|
||||
tab.display_url(),
|
||||
archive_source_label(archived_tab.source())
|
||||
))),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_web_canvas(tab: &BrowserTab) -> AnyElement {
|
||||
@@ -370,3 +426,10 @@ fn render_tab_status(tab: &BrowserTab) -> String {
|
||||
url => url.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn archive_source_label(source: &ArchiveSource) -> &'static str {
|
||||
match source {
|
||||
ArchiveSource::ManualClose => "Closed",
|
||||
ArchiveSource::AutoArchive => "Auto archived",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,17 @@ impl BrowserCore {
|
||||
self.restore_tab(tab)
|
||||
}
|
||||
|
||||
pub fn restore_archived_tab(&mut self, tab_id: &TabId) -> Result<TabId, CoreError> {
|
||||
let index = self
|
||||
.archived_tabs
|
||||
.iter()
|
||||
.position(|archived| archived.tab().id() == tab_id)
|
||||
.ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?;
|
||||
let archived_tab = self.archived_tabs.remove(index);
|
||||
let tab = archived_tab.into_tab();
|
||||
self.restore_tab(tab)
|
||||
}
|
||||
|
||||
pub fn restore_archived_tab_match(&mut self, query: &str) -> Result<Option<TabId>, CoreError> {
|
||||
let normalized_query = query.trim().to_lowercase();
|
||||
if normalized_query.is_empty() {
|
||||
|
||||
@@ -73,6 +73,40 @@ fn restores_last_archived_tab() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restores_archived_tab_by_id() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let example_tab_id = core.open_tab(UrlText::parse("https://example.com")?);
|
||||
let servo_tab_id = core.open_tab(UrlText::parse("https://servo.org")?);
|
||||
|
||||
core.close_active_tab()?;
|
||||
core.select_tab(&example_tab_id)?;
|
||||
core.close_active_tab()?;
|
||||
|
||||
let restored_tab_id = core.restore_archived_tab(&servo_tab_id)?;
|
||||
let snapshot = core.snapshot()?;
|
||||
|
||||
assert_eq!(restored_tab_id, servo_tab_id);
|
||||
assert_eq!(snapshot.active_tab_id, servo_tab_id);
|
||||
assert_eq!(snapshot.archived_tabs.len(), 1);
|
||||
assert_eq!(snapshot.archived_tabs[0].tab().id(), &example_tab_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restore_archived_tab_by_id_returns_error_for_open_tab() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let active_tab_id = core.active_tab()?.id().clone();
|
||||
|
||||
let error = match core.restore_archived_tab(&active_tab_id) {
|
||||
Err(error) => error,
|
||||
Ok(_) => return Err("restore should require an archived tab id".into()),
|
||||
};
|
||||
|
||||
assert_eq!(error, CoreError::TabNotFound { id: active_tab_id });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restores_matching_archived_tab() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
|
||||
+5
-3
@@ -13,12 +13,14 @@
|
||||
│ │ │ Clean browser surface for the current │ │
|
||||
│ Tabs │ │ Space and Profile. │ │
|
||||
│ ● New Tab │ └─────────────────────────────────────────┘ │
|
||||
│ Archive │ │
|
||||
│ ↶ servo.org │ │
|
||||
│ │ │
|
||||
│ Profile │ Ready │
|
||||
│ Default │ │
|
||||
└──────────────────────────────┴───────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Motion register: productive. Command and tab interactions use immediate state changes with
|
||||
hover/press feedback through GPUI styles; future pane transitions should use transform/opacity and
|
||||
respect reduced-motion settings.
|
||||
Motion register: productive. Command, tab, and archive restore interactions use immediate state
|
||||
changes with hover/press feedback through GPUI styles; future pane transitions should use
|
||||
transform/opacity and respect reduced-motion settings.
|
||||
|
||||
Reference in New Issue
Block a user