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:
Mikayla Maki 2025-03-25 00:05:25 -07:00 committed by GitHub
parent b85492bd00
commit 35ec4753b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 181 additions and 294 deletions

View file

@ -19,7 +19,6 @@ test-support = []
[dependencies]
anyhow.workspace = true
arrayvec.workspace = true
chrono.workspace = true
client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true

View file

@ -43,8 +43,6 @@ pub fn init(cx: &mut App) {
.edit_prediction_provider = Some(EditPredictionProvider::None)
},
);
crate::onboarding_banner::clear_dismissed(cx);
});
})
.detach();

View file

@ -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)
}
}

View file

@ -2,7 +2,6 @@ mod completion_diff_element;
mod init;
mod input_excerpt;
mod license_detection;
mod onboarding_banner;
mod onboarding_modal;
mod onboarding_telemetry;
mod rate_completion_modal;
@ -13,7 +12,6 @@ pub use init::*;
use inline_completion::DataCollectionState;
pub use license_detection::is_license_eligible_for_data_collection;
use license_detection::LICENSE_FILES_TO_CHECK;
pub use onboarding_banner::*;
pub use rate_completion_modal::*;
use anyhow::{anyhow, Context as _, Result};