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>) {
|
fn toggle_active_tab_favorite(&mut self, cx: &mut Context<Self>) {
|
||||||
if let ShellState::Ready(core) = &mut self.state
|
if let ShellState::Ready(core) = &mut self.state
|
||||||
&& core.toggle_active_tab_favorite().is_ok()
|
&& core.toggle_active_tab_favorite().is_ok()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use ely_browser_core::BrowserSnapshot;
|
use ely_browser_core::BrowserSnapshot;
|
||||||
use ely_design_system::{ELY_THEME, colors, spacing};
|
use ely_design_system::{ELY_THEME, colors, spacing};
|
||||||
use ely_domain::BrowserTab;
|
use ely_domain::{ArchiveSource, ArchivedTab, BrowserTab};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
||||||
StatefulInteractiveElement, Styled, Window, div, px, rgb,
|
StatefulInteractiveElement, Styled, Window, div, px, rgb,
|
||||||
@@ -176,6 +176,14 @@ impl ElyShell {
|
|||||||
.filter(|tab| !tab.flags().pinned)
|
.filter(|tab| !tab.flags().pinned)
|
||||||
.map(|tab| self.render_tab_row(tab, tab.id() == &snapshot.active_tab_id, cx)),
|
.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(div().flex_1())
|
||||||
.child(section_label("Profile"))
|
.child(section_label("Profile"))
|
||||||
.child(
|
.child(
|
||||||
@@ -316,6 +324,54 @@ impl ElyShell {
|
|||||||
.child(div().text_xs().text_color(rgb(colors::MUTED)).child(tab.display_url()))
|
.child(div().text_xs().text_color(rgb(colors::MUTED)).child(tab.display_url()))
|
||||||
.into_any_element()
|
.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 {
|
fn render_web_canvas(tab: &BrowserTab) -> AnyElement {
|
||||||
@@ -370,3 +426,10 @@ fn render_tab_status(tab: &BrowserTab) -> String {
|
|||||||
url => url.to_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)
|
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> {
|
pub fn restore_archived_tab_match(&mut self, query: &str) -> Result<Option<TabId>, CoreError> {
|
||||||
let normalized_query = query.trim().to_lowercase();
|
let normalized_query = query.trim().to_lowercase();
|
||||||
if normalized_query.is_empty() {
|
if normalized_query.is_empty() {
|
||||||
|
|||||||
@@ -73,6 +73,40 @@ fn restores_last_archived_tab() -> Result<(), Box<dyn Error>> {
|
|||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn restores_matching_archived_tab() -> Result<(), Box<dyn Error>> {
|
fn restores_matching_archived_tab() -> Result<(), Box<dyn Error>> {
|
||||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
|||||||
+5
-3
@@ -13,12 +13,14 @@
|
|||||||
│ │ │ Clean browser surface for the current │ │
|
│ │ │ Clean browser surface for the current │ │
|
||||||
│ Tabs │ │ Space and Profile. │ │
|
│ Tabs │ │ Space and Profile. │ │
|
||||||
│ ● New Tab │ └─────────────────────────────────────────┘ │
|
│ ● New Tab │ └─────────────────────────────────────────┘ │
|
||||||
|
│ Archive │ │
|
||||||
|
│ ↶ servo.org │ │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ Profile │ Ready │
|
│ Profile │ Ready │
|
||||||
│ Default │ │
|
│ Default │ │
|
||||||
└──────────────────────────────┴───────────────────────────────────────────────┘
|
└──────────────────────────────┴───────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
Motion register: productive. Command and tab interactions use immediate state changes with
|
Motion register: productive. Command, tab, and archive restore interactions use immediate state
|
||||||
hover/press feedback through GPUI styles; future pane transitions should use transform/opacity and
|
changes with hover/press feedback through GPUI styles; future pane transitions should use
|
||||||
respect reduced-motion settings.
|
transform/opacity and respect reduced-motion settings.
|
||||||
|
|||||||
Reference in New Issue
Block a user