Compare commits
2 Commits
vim-syntax
...
agent-bann
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7cb23fe78 | ||
|
|
c294b4d0b8 |
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -3197,6 +3197,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"ui",
|
"ui",
|
||||||
"ui_input",
|
"ui_input",
|
||||||
|
"ui_parking_lot",
|
||||||
"workspace",
|
"workspace",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
@@ -15487,6 +15488,19 @@ dependencies = [
|
|||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ui_parking_lot"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"component",
|
||||||
|
"gpui",
|
||||||
|
"linkme",
|
||||||
|
"ui",
|
||||||
|
"util",
|
||||||
|
"workspace",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ui_prompt"
|
name = "ui_prompt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ members = [
|
|||||||
"crates/title_bar",
|
"crates/title_bar",
|
||||||
"crates/toolchain_selector",
|
"crates/toolchain_selector",
|
||||||
"crates/ui",
|
"crates/ui",
|
||||||
|
"crates/ui_parking_lot",
|
||||||
"crates/ui_input",
|
"crates/ui_input",
|
||||||
"crates/ui_macros",
|
"crates/ui_macros",
|
||||||
"crates/ui_prompt",
|
"crates/ui_prompt",
|
||||||
@@ -365,6 +366,7 @@ toolchain_selector = { path = "crates/toolchain_selector" }
|
|||||||
ui = { path = "crates/ui" }
|
ui = { path = "crates/ui" }
|
||||||
ui_input = { path = "crates/ui_input" }
|
ui_input = { path = "crates/ui_input" }
|
||||||
ui_macros = { path = "crates/ui_macros" }
|
ui_macros = { path = "crates/ui_macros" }
|
||||||
|
ui_parking_lot = { path = "crates/ui_parking_lot" }
|
||||||
ui_prompt = { path = "crates/ui_prompt" }
|
ui_prompt = { path = "crates/ui_prompt" }
|
||||||
util = { path = "crates/util" }
|
util = { path = "crates/util" }
|
||||||
util_macros = { path = "crates/util_macros" }
|
util_macros = { path = "crates/util_macros" }
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::any::Any;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
@@ -15,9 +16,20 @@ pub trait Component {
|
|||||||
fn scope() -> ComponentScope {
|
fn scope() -> ComponentScope {
|
||||||
ComponentScope::None
|
ComponentScope::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn init_component(_weak_workspace: Box<dyn Any>) {}
|
||||||
|
|
||||||
|
// In theory we could downcast to a WeakEntity<Workspace> and use that to build
|
||||||
|
// whatever you need to initialize the component, but I haven't tested it yet.
|
||||||
|
//
|
||||||
|
// fn init_component(weak_workspace: Box<dyn Any>) {
|
||||||
|
// let weak_workspace = weak_workspace.downcast::<WeakEntity<Workspace>>().unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
std::any::type_name::<Self>()
|
std::any::type_name::<Self>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a name that the component should be sorted by.
|
/// Returns a name that the component should be sorted by.
|
||||||
///
|
///
|
||||||
/// Implement this if the component should be sorted in an alternate order than its name.
|
/// Implement this if the component should be sorted in an alternate order than its name.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ languages.workspace = true
|
|||||||
notifications.workspace = true
|
notifications.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
ui_parking_lot.workspace = true
|
||||||
ui_input.workspace = true
|
ui_input.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ use ui_input::SingleLineInput;
|
|||||||
use workspace::{AppState, ItemId, SerializableItem};
|
use workspace::{AppState, ItemId, SerializableItem};
|
||||||
use workspace::{Item, Workspace, WorkspaceId, item::ItemEvent};
|
use workspace::{Item, Workspace, WorkspaceId, item::ItemEvent};
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
use ui_parking_lot::*; // Import for component registry, gets culled otherwise
|
||||||
|
|
||||||
pub fn init(app_state: Arc<AppState>, cx: &mut App) {
|
pub fn init(app_state: Arc<AppState>, cx: &mut App) {
|
||||||
workspace::register_serializable_item::<ComponentPreview>(cx);
|
workspace::register_serializable_item::<ComponentPreview>(cx);
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,11 @@ impl RenderOnce for Banner {
|
|||||||
|
|
||||||
let mut container = base.bg(bg_color).border_color(border_color);
|
let mut container = base.bg(bg_color).border_color(border_color);
|
||||||
|
|
||||||
let mut content_area = h_flex().id("content_area").gap_1p5().overflow_x_scroll();
|
let mut content_area = h_flex()
|
||||||
|
.id("content_area")
|
||||||
|
.flex_1()
|
||||||
|
.gap_1p5()
|
||||||
|
.overflow_x_scroll();
|
||||||
|
|
||||||
if self.icon.is_none() {
|
if self.icon.is_none() {
|
||||||
content_area =
|
content_area =
|
||||||
@@ -126,10 +130,13 @@ impl RenderOnce for Banner {
|
|||||||
.pl_2()
|
.pl_2()
|
||||||
.pr_0p5()
|
.pr_0p5()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
.flex_1()
|
||||||
.child(content_area)
|
.child(content_area)
|
||||||
.child(action_slot);
|
.child(action_slot);
|
||||||
} else {
|
} else {
|
||||||
container = container.px_2().child(div().w_full().child(content_area));
|
container = container
|
||||||
|
.px_2()
|
||||||
|
.child(div().w_full().flex_1().child(content_area));
|
||||||
}
|
}
|
||||||
|
|
||||||
container
|
container
|
||||||
|
|||||||
24
crates/ui_parking_lot/Cargo.toml
Normal file
24
crates/ui_parking_lot/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "ui_parking_lot"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/ui_parking_lot.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
component.workspace = true
|
||||||
|
gpui.workspace = true
|
||||||
|
linkme.workspace = true
|
||||||
|
ui.workspace = true
|
||||||
|
util.workspace = true
|
||||||
|
workspace.workspace = true
|
||||||
1
crates/ui_parking_lot/LICENSE-GPL
Symbolic link
1
crates/ui_parking_lot/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-GPL
|
||||||
1
crates/ui_parking_lot/src/agent/mod.rs
Normal file
1
crates/ui_parking_lot/src/agent/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod usage_banner;
|
||||||
475
crates/ui_parking_lot/src/agent/usage_banner.rs
Normal file
475
crates/ui_parking_lot/src/agent/usage_banner.rs
Normal file
@@ -0,0 +1,475 @@
|
|||||||
|
use gpui::Entity;
|
||||||
|
use ui::{Banner, Severity};
|
||||||
|
use ui::{ProgressBar, prelude::*};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum CurrentPlan {
|
||||||
|
Trial,
|
||||||
|
Free,
|
||||||
|
Paid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum CapReason {
|
||||||
|
RequestLimit,
|
||||||
|
SpendLimit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(RegisterComponent)]
|
||||||
|
pub struct UsageBanner {
|
||||||
|
current_plan: CurrentPlan,
|
||||||
|
current_requests: u32,
|
||||||
|
current_spend: u32,
|
||||||
|
monthly_cap: u32,
|
||||||
|
usage_based_enabled: bool,
|
||||||
|
usage_progress: Entity<ProgressBar>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsageBanner {
|
||||||
|
/// Creates a new UsageBanner with the provided values
|
||||||
|
pub fn new(
|
||||||
|
current_plan: CurrentPlan,
|
||||||
|
current_requests: u32,
|
||||||
|
current_spend: u32,
|
||||||
|
monthly_cap: u32,
|
||||||
|
usage_based_enabled: bool,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Self {
|
||||||
|
let usage_progress = cx.new(|cx| {
|
||||||
|
ProgressBar::new(
|
||||||
|
"usage_progress",
|
||||||
|
current_requests as f32,
|
||||||
|
request_cap_for_plan(¤t_plan) as f32,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let banner = Self {
|
||||||
|
current_plan,
|
||||||
|
current_requests,
|
||||||
|
current_spend,
|
||||||
|
monthly_cap,
|
||||||
|
usage_based_enabled,
|
||||||
|
usage_progress,
|
||||||
|
};
|
||||||
|
|
||||||
|
// No need to update styling here as it will be done when rendering
|
||||||
|
banner
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the request cap based on the current plan
|
||||||
|
pub fn request_cap(&self) -> u32 {
|
||||||
|
request_cap_for_plan(&self.current_plan)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the user is capped due to hitting request limits
|
||||||
|
pub fn is_capped_by_requests(&self) -> bool {
|
||||||
|
self.current_requests >= self.request_cap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the user is capped due to hitting spend limits
|
||||||
|
pub fn is_capped_by_spend(&self) -> bool {
|
||||||
|
// Only check spend limit if spending is enabled and cap is set
|
||||||
|
self.usage_based_enabled && self.monthly_cap > 0 && self.current_spend >= self.monthly_cap
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the user is approaching request limit (>=90%)
|
||||||
|
pub fn is_approaching_request_limit(&self) -> bool {
|
||||||
|
let threshold = (self.request_cap() as f32 * 0.9) as u32;
|
||||||
|
self.current_requests >= threshold && self.current_requests < self.request_cap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the user is approaching spend limit (>=90%)
|
||||||
|
pub fn is_approaching_spend_limit(&self) -> bool {
|
||||||
|
// Only check if spending is enabled and cap is set
|
||||||
|
self.usage_based_enabled
|
||||||
|
&& self.monthly_cap > 0
|
||||||
|
&& self.current_spend >= (self.monthly_cap as f32 * 0.9) as u32
|
||||||
|
&& self.current_spend < self.monthly_cap
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the user is capped and returns the reason
|
||||||
|
pub fn cap_status(&self) -> Option<CapReason> {
|
||||||
|
if self.is_capped_by_requests() {
|
||||||
|
Some(CapReason::RequestLimit)
|
||||||
|
} else if self.is_capped_by_spend() {
|
||||||
|
Some(CapReason::SpendLimit)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the user is capped for any reason
|
||||||
|
pub fn is_capped(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.cap_status(),
|
||||||
|
Some(CapReason::RequestLimit | CapReason::SpendLimit)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the current request count and progress bar
|
||||||
|
pub fn update_requests(&mut self, requests: u32, cx: &mut Context<Self>) {
|
||||||
|
self.current_requests = requests;
|
||||||
|
self.update_progress_bar(cx);
|
||||||
|
self.update_progress_styling(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the current spend amount
|
||||||
|
pub fn update_spend(&mut self, spend: u32, cx: &mut Context<Self>) {
|
||||||
|
self.current_spend = spend;
|
||||||
|
self.update_progress_bar(cx);
|
||||||
|
self.update_progress_styling(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the progress bar styling based on current usage levels
|
||||||
|
fn update_progress_styling(&self, cx: &mut Context<Self>) {
|
||||||
|
let is_near_cap = self.current_requests as f32 >= self.request_cap() as f32 * 0.9;
|
||||||
|
let is_capped = self.is_capped();
|
||||||
|
|
||||||
|
self.usage_progress.update(cx, |progress_bar, cx| {
|
||||||
|
if is_capped {
|
||||||
|
progress_bar.fg_color(cx.theme().status().error);
|
||||||
|
} else if is_near_cap {
|
||||||
|
progress_bar.fg_color(cx.theme().status().warning);
|
||||||
|
} else {
|
||||||
|
progress_bar.fg_color(cx.theme().status().info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_show_request_progress(&self) -> bool {
|
||||||
|
// Show request progress for all plans as long as not capped
|
||||||
|
// Only show if we have a non-zero request cap
|
||||||
|
self.request_cap() > 0 && !self.is_capped_by_requests() && !self.is_capped_by_spend()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show the spend progress bar once requests are capped
|
||||||
|
/// if the user has usage based enabled
|
||||||
|
fn should_show_spend_progress(&self) -> bool {
|
||||||
|
// Only show spend progress for paid plans with usage-based pricing enabled
|
||||||
|
// and when a monthly cap is set
|
||||||
|
self.current_plan == CurrentPlan::Paid
|
||||||
|
&& self.usage_based_enabled
|
||||||
|
&& self.monthly_cap > 0
|
||||||
|
&& self.is_capped_by_requests()
|
||||||
|
&& !self.is_capped_by_spend()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the progress bar with current values
|
||||||
|
fn update_progress_bar(&mut self, cx: &mut Context<Self>) {
|
||||||
|
// Update the progress bar with new values
|
||||||
|
// We need to recreate it to update both value and max
|
||||||
|
self.usage_progress.update(cx, |progress_bar, cx| {
|
||||||
|
// Update progress bar value
|
||||||
|
*progress_bar = ProgressBar::new(
|
||||||
|
"usage_progress",
|
||||||
|
self.current_requests as f32,
|
||||||
|
self.request_cap() as f32,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn severity(&self) -> Severity {
|
||||||
|
if self.is_capped_by_spend() || self.is_capped_by_requests() {
|
||||||
|
return Severity::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_approaching_request_limit() || self.is_approaching_spend_limit() {
|
||||||
|
return Severity::Warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
Severity::Info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to get the request cap based on plan type
|
||||||
|
fn request_cap_for_plan(plan: &CurrentPlan) -> u32 {
|
||||||
|
match plan {
|
||||||
|
CurrentPlan::Trial => 150,
|
||||||
|
CurrentPlan::Free => 50,
|
||||||
|
CurrentPlan::Paid => 500,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for UsageBanner {
|
||||||
|
fn render(&mut self, _: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let formatted_requests = format!("{} / {}", self.current_requests, self.request_cap());
|
||||||
|
let formatted_spend = format!(
|
||||||
|
"${:.2} / ${:.2}",
|
||||||
|
self.current_spend as f32 / 100.0,
|
||||||
|
if self.monthly_cap > 0 {
|
||||||
|
self.monthly_cap as f32 / 100.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let (message, action_button) = if self.is_capped_by_spend() {
|
||||||
|
(
|
||||||
|
"Monthly spending limit reached",
|
||||||
|
Some(Button::new("manage", "Manage Spending").into_any_element()),
|
||||||
|
)
|
||||||
|
} else if self.is_capped_by_requests() {
|
||||||
|
let msg = match self.current_plan {
|
||||||
|
CurrentPlan::Trial => "Trial request limit reached",
|
||||||
|
CurrentPlan::Free => "Free tier request limit reached",
|
||||||
|
CurrentPlan::Paid => "Monthly request limit reached",
|
||||||
|
};
|
||||||
|
|
||||||
|
let action = match self.current_plan {
|
||||||
|
CurrentPlan::Trial | CurrentPlan::Free => {
|
||||||
|
Some(Button::new("upgrade", "Upgrade").into_any_element())
|
||||||
|
}
|
||||||
|
CurrentPlan::Paid => {
|
||||||
|
if self.usage_based_enabled {
|
||||||
|
Some(Button::new("manage", "Manage").into_any_element())
|
||||||
|
} else {
|
||||||
|
Some(Button::new("enable-usage", "Try Usaged-Based").into_any_element())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(msg, action)
|
||||||
|
} else if self.is_approaching_request_limit() {
|
||||||
|
let msg = "Approaching request limit";
|
||||||
|
|
||||||
|
let action = match self.current_plan {
|
||||||
|
CurrentPlan::Trial | CurrentPlan::Free => {
|
||||||
|
Some(Button::new("upgrade", "Upgrade").into_any_element())
|
||||||
|
}
|
||||||
|
CurrentPlan::Paid => {
|
||||||
|
if !self.usage_based_enabled {
|
||||||
|
Some(Button::new("enable-usage", "Manage").into_any_element())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(msg, action)
|
||||||
|
} else if self.is_approaching_spend_limit() {
|
||||||
|
(
|
||||||
|
"Approaching monthly spend limit",
|
||||||
|
Some(Button::new("manage", "Manage Spending").into_any_element()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let msg = match self.current_plan {
|
||||||
|
CurrentPlan::Trial => "Zed AI Trial",
|
||||||
|
CurrentPlan::Free => "Zed AI Free",
|
||||||
|
CurrentPlan::Paid => "Zed AI Paid",
|
||||||
|
};
|
||||||
|
|
||||||
|
(msg, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build the content section with usage information
|
||||||
|
let mut content = h_flex().flex_1().gap_1().child(Label::new(message));
|
||||||
|
|
||||||
|
// Add usage progress section if we should show it
|
||||||
|
if self.should_show_request_progress() {
|
||||||
|
content = content.child(
|
||||||
|
h_flex()
|
||||||
|
.flex_1()
|
||||||
|
.justify_end()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.items_center()
|
||||||
|
.w_full()
|
||||||
|
.max_w(px(180.))
|
||||||
|
.child(self.usage_progress.clone()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(formatted_requests)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add spending information for Paid users with usage-based pricing
|
||||||
|
if self.should_show_spend_progress() {
|
||||||
|
content = content.child(
|
||||||
|
h_flex().flex_1().justify_end().gap_1p5().child(
|
||||||
|
Label::new(formatted_spend)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the banner with appropriate severity and content
|
||||||
|
let mut banner = Banner::new().severity(self.severity()).children(content);
|
||||||
|
|
||||||
|
// Add action button if available
|
||||||
|
if let Some(action) = action_button {
|
||||||
|
banner = banner.action_slot(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
banner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for UsageBanner {
|
||||||
|
fn scope() -> ComponentScope {
|
||||||
|
ComponentScope::Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||||
|
// Create instances of UsageBanner for different scenarios
|
||||||
|
// Trial plan examples (cap = 150)
|
||||||
|
let new_trial_user = cx.new(|cx| UsageBanner::new(CurrentPlan::Trial, 10, 0, 0, false, cx));
|
||||||
|
let trial_user_warning =
|
||||||
|
cx.new(|cx| UsageBanner::new(CurrentPlan::Trial, 135, 0, 0, false, cx));
|
||||||
|
let trial_user_capped =
|
||||||
|
cx.new(|cx| UsageBanner::new(CurrentPlan::Trial, 150, 0, 0, false, cx));
|
||||||
|
|
||||||
|
// Free plan examples (cap = 50)
|
||||||
|
let free_user = cx.new(|cx| UsageBanner::new(CurrentPlan::Free, 25, 0, 0, false, cx));
|
||||||
|
let free_user_warning =
|
||||||
|
cx.new(|cx| UsageBanner::new(CurrentPlan::Free, 45, 0, 0, false, cx));
|
||||||
|
let free_user_capped =
|
||||||
|
cx.new(|cx| UsageBanner::new(CurrentPlan::Free, 50, 0, 0, false, cx));
|
||||||
|
|
||||||
|
// Pro plan examples without usage-based pricing (cap = 500)
|
||||||
|
let paid_user = cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 250, 0, 0, false, cx));
|
||||||
|
let paid_user_warning =
|
||||||
|
cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 450, 0, 0, false, cx));
|
||||||
|
let paid_user_capped =
|
||||||
|
cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 500, 0, 0, false, cx));
|
||||||
|
|
||||||
|
// Pro plan examples with usage-based pricing and monthly spend cap (cap = 500)
|
||||||
|
let paid_user_usage_based =
|
||||||
|
cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 500, 5000, 20000, true, cx));
|
||||||
|
let paid_user_usage_based_warning =
|
||||||
|
cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 500, 18000, 20000, true, cx));
|
||||||
|
let paid_user_usage_based_capped =
|
||||||
|
cx.new(|cx| UsageBanner::new(CurrentPlan::Paid, 500, 20000, 20000, true, cx));
|
||||||
|
|
||||||
|
// Group examples by plan type
|
||||||
|
let trial_examples = vec![
|
||||||
|
single_example(
|
||||||
|
"Trial - New User",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(new_trial_user.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Trial - Approaching Limit",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(trial_user_warning.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Trial - Request Limit Reached",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(trial_user_capped.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let free_examples = vec![
|
||||||
|
single_example(
|
||||||
|
"Free - Normal Usage",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(free_user.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Free - Approaching Limit",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(free_user_warning.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Free - Request Limit Reached",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(free_user_capped.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let paid_examples = vec![
|
||||||
|
single_example(
|
||||||
|
"Pro - Normal Usage",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(paid_user.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Pro - Approaching Limit",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(paid_user_warning.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Pro - Request Limit Reached",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(paid_user_capped.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let paid_usage_based_examples = vec![
|
||||||
|
single_example(
|
||||||
|
"Pro with UBP - After Request Cap",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(paid_user_usage_based.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Pro with UBP - Approaching Spend Cap",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(paid_user_usage_based_warning.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Pro with UBP - Spend Cap Reached",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(paid_user_usage_based_capped.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Combine all examples
|
||||||
|
Some(
|
||||||
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.p_4()
|
||||||
|
.children(vec![
|
||||||
|
Label::new("Trial Plan")
|
||||||
|
.size(LabelSize::Large)
|
||||||
|
.into_any_element(),
|
||||||
|
example_group(trial_examples).vertical().into_any_element(),
|
||||||
|
Label::new("Free Plan")
|
||||||
|
.size(LabelSize::Large)
|
||||||
|
.into_any_element(),
|
||||||
|
example_group(free_examples).vertical().into_any_element(),
|
||||||
|
Label::new("Pro Plan")
|
||||||
|
.size(LabelSize::Large)
|
||||||
|
.into_any_element(),
|
||||||
|
example_group(paid_examples).vertical().into_any_element(),
|
||||||
|
Label::new("Pro Plan with Usage-Based Pricing")
|
||||||
|
.size(LabelSize::Large)
|
||||||
|
.into_any_element(),
|
||||||
|
example_group(paid_usage_based_examples)
|
||||||
|
.vertical()
|
||||||
|
.into_any_element(),
|
||||||
|
])
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
8
crates/ui_parking_lot/src/ui_parking_lot.rs
Normal file
8
crates/ui_parking_lot/src/ui_parking_lot.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//! # UI Parking Lot
|
||||||
|
//!
|
||||||
|
//! A place for engineering-ready components to be parked
|
||||||
|
//! until someone has the time to pick them up and implement them further.
|
||||||
|
|
||||||
|
mod agent;
|
||||||
|
|
||||||
|
pub use agent::usage_banner::UsageBanner;
|
||||||
@@ -113,7 +113,6 @@ use crate::persistence::{
|
|||||||
SerializedAxis,
|
SerializedAxis,
|
||||||
model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
|
model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200);
|
pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
static ZED_WINDOW_SIZE: LazyLock<Option<Size<Pixels>>> = LazyLock::new(|| {
|
static ZED_WINDOW_SIZE: LazyLock<Option<Size<Pixels>>> = LazyLock::new(|| {
|
||||||
|
|||||||
Reference in New Issue
Block a user