From 2ddd2d3b1791ffbe40bad3ecfd963c97195580b1 Mon Sep 17 00:00:00 2001 From: reya Date: Fri, 27 Dec 2024 09:56:56 +0700 Subject: [PATCH] wip: refactor --- Cargo.lock | 149 ++++++++++++++++++++++++ crates/app/Cargo.toml | 1 + crates/app/src/states/chat.rs | 17 ++- crates/app/src/states/metadata.rs | 4 +- crates/app/src/views/dock/chat/mod.rs | 23 +++- crates/app/src/views/dock/chat/room.rs | 8 +- crates/app/src/views/dock/inbox/item.rs | 2 +- crates/app/src/views/dock/left_dock.rs | 2 +- crates/app/src/views/dock/welcome.rs | 2 +- crates/ui/Cargo.toml | 1 + crates/ui/src/dock/invalid_panel.rs | 6 +- crates/ui/src/dock/panel.rs | 25 ++-- crates/ui/src/dock/stack_panel.rs | 2 +- crates/ui/src/dock/state.rs | 2 +- crates/ui/src/dock/tab_panel.rs | 51 ++++++-- crates/ui/src/dock/tiles.rs | 4 +- crates/ui/src/tab/tab.rs | 41 +++++-- 17 files changed, 294 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 754e460..ccf78cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.95" @@ -967,6 +1016,46 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.91", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "cocoa" version = "0.25.0" @@ -1051,6 +1140,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1099,6 +1194,7 @@ dependencies = [ "keyring", "keyring-search", "nostr-sdk", + "random_name_generator", "reqwest_client", "rust-embed", "serde", @@ -1117,6 +1213,7 @@ dependencies = [ "gpui", "image", "itertools 0.13.0", + "nostr-sdk", "once_cell", "paste", "regex", @@ -2739,6 +2836,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.12.1" @@ -2772,6 +2875,12 @@ dependencies = [ "libc", ] +[[package]] +name = "joinery" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" + [[package]] name = "jpeg-decoder" version = "0.3.1" @@ -3967,6 +4076,23 @@ dependencies = [ "getrandom", ] +[[package]] +name = "random_name_generator" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f35cf4ff1039c849a4d890c6aa4332df47f9def1e9398ef1e5959bc7f89992" +dependencies = [ + "anyhow", + "bitflags 2.6.0", + "clap", + "lazy_static", + "log", + "rand", + "regex", + "rust-embed", + "titlecase", +] + [[package]] name = "rangemap" version = "1.5.1" @@ -4912,6 +5038,12 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.25.0" @@ -5328,6 +5460,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "titlecase" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38397a8cdb017cfeb48bf6c154d6de975ac69ffeed35980fde199d2ee0842042" +dependencies = [ + "joinery", + "lazy_static", + "regex", +] + [[package]] name = "tokio" version = "1.42.0" @@ -5703,6 +5846,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "util" version = "0.1.0" diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index f9a2c6a..6a4e27a 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -28,3 +28,4 @@ rust-embed.workspace = true smol.workspace = true tracing-subscriber = { version = "0.3.18", features = ["fmt"] } +random_name_generator = "0.3.6" diff --git a/crates/app/src/states/chat.rs b/crates/app/src/states/chat.rs index 4159d25..6d3a0df 100644 --- a/crates/app/src/states/chat.rs +++ b/crates/app/src/states/chat.rs @@ -1,8 +1,10 @@ use gpui::*; use nostr_sdk::prelude::*; +use rnglib::{Language, RNG}; use serde::Deserialize; use std::sync::{Arc, RwLock}; +use super::metadata::MetadataRegistry; use crate::utils::get_room_id; #[derive(Clone, PartialEq, Eq, Deserialize)] @@ -12,31 +14,40 @@ pub struct Room { pub members: Vec, pub last_seen: Timestamp, pub title: Option, + pub metadata: Option, } impl Room { - pub fn new(event: &Event) -> Self { + pub fn new(event: &Event, cx: &mut WindowContext<'_>) -> Self { let owner = event.pubkey; let last_seen = event.created_at; + // Get all members from event's tag let members: Vec = event.tags.public_keys().copied().collect(); + // Get title from event's tag let title = if let Some(tag) = event.tags.find(TagKind::Title) { tag.content().map(|s| s.to_owned().into()) } else { - // TODO: create random name? - None + let rng = RNG::from(&Language::Roman); + let name = rng.generate_names(2, true).join("-").to_lowercase(); + + Some(name.into()) }; // Get unique id based on members let id = get_room_id(&owner, &members).into(); + // Get metadata for all members if exists + let metadata = cx.global::().get(&owner); + Self { id, title, members, last_seen, owner, + metadata, } } } diff --git a/crates/app/src/states/metadata.rs b/crates/app/src/states/metadata.rs index 0dd9bd9..c81abfe 100644 --- a/crates/app/src/states/metadata.rs +++ b/crates/app/src/states/metadata.rs @@ -35,8 +35,8 @@ impl MetadataRegistry { } } - pub fn get(&self, public_key: PublicKey) -> Option { - self.profiles.read().unwrap().get(&public_key).cloned() + pub fn get(&self, public_key: &PublicKey) -> Option { + self.profiles.read().unwrap().get(public_key).cloned() } fn new() -> Self { diff --git a/crates/app/src/views/dock/chat/mod.rs b/crates/app/src/views/dock/chat/mod.rs index 38a1db1..229b67c 100644 --- a/crates/app/src/views/dock/chat/mod.rs +++ b/crates/app/src/views/dock/chat/mod.rs @@ -1,11 +1,13 @@ +use std::sync::Arc; + use coop_ui::{ button::Button, dock::{Panel, PanelEvent, PanelState}, popup_menu::PopupMenu, }; use gpui::*; -use room::ChatRoom; -use std::sync::Arc; +use nostr_sdk::prelude::*; +use room::RoomPanel; use crate::states::chat::Room; @@ -20,14 +22,18 @@ pub struct ChatPanel { focus_handle: FocusHandle, // Room id: SharedString, - room: View, + room: View, + metadata: Option, } impl ChatPanel { pub fn new(room: &Arc, cx: &mut WindowContext) -> View { let id = room.id.clone(); + let title = room.title.clone(); + let metadata = room.metadata.clone(); + let room = cx.new_view(|cx| { - let view = ChatRoom::new(room, cx); + let view = RoomPanel::new(room, cx); // Load messages view.load(cx); // Subscribe for new messages @@ -37,21 +43,26 @@ impl ChatPanel { }); cx.new_view(|cx| Self { - name: "Chat".into(), + name: title.unwrap_or("Untitled".into()), closeable: true, zoomable: true, focus_handle: cx.focus_handle(), id, room, + metadata, }) } } impl Panel for ChatPanel { - fn panel_name(&self) -> SharedString { + fn panel_id(&self) -> SharedString { self.id.clone() } + fn panel_metadata(&self) -> Option { + self.metadata.clone() + } + fn title(&self, _cx: &WindowContext) -> AnyElement { self.name.clone().into_any_element() } diff --git a/crates/app/src/views/dock/chat/room.rs b/crates/app/src/views/dock/chat/room.rs index 76b4280..9ccd5d4 100644 --- a/crates/app/src/views/dock/chat/room.rs +++ b/crates/app/src/views/dock/chat/room.rs @@ -24,7 +24,7 @@ pub struct Messages { items: Vec, } -pub struct ChatRoom { +pub struct RoomPanel { owner: PublicKey, members: Arc<[PublicKey]>, // Form @@ -34,7 +34,7 @@ pub struct ChatRoom { messages: Model, } -impl ChatRoom { +impl RoomPanel { pub fn new(room: &Arc, cx: &mut ViewContext<'_, Self>) -> Self { let members: Arc<[PublicKey]> = room.members.clone().into(); let owner = room.owner; @@ -125,7 +125,7 @@ impl ChatRoom { // Get user's metadata let metadata = async_cx .read_global::(|state, _cx| { - state.get(ev.pubkey) + state.get(&ev.pubkey) }) .unwrap(); @@ -231,7 +231,7 @@ impl ChatRoom { } } -impl Render for ChatRoom { +impl Render for RoomPanel { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl IntoElement { v_flex() .size_full() diff --git a/crates/app/src/views/dock/inbox/item.rs b/crates/app/src/views/dock/inbox/item.rs index a0f5596..33539d4 100644 --- a/crates/app/src/views/dock/inbox/item.rs +++ b/crates/app/src/views/dock/inbox/item.rs @@ -70,7 +70,7 @@ impl InboxItem { } pub fn action(&self, cx: &mut WindowContext<'_>) { - let room = Arc::new(Room::new(&self.event)); + let room = Arc::new(Room::new(&self.event, cx)); cx.dispatch_action(Box::new(AddPanel { room, diff --git a/crates/app/src/views/dock/left_dock.rs b/crates/app/src/views/dock/left_dock.rs index 1df36c0..05c1d73 100644 --- a/crates/app/src/views/dock/left_dock.rs +++ b/crates/app/src/views/dock/left_dock.rs @@ -40,7 +40,7 @@ impl LeftDock { } impl Panel for LeftDock { - fn panel_name(&self) -> SharedString { + fn panel_id(&self) -> SharedString { "LeftDock".into() } diff --git a/crates/app/src/views/dock/welcome.rs b/crates/app/src/views/dock/welcome.rs index 2efa29a..de6b19a 100644 --- a/crates/app/src/views/dock/welcome.rs +++ b/crates/app/src/views/dock/welcome.rs @@ -30,7 +30,7 @@ impl WelcomePanel { } impl Panel for WelcomePanel { - fn panel_name(&self) -> SharedString { + fn panel_id(&self) -> SharedString { "WelcomePanel".into() } diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml index 7c073d7..a474b9f 100644 --- a/crates/ui/Cargo.toml +++ b/crates/ui/Cargo.toml @@ -15,6 +15,7 @@ smallvec.workspace = true anyhow.workspace = true itertools.workspace = true chrono.workspace = true +nostr-sdk.workspace = true paste = "1" regex = "1" diff --git a/crates/ui/src/dock/invalid_panel.rs b/crates/ui/src/dock/invalid_panel.rs index f2496fe..e558bc5 100644 --- a/crates/ui/src/dock/invalid_panel.rs +++ b/crates/ui/src/dock/invalid_panel.rs @@ -21,8 +21,9 @@ impl InvalidPanel { } } } + impl Panel for InvalidPanel { - fn panel_name(&self) -> SharedString { + fn panel_id(&self) -> SharedString { "InvalidPanel".into() } @@ -30,12 +31,15 @@ impl Panel for InvalidPanel { self.old_state.clone() } } + impl EventEmitter for InvalidPanel {} + impl FocusableView for InvalidPanel { fn focus_handle(&self, _: &AppContext) -> FocusHandle { self.focus_handle.clone() } } + impl Render for InvalidPanel { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl gpui::IntoElement { gpui::div() diff --git a/crates/ui/src/dock/panel.rs b/crates/ui/src/dock/panel.rs index abbfed8..8f81f5d 100644 --- a/crates/ui/src/dock/panel.rs +++ b/crates/ui/src/dock/panel.rs @@ -2,6 +2,7 @@ use gpui::{ AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Global, Hsla, IntoElement, SharedString, View, WeakView, WindowContext, }; +use nostr_sdk::prelude::Metadata; use std::{collections::HashMap, sync::Arc}; use super::{DockArea, PanelInfo, PanelState}; @@ -31,8 +32,13 @@ pub trait Panel: EventEmitter + FocusableView { /// The name of the panel used to serialize, deserialize and identify the panel. /// /// This is used to identify the panel when deserializing the panel. - /// Once you have defined a panel name, this must not be changed. - fn panel_name(&self) -> SharedString; + /// Once you have defined a panel id, this must not be changed. + fn panel_id(&self) -> SharedString; + + /// The optional metadata of the panel + fn panel_metadata(&self) -> Option { + None + } /// The title of the panel fn title(&self, _cx: &WindowContext) -> AnyElement { @@ -66,7 +72,8 @@ pub trait Panel: EventEmitter + FocusableView { } pub trait PanelView: 'static + Send + Sync { - fn panel_name(&self, cx: &WindowContext) -> SharedString; + fn panel_id(&self, cx: &WindowContext) -> SharedString; + fn panel_metadata(&self, cx: &WindowContext) -> Option; fn title(&self, _cx: &WindowContext) -> AnyElement; fn closeable(&self, cx: &WindowContext) -> bool; fn zoomable(&self, cx: &WindowContext) -> bool; @@ -78,8 +85,12 @@ pub trait PanelView: 'static + Send + Sync { } impl PanelView for View { - fn panel_name(&self, cx: &WindowContext) -> SharedString { - self.read(cx).panel_name() + fn panel_id(&self, cx: &WindowContext) -> SharedString { + self.read(cx).panel_id() + } + + fn panel_metadata(&self, cx: &WindowContext) -> Option { + self.read(cx).panel_metadata() } fn title(&self, cx: &WindowContext) -> AnyElement { @@ -166,7 +177,7 @@ impl Default for PanelRegistry { impl Global for PanelRegistry {} /// Register the Panel init by panel_name to global registry. -pub fn register_panel(cx: &mut AppContext, panel_name: &str, deserialize: F) +pub fn register_panel(cx: &mut AppContext, panel_id: &str, deserialize: F) where F: Fn(WeakView, &PanelState, &PanelInfo, &mut WindowContext) -> Box + 'static, @@ -177,5 +188,5 @@ where cx.global_mut::() .items - .insert(panel_name.to_string(), Arc::new(deserialize)); + .insert(panel_id.to_string(), Arc::new(deserialize)); } diff --git a/crates/ui/src/dock/stack_panel.rs b/crates/ui/src/dock/stack_panel.rs index 692e00f..677ccbd 100644 --- a/crates/ui/src/dock/stack_panel.rs +++ b/crates/ui/src/dock/stack_panel.rs @@ -25,7 +25,7 @@ pub struct StackPanel { } impl Panel for StackPanel { - fn panel_name(&self) -> SharedString { + fn panel_id(&self) -> SharedString { "StackPanel".into() } diff --git a/crates/ui/src/dock/state.rs b/crates/ui/src/dock/state.rs index ede4896..82ecb40 100644 --- a/crates/ui/src/dock/state.rs +++ b/crates/ui/src/dock/state.rs @@ -169,7 +169,7 @@ impl Default for PanelState { impl PanelState { pub fn new(panel: &P) -> Self { Self { - panel_name: panel.panel_name().to_string(), + panel_name: panel.panel_id().to_string(), ..Default::default() } } diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index 00b1baf..47f4140 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -42,14 +42,18 @@ impl Render for DragPanel { .id("drag-panel") .cursor_grab() .py_1() - .px_3() + .px_2() .w_24() + .flex() + .items_center() + .justify_center() .overflow_hidden() .whitespace_nowrap() .border_1() .border_color(cx.theme().border) .rounded_md() .text_color(cx.theme().tab_foreground) + .text_xs() .bg(cx.theme().tab_active) .opacity(0.75) .child(self.panel.title(cx)) @@ -74,7 +78,7 @@ pub struct TabPanel { } impl Panel for TabPanel { - fn panel_name(&self) -> SharedString { + fn panel_id(&self) -> SharedString { "TabPanel".into() } @@ -177,14 +181,14 @@ impl TabPanel { if self .panels .iter() - .any(|p| p.panel_name(cx) == panel.panel_name(cx)) + .any(|p| p.panel_id(cx) == panel.panel_id(cx)) { - // set the active panel to the matched panel + // Set the active panel to the matched panel if active { if let Some(ix) = self .panels .iter() - .position(|p| p.panel_name(cx) == panel.panel_name(cx)) + .position(|p| p.panel_id(cx) == panel.panel_id(cx)) { self.set_active_ix(ix, cx); } @@ -195,7 +199,7 @@ impl TabPanel { self.panels.push(panel); - // set the active panel to the new panel + // Set the active panel to the new panel if active { self.set_active_ix(self.panels.len() - 1, cx); } @@ -459,6 +463,7 @@ impl TabPanel { let Some(dock_area) = self.dock_area.upgrade() else { return div().into_any_element(); }; + let panel_style = dock_area.read(cx).panel_style; let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, cx); @@ -496,9 +501,37 @@ impl TabPanel { .flex_1() .min_w_16() .overflow_hidden() - .text_ellipsis() .whitespace_nowrap() - .child(panel.title(cx)) + .child( + div() + .w_full() + .flex() + .items_center() + .gap_1() + .text_ellipsis() + .text_xs() + .child(div().when_some( + panel.panel_metadata(cx), + |this, metadata| { + if let Some(picture) = metadata.picture { + this.flex_shrink_0().child( + img(format!( + "https://wsrv.nl/?url={}&w=100&h=100&n=-1", + picture + )) + .size_4() + .rounded_full() + .object_fit(ObjectFit::Cover), + ) + } else { + this.flex_shrink_0().child( + img("brand/avatar.png").size_4().rounded_full(), + ) + } + }, + )) + .child(panel.title(cx)), + ) .when(state.draggable, |this| { this.on_drag( DragPanel { @@ -556,7 +589,7 @@ impl TabPanel { active = false; } - Tab::new(("tab", ix), panel.title(cx)) + Tab::new(("tab", ix), panel.title(cx), panel.panel_metadata(cx)) .py_2() .selected(active) .disabled(disabled) diff --git a/crates/ui/src/dock/tiles.rs b/crates/ui/src/dock/tiles.rs index 08a6062..ff6550c 100644 --- a/crates/ui/src/dock/tiles.rs +++ b/crates/ui/src/dock/tiles.rs @@ -86,7 +86,7 @@ pub struct Tiles { } impl Panel for Tiles { - fn panel_name(&self) -> SharedString { + fn panel_id(&self) -> SharedString { "Tiles".into() } @@ -111,7 +111,7 @@ impl Panel for Tiles { .collect(); let mut state = PanelState::new(self); - state.panel_name = self.panel_name().to_string(); + state.panel_name = self.panel_id().to_string(); state.children = panels; state.info = PanelInfo::Tiles { metas }; state diff --git a/crates/ui/src/tab/tab.rs b/crates/ui/src/tab/tab.rs index 89ea53a..44e2e65 100644 --- a/crates/ui/src/tab/tab.rs +++ b/crates/ui/src/tab/tab.rs @@ -1,16 +1,16 @@ +use gpui::*; +use nostr_sdk::prelude::*; +use prelude::FluentBuilder as _; + use crate::theme::ActiveTheme; use crate::Selectable; -use gpui::prelude::FluentBuilder as _; -use gpui::{ - div, px, AnyElement, Div, ElementId, InteractiveElement, IntoElement, ParentElement as _, - RenderOnce, Stateful, StatefulInteractiveElement, Styled, WindowContext, -}; #[derive(IntoElement)] pub struct Tab { id: ElementId, base: Stateful
, label: AnyElement, + metadata: Option, prefix: Option, suffix: Option, disabled: bool, @@ -18,12 +18,18 @@ pub struct Tab { } impl Tab { - pub fn new(id: impl Into, label: impl IntoElement) -> Self { + pub fn new( + id: impl Into, + label: impl IntoElement, + metadata: Option, + ) -> Self { let id: ElementId = id.into(); + Self { id: id.clone(), base: div().id(id).gap_1().py_1p5().px_3().h(px(30.)), label: label.into_any_element(), + metadata, disabled: false, selected: false, prefix: None, @@ -100,7 +106,28 @@ impl RenderOnce for Tab { .when_some(self.prefix, |this, prefix| { this.child(prefix).text_color(text_color) }) - .child(div().text_ellipsis().child(self.label)) + .child( + div() + .flex() + .items_center() + .gap_1() + .text_ellipsis() + .text_xs() + .child(div().when_some(self.metadata, |this, metadata| { + if let Some(picture) = metadata.picture { + this.flex_shrink_0().child( + img(format!("https://wsrv.nl/?url={}&w=100&h=100&n=-1", picture)) + .size_4() + .rounded_full() + .object_fit(ObjectFit::Cover), + ) + } else { + this.flex_shrink_0() + .child(img("brand/avatar.png").size_4().rounded_full()) + } + })) + .child(self.label), + ) .when_some(self.suffix, |this, suffix| this.child(suffix)) } }