mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-27 10:59:53 +00:00
Add stories for collab notifications (#3967)
This PR adds some basic stories for collab notifications to make them easier to work on: <img width="1076" alt="Screenshot 2024-01-08 at 9 43 39 PM" src="https://github.com/zed-industries/zed/assets/1486634/4a0adcfa-1134-49c2-b589-74ac1d52af4c"> I factored out a `CollabNotification` component that defines the general structure for one of these notifications, and this is the component that we use in the stories, with representative values passed to it to simulate the different instances of the notification. We can't use the actual notification components in the stories due to their data dependencies. Release Notes: - N/A
This commit is contained in:
parent
c40a7f3445
commit
4afa5fb23e
10 changed files with 169 additions and 83 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1559,6 +1559,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"story",
|
||||||
"theme",
|
"theme",
|
||||||
"theme_selector",
|
"theme_selector",
|
||||||
"time",
|
"time",
|
||||||
|
@ -7447,6 +7448,7 @@ dependencies = [
|
||||||
"backtrace-on-stack-overflow",
|
"backtrace-on-stack-overflow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap 4.4.4",
|
"clap 4.4.4",
|
||||||
|
"collab_ui",
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
"editor",
|
"editor",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
|
|
|
@ -9,6 +9,8 @@ path = "src/collab_ui.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = []
|
||||||
|
stories = ["dep:story"]
|
||||||
test-support = [
|
test-support = [
|
||||||
"call/test-support",
|
"call/test-support",
|
||||||
"client/test-support",
|
"client/test-support",
|
||||||
|
@ -44,6 +46,7 @@ project = { path = "../project" }
|
||||||
recent_projects = { path = "../recent_projects" }
|
recent_projects = { path = "../recent_projects" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { path = "../rpc" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
|
story = { path = "../story", optional = true }
|
||||||
feature_flags = { path = "../feature_flags"}
|
feature_flags = { path = "../feature_flags"}
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
theme_selector = { path = "../theme_selector" }
|
theme_selector = { path = "../theme_selector" }
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
|
mod collab_notification;
|
||||||
|
pub mod incoming_call_notification;
|
||||||
|
pub mod project_shared_notification;
|
||||||
|
|
||||||
|
#[cfg(feature = "stories")]
|
||||||
|
mod stories;
|
||||||
|
|
||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
|
||||||
pub mod incoming_call_notification;
|
#[cfg(feature = "stories")]
|
||||||
pub mod project_shared_notification;
|
pub use stories::*;
|
||||||
|
|
||||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
incoming_call_notification::init(app_state, cx);
|
incoming_call_notification::init(app_state, cx);
|
||||||
|
|
52
crates/collab_ui/src/notifications/collab_notification.rs
Normal file
52
crates/collab_ui/src/notifications/collab_notification.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use gpui::{img, prelude::*, AnyElement};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use ui::prelude::*;
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct CollabNotification {
|
||||||
|
avatar_uri: SharedString,
|
||||||
|
accept_button: Button,
|
||||||
|
dismiss_button: Button,
|
||||||
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CollabNotification {
|
||||||
|
pub fn new(
|
||||||
|
avatar_uri: impl Into<SharedString>,
|
||||||
|
accept_button: Button,
|
||||||
|
dismiss_button: Button,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
avatar_uri: avatar_uri.into(),
|
||||||
|
accept_button,
|
||||||
|
dismiss_button,
|
||||||
|
children: SmallVec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParentElement for CollabNotification {
|
||||||
|
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||||
|
&mut self.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for CollabNotification {
|
||||||
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
|
h_stack()
|
||||||
|
.text_ui()
|
||||||
|
.justify_between()
|
||||||
|
.size_full()
|
||||||
|
.overflow_hidden()
|
||||||
|
.elevation_3(cx)
|
||||||
|
.p_2()
|
||||||
|
.gap_2()
|
||||||
|
.child(img(self.avatar_uri).w_12().h_12().rounded_full())
|
||||||
|
.child(v_stack().overflow_hidden().children(self.children))
|
||||||
|
.child(
|
||||||
|
v_stack()
|
||||||
|
.child(self.accept_button)
|
||||||
|
.child(self.dismiss_button),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,12 @@
|
||||||
use crate::notification_window_options;
|
use crate::notification_window_options;
|
||||||
|
use crate::notifications::collab_notification::CollabNotification;
|
||||||
use call::{ActiveCall, IncomingCall};
|
use call::{ActiveCall, IncomingCall};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{prelude::*, AppContext, WindowHandle};
|
||||||
img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext,
|
|
||||||
VisualContext as _, WindowHandle,
|
|
||||||
};
|
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, Button, Label};
|
||||||
use ui::{h_stack, v_stack, Button, Label};
|
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
|
||||||
|
@ -31,8 +28,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
if let Some(incoming_call) = incoming_call {
|
if let Some(incoming_call) = incoming_call {
|
||||||
let unique_screens = cx.update(|cx| cx.displays()).unwrap();
|
let unique_screens = cx.update(|cx| cx.displays()).unwrap();
|
||||||
let window_size = gpui::Size {
|
let window_size = gpui::Size {
|
||||||
width: px(380.),
|
width: px(400.),
|
||||||
height: px(64.),
|
height: px(72.),
|
||||||
};
|
};
|
||||||
|
|
||||||
for screen in unique_screens {
|
for screen in unique_screens {
|
||||||
|
@ -129,35 +126,22 @@ impl Render for IncomingCallNotification {
|
||||||
|
|
||||||
cx.set_rem_size(ui_font_size);
|
cx.set_rem_size(ui_font_size);
|
||||||
|
|
||||||
h_stack()
|
div().size_full().font(ui_font).child(
|
||||||
.font(ui_font)
|
CollabNotification::new(
|
||||||
.text_ui()
|
self.state.call.calling_user.avatar_uri.clone(),
|
||||||
.justify_between()
|
Button::new("accept", "Accept").on_click({
|
||||||
.size_full()
|
let state = self.state.clone();
|
||||||
.overflow_hidden()
|
move |_, cx| state.respond(true, cx)
|
||||||
.elevation_3(cx)
|
}),
|
||||||
.p_2()
|
Button::new("decline", "Decline").on_click({
|
||||||
.gap_2()
|
let state = self.state.clone();
|
||||||
.child(
|
move |_, cx| state.respond(false, cx)
|
||||||
img(self.state.call.calling_user.avatar_uri.clone())
|
}),
|
||||||
.w_12()
|
|
||||||
.h_12()
|
|
||||||
.rounded_full(),
|
|
||||||
)
|
)
|
||||||
.child(v_stack().overflow_hidden().child(Label::new(format!(
|
.child(v_stack().overflow_hidden().child(Label::new(format!(
|
||||||
"{} is sharing a project in Zed",
|
"{} is sharing a project in Zed",
|
||||||
self.state.call.calling_user.github_login
|
self.state.call.calling_user.github_login
|
||||||
))))
|
)))),
|
||||||
.child(
|
)
|
||||||
v_stack()
|
|
||||||
.child(Button::new("accept", "Accept").render(cx).on_click({
|
|
||||||
let state = self.state.clone();
|
|
||||||
move |_, cx| state.respond(true, cx)
|
|
||||||
}))
|
|
||||||
.child(Button::new("decline", "Decline").render(cx).on_click({
|
|
||||||
let state = self.state.clone();
|
|
||||||
move |_, cx| state.respond(false, cx)
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use crate::notification_window_options;
|
use crate::notification_window_options;
|
||||||
|
use crate::notifications::collab_notification::CollabNotification;
|
||||||
use call::{room, ActiveCall};
|
use call::{room, ActiveCall};
|
||||||
use client::User;
|
use client::User;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext};
|
use gpui::{AppContext, Size};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{h_stack, prelude::*, v_stack, Button, Label};
|
use ui::{prelude::*, Button, Label};
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
|
||||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
|
@ -130,51 +131,30 @@ impl Render for ProjectSharedNotification {
|
||||||
|
|
||||||
cx.set_rem_size(ui_font_size);
|
cx.set_rem_size(ui_font_size);
|
||||||
|
|
||||||
h_stack()
|
div().size_full().font(ui_font).child(
|
||||||
.font(ui_font)
|
CollabNotification::new(
|
||||||
.text_ui()
|
self.owner.avatar_uri.clone(),
|
||||||
.justify_between()
|
Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
|
||||||
.size_full()
|
this.join(cx);
|
||||||
.overflow_hidden()
|
})),
|
||||||
.elevation_3(cx)
|
Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| {
|
||||||
.p_2()
|
this.dismiss(cx);
|
||||||
.gap_2()
|
})),
|
||||||
.child(
|
|
||||||
img(self.owner.avatar_uri.clone())
|
|
||||||
.w_12()
|
|
||||||
.h_12()
|
|
||||||
.rounded_full(),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
v_stack()
|
|
||||||
.overflow_hidden()
|
|
||||||
.child(Label::new(self.owner.github_login.clone()))
|
|
||||||
.child(Label::new(format!(
|
|
||||||
"is sharing a project in Zed{}",
|
|
||||||
if self.worktree_root_names.is_empty() {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
":"
|
|
||||||
}
|
|
||||||
)))
|
|
||||||
.children(if self.worktree_root_names.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(Label::new(self.worktree_root_names.join(", ")))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
v_stack()
|
|
||||||
.child(Button::new("open", "Open").on_click(cx.listener(
|
|
||||||
move |this, _event, cx| {
|
|
||||||
this.join(cx);
|
|
||||||
},
|
|
||||||
)))
|
|
||||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
|
||||||
move |this, _event, cx| {
|
|
||||||
this.dismiss(cx);
|
|
||||||
},
|
|
||||||
))),
|
|
||||||
)
|
)
|
||||||
|
.child(Label::new(self.owner.github_login.clone()))
|
||||||
|
.child(Label::new(format!(
|
||||||
|
"is sharing a project in Zed{}",
|
||||||
|
if self.worktree_root_names.is_empty() {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
":"
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
.children(if self.worktree_root_names.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Label::new(self.worktree_root_names.join(", ")))
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3
crates/collab_ui/src/notifications/stories.rs
Normal file
3
crates/collab_ui/src/notifications/stories.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
mod collab_notification;
|
||||||
|
|
||||||
|
pub use collab_notification::*;
|
|
@ -0,0 +1,50 @@
|
||||||
|
use gpui::prelude::*;
|
||||||
|
use story::{StoryContainer, StoryItem, StorySection};
|
||||||
|
use ui::prelude::*;
|
||||||
|
|
||||||
|
use crate::notifications::collab_notification::CollabNotification;
|
||||||
|
|
||||||
|
pub struct CollabNotificationStory;
|
||||||
|
|
||||||
|
impl Render for CollabNotificationStory {
|
||||||
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let window_container = |width, height| div().w(px(width)).h(px(height));
|
||||||
|
|
||||||
|
StoryContainer::new(
|
||||||
|
"CollabNotification Story",
|
||||||
|
"crates/collab_ui/src/notifications/stories/collab_notification.rs",
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
StorySection::new().child(StoryItem::new(
|
||||||
|
"Incoming Call Notification",
|
||||||
|
window_container(400., 72.).child(
|
||||||
|
CollabNotification::new(
|
||||||
|
"https://avatars.githubusercontent.com/u/1486634?v=4",
|
||||||
|
Button::new("accept", "Accept"),
|
||||||
|
Button::new("decline", "Decline"),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_stack()
|
||||||
|
.overflow_hidden()
|
||||||
|
.child(Label::new("maxdeviant is sharing a project in Zed")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
StorySection::new().child(StoryItem::new(
|
||||||
|
"Project Shared Notification",
|
||||||
|
window_container(400., 72.).child(
|
||||||
|
CollabNotification::new(
|
||||||
|
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||||
|
Button::new("open", "Open"),
|
||||||
|
Button::new("dismiss", "Dismiss"),
|
||||||
|
)
|
||||||
|
.child(Label::new("iamnbutler"))
|
||||||
|
.child(Label::new("is sharing a project in Zed:"))
|
||||||
|
.child(Label::new("zed")),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ anyhow.workspace = true
|
||||||
backtrace-on-stack-overflow = "0.3.0"
|
backtrace-on-stack-overflow = "0.3.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = { version = "4.4", features = ["derive", "string"] }
|
clap = { version = "4.4", features = ["derive", "string"] }
|
||||||
|
collab_ui = { path = "../collab_ui", features = ["stories"] }
|
||||||
strum = { version = "0.25.0", features = ["derive"] }
|
strum = { version = "0.25.0", features = ["derive"] }
|
||||||
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
|
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub enum ComponentStory {
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
CollabNotification,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
Cursor,
|
Cursor,
|
||||||
Disclosure,
|
Disclosure,
|
||||||
|
@ -45,6 +46,9 @@ impl ComponentStory {
|
||||||
Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(),
|
Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(),
|
||||||
Self::Button => cx.new_view(|_| ui::ButtonStory).into(),
|
Self::Button => cx.new_view(|_| ui::ButtonStory).into(),
|
||||||
Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(),
|
Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(),
|
||||||
|
Self::CollabNotification => cx
|
||||||
|
.new_view(|_| collab_ui::notifications::CollabNotificationStory)
|
||||||
|
.into(),
|
||||||
Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(),
|
Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(),
|
||||||
Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(),
|
Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(),
|
||||||
Self::Disclosure => cx.new_view(|_| ui::DisclosureStory).into(),
|
Self::Disclosure => cx.new_view(|_| ui::DisclosureStory).into(),
|
||||||
|
|
Loading…
Reference in a new issue