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
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -5636,7 +5636,6 @@ dependencies = [
|
||||||
"askpass",
|
"askpass",
|
||||||
"assistant_settings",
|
"assistant_settings",
|
||||||
"buffer_diff",
|
"buffer_diff",
|
||||||
"chrono",
|
|
||||||
"collections",
|
"collections",
|
||||||
"command_palette_hooks",
|
"command_palette_hooks",
|
||||||
"component",
|
"component",
|
||||||
|
@ -14195,10 +14194,11 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"auto_update",
|
"auto_update",
|
||||||
"call",
|
"call",
|
||||||
|
"chrono",
|
||||||
"client",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
|
"db",
|
||||||
"feature_flags",
|
"feature_flags",
|
||||||
"git_ui",
|
|
||||||
"gpui",
|
"gpui",
|
||||||
"http_client",
|
"http_client",
|
||||||
"notifications",
|
"notifications",
|
||||||
|
@ -14219,7 +14219,6 @@ dependencies = [
|
||||||
"windows 0.61.1",
|
"windows 0.61.1",
|
||||||
"workspace",
|
"workspace",
|
||||||
"zed_actions",
|
"zed_actions",
|
||||||
"zeta",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -17371,6 +17370,7 @@ dependencies = [
|
||||||
"theme_extension",
|
"theme_extension",
|
||||||
"theme_selector",
|
"theme_selector",
|
||||||
"time",
|
"time",
|
||||||
|
"title_bar",
|
||||||
"toolchain_selector",
|
"toolchain_selector",
|
||||||
"tree-sitter-md",
|
"tree-sitter-md",
|
||||||
"tree-sitter-rust",
|
"tree-sitter-rust",
|
||||||
|
@ -17629,7 +17629,6 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"call",
|
"call",
|
||||||
"chrono",
|
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
|
|
|
@ -21,7 +21,6 @@ anyhow.workspace = true
|
||||||
askpass.workspace = true
|
askpass.workspace = true
|
||||||
assistant_settings.workspace = true
|
assistant_settings.workspace = true
|
||||||
buffer_diff.workspace = true
|
buffer_diff.workspace = true
|
||||||
chrono.workspace = true
|
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
command_palette_hooks.workspace = true
|
command_palette_hooks.workspace = true
|
||||||
component.workspace = true
|
component.workspace = true
|
||||||
|
|
|
@ -9,7 +9,7 @@ use git::{
|
||||||
};
|
};
|
||||||
use git_panel_settings::GitPanelSettings;
|
use git_panel_settings::GitPanelSettings;
|
||||||
use gpui::{actions, App, FocusHandle};
|
use gpui::{actions, App, FocusHandle};
|
||||||
use onboarding::{clear_dismissed, GitOnboardingModal};
|
use onboarding::GitOnboardingModal;
|
||||||
use project_diff::ProjectDiff;
|
use project_diff::ProjectDiff;
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
@ -103,7 +103,7 @@ pub fn init(cx: &mut App) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
workspace.register_action(move |_, _: &ResetOnboarding, window, cx| {
|
workspace.register_action(move |_, _: &ResetOnboarding, window, cx| {
|
||||||
clear_dismissed(cx);
|
cx.dispatch_action(&workspace::RestoreBanner);
|
||||||
window.refresh();
|
window.refresh();
|
||||||
});
|
});
|
||||||
workspace.register_action(|workspace, _action: &git::Init, window, cx| {
|
workspace.register_action(|workspace, _action: &git::Init, window, cx| {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
svg, ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Global,
|
svg, ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent,
|
||||||
MouseDownEvent, Render,
|
Render,
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, ButtonLike, TintColor, Tooltip};
|
use ui::{prelude::*, TintColor};
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::{ModalView, Workspace};
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
use crate::git_panel::GitPanel;
|
use crate::git_panel::GitPanel;
|
||||||
|
@ -144,130 +143,3 @@ impl Render for GitOnboardingModal {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prompts the user to try Zed's git features
|
|
||||||
pub struct GitBanner {
|
|
||||||
dismissed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct GitBannerGlobal(Entity<GitBanner>);
|
|
||||||
impl Global for GitBannerGlobal {}
|
|
||||||
|
|
||||||
impl GitBanner {
|
|
||||||
pub fn new(cx: &mut Context<Self>) -> Self {
|
|
||||||
cx.set_global(GitBannerGlobal(cx.entity()));
|
|
||||||
Self {
|
|
||||||
dismissed: get_dismissed(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_show(&self, _cx: &mut App) -> bool {
|
|
||||||
!self.dismissed
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dismiss(&mut self, cx: &mut Context<Self>) {
|
|
||||||
git_onboarding_event!("Banner Dismissed");
|
|
||||||
persist_dismissed(cx);
|
|
||||||
self.dismissed = true;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const DISMISSED_AT_KEY: &str = "zed_git_banner_dismissed_at";
|
|
||||||
|
|
||||||
fn get_dismissed() -> bool {
|
|
||||||
db::kvp::KEY_VALUE_STORE
|
|
||||||
.read_kvp(DISMISSED_AT_KEY)
|
|
||||||
.log_err()
|
|
||||||
.map_or(false, |dismissed| dismissed.is_some())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn persist_dismissed(cx: &mut App) {
|
|
||||||
cx.spawn(async |_| {
|
|
||||||
let time = chrono::Utc::now().to_rfc3339();
|
|
||||||
db::kvp::KEY_VALUE_STORE
|
|
||||||
.write_kvp(DISMISSED_AT_KEY.into(), time)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn clear_dismissed(cx: &mut App) {
|
|
||||||
cx.defer(|cx| {
|
|
||||||
cx.global::<GitBannerGlobal>()
|
|
||||||
.clone()
|
|
||||||
.0
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.dismissed = false;
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.spawn(async |_| {
|
|
||||||
db::kvp::KEY_VALUE_STORE
|
|
||||||
.delete_kvp(DISMISSED_AT_KEY.into())
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for GitBanner {
|
|
||||||
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-git")
|
|
||||||
.child(
|
|
||||||
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)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
git_onboarding_event!("Banner Clicked");
|
|
||||||
this.dismiss(cx);
|
|
||||||
window.dispatch_action(
|
|
||||||
Box::new(zed_actions::OpenGitIntegrationOnboarding),
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -29,7 +29,9 @@ test-support = [
|
||||||
[dependencies]
|
[dependencies]
|
||||||
auto_update.workspace = true
|
auto_update.workspace = true
|
||||||
call.workspace = true
|
call.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
|
db.workspace = true
|
||||||
feature_flags.workspace = true
|
feature_flags.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
notifications.workspace = true
|
notifications.workspace = true
|
||||||
|
@ -47,8 +49,6 @@ ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
zed_actions.workspace = true
|
zed_actions.workspace = true
|
||||||
zeta.workspace = true
|
|
||||||
git_ui.workspace = true
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows.workspace = true
|
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 application_menu;
|
||||||
|
mod banner;
|
||||||
mod collab;
|
mod collab;
|
||||||
mod platforms;
|
mod platforms;
|
||||||
mod window_controls;
|
mod window_controls;
|
||||||
|
@ -15,10 +16,10 @@ use crate::application_menu::{
|
||||||
|
|
||||||
use crate::platforms::{platform_linux, platform_mac, platform_windows};
|
use crate::platforms::{platform_linux, platform_mac, platform_windows};
|
||||||
use auto_update::AutoUpdateStatus;
|
use auto_update::AutoUpdateStatus;
|
||||||
|
use banner::{Banner, BannerDetails};
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use client::{Client, UserStore};
|
use client::{Client, UserStore};
|
||||||
use feature_flags::{FeatureFlagAppExt, ZedPro};
|
use feature_flags::{FeatureFlagAppExt, ZedPro};
|
||||||
use git_ui::onboarding::GitBanner;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, px, Action, AnyElement, App, Context, Decorations, Element, Entity,
|
actions, div, px, Action, AnyElement, App, Context, Decorations, Element, Entity,
|
||||||
InteractiveElement, Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful,
|
InteractiveElement, Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful,
|
||||||
|
@ -37,7 +38,8 @@ use ui::{
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||||
use zed_actions::{OpenBrowser, OpenRecent, OpenRemote};
|
use zed_actions::{OpenBrowser, OpenRecent, OpenRemote};
|
||||||
use zeta::ZedPredictBanner;
|
|
||||||
|
pub use banner::restore_banner;
|
||||||
|
|
||||||
#[cfg(feature = "stories")]
|
#[cfg(feature = "stories")]
|
||||||
pub use stories::*;
|
pub use stories::*;
|
||||||
|
@ -126,8 +128,7 @@ pub struct TitleBar {
|
||||||
should_move: bool,
|
should_move: bool,
|
||||||
application_menu: Option<Entity<ApplicationMenu>>,
|
application_menu: Option<Entity<ApplicationMenu>>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
zed_predict_banner: Entity<ZedPredictBanner>,
|
banner: Entity<Banner>,
|
||||||
git_banner: Entity<GitBanner>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for TitleBar {
|
impl Render for TitleBar {
|
||||||
|
@ -211,8 +212,7 @@ impl Render for TitleBar {
|
||||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()),
|
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()),
|
||||||
)
|
)
|
||||||
.child(self.render_collaborator_list(window, cx))
|
.child(self.render_collaborator_list(window, cx))
|
||||||
.child(self.zed_predict_banner.clone())
|
.child(self.banner.clone())
|
||||||
.child(self.git_banner.clone())
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
@ -315,8 +315,33 @@ impl TitleBar {
|
||||||
subscriptions.push(cx.observe_window_activation(window, Self::window_activation_changed));
|
subscriptions.push(cx.observe_window_activation(window, Self::window_activation_changed));
|
||||||
subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
|
subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
|
||||||
|
|
||||||
let zed_predict_banner = cx.new(ZedPredictBanner::new);
|
let banner = cx.new(|cx| {
|
||||||
let git_banner = cx.new(GitBanner::new);
|
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 {
|
Self {
|
||||||
platform_style,
|
platform_style,
|
||||||
|
@ -329,8 +354,7 @@ impl TitleBar {
|
||||||
user_store,
|
user_store,
|
||||||
client,
|
client,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
zed_predict_banner,
|
banner,
|
||||||
git_banner,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,6 +182,7 @@ actions!(
|
||||||
ToggleZoom,
|
ToggleZoom,
|
||||||
Unfollow,
|
Unfollow,
|
||||||
Welcome,
|
Welcome,
|
||||||
|
RestoreBanner,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -122,6 +122,7 @@ theme.workspace = true
|
||||||
theme_extension.workspace = true
|
theme_extension.workspace = true
|
||||||
theme_selector.workspace = true
|
theme_selector.workspace = true
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
|
title_bar.workspace = true
|
||||||
toolchain_selector.workspace = true
|
toolchain_selector.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
ui_prompt.workspace = true
|
ui_prompt.workspace = true
|
||||||
|
|
|
@ -65,12 +65,12 @@ use uuid::Uuid;
|
||||||
use vim_mode_setting::VimModeSetting;
|
use vim_mode_setting::VimModeSetting;
|
||||||
use welcome::{BaseKeymap, MultibufferHint};
|
use welcome::{BaseKeymap, MultibufferHint};
|
||||||
use workspace::notifications::{dismiss_app_notification, show_app_notification, NotificationId};
|
use workspace::notifications::{dismiss_app_notification, show_app_notification, NotificationId};
|
||||||
use workspace::CloseIntent;
|
|
||||||
use workspace::{
|
use workspace::{
|
||||||
create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
|
create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
|
||||||
open_new, AppState, NewFile, NewWindow, OpenLog, Toast, Workspace, WorkspaceSettings,
|
open_new, AppState, NewFile, NewWindow, OpenLog, Toast, Workspace, WorkspaceSettings,
|
||||||
};
|
};
|
||||||
use workspace::{notifications::DetachAndPromptErr, Pane};
|
use workspace::{notifications::DetachAndPromptErr, Pane};
|
||||||
|
use workspace::{CloseIntent, RestoreBanner};
|
||||||
use zed_actions::{
|
use zed_actions::{
|
||||||
OpenAccountSettings, OpenBrowser, OpenServerSettings, OpenSettings, OpenZedUrl, Quit,
|
OpenAccountSettings, OpenBrowser, OpenServerSettings, OpenSettings, OpenZedUrl, Quit,
|
||||||
};
|
};
|
||||||
|
@ -105,6 +105,8 @@ pub fn init(cx: &mut App) {
|
||||||
cx.on_action(|_: &ShowAll, cx| cx.unhide_other_apps());
|
cx.on_action(|_: &ShowAll, cx| cx.unhide_other_apps());
|
||||||
cx.on_action(quit);
|
cx.on_action(quit);
|
||||||
|
|
||||||
|
cx.on_action(|_: &RestoreBanner, cx| title_bar::restore_banner(cx));
|
||||||
|
|
||||||
if ReleaseChannel::global(cx) == ReleaseChannel::Dev {
|
if ReleaseChannel::global(cx) == ReleaseChannel::Dev {
|
||||||
cx.on_action(test_panic);
|
cx.on_action(test_panic);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ test-support = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
arrayvec.workspace = true
|
arrayvec.workspace = true
|
||||||
chrono.workspace = true
|
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
command_palette_hooks.workspace = true
|
command_palette_hooks.workspace = true
|
||||||
|
|
|
@ -43,8 +43,6 @@ pub fn init(cx: &mut App) {
|
||||||
.edit_prediction_provider = Some(EditPredictionProvider::None)
|
.edit_prediction_provider = Some(EditPredictionProvider::None)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
crate::onboarding_banner::clear_dismissed(cx);
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
use chrono::Utc;
|
|
||||||
use feature_flags::{FeatureFlagAppExt as _, PredictEditsFeatureFlag};
|
|
||||||
use gpui::Subscription;
|
|
||||||
use language::language_settings::{all_language_settings, EditPredictionProvider};
|
|
||||||
use settings::SettingsStore;
|
|
||||||
use ui::{prelude::*, ButtonLike, Tooltip};
|
|
||||||
use util::ResultExt;
|
|
||||||
|
|
||||||
use crate::onboarding_event;
|
|
||||||
|
|
||||||
/// Prompts the user to try Zed's Edit Prediction feature
|
|
||||||
pub struct ZedPredictBanner {
|
|
||||||
dismissed: bool,
|
|
||||||
provider: EditPredictionProvider,
|
|
||||||
_subscription: Subscription,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ZedPredictBanner {
|
|
||||||
pub fn new(cx: &mut Context<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
dismissed: get_dismissed(),
|
|
||||||
provider: all_language_settings(None, cx).edit_predictions.provider,
|
|
||||||
_subscription: cx.observe_global::<SettingsStore>(Self::handle_settings_changed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_show(&self, cx: &mut App) -> bool {
|
|
||||||
cx.has_flag::<PredictEditsFeatureFlag>() && !self.dismissed && !self.provider.is_zed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_settings_changed(&mut self, cx: &mut Context<Self>) {
|
|
||||||
let new_provider = all_language_settings(None, cx).edit_predictions.provider;
|
|
||||||
|
|
||||||
if new_provider == self.provider {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_provider.is_zed() {
|
|
||||||
self.dismiss(cx);
|
|
||||||
} else {
|
|
||||||
self.dismissed = get_dismissed();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.provider = new_provider;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dismiss(&mut self, cx: &mut Context<Self>) {
|
|
||||||
onboarding_event!("Banner Dismissed");
|
|
||||||
persist_dismissed(cx);
|
|
||||||
self.dismissed = true;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const DISMISSED_AT_KEY: &str = "zed_predict_banner_dismissed_at";
|
|
||||||
|
|
||||||
fn get_dismissed() -> bool {
|
|
||||||
db::kvp::KEY_VALUE_STORE
|
|
||||||
.read_kvp(DISMISSED_AT_KEY)
|
|
||||||
.log_err()
|
|
||||||
.map_or(false, |dismissed| dismissed.is_some())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn persist_dismissed(cx: &mut App) {
|
|
||||||
cx.spawn(async |_| {
|
|
||||||
let time = Utc::now().to_rfc3339();
|
|
||||||
db::kvp::KEY_VALUE_STORE
|
|
||||||
.write_kvp(DISMISSED_AT_KEY.into(), time)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn clear_dismissed(cx: &mut App) {
|
|
||||||
cx.spawn(async |_| {
|
|
||||||
db::kvp::KEY_VALUE_STORE
|
|
||||||
.delete_kvp(DISMISSED_AT_KEY.into())
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for ZedPredictBanner {
|
|
||||||
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-zed-predict")
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.h_full()
|
|
||||||
.items_center()
|
|
||||||
.gap_1p5()
|
|
||||||
.child(Icon::new(IconName::ZedPredict).size(IconSize::Small))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_0p5()
|
|
||||||
.child(
|
|
||||||
Label::new("Introducing:")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(Label::new("Edit Prediction").size(LabelSize::Small)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.on_click(|_, window, cx| {
|
|
||||||
onboarding_event!("Banner Clicked");
|
|
||||||
window.dispatch_action(Box::new(zed_actions::OpenZedPredictOnboarding), 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ mod completion_diff_element;
|
||||||
mod init;
|
mod init;
|
||||||
mod input_excerpt;
|
mod input_excerpt;
|
||||||
mod license_detection;
|
mod license_detection;
|
||||||
mod onboarding_banner;
|
|
||||||
mod onboarding_modal;
|
mod onboarding_modal;
|
||||||
mod onboarding_telemetry;
|
mod onboarding_telemetry;
|
||||||
mod rate_completion_modal;
|
mod rate_completion_modal;
|
||||||
|
@ -13,7 +12,6 @@ pub use init::*;
|
||||||
use inline_completion::DataCollectionState;
|
use inline_completion::DataCollectionState;
|
||||||
pub use license_detection::is_license_eligible_for_data_collection;
|
pub use license_detection::is_license_eligible_for_data_collection;
|
||||||
use license_detection::LICENSE_FILES_TO_CHECK;
|
use license_detection::LICENSE_FILES_TO_CHECK;
|
||||||
pub use onboarding_banner::*;
|
|
||||||
pub use rate_completion_modal::*;
|
pub use rate_completion_modal::*;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue