feat: manually handle NIP-42 auth request (#132)
* improve fetch relays * . * . * . * refactor * refactor * remove identity * manually auth * auth * prevent duplicate message * clean up
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
use std::any::TypeId;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::Arc;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
blue, div, green, px, red, yellow, Animation, AnimationExt, App, AppContext, ClickEvent,
|
||||
Context, DismissEvent, ElementId, Entity, EventEmitter, InteractiveElement as _, IntoElement,
|
||||
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, Subscription,
|
||||
Window,
|
||||
div, px, Animation, AnimationExt, AnyElement, App, AppContext, ClickEvent, Context,
|
||||
DismissEvent, ElementId, Entity, EventEmitter, InteractiveElement as _, IntoElement,
|
||||
ParentElement as _, Render, SharedString, StatefulInteractiveElement, StyleRefinement, Styled,
|
||||
Subscription, Window,
|
||||
};
|
||||
use smol::Timer;
|
||||
use theme::ActiveTheme;
|
||||
@@ -18,13 +18,30 @@ use crate::animation::cubic_bezier;
|
||||
use crate::button::{Button, ButtonVariants as _};
|
||||
use crate::{h_flex, v_flex, Icon, IconName, Sizable as _, StyledExt};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub enum NotificationType {
|
||||
#[default]
|
||||
Info,
|
||||
Success,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl NotificationType {
|
||||
fn icon(&self, cx: &App) -> Icon {
|
||||
match self {
|
||||
Self::Info => Icon::new(IconName::Info).text_color(cx.theme().element_active),
|
||||
Self::Warning => Icon::new(IconName::Report).text_color(cx.theme().warning_foreground),
|
||||
Self::Success => {
|
||||
Icon::new(IconName::CheckCircle).text_color(cx.theme().element_foreground)
|
||||
}
|
||||
Self::Error => {
|
||||
Icon::new(IconName::CloseCircle).text_color(cx.theme().danger_foreground)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
|
||||
pub(crate) enum NotificationId {
|
||||
Id(TypeId),
|
||||
@@ -43,8 +60,6 @@ impl From<(TypeId, ElementId)> for NotificationId {
|
||||
}
|
||||
}
|
||||
|
||||
type OnClick = Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>;
|
||||
|
||||
/// A notification element.
|
||||
pub struct Notification {
|
||||
/// The id is used make the notification unique.
|
||||
@@ -52,48 +67,54 @@ pub struct Notification {
|
||||
///
|
||||
/// None means the notification will be added to the end of the list.
|
||||
id: NotificationId,
|
||||
kind: NotificationType,
|
||||
style: StyleRefinement,
|
||||
type_: Option<NotificationType>,
|
||||
title: Option<SharedString>,
|
||||
message: SharedString,
|
||||
message: Option<SharedString>,
|
||||
icon: Option<Icon>,
|
||||
autohide: bool,
|
||||
on_click: OnClick,
|
||||
#[allow(clippy::type_complexity)]
|
||||
action_builder: Option<Rc<dyn Fn(&mut Window, &mut Context<Self>) -> Button>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
content_builder: Option<Rc<dyn Fn(&mut Window, &mut Context<Self>) -> AnyElement>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
closing: bool,
|
||||
}
|
||||
|
||||
impl From<String> for Notification {
|
||||
fn from(s: String) -> Self {
|
||||
Self::new(s)
|
||||
Self::new().message(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for Notification {
|
||||
fn from(s: Cow<'static, str>) -> Self {
|
||||
Self::new(s)
|
||||
Self::new().message(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SharedString> for Notification {
|
||||
fn from(s: SharedString) -> Self {
|
||||
Self::new(s)
|
||||
Self::new().message(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Notification {
|
||||
fn from(s: &'static str) -> Self {
|
||||
Self::new(s)
|
||||
Self::new().message(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(NotificationType, &'static str)> for Notification {
|
||||
fn from((type_, content): (NotificationType, &'static str)) -> Self {
|
||||
Self::new(content).with_type(type_)
|
||||
Self::new().message(content).with_type(type_)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(NotificationType, SharedString)> for Notification {
|
||||
fn from((type_, content): (NotificationType, SharedString)) -> Self {
|
||||
Self::new(content).with_type(type_)
|
||||
Self::new().message(content).with_type(type_)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,36 +124,52 @@ impl Notification {
|
||||
/// Create a new notification with the given content.
|
||||
///
|
||||
/// default width is 320px.
|
||||
pub fn new(message: impl Into<SharedString>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
let id: SharedString = uuid::Uuid::new_v4().to_string().into();
|
||||
let id = (TypeId::of::<DefaultIdType>(), id.into());
|
||||
|
||||
Self {
|
||||
id: id.into(),
|
||||
style: StyleRefinement::default(),
|
||||
title: None,
|
||||
message: message.into(),
|
||||
kind: NotificationType::Info,
|
||||
message: None,
|
||||
type_: None,
|
||||
icon: None,
|
||||
autohide: true,
|
||||
action_builder: None,
|
||||
content_builder: None,
|
||||
on_click: None,
|
||||
closing: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(mut self, message: impl Into<SharedString>) -> Self {
|
||||
self.message = Some(message.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn info(message: impl Into<SharedString>) -> Self {
|
||||
Self::new(message).with_type(NotificationType::Info)
|
||||
Self::new()
|
||||
.message(message)
|
||||
.with_type(NotificationType::Info)
|
||||
}
|
||||
|
||||
pub fn success(message: impl Into<SharedString>) -> Self {
|
||||
Self::new(message).with_type(NotificationType::Success)
|
||||
Self::new()
|
||||
.message(message)
|
||||
.with_type(NotificationType::Success)
|
||||
}
|
||||
|
||||
pub fn warning(message: impl Into<SharedString>) -> Self {
|
||||
Self::new(message).with_type(NotificationType::Warning)
|
||||
Self::new()
|
||||
.message(message)
|
||||
.with_type(NotificationType::Warning)
|
||||
}
|
||||
|
||||
pub fn error(message: impl Into<SharedString>) -> Self {
|
||||
Self::new(message).with_type(NotificationType::Error)
|
||||
Self::new()
|
||||
.message(message)
|
||||
.with_type(NotificationType::Error)
|
||||
}
|
||||
|
||||
/// Set the type for unique identification of the notification.
|
||||
@@ -147,8 +184,8 @@ impl Notification {
|
||||
}
|
||||
|
||||
/// Set the type and id of the notification, used to uniquely identify the notification.
|
||||
pub fn id1<T: Sized + 'static>(mut self, key: impl Into<ElementId>) -> Self {
|
||||
self.id = (TypeId::of::<T>(), key.into()).into();
|
||||
pub fn custom_id(mut self, key: impl Into<ElementId>) -> Self {
|
||||
self.id = (TypeId::of::<DefaultIdType>(), key.into()).into();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -170,7 +207,7 @@ impl Notification {
|
||||
|
||||
/// Set the type of the notification, default is NotificationType::Info.
|
||||
pub fn with_type(mut self, type_: NotificationType) -> Self {
|
||||
self.kind = type_;
|
||||
self.type_ = Some(type_);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -185,11 +222,21 @@ impl Notification {
|
||||
mut self,
|
||||
on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_click = Some(Arc::new(on_click));
|
||||
self.on_click = Some(Rc::new(on_click));
|
||||
self
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &ClickEvent, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
/// Set the action button of the notification.
|
||||
pub fn action<F>(mut self, action: F) -> Self
|
||||
where
|
||||
F: Fn(&mut Window, &mut Context<Self>) -> Button + 'static,
|
||||
{
|
||||
self.action_builder = Some(Rc::new(action));
|
||||
self
|
||||
}
|
||||
|
||||
/// Dismiss the notification.
|
||||
pub fn dismiss(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.closing = true;
|
||||
cx.notify();
|
||||
|
||||
@@ -207,31 +254,48 @@ impl Notification {
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
|
||||
/// Set the content of the notification.
|
||||
pub fn content(
|
||||
mut self,
|
||||
content: impl Fn(&mut Window, &mut Context<Self>) -> AnyElement + 'static,
|
||||
) -> Self {
|
||||
self.content_builder = Some(Rc::new(content));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Notification {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for Notification {}
|
||||
|
||||
impl FluentBuilder for Notification {}
|
||||
|
||||
impl Styled for Notification {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Notification {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let closing = self.closing;
|
||||
let icon = match self.icon.clone() {
|
||||
Some(icon) => icon,
|
||||
None => match self.kind {
|
||||
NotificationType::Info => Icon::new(IconName::Info).text_color(blue()),
|
||||
NotificationType::Warning => Icon::new(IconName::Info).text_color(yellow()),
|
||||
NotificationType::Error => Icon::new(IconName::CloseCircle).text_color(red()),
|
||||
NotificationType::Success => Icon::new(IconName::CheckCircle).text_color(green()),
|
||||
},
|
||||
let icon = match self.type_ {
|
||||
None => self.icon.clone(),
|
||||
Some(type_) => Some(type_.icon(cx)),
|
||||
};
|
||||
|
||||
div()
|
||||
h_flex()
|
||||
.id("notification")
|
||||
.refine_style(&self.style)
|
||||
.group("")
|
||||
.occlude()
|
||||
.relative()
|
||||
.w_72()
|
||||
.w_96()
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.bg(cx.theme().surface_background)
|
||||
@@ -239,52 +303,70 @@ impl Render for Notification {
|
||||
.shadow_md()
|
||||
.p_2()
|
||||
.gap_3()
|
||||
.child(div().absolute().top_2p5().left_2().child(icon))
|
||||
.justify_start()
|
||||
.items_start()
|
||||
.when_some(icon, |this, icon| {
|
||||
this.child(div().flex_shrink_0().pt_1().child(icon))
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
.pl_6()
|
||||
.flex_1()
|
||||
.gap_1()
|
||||
.when_some(self.title.clone(), |this, title| {
|
||||
this.child(div().text_xs().font_semibold().child(title))
|
||||
})
|
||||
.overflow_hidden()
|
||||
.child(div().text_xs().child(self.message.clone())),
|
||||
.when_some(self.title.clone(), |this, title| {
|
||||
this.child(div().text_sm().font_semibold().child(title))
|
||||
})
|
||||
.when_some(self.message.clone(), |this, message| {
|
||||
this.child(div().text_sm().child(message))
|
||||
})
|
||||
.when_some(self.content_builder.clone(), |this, child_builder| {
|
||||
this.child(child_builder(window, cx))
|
||||
})
|
||||
.when_some(self.action_builder.clone(), |this, action_builder| {
|
||||
this.child(action_builder(window, cx).small().w_full().my_2())
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_2p5()
|
||||
.right_2p5()
|
||||
.invisible()
|
||||
.group_hover("", |this| this.visible())
|
||||
.child(
|
||||
Button::new("close")
|
||||
.icon(IconName::Close)
|
||||
.ghost()
|
||||
.xsmall()
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dismiss(window, cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
.when_some(self.on_click.clone(), |this, on_click| {
|
||||
this.cursor_pointer()
|
||||
.on_click(cx.listener(move |view, event, window, cx| {
|
||||
view.dismiss(event, window, cx);
|
||||
on_click(event, window, cx);
|
||||
}))
|
||||
})
|
||||
.when(!self.autohide, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.absolute()
|
||||
.top_1()
|
||||
.right_1()
|
||||
.invisible()
|
||||
.group_hover("", |this| this.visible())
|
||||
.child(
|
||||
Button::new("close")
|
||||
.icon(IconName::Close)
|
||||
.ghost()
|
||||
.xsmall()
|
||||
.on_click(cx.listener(Self::dismiss)),
|
||||
),
|
||||
)
|
||||
this.on_click(cx.listener(move |view, event, window, cx| {
|
||||
view.dismiss(window, cx);
|
||||
on_click(event, window, cx);
|
||||
}))
|
||||
})
|
||||
.with_animation(
|
||||
ElementId::NamedInteger("slide-down".into(), closing as u64),
|
||||
Animation::new(Duration::from_secs_f64(0.15))
|
||||
Animation::new(Duration::from_secs_f64(0.25))
|
||||
.with_easing(cubic_bezier(0.4, 0., 0.2, 1.)),
|
||||
move |this, delta| {
|
||||
if closing {
|
||||
let x_offset = px(0.) + delta * px(45.);
|
||||
this.left(px(0.) + x_offset).opacity(1. - delta)
|
||||
let opacity = 1. - delta;
|
||||
this.left(px(0.) + x_offset)
|
||||
.shadow_none()
|
||||
.opacity(opacity)
|
||||
.when(opacity < 0.85, |this| this.shadow_none())
|
||||
} else {
|
||||
let y_offset = px(-45.) + delta * px(45.);
|
||||
this.top(px(0.) + y_offset).opacity(delta)
|
||||
let opacity = delta;
|
||||
this.top(px(0.) + y_offset)
|
||||
.opacity(opacity)
|
||||
.when(opacity < 0.85, |this| this.shadow_none())
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -296,7 +378,7 @@ pub struct NotificationList {
|
||||
/// Notifications that will be auto hidden.
|
||||
pub(crate) notifications: VecDeque<Entity<Notification>>,
|
||||
expanded: bool,
|
||||
subscriptions: HashMap<NotificationId, Subscription>,
|
||||
_subscriptions: HashMap<NotificationId, Subscription>,
|
||||
}
|
||||
|
||||
impl NotificationList {
|
||||
@@ -304,16 +386,14 @@ impl NotificationList {
|
||||
Self {
|
||||
notifications: VecDeque::new(),
|
||||
expanded: false,
|
||||
subscriptions: HashMap::new(),
|
||||
_subscriptions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(
|
||||
&mut self,
|
||||
notification: impl Into<Notification>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
pub fn push<T>(&mut self, notification: T, window: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
T: Into<Notification>,
|
||||
{
|
||||
let notification = notification.into();
|
||||
let id = notification.id.clone();
|
||||
let autohide = notification.autohide;
|
||||
@@ -323,28 +403,47 @@ impl NotificationList {
|
||||
|
||||
let notification = cx.new(|_| notification);
|
||||
|
||||
self.subscriptions.insert(
|
||||
self._subscriptions.insert(
|
||||
id.clone(),
|
||||
cx.subscribe(¬ification, move |view, _, _: &DismissEvent, cx| {
|
||||
view.notifications.retain(|note| id != note.read(cx).id);
|
||||
view.subscriptions.remove(&id);
|
||||
view._subscriptions.remove(&id);
|
||||
}),
|
||||
);
|
||||
|
||||
self.notifications.push_back(notification.clone());
|
||||
|
||||
if autohide {
|
||||
// Sleep for 3 seconds to autohide the notification
|
||||
// Sleep for 5 seconds to autohide the notification
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
Timer::after(Duration::from_secs(3)).await;
|
||||
_ = notification.update_in(cx, |note, window, cx| {
|
||||
note.dismiss(&ClickEvent::default(), window, cx)
|
||||
});
|
||||
Timer::after(Duration::from_secs(5)).await;
|
||||
|
||||
if let Err(error) =
|
||||
notification.update_in(cx, |note, window, cx| note.dismiss(window, cx))
|
||||
{
|
||||
log::error!("Failed to auto hide notification: {error}");
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn close<T>(&mut self, key: T, window: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
T: Into<ElementId>,
|
||||
{
|
||||
let id = (TypeId::of::<DefaultIdType>(), key.into()).into();
|
||||
|
||||
if let Some(n) = self.notifications.iter().find(|n| n.read(cx).id == id) {
|
||||
n.update(cx, |note, cx| {
|
||||
note.dismiss(window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.notifications.clear();
|
||||
cx.notify();
|
||||
@@ -356,24 +455,25 @@ impl NotificationList {
|
||||
}
|
||||
|
||||
impl Render for NotificationList {
|
||||
fn render(
|
||||
&mut self,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let size = window.viewport_size();
|
||||
let items = self.notifications.iter().rev().take(10).rev().cloned();
|
||||
|
||||
div().absolute().top_4().right_4().child(
|
||||
v_flex()
|
||||
.id("notification-list")
|
||||
.h(size.height - px(8.))
|
||||
.on_hover(cx.listener(|view, hovered, _, cx| {
|
||||
view.expanded = *hovered;
|
||||
cx.notify()
|
||||
}))
|
||||
.gap_3()
|
||||
.children(items),
|
||||
)
|
||||
div()
|
||||
.id("notification-wrapper")
|
||||
.absolute()
|
||||
.top_4()
|
||||
.right_4()
|
||||
.child(
|
||||
v_flex()
|
||||
.id("notification-list")
|
||||
.h(size.height - px(8.))
|
||||
.gap_3()
|
||||
.children(items)
|
||||
.on_hover(cx.listener(|view, hovered, _, cx| {
|
||||
view.expanded = *hovered;
|
||||
cx.notify()
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ use std::rc::Rc;
|
||||
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
actions, anchored, canvas, div, px, rems, Action, AnyElement, App, AppContext, Bounds, Context,
|
||||
Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
|
||||
IntoElement, KeyBinding, Keystroke, ParentElement, Pixels, Render, ScrollHandle, SharedString,
|
||||
StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window,
|
||||
actions, anchored, canvas, div, px, rems, Action, AnyElement, App, AppContext, AsKeystroke,
|
||||
Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, KeyBinding, Keystroke, ParentElement, Pixels, Render,
|
||||
ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Subscription, WeakEntity,
|
||||
Window,
|
||||
};
|
||||
use theme::ActiveTheme;
|
||||
|
||||
@@ -472,7 +473,7 @@ impl PopupMenu {
|
||||
keybinding
|
||||
.keystrokes()
|
||||
.iter()
|
||||
.map(|key| key_shortcut(key.clone())),
|
||||
.map(|key| key_shortcut(key.as_keystroke().clone())),
|
||||
);
|
||||
|
||||
return Some(el);
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::rc::Rc;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, AnyView, App, AppContext, Context, Decorations, Entity, FocusHandle, InteractiveElement,
|
||||
IntoElement, ParentElement as _, Render, Styled, Window,
|
||||
IntoElement, ParentElement as _, Render, SharedString, Styled, Window,
|
||||
};
|
||||
use theme::{ActiveTheme, CLIENT_SIDE_DECORATION_ROUNDING};
|
||||
|
||||
@@ -34,6 +34,9 @@ pub trait ContextModal: Sized {
|
||||
/// Pushes a notification to the notification list.
|
||||
fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App);
|
||||
|
||||
/// Clears a notification by its ID.
|
||||
fn clear_notification_by_id(&mut self, id: SharedString, cx: &mut App);
|
||||
|
||||
/// Clear all notifications
|
||||
fn clear_notifications(&mut self, cx: &mut App);
|
||||
|
||||
@@ -112,6 +115,15 @@ impl ContextModal for Window {
|
||||
})
|
||||
}
|
||||
|
||||
fn clear_notification_by_id(&mut self, id: SharedString, cx: &mut App) {
|
||||
Root::update(self, cx, move |root, window, cx| {
|
||||
root.notification.update(cx, |view, cx| {
|
||||
view.close(id.clone(), window, cx);
|
||||
});
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
||||
fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>> {
|
||||
let entity = Root::read(self, cx).notification.clone();
|
||||
Rc::new(entity.read(cx).notifications())
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use gpui::{
|
||||
bounce, div, ease_in_out, Animation, AnimationExt, Div, IntoElement, ParentElement as _,
|
||||
RenderOnce, Styled,
|
||||
bounce, div, ease_in_out, Animation, AnimationExt, IntoElement, RenderOnce, StyleRefinement,
|
||||
Styled,
|
||||
};
|
||||
use theme::ActiveTheme;
|
||||
|
||||
use crate::StyledExt;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Skeleton {
|
||||
base: Div,
|
||||
style: StyleRefinement,
|
||||
secondary: bool,
|
||||
}
|
||||
|
||||
impl Skeleton {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base: div().w_full().h_4().rounded_md(),
|
||||
style: StyleRefinement::default(),
|
||||
secondary: false,
|
||||
}
|
||||
}
|
||||
@@ -34,7 +36,7 @@ impl Default for Skeleton {
|
||||
|
||||
impl Styled for Skeleton {
|
||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||
self.base.style()
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +48,13 @@ impl RenderOnce for Skeleton {
|
||||
cx.theme().ghost_element_active
|
||||
};
|
||||
|
||||
div().child(
|
||||
self.base.bg(color).with_animation(
|
||||
div()
|
||||
.w_full()
|
||||
.h_4()
|
||||
.rounded_md()
|
||||
.refine_style(&self.style)
|
||||
.bg(color)
|
||||
.with_animation(
|
||||
"skeleton",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
@@ -56,7 +63,6 @@ impl RenderOnce for Skeleton {
|
||||
let v = 1.0 - delta * 0.5;
|
||||
this.opacity(v)
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::display::DisplayProfile;
|
||||
use common::display::ReadableProfile;
|
||||
use gpui::{
|
||||
AnyElement, AnyView, App, ElementId, HighlightStyle, InteractiveText, IntoElement,
|
||||
SharedString, StyledText, UnderlineStyle, Window,
|
||||
|
||||
Reference in New Issue
Block a user