diff --git a/assets/icons/ellipsis.svg b/assets/icons/ellipsis.svg
new file mode 100644
index 0000000..05eec51
--- /dev/null
+++ b/assets/icons/ellipsis.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/panel-left-open.svg b/assets/icons/panel-left-open.svg
new file mode 100644
index 0000000..a4914be
--- /dev/null
+++ b/assets/icons/panel-left-open.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/panel-left.svg b/assets/icons/panel-left.svg
new file mode 100644
index 0000000..a4914be
--- /dev/null
+++ b/assets/icons/panel-left.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml
index f23ba3a..2af1b2f 100644
--- a/crates/ui/Cargo.toml
+++ b/crates/ui/Cargo.toml
@@ -11,6 +11,8 @@ path = "src/main.rs"
[dependencies]
gpui.workspace = true
components.workspace = true
+reqwest_client.workspace = true
+
tokio.workspace = true
nostr-sdk.workspace = true
keyring-search.workspace = true
@@ -18,7 +20,6 @@ keyring.workspace = true
anyhow.workspace = true
serde.workspace = true
serde_json.workspace = true
-reqwest_client.workspace = true
client = { version = "0.1.0", path = "../client" }
rust-embed = "8.5.0"
diff --git a/crates/ui/src/views/app.rs b/crates/ui/src/views/app.rs
index 3caddea..1d9c3cd 100644
--- a/crates/ui/src/views/app.rs
+++ b/crates/ui/src/views/app.rs
@@ -1,12 +1,30 @@
-use components::theme::{ActiveTheme, Theme};
+use std::sync::Arc;
+
+use components::{
+ dock::{DockArea, DockItem},
+ theme::{ActiveTheme, Theme},
+};
use gpui::*;
-use super::{chat_space::ChatSpace, onboarding::Onboarding};
+use super::{
+ block::{welcome::WelcomeBlock, BlockContainer},
+ onboarding::Onboarding,
+};
use crate::state::AppState;
+pub struct DockAreaTab {
+ id: &'static str,
+ version: usize,
+}
+
+pub const DOCK_AREA: DockAreaTab = DockAreaTab {
+ id: "dock",
+ version: 1,
+};
+
pub struct AppView {
onboarding: View,
- chat_space: View,
+ dock_area: View,
}
impl AppView {
@@ -20,14 +38,50 @@ impl AppView {
// Onboarding
let onboarding = cx.new_view(Onboarding::new);
- // Chat Space
- let chat_space = cx.new_view(ChatSpace::new);
+ // Dock
+ let dock_area = cx.new_view(|cx| DockArea::new(DOCK_AREA.id, Some(DOCK_AREA.version), cx));
+ let weak_dock_area = dock_area.downgrade();
+
+ // Set dock layout
+ Self::init_layout(weak_dock_area, cx);
AppView {
onboarding,
- chat_space,
+ dock_area,
}
}
+
+ fn init_layout(dock_area: WeakView, cx: &mut WindowContext) {
+ let dock_item = Self::init_dock_items(&dock_area, cx);
+ let left_panels =
+ DockItem::split_with_sizes(Axis::Vertical, vec![], vec![None, None], &dock_area, cx);
+
+ _ = dock_area.update(cx, |view, cx| {
+ view.set_version(DOCK_AREA.version, cx);
+ view.set_left_dock(left_panels, Some(px(260.)), true, cx);
+ view.set_root(dock_item, cx);
+ // TODO: support right dock?
+ // TODO: support bottom dock?
+ });
+ }
+
+ fn init_dock_items(dock_area: &WeakView, cx: &mut WindowContext) -> DockItem {
+ DockItem::split_with_sizes(
+ Axis::Vertical,
+ vec![DockItem::tabs(
+ vec![
+ Arc::new(BlockContainer::panel::(cx)),
+ // TODO: add chat block
+ ],
+ None,
+ dock_area,
+ cx,
+ )],
+ vec![None],
+ dock_area,
+ cx,
+ )
+ }
}
impl Render for AppView {
@@ -37,7 +91,7 @@ impl Render for AppView {
if cx.global::().signer.is_none() {
content = content.child(self.onboarding.clone())
} else {
- content = content.child(self.chat_space.clone())
+ content = content.child(self.dock_area.clone())
}
div()
diff --git a/crates/ui/src/views/block/mod.rs b/crates/ui/src/views/block/mod.rs
new file mode 100644
index 0000000..e464beb
--- /dev/null
+++ b/crates/ui/src/views/block/mod.rs
@@ -0,0 +1,190 @@
+use components::{
+ button::Button,
+ dock::{DockItemState, Panel, PanelEvent, TitleStyle},
+ h_flex,
+ popup_menu::PopupMenu,
+ theme::ActiveTheme,
+ v_flex,
+};
+use gpui::*;
+use prelude::FluentBuilder;
+
+pub mod welcome;
+
+actions!(block, [PanelInfo]);
+
+pub fn section(title: impl IntoElement, cx: &WindowContext) -> Div {
+ h_flex()
+ .items_center()
+ .gap_4()
+ .p_4()
+ .w_full()
+ .rounded_lg()
+ .border_1()
+ .border_color(cx.theme().border)
+ .flex_wrap()
+ .justify_around()
+ .child(div().flex_none().w_full().child(title))
+}
+
+pub struct BlockContainer {
+ focus_handle: FocusHandle,
+ name: SharedString,
+ title_bg: Option,
+ description: SharedString,
+ width: Option,
+ height: Option,
+ block: Option,
+ closeable: bool,
+ zoomable: bool,
+}
+
+#[derive(Debug)]
+pub enum ContainerEvent {
+ Close,
+}
+
+pub trait Block: FocusableView {
+ fn klass() -> &'static str {
+ std::any::type_name::().split("::").last().unwrap()
+ }
+
+ fn title() -> &'static str;
+
+ fn description() -> &'static str {
+ ""
+ }
+
+ fn closeable() -> bool {
+ true
+ }
+
+ fn zoomable() -> bool {
+ true
+ }
+
+ fn title_bg() -> Option {
+ None
+ }
+
+ fn new_view(cx: &mut WindowContext) -> View;
+}
+
+impl EventEmitter for BlockContainer {}
+
+impl BlockContainer {
+ pub fn new(cx: &mut WindowContext) -> Self {
+ let focus_handle = cx.focus_handle();
+
+ Self {
+ focus_handle,
+ name: "".into(),
+ title_bg: None,
+ description: "".into(),
+ width: None,
+ height: None,
+ block: None,
+ closeable: true,
+ zoomable: true,
+ }
+ }
+
+ pub fn panel(cx: &mut WindowContext) -> View {
+ let name = B::title();
+ let description = B::description();
+ let block = B::new_view(cx);
+ let focus_handle = block.focus_handle(cx);
+
+ cx.new_view(|cx| {
+ let mut story = Self::new(cx).block(block.into());
+
+ story.focus_handle = focus_handle;
+ story.closeable = B::closeable();
+ story.zoomable = B::zoomable();
+ story.name = name.into();
+ story.description = description.into();
+ story.title_bg = B::title_bg();
+
+ story
+ })
+ }
+
+ pub fn width(mut self, width: gpui::Pixels) -> Self {
+ self.width = Some(width);
+ self
+ }
+
+ pub fn height(mut self, height: gpui::Pixels) -> Self {
+ self.height = Some(height);
+ self
+ }
+
+ pub fn block(mut self, block: AnyView) -> Self {
+ self.block = Some(block);
+ self
+ }
+
+ fn on_action_panel_info(&mut self, _: &PanelInfo, _cx: &mut ViewContext) {
+ // struct Info;
+ // let note = Notification::new(format!("You have clicked panel info on: {}", self.name)).id::();
+ // cx.push_notification(note);
+ }
+}
+
+impl Panel for BlockContainer {
+ fn panel_name(&self) -> &'static str {
+ "BlockContainer"
+ }
+
+ fn title(&self, _cx: &WindowContext) -> AnyElement {
+ self.name.clone().into_any_element()
+ }
+
+ fn title_style(&self, cx: &WindowContext) -> Option {
+ self.title_bg.map(|bg| TitleStyle {
+ background: bg,
+ foreground: cx.theme().foreground,
+ })
+ }
+
+ fn closeable(&self, _cx: &WindowContext) -> bool {
+ self.closeable
+ }
+
+ fn zoomable(&self, _cx: &WindowContext) -> bool {
+ self.zoomable
+ }
+
+ fn popup_menu(&self, menu: PopupMenu, _cx: &WindowContext) -> PopupMenu {
+ menu.track_focus(&self.focus_handle)
+ .menu("Info", Box::new(PanelInfo))
+ }
+
+ fn toolbar_buttons(&self, _cx: &WindowContext) -> Vec