Make a single re-usable banner component (#27412)
Release Notes: - Fixed an issue where both the predict edit and git onboarding banners would both show at the same time.
This commit is contained in:
parent
b85492bd00
commit
35ec4753b4
14 changed files with 181 additions and 294 deletions
|
@ -29,7 +29,9 @@ test-support = [
|
|||
[dependencies]
|
||||
auto_update.workspace = true
|
||||
call.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
db.workspace = true
|
||||
feature_flags.workspace = true
|
||||
gpui.workspace = true
|
||||
notifications.workspace = true
|
||||
|
@ -47,8 +49,6 @@ ui.workspace = true
|
|||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
zeta.workspace = true
|
||||
git_ui.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
|
|
132
crates/title_bar/src/banner.rs
Normal file
132
crates/title_bar/src/banner.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
use gpui::{Action, Entity, Global, Render};
|
||||
use ui::{prelude::*, ButtonLike, Tooltip};
|
||||
use util::ResultExt;
|
||||
|
||||
/// Prompts the user to try Zed's features
|
||||
pub struct Banner {
|
||||
dismissed: bool,
|
||||
source: String,
|
||||
details: BannerDetails,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct BannerGlobal {
|
||||
entity: Entity<Banner>,
|
||||
}
|
||||
impl Global for BannerGlobal {}
|
||||
|
||||
pub struct BannerDetails {
|
||||
pub action: Box<dyn Action>,
|
||||
pub banner_label: Box<dyn Fn(&Window, &mut Context<Banner>) -> AnyElement>,
|
||||
}
|
||||
|
||||
impl Banner {
|
||||
pub fn new(source: &str, details: BannerDetails, cx: &mut Context<Self>) -> Self {
|
||||
cx.set_global(BannerGlobal {
|
||||
entity: cx.entity(),
|
||||
});
|
||||
Self {
|
||||
source: source.to_string(),
|
||||
details,
|
||||
dismissed: get_dismissed(source),
|
||||
}
|
||||
}
|
||||
|
||||
fn should_show(&self, _cx: &mut App) -> bool {
|
||||
!self.dismissed
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, cx: &mut Context<Self>) {
|
||||
telemetry::event!("Banner Dismissed", source = self.source);
|
||||
persist_dismissed(&self.source, cx);
|
||||
self.dismissed = true;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed_at_key(source: &str) -> String {
|
||||
format!(
|
||||
"{}_{}",
|
||||
"_banner_dismissed_at",
|
||||
source.to_lowercase().trim().replace(" ", "_")
|
||||
)
|
||||
}
|
||||
|
||||
fn get_dismissed(source: &str) -> bool {
|
||||
let dismissed_at = if source == "Git Onboarding" {
|
||||
"zed_git_banner_dismissed_at".to_string()
|
||||
} else {
|
||||
dismissed_at_key(source)
|
||||
};
|
||||
db::kvp::KEY_VALUE_STORE
|
||||
.read_kvp(&dismissed_at)
|
||||
.log_err()
|
||||
.map_or(false, |dismissed| dismissed.is_some())
|
||||
}
|
||||
|
||||
fn persist_dismissed(source: &str, cx: &mut App) {
|
||||
let dismissed_at = dismissed_at_key(source);
|
||||
cx.spawn(async |_| {
|
||||
let time = chrono::Utc::now().to_rfc3339();
|
||||
db::kvp::KEY_VALUE_STORE.write_kvp(dismissed_at, time).await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn restore_banner(cx: &mut App) {
|
||||
cx.defer(|cx| {
|
||||
cx.global::<BannerGlobal>()
|
||||
.entity
|
||||
.clone()
|
||||
.update(cx, |this, cx| {
|
||||
this.dismissed = false;
|
||||
cx.notify();
|
||||
});
|
||||
});
|
||||
|
||||
let source = &cx.global::<BannerGlobal>().entity.read(cx).source;
|
||||
let dismissed_at = dismissed_at_key(source);
|
||||
cx.spawn(async |_| db::kvp::KEY_VALUE_STORE.delete_kvp(dismissed_at).await)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
impl Render for Banner {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
if !self.should_show(cx) {
|
||||
return div();
|
||||
}
|
||||
|
||||
let border_color = cx.theme().colors().editor_foreground.opacity(0.3);
|
||||
let banner = h_flex()
|
||||
.rounded_sm()
|
||||
.border_1()
|
||||
.border_color(border_color)
|
||||
.child(
|
||||
ButtonLike::new("try-a-feature")
|
||||
.child((self.details.banner_label)(window, cx))
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
telemetry::event!("Banner Clicked", source = this.source);
|
||||
this.dismiss(cx);
|
||||
window.dispatch_action(this.details.action.boxed_clone(), cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div().border_l_1().border_color(border_color).child(
|
||||
IconButton::new("close", IconName::Close)
|
||||
.icon_size(IconSize::Indicator)
|
||||
.on_click(cx.listener(|this, _, _window, cx| this.dismiss(cx)))
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Close Announcement Banner",
|
||||
None,
|
||||
"It won't show again for this feature",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
div().pr_2().child(banner)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod application_menu;
|
||||
mod banner;
|
||||
mod collab;
|
||||
mod platforms;
|
||||
mod window_controls;
|
||||
|
@ -15,10 +16,10 @@ use crate::application_menu::{
|
|||
|
||||
use crate::platforms::{platform_linux, platform_mac, platform_windows};
|
||||
use auto_update::AutoUpdateStatus;
|
||||
use banner::{Banner, BannerDetails};
|
||||
use call::ActiveCall;
|
||||
use client::{Client, UserStore};
|
||||
use feature_flags::{FeatureFlagAppExt, ZedPro};
|
||||
use git_ui::onboarding::GitBanner;
|
||||
use gpui::{
|
||||
actions, div, px, Action, AnyElement, App, Context, Decorations, Element, Entity,
|
||||
InteractiveElement, Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful,
|
||||
|
@ -37,7 +38,8 @@ use ui::{
|
|||
use util::ResultExt;
|
||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||
use zed_actions::{OpenBrowser, OpenRecent, OpenRemote};
|
||||
use zeta::ZedPredictBanner;
|
||||
|
||||
pub use banner::restore_banner;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
@ -126,8 +128,7 @@ pub struct TitleBar {
|
|||
should_move: bool,
|
||||
application_menu: Option<Entity<ApplicationMenu>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
zed_predict_banner: Entity<ZedPredictBanner>,
|
||||
git_banner: Entity<GitBanner>,
|
||||
banner: Entity<Banner>,
|
||||
}
|
||||
|
||||
impl Render for TitleBar {
|
||||
|
@ -211,8 +212,7 @@ impl Render for TitleBar {
|
|||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()),
|
||||
)
|
||||
.child(self.render_collaborator_list(window, cx))
|
||||
.child(self.zed_predict_banner.clone())
|
||||
.child(self.git_banner.clone())
|
||||
.child(self.banner.clone())
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
|
@ -315,8 +315,33 @@ impl TitleBar {
|
|||
subscriptions.push(cx.observe_window_activation(window, Self::window_activation_changed));
|
||||
subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
|
||||
|
||||
let zed_predict_banner = cx.new(ZedPredictBanner::new);
|
||||
let git_banner = cx.new(GitBanner::new);
|
||||
let banner = cx.new(|cx| {
|
||||
Banner::new(
|
||||
"Git Onboarding",
|
||||
BannerDetails {
|
||||
action: zed_actions::OpenGitIntegrationOnboarding.boxed_clone(),
|
||||
banner_label: Box::new(|_, _| {
|
||||
h_flex()
|
||||
.h_full()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::GitBranchSmall).size(IconSize::Small))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Label::new("Introducing:")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(Label::new("Git Support").size(LabelSize::Small)),
|
||||
)
|
||||
.into_any_element()
|
||||
}),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
Self {
|
||||
platform_style,
|
||||
|
@ -329,8 +354,7 @@ impl TitleBar {
|
|||
user_store,
|
||||
client,
|
||||
_subscriptions: subscriptions,
|
||||
zed_predict_banner,
|
||||
git_banner,
|
||||
banner,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue