Cargo.lock 🔗
@@ -7818,6 +7818,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"backtrace-on-stack-overflow",
+ "chrono",
"clap 4.4.4",
"gpui3",
"itertools 0.11.0",
Marshall Bowers created
Cargo.lock | 1
crates/storybook2/Cargo.toml | 1
crates/storybook2/src/stories/components.rs | 1
crates/storybook2/src/stories/components/chat_panel.rs | 56 ++++++
crates/storybook2/src/story_selector.rs | 2
crates/ui2/src/components.rs | 2
crates/ui2/src/components/chat_panel.rs | 108 ++++++++++++
crates/ui2/src/components/workspace.rs | 67 +++----
8 files changed, 199 insertions(+), 39 deletions(-)
@@ -7818,6 +7818,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"backtrace-on-stack-overflow",
+ "chrono",
"clap 4.4.4",
"gpui3",
"itertools 0.11.0",
@@ -13,6 +13,7 @@ anyhow.workspace = true
# TODO: Remove after diagnosing stack overflow.
backtrace-on-stack-overflow = "0.3.0"
clap = { version = "4.4", features = ["derive", "string"] }
+chrono = "0.4"
gpui3 = { path = "../gpui3" }
itertools = "0.11.0"
log.workspace = true
@@ -1,6 +1,7 @@
pub mod assistant_panel;
pub mod breadcrumb;
pub mod buffer;
+pub mod chat_panel;
pub mod panel;
pub mod project_panel;
pub mod tab;
@@ -0,0 +1,56 @@
+use std::marker::PhantomData;
+
+use chrono::DateTime;
+use ui::prelude::*;
+use ui::{ChatMessage, ChatPanel, Panel};
+
+use crate::story::Story;
+
+#[derive(Element)]
+pub struct ChatPanelStory<S: 'static + Send + Sync + Clone> {
+ state_type: PhantomData<S>,
+}
+
+impl<S: 'static + Send + Sync + Clone> ChatPanelStory<S> {
+ pub fn new() -> Self {
+ Self {
+ state_type: PhantomData,
+ }
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+ Story::container(cx)
+ .child(Story::title_for::<_, ChatPanel<S>>(cx))
+ .child(Story::label(cx, "Default"))
+ .child(Panel::new(
+ ScrollState::default(),
+ |_, _| vec![ChatPanel::new(ScrollState::default()).into_any()],
+ Box::new(()),
+ ))
+ .child(Story::label(cx, "With Mesages"))
+ .child(Panel::new(
+ ScrollState::default(),
+ |_, _| {
+ vec![ChatPanel::new(ScrollState::default())
+ .with_messages(vec![
+ ChatMessage::new(
+ "osiewicz".to_string(),
+ "is this thing on?".to_string(),
+ DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
+ .unwrap()
+ .naive_local(),
+ ),
+ ChatMessage::new(
+ "maxdeviant".to_string(),
+ "Reading you loud and clear!".to_string(),
+ DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
+ .unwrap()
+ .naive_local(),
+ ),
+ ])
+ .into_any()]
+ },
+ Box::new(()),
+ ))
+ }
+}
@@ -39,6 +39,7 @@ pub enum ComponentStory {
AssistantPanel,
Breadcrumb,
Buffer,
+ ChatPanel,
Panel,
ProjectPanel,
Tab,
@@ -58,6 +59,7 @@ impl ComponentStory {
}
Self::Buffer => components::buffer::BufferStory::new().into_any(),
Self::Breadcrumb => components::breadcrumb::BreadcrumbStory::new().into_any(),
+ Self::ChatPanel => components::chat_panel::ChatPanelStory::new().into_any(),
Self::Panel => components::panel::PanelStory::new().into_any(),
Self::ProjectPanel => components::project_panel::ProjectPanelStory::new().into_any(),
Self::Tab => components::tab::TabStory::new().into_any(),
@@ -1,6 +1,7 @@
mod assistant_panel;
mod breadcrumb;
mod buffer;
+mod chat_panel;
mod editor_pane;
mod icon_button;
mod list;
@@ -17,6 +18,7 @@ mod workspace;
pub use assistant_panel::*;
pub use breadcrumb::*;
pub use buffer::*;
+pub use chat_panel::*;
pub use editor_pane::*;
pub use icon_button::*;
pub use list::*;
@@ -0,0 +1,108 @@
+use std::marker::PhantomData;
+
+use chrono::NaiveDateTime;
+
+use crate::prelude::*;
+use crate::theme::theme;
+use crate::{Icon, IconButton, Input, Label, LabelColor};
+
+#[derive(Element)]
+pub struct ChatPanel<S: 'static + Send + Sync + Clone> {
+ scroll_state: ScrollState,
+ messages: Vec<ChatMessage<S>>,
+}
+
+impl<S: 'static + Send + Sync + Clone> ChatPanel<S> {
+ pub fn new(scroll_state: ScrollState) -> Self {
+ Self {
+ scroll_state,
+ messages: Vec::new(),
+ }
+ }
+
+ pub fn with_messages(mut self, messages: Vec<ChatMessage<S>>) -> Self {
+ self.messages = messages;
+ self
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+ let theme = theme(cx);
+
+ div()
+ .flex()
+ .flex_col()
+ .justify_between()
+ .h_full()
+ .px_2()
+ .gap_2()
+ // Header
+ .child(
+ div()
+ .flex()
+ .justify_between()
+ .py_2()
+ .child(div().flex().child(Label::new("#design")))
+ .child(
+ div()
+ .flex()
+ .items_center()
+ .gap_px()
+ .child(IconButton::new(Icon::File))
+ .child(IconButton::new(Icon::AudioOn)),
+ ),
+ )
+ .child(
+ div()
+ .flex()
+ .flex_col()
+ // Chat Body
+ .child(
+ div()
+ .w_full()
+ .flex()
+ .flex_col()
+ .gap_3()
+ .overflow_y_scroll(self.scroll_state.clone())
+ .children(self.messages.clone()),
+ )
+ // Composer
+ .child(div().flex().my_2().child(Input::new("Message #design"))),
+ )
+ }
+}
+
+#[derive(Element, Clone)]
+pub struct ChatMessage<S: 'static + Send + Sync + Clone> {
+ state_type: PhantomData<S>,
+ author: String,
+ text: String,
+ sent_at: NaiveDateTime,
+}
+
+impl<S: 'static + Send + Sync + Clone> ChatMessage<S> {
+ pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
+ Self {
+ state_type: PhantomData,
+ author,
+ text,
+ sent_at,
+ }
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+ div()
+ .flex()
+ .flex_col()
+ .child(
+ div()
+ .flex()
+ .gap_2()
+ .child(Label::new(self.author.clone()))
+ .child(
+ Label::new(self.sent_at.format("%m/%d/%Y").to_string())
+ .color(LabelColor::Muted),
+ ),
+ )
+ .child(div().child(Label::new(self.text.clone())))
+ }
+}
@@ -6,8 +6,9 @@ use gpui3::{relative, rems, Size};
use crate::prelude::*;
use crate::{
- hello_world_rust_editor_with_status_example, theme, v_stack, EditorPane, Pane, PaneGroup,
- Panel, PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal,
+ hello_world_rust_editor_with_status_example, theme, v_stack, ChatMessage, ChatPanel,
+ EditorPane, Pane, PaneGroup, Panel, PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection,
+ StatusBar, Terminal,
};
#[derive(Element)]
@@ -139,11 +140,7 @@ impl<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
.child(
Panel::new(
self.bottom_panel_scroll_state.clone(),
- |_, _| {
- vec![
- // Terminal::new().into_any()
- ]
- },
+ |_, _| vec![Terminal::new().into_any()],
Box::new(()),
)
.allowed_sides(PanelAllowedSides::BottomOnly)
@@ -153,42 +150,34 @@ impl<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
.child(
Panel::new(
self.right_panel_scroll_state.clone(),
- |_, payload| vec![ProjectPanel::new(ScrollState::default()).into_any()],
+ |_, payload| {
+ vec![ChatPanel::new(ScrollState::default())
+ .with_messages(vec![
+ ChatMessage::new(
+ "osiewicz".to_string(),
+ "is this thing on?".to_string(),
+ DateTime::parse_from_rfc3339(
+ "2023-09-27T15:40:52.707Z",
+ )
+ .unwrap()
+ .naive_local(),
+ ),
+ ChatMessage::new(
+ "maxdeviant".to_string(),
+ "Reading you loud and clear!".to_string(),
+ DateTime::parse_from_rfc3339(
+ "2023-09-28T15:40:52.707Z",
+ )
+ .unwrap()
+ .naive_local(),
+ ),
+ ])
+ .into_any()]
+ },
Box::new(()),
)
.side(PanelSide::Right),
),
- // .child(
- // Panel::new(
- // self.right_panel_scroll_state.clone(),
- // |_, payload| {
- // vec![ChatPanel::new(ScrollState::default())
- // .with_messages(vec![
- // ChatMessage::new(
- // "osiewicz".to_string(),
- // "is this thing on?".to_string(),
- // DateTime::parse_from_rfc3339(
- // "2023-09-27T15:40:52.707Z",
- // )
- // .unwrap()
- // .naive_local(),
- // ),
- // ChatMessage::new(
- // "maxdeviant".to_string(),
- // "Reading you loud and clear!".to_string(),
- // DateTime::parse_from_rfc3339(
- // "2023-09-28T15:40:52.707Z",
- // )
- // .unwrap()
- // .naive_local(),
- // ),
- // ])
- // .into_any()]
- // },
- // Box::new(()),
- // )
- // .side(PanelSide::Right),
- // ),
)
.child(StatusBar::new())
// An example of a toast is below