update sidebar
This commit is contained in:
@@ -116,26 +116,20 @@ pub trait ButtonVariants: Sized {
|
|||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
base: BaseButton,
|
base: BaseButton,
|
||||||
|
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
tooltip: Option<SharedString>,
|
tooltip: Option<SharedString>,
|
||||||
children: Vec<AnyElement>,
|
children: Vec<AnyElement>,
|
||||||
|
|
||||||
variant: ButtonVariant,
|
variant: ButtonVariant,
|
||||||
size: Size,
|
size: Size,
|
||||||
|
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
loading: bool,
|
loading: bool,
|
||||||
|
|
||||||
rounded: bool,
|
rounded: bool,
|
||||||
compact: bool,
|
compact: bool,
|
||||||
caret: bool,
|
caret: bool,
|
||||||
indicator: bool,
|
indicator: bool,
|
||||||
|
|
||||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||||
on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
|
on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
|
||||||
|
|
||||||
tab_index: isize,
|
tab_index: isize,
|
||||||
tab_stop: bool,
|
tab_stop: bool,
|
||||||
|
|
||||||
|
|||||||
@@ -255,35 +255,40 @@ fn rows_for(tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rows = vec![SidebarRow::Section {
|
let mut rows = Vec::new();
|
||||||
label: "Communities".into(),
|
|
||||||
count: community_count,
|
|
||||||
}];
|
|
||||||
|
|
||||||
rows.extend(
|
if !communities.is_empty() {
|
||||||
communities
|
rows.push(SidebarRow::Section {
|
||||||
.into_iter()
|
label: "Communities".into(),
|
||||||
.map(|community| SidebarRow::Community { community }),
|
count: community_count,
|
||||||
);
|
});
|
||||||
rows.push(SidebarRow::Action {
|
rows.extend(
|
||||||
label: "Show all communities".into(),
|
communities
|
||||||
tab: SidebarTab::Communities,
|
.into_iter()
|
||||||
});
|
.map(|community| SidebarRow::Community { community }),
|
||||||
|
);
|
||||||
|
rows.push(SidebarRow::Action {
|
||||||
|
label: "Show all communities".into(),
|
||||||
|
tab: SidebarTab::Communities,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
rows.push(SidebarRow::Section {
|
if !rooms.is_empty() {
|
||||||
label: "Chats".into(),
|
rows.push(SidebarRow::Section {
|
||||||
count: rooms.len(),
|
label: "Chats".into(),
|
||||||
});
|
count: rooms.len(),
|
||||||
rows.extend(
|
});
|
||||||
rooms
|
rows.extend(
|
||||||
.into_iter()
|
rooms
|
||||||
.take(5)
|
.into_iter()
|
||||||
.map(|room| SidebarRow::Room { room }),
|
.take(5)
|
||||||
);
|
.map(|room| SidebarRow::Room { room }),
|
||||||
rows.push(SidebarRow::Action {
|
);
|
||||||
label: "Show all chats".into(),
|
rows.push(SidebarRow::Action {
|
||||||
tab: SidebarTab::Chats,
|
label: "Show all chats".into(),
|
||||||
});
|
tab: SidebarTab::Chats,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
rows
|
rows
|
||||||
}
|
}
|
||||||
@@ -400,6 +405,7 @@ fn render_rows(range: Range<usize>, rows: &[SidebarRow], cx: &Context<Sidebar>)
|
|||||||
TreeRowKind::Action,
|
TreeRowKind::Action,
|
||||||
label.clone(),
|
label.clone(),
|
||||||
)
|
)
|
||||||
|
.icon(IconName::ArrowRight)
|
||||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||||
this.select_tab(tab, cx);
|
this.select_tab(tab, cx);
|
||||||
}))
|
}))
|
||||||
@@ -590,7 +596,7 @@ impl Render for Sidebar {
|
|||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.bottom_12()
|
.bottom_16()
|
||||||
.left_0()
|
.left_0()
|
||||||
.h_9()
|
.h_9()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{App, IntoElement, ParentElement, RenderOnce, Styled, Window, div};
|
use gpui::{App, InteractiveElement, IntoElement, ParentElement, RenderOnce, Styled, Window, div};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::{IconName, Selectable, h_flex};
|
use ui::{IconName, Selectable, h_flex};
|
||||||
@@ -85,29 +85,37 @@ impl RenderOnce for TabBar {
|
|||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let Self { active, on_select } = self;
|
let Self { active, on_select } = self;
|
||||||
|
|
||||||
div().absolute().bottom_2().left_0().w_full().px_2().child(
|
div()
|
||||||
h_flex()
|
.id("sidebar-tabs")
|
||||||
.w_full()
|
.absolute()
|
||||||
.p_1()
|
.bottom_3()
|
||||||
.gap_1()
|
.left_0()
|
||||||
.rounded(cx.theme().radius_lg)
|
.w_full()
|
||||||
.bg(cx.theme().elevated_surface_background)
|
.px_4()
|
||||||
.when(cx.theme().shadow, |this| this.shadow_md())
|
.child(
|
||||||
.children(SidebarTab::ALL.into_iter().map(|tab| {
|
h_flex()
|
||||||
let on_select = on_select.clone();
|
.w_full()
|
||||||
|
.p_1()
|
||||||
|
.gap_1()
|
||||||
|
.rounded_full()
|
||||||
|
.bg(cx.theme().background)
|
||||||
|
.when(cx.theme().shadow, |this| this.shadow_md())
|
||||||
|
.children(SidebarTab::ALL.into_iter().map(|tab| {
|
||||||
|
let on_select = on_select.clone();
|
||||||
|
|
||||||
Button::new(format!("tab-{}", tab.list_id()))
|
Button::new(format!("tab-{}", tab.list_id()))
|
||||||
.icon(tab.icon())
|
.icon(tab.icon())
|
||||||
.ghost()
|
.ghost()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.selected(tab == active)
|
.rounded()
|
||||||
.tooltip(tab.label())
|
.selected(tab == active)
|
||||||
.on_click(move |_event, window, cx| {
|
.tooltip(tab.label())
|
||||||
if let Some(on_select) = on_select.as_ref() {
|
.on_click(move |_event, window, cx| {
|
||||||
on_select(tab, window, cx);
|
if let Some(on_select) = on_select.as_ref() {
|
||||||
}
|
on_select(tab, window, cx);
|
||||||
})
|
}
|
||||||
})),
|
})
|
||||||
)
|
})),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use community::Community;
|
|||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, ClickEvent, ElementId, Entity, ImageSource, InteractiveElement, IntoElement,
|
App, ClickEvent, ElementId, Entity, ImageSource, InteractiveElement, IntoElement,
|
||||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div,
|
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px,
|
||||||
};
|
};
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
@@ -50,6 +50,7 @@ pub struct TreeRow {
|
|||||||
label: SharedString,
|
label: SharedString,
|
||||||
avatar: Option<SharedString>,
|
avatar: Option<SharedString>,
|
||||||
picture: Option<ImageSource>,
|
picture: Option<ImageSource>,
|
||||||
|
icon: Option<IconName>,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
created_at: Option<SharedString>,
|
created_at: Option<SharedString>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
@@ -69,6 +70,7 @@ impl TreeRow {
|
|||||||
label: label.into(),
|
label: label.into(),
|
||||||
avatar: None,
|
avatar: None,
|
||||||
picture: None,
|
picture: None,
|
||||||
|
icon: None,
|
||||||
count: None,
|
count: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
selected: false,
|
selected: false,
|
||||||
@@ -88,6 +90,12 @@ impl TreeRow {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows `icon` in the avatar slot when the row has no avatar or picture.
|
||||||
|
pub fn icon(mut self, icon: IconName) -> Self {
|
||||||
|
self.icon = Some(icon);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn count(mut self, count: usize) -> Self {
|
pub fn count(mut self, count: usize) -> Self {
|
||||||
self.count = Some(count);
|
self.count = Some(count);
|
||||||
self
|
self
|
||||||
@@ -150,6 +158,18 @@ impl RenderOnce for TreeRow {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let avatar = avatar.or_else(|| {
|
||||||
|
self.icon.map(|icon| {
|
||||||
|
h_flex()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.w(px(20.))
|
||||||
|
.justify_center()
|
||||||
|
.text_color(cx.theme().icon_muted)
|
||||||
|
.child(Icon::new(icon).small())
|
||||||
|
.into_any_element()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.h_8()
|
.h_8()
|
||||||
@@ -159,14 +179,12 @@ impl RenderOnce for TreeRow {
|
|||||||
.rounded(cx.theme().radius)
|
.rounded(cx.theme().radius)
|
||||||
.when(is_section, |this| {
|
.when(is_section, |this| {
|
||||||
this.text_xs()
|
this.text_xs()
|
||||||
.text_color(cx.theme().text_muted)
|
.text_color(cx.theme().text_placeholder)
|
||||||
.font_semibold()
|
.font_semibold()
|
||||||
})
|
})
|
||||||
.when(is_room || is_community, |this| this.text_sm())
|
.when(is_room || is_community, |this| this.text_sm())
|
||||||
.when(is_action, |this| {
|
.when(is_action, |this| {
|
||||||
this.text_sm()
|
this.text_sm().text_color(cx.theme().text_muted)
|
||||||
.font_medium()
|
|
||||||
.text_color(cx.theme().text_accent)
|
|
||||||
})
|
})
|
||||||
.when(is_hint, |this| {
|
.when(is_hint, |this| {
|
||||||
this.text_xs()
|
this.text_xs()
|
||||||
@@ -197,11 +215,11 @@ impl RenderOnce for TreeRow {
|
|||||||
this.child(div().flex_shrink_0().font_normal().child(count.to_string()))
|
this.child(div().flex_shrink_0().font_normal().child(count.to_string()))
|
||||||
})
|
})
|
||||||
.when_some(self.created_at, |this, created_at| {
|
.when_some(self.created_at, |this, created_at| {
|
||||||
this.child(
|
this.child(div().flex_1()).child(
|
||||||
div()
|
div()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.text_xs()
|
|
||||||
.text_color(cx.theme().text_placeholder)
|
.text_color(cx.theme().text_placeholder)
|
||||||
|
.text_xs()
|
||||||
.child(created_at),
|
.child(created_at),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -602,9 +602,10 @@ client.subscribe(filter).with_id(sub_id).await?;
|
|||||||
`crates/community`.** `concord` has no subscriptions, no `init`, and no
|
`crates/community`.** `concord` has no subscriptions, no `init`, and no
|
||||||
`Entity<Community>`; `community::CommunityRegistry` owns one `Entity<Community>`
|
`Entity<Community>`; `community::CommunityRegistry` owns one `Entity<Community>`
|
||||||
per state document, subscribes when a community's plane set changes, and
|
per state document, subscribes when a community's plane set changes, and
|
||||||
re-folds on an inbound wrap. The sidebar observes the registry, logs
|
re-folds on an inbound wrap. The sidebar subscribes to the registry, surfaces
|
||||||
`CommunityEvent::Error` through `log::error!`, and its "New community" row opens
|
`CommunityEvent::Error` as a window notification, and its "New community" row
|
||||||
a name prompt that calls `CommunityRegistry::create`. `create` persists the
|
in the Communities tab dispatches `Command::NewCommunity`, whose name prompt
|
||||||
|
calls `CommunityRegistry::create`. `create` persists the
|
||||||
genesis locally, publishes the wraps to the community's relays, and records the
|
genesis locally, publishes the wraps to the community's relays, and records the
|
||||||
membership in the account's Community List — all best-effort, so a relay that is
|
membership in the account's Community List — all best-effort, so a relay that is
|
||||||
down warns without losing the community. Discovery
|
down warns without losing the community. Discovery
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ Empty list shows the existing "No communities yet" hint.
|
|||||||
|
|
||||||
Each phase is independently reviewable and leaves the app runnable.
|
Each phase is independently reviewable and leaves the app runnable.
|
||||||
|
|
||||||
### Phase 1 — tab shell
|
### Phase 1 — tab shell — DONE
|
||||||
|
|
||||||
Files: `crates/workspace/src/sidebar/mod.rs`,
|
Files: `crates/workspace/src/sidebar/mod.rs`,
|
||||||
`crates/workspace/src/sidebar/tab.rs` (new), `sidebar/tree.rs`,
|
`crates/workspace/src/sidebar/tab.rs` (new), `sidebar/tree.rs`,
|
||||||
@@ -228,7 +228,7 @@ Files: `crates/workspace/src/sidebar/mod.rs`,
|
|||||||
Validation: app runs signed in and signed out; chats and communities list and
|
Validation: app runs signed in and signed out; chats and communities list and
|
||||||
open as before; tab switching works; requests dot still clears.
|
open as before; tab switching works; requests dot still clears.
|
||||||
|
|
||||||
### Phase 2 — Recents tab
|
### Phase 2 — Recents tab — DONE
|
||||||
|
|
||||||
Files: `crates/settings/src/lib.rs`, `crates/workspace/src/sidebar/mod.rs`,
|
Files: `crates/settings/src/lib.rs`, `crates/workspace/src/sidebar/mod.rs`,
|
||||||
`sidebar/tree.rs`.
|
`sidebar/tree.rs`.
|
||||||
@@ -242,7 +242,7 @@ Files: `crates/settings/src/lib.rs`, `crates/workspace/src/sidebar/mod.rs`,
|
|||||||
Validation: `cargo test -p settings`; manually open communities, restart, and
|
Validation: `cargo test -p settings`; manually open communities, restart, and
|
||||||
confirm the Recents order; confirm ≤3 / ≤5 rendering and both "Show all" rows.
|
confirm the Recents order; confirm ≤3 / ≤5 rendering and both "Show all" rows.
|
||||||
|
|
||||||
### Phase 3 — tab actions
|
### Phase 3 — tab actions — DONE
|
||||||
|
|
||||||
Files: `crates/workspace/src/dialogs/new_chat.rs` (new),
|
Files: `crates/workspace/src/dialogs/new_chat.rs` (new),
|
||||||
`dialogs/new_community.rs` (new), `crates/workspace/src/dialogs/mod.rs`,
|
`dialogs/new_community.rs` (new), `crates/workspace/src/dialogs/mod.rs`,
|
||||||
@@ -264,7 +264,7 @@ Validation: create a chat from an npub and confirm the room opens; create a
|
|||||||
community and confirm it appears in the Communities tab and in Recents;
|
community and confirm it appears in the Communities tab and in Recents;
|
||||||
requests/contacts/browse still dispatch.
|
requests/contacts/browse still dispatch.
|
||||||
|
|
||||||
### Phase 4 — onboarding sidebar
|
### Phase 4 — onboarding sidebar — DONE, except `Join now`
|
||||||
|
|
||||||
Files: `crates/state/src/lib.rs`, `crates/workspace/src/sidebar/mod.rs`,
|
Files: `crates/state/src/lib.rs`, `crates/workspace/src/sidebar/mod.rs`,
|
||||||
`sidebar/onboarding.rs` (new), `crates/workspace/src/dialogs/create_identity.rs`
|
`sidebar/onboarding.rs` (new), `crates/workspace/src/dialogs/create_identity.rs`
|
||||||
@@ -291,7 +291,11 @@ Validation: with no stored credentials the sidebar shows onboarding and no
|
|||||||
modal; `Import identity` still signs in; `Join now` signs in with a fresh key;
|
modal; `Import identity` still signs in; `Join now` signs in with a fresh key;
|
||||||
with bunker credentials the tabs appear without an onboarding flash.
|
with bunker credentials the tabs appear without an onboarding flash.
|
||||||
|
|
||||||
### Phase 5 — polish and cleanup
|
**Deferred.** `dialogs/create_identity.rs` is not implemented, so `Join now`
|
||||||
|
renders without a click handler, and the §2.1 banner assets were skipped in
|
||||||
|
favor of a plain theme-colored background with the brand mark.
|
||||||
|
|
||||||
|
### Phase 5 — polish and cleanup — DONE
|
||||||
|
|
||||||
- Reposition the "Getting messages…" pill above the tab bar.
|
- Reposition the "Getting messages…" pill above the tab bar.
|
||||||
- Empty states and counts for all three tabs; truncation rules (§3).
|
- Empty states and counts for all three tabs; truncation rules (§3).
|
||||||
|
|||||||
Reference in New Issue
Block a user