Work on default theme, Notifications, Add ListHeader meta
(#3203)
Work on default theme, Notifications, Add ListHeader `meta` Also adds `naive_format_distance` & `naive_format_distance_from_now` Release Notes: - N/A
This commit is contained in:
commit
5e12b48ae0
22 changed files with 1130 additions and 302 deletions
|
@ -77,7 +77,7 @@ fn main() {
|
|||
WindowOptions {
|
||||
bounds: WindowBounds::Fixed(Bounds {
|
||||
origin: Default::default(),
|
||||
size: size(px(1700.), px(980.)).into(),
|
||||
size: size(px(1500.), px(780.)).into(),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|
|
|
@ -64,6 +64,7 @@ pub struct ThemeColors {
|
|||
pub element_selected: Hsla,
|
||||
pub element_disabled: Hsla,
|
||||
pub element_placeholder: Hsla,
|
||||
pub element_drop_target: Hsla,
|
||||
pub ghost_element: Hsla,
|
||||
pub ghost_element_hover: Hsla,
|
||||
pub ghost_element_active: Hsla,
|
||||
|
@ -83,6 +84,8 @@ pub struct ThemeColors {
|
|||
pub title_bar: Hsla,
|
||||
pub toolbar: Hsla,
|
||||
pub tab_bar: Hsla,
|
||||
pub tab_inactive: Hsla,
|
||||
pub tab_active: Hsla,
|
||||
pub editor: Hsla,
|
||||
pub editor_subheader: Hsla,
|
||||
pub editor_active_line: Hsla,
|
||||
|
|
|
@ -9,6 +9,10 @@ use crate::{
|
|||
ColorScale,
|
||||
};
|
||||
|
||||
fn neutral() -> ColorScaleSet {
|
||||
slate()
|
||||
}
|
||||
|
||||
impl Default for SystemColors {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
@ -24,16 +28,16 @@ impl Default for StatusColors {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
conflict: red().dark().step_11(),
|
||||
created: gpui2::black(),
|
||||
deleted: gpui2::black(),
|
||||
error: gpui2::black(),
|
||||
hidden: gpui2::black(),
|
||||
ignored: gpui2::black(),
|
||||
info: gpui2::black(),
|
||||
modified: gpui2::black(),
|
||||
renamed: gpui2::black(),
|
||||
success: gpui2::black(),
|
||||
warning: gpui2::black(),
|
||||
created: grass().dark().step_11(),
|
||||
deleted: red().dark().step_11(),
|
||||
error: red().dark().step_11(),
|
||||
hidden: neutral().dark().step_11(),
|
||||
ignored: neutral().dark().step_11(),
|
||||
info: blue().dark().step_11(),
|
||||
modified: yellow().dark().step_11(),
|
||||
renamed: blue().dark().step_11(),
|
||||
success: grass().dark().step_11(),
|
||||
warning: yellow().dark().step_11(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,12 +45,12 @@ impl Default for StatusColors {
|
|||
impl Default for GitStatusColors {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
conflict: gpui2::rgba(0xdec184ff).into(),
|
||||
created: gpui2::rgba(0xa1c181ff).into(),
|
||||
deleted: gpui2::rgba(0xd07277ff).into(),
|
||||
ignored: gpui2::rgba(0x555a63ff).into(),
|
||||
modified: gpui2::rgba(0x74ade8ff).into(),
|
||||
renamed: gpui2::rgba(0xdec184ff).into(),
|
||||
conflict: orange().dark().step_11(),
|
||||
created: grass().dark().step_11(),
|
||||
deleted: red().dark().step_11(),
|
||||
ignored: neutral().dark().step_11(),
|
||||
modified: yellow().dark().step_11(),
|
||||
renamed: blue().dark().step_11(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,54 +86,57 @@ impl SyntaxTheme {
|
|||
pub fn default_light() -> Self {
|
||||
Self {
|
||||
highlights: vec![
|
||||
("attribute".into(), cyan().light().step_11().into()),
|
||||
("boolean".into(), tomato().light().step_11().into()),
|
||||
("comment".into(), neutral().light().step_11().into()),
|
||||
("comment.doc".into(), iris().light().step_12().into()),
|
||||
("constant".into(), red().light().step_7().into()),
|
||||
("constructor".into(), red().light().step_7().into()),
|
||||
("embedded".into(), red().light().step_7().into()),
|
||||
("emphasis".into(), red().light().step_7().into()),
|
||||
("emphasis.strong".into(), red().light().step_7().into()),
|
||||
("enum".into(), red().light().step_7().into()),
|
||||
("function".into(), red().light().step_7().into()),
|
||||
("hint".into(), red().light().step_7().into()),
|
||||
("keyword".into(), orange().light().step_11().into()),
|
||||
("label".into(), red().light().step_7().into()),
|
||||
("link_text".into(), red().light().step_7().into()),
|
||||
("link_uri".into(), red().light().step_7().into()),
|
||||
("number".into(), red().light().step_7().into()),
|
||||
("operator".into(), red().light().step_7().into()),
|
||||
("predictive".into(), red().light().step_7().into()),
|
||||
("preproc".into(), red().light().step_7().into()),
|
||||
("primary".into(), red().light().step_7().into()),
|
||||
("property".into(), red().light().step_7().into()),
|
||||
("punctuation".into(), neutral().light().step_11().into()),
|
||||
(
|
||||
"string.special.symbol".into(),
|
||||
gpui2::rgba(0xad6e26ff).into(),
|
||||
"punctuation.bracket".into(),
|
||||
neutral().light().step_11().into(),
|
||||
),
|
||||
("hint".into(), gpui2::rgba(0x9294beff).into()),
|
||||
("link_uri".into(), gpui2::rgba(0x3882b7ff).into()),
|
||||
("type".into(), gpui2::rgba(0x3882b7ff).into()),
|
||||
("string.regex".into(), gpui2::rgba(0xad6e26ff).into()),
|
||||
("constant".into(), gpui2::rgba(0x669f59ff).into()),
|
||||
("function".into(), gpui2::rgba(0x5b79e3ff).into()),
|
||||
("string.special".into(), gpui2::rgba(0xad6e26ff).into()),
|
||||
("punctuation.bracket".into(), gpui2::rgba(0x4d4f52ff).into()),
|
||||
("variable".into(), gpui2::rgba(0x383a41ff).into()),
|
||||
("punctuation".into(), gpui2::rgba(0x383a41ff).into()),
|
||||
("property".into(), gpui2::rgba(0xd3604fff).into()),
|
||||
("string".into(), gpui2::rgba(0x649f57ff).into()),
|
||||
("predictive".into(), gpui2::rgba(0x9b9ec6ff).into()),
|
||||
("attribute".into(), gpui2::rgba(0x5c78e2ff).into()),
|
||||
("number".into(), gpui2::rgba(0xad6e25ff).into()),
|
||||
("constructor".into(), gpui2::rgba(0x5c78e2ff).into()),
|
||||
("embedded".into(), gpui2::rgba(0x383a41ff).into()),
|
||||
("title".into(), gpui2::rgba(0xd3604fff).into()),
|
||||
("tag".into(), gpui2::rgba(0x5c78e2ff).into()),
|
||||
("boolean".into(), gpui2::rgba(0xad6e25ff).into()),
|
||||
(
|
||||
"punctuation.list_marker".into(),
|
||||
gpui2::rgba(0xd3604fff).into(),
|
||||
),
|
||||
("variant".into(), gpui2::rgba(0x5b79e3ff).into()),
|
||||
("emphasis".into(), gpui2::rgba(0x5c78e2ff).into()),
|
||||
("link_text".into(), gpui2::rgba(0x5b79e3ff).into()),
|
||||
("comment".into(), gpui2::rgba(0xa2a3a7ff).into()),
|
||||
("punctuation.special".into(), gpui2::rgba(0xb92b46ff).into()),
|
||||
("emphasis.strong".into(), gpui2::rgba(0xad6e25ff).into()),
|
||||
("primary".into(), gpui2::rgba(0x383a41ff).into()),
|
||||
(
|
||||
"punctuation.delimiter".into(),
|
||||
gpui2::rgba(0x4d4f52ff).into(),
|
||||
neutral().light().step_11().into(),
|
||||
),
|
||||
("label".into(), gpui2::rgba(0x5c78e2ff).into()),
|
||||
("keyword".into(), gpui2::rgba(0xa449abff).into()),
|
||||
("string.escape".into(), gpui2::rgba(0x7c7e86ff).into()),
|
||||
("text.literal".into(), gpui2::rgba(0x649f57ff).into()),
|
||||
("variable.special".into(), gpui2::rgba(0xad6e25ff).into()),
|
||||
("comment.doc".into(), gpui2::rgba(0x7c7e86ff).into()),
|
||||
("enum".into(), gpui2::rgba(0xd3604fff).into()),
|
||||
("operator".into(), gpui2::rgba(0x3882b7ff).into()),
|
||||
("preproc".into(), gpui2::rgba(0x383a41ff).into()),
|
||||
(
|
||||
"punctuation.list_marker".into(),
|
||||
blue().light().step_11().into(),
|
||||
),
|
||||
("punctuation.special".into(), red().light().step_7().into()),
|
||||
("string".into(), jade().light().step_11().into()),
|
||||
("string.escape".into(), red().light().step_7().into()),
|
||||
("string.regex".into(), tomato().light().step_11().into()),
|
||||
("string.special".into(), red().light().step_7().into()),
|
||||
(
|
||||
"string.special.symbol".into(),
|
||||
red().light().step_7().into(),
|
||||
),
|
||||
("tag".into(), red().light().step_7().into()),
|
||||
("text.literal".into(), red().light().step_7().into()),
|
||||
("title".into(), red().light().step_7().into()),
|
||||
("type".into(), red().light().step_7().into()),
|
||||
("variable".into(), red().light().step_7().into()),
|
||||
("variable.special".into(), red().light().step_7().into()),
|
||||
("variant".into(), red().light().step_7().into()),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
@ -137,54 +144,54 @@ impl SyntaxTheme {
|
|||
pub fn default_dark() -> Self {
|
||||
Self {
|
||||
highlights: vec![
|
||||
("keyword".into(), gpui2::rgba(0xb477cfff).into()),
|
||||
("comment.doc".into(), gpui2::rgba(0x878e98ff).into()),
|
||||
("variant".into(), gpui2::rgba(0x73ade9ff).into()),
|
||||
("property".into(), gpui2::rgba(0xd07277ff).into()),
|
||||
("function".into(), gpui2::rgba(0x73ade9ff).into()),
|
||||
("type".into(), gpui2::rgba(0x6eb4bfff).into()),
|
||||
("tag".into(), gpui2::rgba(0x74ade8ff).into()),
|
||||
("string.escape".into(), gpui2::rgba(0x878e98ff).into()),
|
||||
("punctuation.bracket".into(), gpui2::rgba(0xb2b9c6ff).into()),
|
||||
("hint".into(), gpui2::rgba(0x5a6f89ff).into()),
|
||||
("punctuation".into(), gpui2::rgba(0xacb2beff).into()),
|
||||
("comment".into(), gpui2::rgba(0x5d636fff).into()),
|
||||
("emphasis".into(), gpui2::rgba(0x74ade8ff).into()),
|
||||
("punctuation.special".into(), gpui2::rgba(0xb1574bff).into()),
|
||||
("link_uri".into(), gpui2::rgba(0x6eb4bfff).into()),
|
||||
("string.regex".into(), gpui2::rgba(0xbf956aff).into()),
|
||||
("constructor".into(), gpui2::rgba(0x73ade9ff).into()),
|
||||
("operator".into(), gpui2::rgba(0x6eb4bfff).into()),
|
||||
("constant".into(), gpui2::rgba(0xdfc184ff).into()),
|
||||
("string.special".into(), gpui2::rgba(0xbf956aff).into()),
|
||||
("emphasis.strong".into(), gpui2::rgba(0xbf956aff).into()),
|
||||
("attribute".into(), cyan().dark().step_11().into()),
|
||||
("boolean".into(), tomato().dark().step_11().into()),
|
||||
("comment".into(), neutral().dark().step_11().into()),
|
||||
("comment.doc".into(), iris().dark().step_12().into()),
|
||||
("constant".into(), red().dark().step_7().into()),
|
||||
("constructor".into(), red().dark().step_7().into()),
|
||||
("embedded".into(), red().dark().step_7().into()),
|
||||
("emphasis".into(), red().dark().step_7().into()),
|
||||
("emphasis.strong".into(), red().dark().step_7().into()),
|
||||
("enum".into(), red().dark().step_7().into()),
|
||||
("function".into(), red().dark().step_7().into()),
|
||||
("hint".into(), red().dark().step_7().into()),
|
||||
("keyword".into(), orange().dark().step_11().into()),
|
||||
("label".into(), red().dark().step_7().into()),
|
||||
("link_text".into(), red().dark().step_7().into()),
|
||||
("link_uri".into(), red().dark().step_7().into()),
|
||||
("number".into(), red().dark().step_7().into()),
|
||||
("operator".into(), red().dark().step_7().into()),
|
||||
("predictive".into(), red().dark().step_7().into()),
|
||||
("preproc".into(), red().dark().step_7().into()),
|
||||
("primary".into(), red().dark().step_7().into()),
|
||||
("property".into(), red().dark().step_7().into()),
|
||||
("punctuation".into(), neutral().dark().step_11().into()),
|
||||
(
|
||||
"string.special.symbol".into(),
|
||||
gpui2::rgba(0xbf956aff).into(),
|
||||
"punctuation.bracket".into(),
|
||||
neutral().dark().step_11().into(),
|
||||
),
|
||||
("primary".into(), gpui2::rgba(0xacb2beff).into()),
|
||||
("preproc".into(), gpui2::rgba(0xc8ccd4ff).into()),
|
||||
("string".into(), gpui2::rgba(0xa1c181ff).into()),
|
||||
(
|
||||
"punctuation.delimiter".into(),
|
||||
gpui2::rgba(0xb2b9c6ff).into(),
|
||||
neutral().dark().step_11().into(),
|
||||
),
|
||||
("embedded".into(), gpui2::rgba(0xc8ccd4ff).into()),
|
||||
("enum".into(), gpui2::rgba(0xd07277ff).into()),
|
||||
("variable.special".into(), gpui2::rgba(0xbf956aff).into()),
|
||||
("text.literal".into(), gpui2::rgba(0xa1c181ff).into()),
|
||||
("attribute".into(), gpui2::rgba(0x74ade8ff).into()),
|
||||
("link_text".into(), gpui2::rgba(0x73ade9ff).into()),
|
||||
("title".into(), gpui2::rgba(0xd07277ff).into()),
|
||||
("predictive".into(), gpui2::rgba(0x5a6a87ff).into()),
|
||||
("number".into(), gpui2::rgba(0xbf956aff).into()),
|
||||
("label".into(), gpui2::rgba(0x74ade8ff).into()),
|
||||
("variable".into(), gpui2::rgba(0xc8ccd4ff).into()),
|
||||
("boolean".into(), gpui2::rgba(0xbf956aff).into()),
|
||||
(
|
||||
"punctuation.list_marker".into(),
|
||||
gpui2::rgba(0xd07277ff).into(),
|
||||
blue().dark().step_11().into(),
|
||||
),
|
||||
("punctuation.special".into(), red().dark().step_7().into()),
|
||||
("string".into(), jade().dark().step_11().into()),
|
||||
("string.escape".into(), red().dark().step_7().into()),
|
||||
("string.regex".into(), tomato().dark().step_11().into()),
|
||||
("string.special".into(), red().dark().step_7().into()),
|
||||
("string.special.symbol".into(), red().dark().step_7().into()),
|
||||
("tag".into(), red().dark().step_7().into()),
|
||||
("text.literal".into(), red().dark().step_7().into()),
|
||||
("title".into(), red().dark().step_7().into()),
|
||||
("type".into(), red().dark().step_7().into()),
|
||||
("variable".into(), red().dark().step_7().into()),
|
||||
("variable.special".into(), red().dark().step_7().into()),
|
||||
("variant".into(), red().dark().step_7().into()),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
@ -192,82 +199,92 @@ impl SyntaxTheme {
|
|||
|
||||
impl ThemeColors {
|
||||
pub fn default_light() -> Self {
|
||||
let system = SystemColors::default();
|
||||
|
||||
Self {
|
||||
border: gpui2::white(),
|
||||
border_variant: gpui2::white(),
|
||||
border_focused: gpui2::white(),
|
||||
border_transparent: gpui2::white(),
|
||||
elevated_surface: gpui2::white(),
|
||||
surface: gpui2::white(),
|
||||
background: gpui2::white(),
|
||||
element: gpui2::white(),
|
||||
element_hover: gpui2::white(),
|
||||
element_active: gpui2::white(),
|
||||
element_selected: gpui2::white(),
|
||||
element_disabled: gpui2::white(),
|
||||
element_placeholder: gpui2::white(),
|
||||
ghost_element: gpui2::white(),
|
||||
ghost_element_hover: gpui2::white(),
|
||||
ghost_element_active: gpui2::white(),
|
||||
ghost_element_selected: gpui2::white(),
|
||||
ghost_element_disabled: gpui2::white(),
|
||||
text: gpui2::white(),
|
||||
text_muted: gpui2::white(),
|
||||
text_placeholder: gpui2::white(),
|
||||
text_disabled: gpui2::white(),
|
||||
text_accent: gpui2::white(),
|
||||
icon: gpui2::white(),
|
||||
icon_muted: gpui2::white(),
|
||||
icon_disabled: gpui2::white(),
|
||||
icon_placeholder: gpui2::white(),
|
||||
icon_accent: gpui2::white(),
|
||||
status_bar: gpui2::white(),
|
||||
title_bar: gpui2::white(),
|
||||
toolbar: gpui2::white(),
|
||||
tab_bar: gpui2::white(),
|
||||
editor: gpui2::white(),
|
||||
editor_subheader: gpui2::white(),
|
||||
editor_active_line: gpui2::white(),
|
||||
border: neutral().light().step_6(),
|
||||
border_variant: neutral().light().step_5(),
|
||||
border_focused: blue().light().step_5(),
|
||||
border_transparent: system.transparent,
|
||||
elevated_surface: neutral().light().step_2(),
|
||||
surface: neutral().light().step_2(),
|
||||
background: neutral().light().step_1(),
|
||||
element: neutral().light().step_3(),
|
||||
element_hover: neutral().light().step_4(),
|
||||
element_active: neutral().light().step_5(),
|
||||
element_selected: neutral().light().step_5(),
|
||||
element_disabled: neutral().light_alpha().step_3(),
|
||||
element_placeholder: neutral().light().step_11(),
|
||||
element_drop_target: blue().light_alpha().step_2(),
|
||||
ghost_element: system.transparent,
|
||||
ghost_element_hover: neutral().light().step_4(),
|
||||
ghost_element_active: neutral().light().step_5(),
|
||||
ghost_element_selected: neutral().light().step_5(),
|
||||
ghost_element_disabled: neutral().light_alpha().step_3(),
|
||||
text: neutral().light().step_12(),
|
||||
text_muted: neutral().light().step_11(),
|
||||
text_placeholder: neutral().light().step_10(),
|
||||
text_disabled: neutral().light().step_9(),
|
||||
text_accent: blue().light().step_11(),
|
||||
icon: neutral().light().step_11(),
|
||||
icon_muted: neutral().light().step_10(),
|
||||
icon_disabled: neutral().light().step_9(),
|
||||
icon_placeholder: neutral().light().step_10(),
|
||||
icon_accent: blue().light().step_11(),
|
||||
status_bar: neutral().light().step_2(),
|
||||
title_bar: neutral().light().step_2(),
|
||||
toolbar: neutral().light().step_1(),
|
||||
tab_bar: neutral().light().step_2(),
|
||||
tab_active: neutral().light().step_1(),
|
||||
tab_inactive: neutral().light().step_2(),
|
||||
editor: neutral().light().step_1(),
|
||||
editor_subheader: neutral().light().step_2(),
|
||||
editor_active_line: neutral().light_alpha().step_3(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_dark() -> Self {
|
||||
let system = SystemColors::default();
|
||||
|
||||
Self {
|
||||
border: gpui2::rgba(0x464b57ff).into(),
|
||||
border_variant: gpui2::rgba(0x464b57ff).into(),
|
||||
border_focused: gpui2::rgba(0x293b5bff).into(),
|
||||
border_transparent: gpui2::rgba(0x00000000).into(),
|
||||
elevated_surface: gpui2::rgba(0x3b414dff).into(),
|
||||
surface: gpui2::rgba(0x2f343eff).into(),
|
||||
background: gpui2::rgba(0x3b414dff).into(),
|
||||
element: gpui2::rgba(0x3b414dff).into(),
|
||||
element_hover: gpui2::rgba(0xffffff1e).into(),
|
||||
element_active: gpui2::rgba(0xffffff28).into(),
|
||||
element_selected: gpui2::rgba(0x18243dff).into(),
|
||||
element_disabled: gpui2::rgba(0x00000000).into(),
|
||||
element_placeholder: gpui2::black(),
|
||||
ghost_element: gpui2::rgba(0x00000000).into(),
|
||||
ghost_element_hover: gpui2::rgba(0xffffff14).into(),
|
||||
ghost_element_active: gpui2::rgba(0xffffff1e).into(),
|
||||
ghost_element_selected: gpui2::rgba(0x18243dff).into(),
|
||||
ghost_element_disabled: gpui2::rgba(0x00000000).into(),
|
||||
text: gpui2::rgba(0xc8ccd4ff).into(),
|
||||
text_muted: gpui2::rgba(0x838994ff).into(),
|
||||
text_placeholder: gpui2::rgba(0xd07277ff).into(),
|
||||
text_disabled: gpui2::rgba(0x555a63ff).into(),
|
||||
text_accent: gpui2::rgba(0x74ade8ff).into(),
|
||||
icon: gpui2::black(),
|
||||
icon_muted: gpui2::rgba(0x838994ff).into(),
|
||||
icon_disabled: gpui2::black(),
|
||||
icon_placeholder: gpui2::black(),
|
||||
icon_accent: gpui2::black(),
|
||||
status_bar: gpui2::rgba(0x3b414dff).into(),
|
||||
title_bar: gpui2::rgba(0x3b414dff).into(),
|
||||
toolbar: gpui2::rgba(0x282c33ff).into(),
|
||||
tab_bar: gpui2::rgba(0x2f343eff).into(),
|
||||
editor: gpui2::rgba(0x282c33ff).into(),
|
||||
editor_subheader: gpui2::rgba(0x2f343eff).into(),
|
||||
editor_active_line: gpui2::rgba(0x2f343eff).into(),
|
||||
border: neutral().dark().step_6(),
|
||||
border_variant: neutral().dark().step_5(),
|
||||
border_focused: blue().dark().step_5(),
|
||||
border_transparent: system.transparent,
|
||||
elevated_surface: neutral().dark().step_2(),
|
||||
surface: neutral().dark().step_2(),
|
||||
background: neutral().dark().step_1(),
|
||||
element: neutral().dark().step_3(),
|
||||
element_hover: neutral().dark().step_4(),
|
||||
element_active: neutral().dark().step_5(),
|
||||
element_selected: neutral().dark().step_5(),
|
||||
element_disabled: neutral().dark_alpha().step_3(),
|
||||
element_placeholder: neutral().dark().step_11(),
|
||||
element_drop_target: blue().dark_alpha().step_2(),
|
||||
ghost_element: system.transparent,
|
||||
ghost_element_hover: neutral().dark().step_4(),
|
||||
ghost_element_active: neutral().dark().step_5(),
|
||||
ghost_element_selected: neutral().dark().step_5(),
|
||||
ghost_element_disabled: neutral().dark_alpha().step_3(),
|
||||
text: neutral().dark().step_12(),
|
||||
text_muted: neutral().dark().step_11(),
|
||||
text_placeholder: neutral().dark().step_10(),
|
||||
text_disabled: neutral().dark().step_9(),
|
||||
text_accent: blue().dark().step_11(),
|
||||
icon: neutral().dark().step_11(),
|
||||
icon_muted: neutral().dark().step_10(),
|
||||
icon_disabled: neutral().dark().step_9(),
|
||||
icon_placeholder: neutral().dark().step_10(),
|
||||
icon_accent: blue().dark().step_11(),
|
||||
status_bar: neutral().dark().step_2(),
|
||||
title_bar: neutral().dark().step_2(),
|
||||
toolbar: neutral().dark().step_1(),
|
||||
tab_bar: neutral().dark().step_2(),
|
||||
tab_active: neutral().dark().step_1(),
|
||||
tab_inactive: neutral().dark().step_2(),
|
||||
editor: neutral().dark().step_1(),
|
||||
editor_subheader: neutral().dark().step_2(),
|
||||
editor_active_line: neutral().dark_alpha().step_3(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,18 @@ impl ThemeVariant {
|
|||
&self.styles.syntax
|
||||
}
|
||||
|
||||
/// Returns the [`StatusColors`] for the theme.
|
||||
#[inline(always)]
|
||||
pub fn status(&self) -> &StatusColors {
|
||||
&self.styles.status
|
||||
}
|
||||
|
||||
/// Returns the [`GitStatusColors`] for the theme.
|
||||
#[inline(always)]
|
||||
pub fn git(&self) -> &GitStatusColors {
|
||||
&self.styles.git
|
||||
}
|
||||
|
||||
/// Returns the color for the syntax node with the given name.
|
||||
#[inline(always)]
|
||||
pub fn syntax_color(&self, name: &str) -> Hsla {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use gpui2::{div, relative, Div};
|
||||
use gpui2::{div, px, relative, Div};
|
||||
|
||||
use crate::settings::user_settings;
|
||||
use crate::{
|
||||
|
@ -15,12 +15,20 @@ pub enum ListItemVariant {
|
|||
Inset,
|
||||
}
|
||||
|
||||
pub enum ListHeaderMeta {
|
||||
// TODO: These should be IconButtons
|
||||
Tools(Vec<Icon>),
|
||||
// TODO: This should be a button
|
||||
Button(Label),
|
||||
Text(Label),
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ListHeader {
|
||||
label: SharedString,
|
||||
left_icon: Option<Icon>,
|
||||
meta: Option<ListHeaderMeta>,
|
||||
variant: ListItemVariant,
|
||||
state: InteractionState,
|
||||
toggleable: Toggleable,
|
||||
}
|
||||
|
||||
|
@ -29,9 +37,9 @@ impl ListHeader {
|
|||
Self {
|
||||
label: label.into(),
|
||||
left_icon: None,
|
||||
meta: None,
|
||||
variant: ListItemVariant::default(),
|
||||
state: InteractionState::default(),
|
||||
toggleable: Toggleable::Toggleable(ToggleState::Toggled),
|
||||
toggleable: Toggleable::NotToggleable,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,8 +58,8 @@ impl ListHeader {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn state(mut self, state: InteractionState) -> Self {
|
||||
self.state = state;
|
||||
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
|
||||
self.meta = meta;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -74,34 +82,36 @@ impl ListHeader {
|
|||
}
|
||||
}
|
||||
|
||||
fn label_color(&self) -> LabelColor {
|
||||
match self.state {
|
||||
InteractionState::Disabled => LabelColor::Disabled,
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_color(&self) -> IconColor {
|
||||
match self.state {
|
||||
InteractionState::Disabled => IconColor::Disabled,
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
|
||||
let is_toggled = self.toggleable.is_toggled();
|
||||
|
||||
let disclosure_control = self.disclosure_control();
|
||||
|
||||
let meta = match self.meta {
|
||||
Some(ListHeaderMeta::Tools(icons)) => div().child(
|
||||
h_stack()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.children(icons.into_iter().map(|i| {
|
||||
IconElement::new(i)
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small)
|
||||
})),
|
||||
),
|
||||
Some(ListHeaderMeta::Button(label)) => div().child(label),
|
||||
Some(ListHeaderMeta::Text(label)) => div().child(label),
|
||||
None => div(),
|
||||
};
|
||||
|
||||
h_stack()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().surface)
|
||||
.when(self.state == InteractionState::Focused, |this| {
|
||||
this.border()
|
||||
.border_color(cx.theme().colors().border_focused)
|
||||
})
|
||||
// TODO: Add focus state
|
||||
// .when(self.state == InteractionState::Focused, |this| {
|
||||
// this.border()
|
||||
// .border_color(cx.theme().colors().border_focused)
|
||||
// })
|
||||
.relative()
|
||||
.child(
|
||||
div()
|
||||
|
@ -109,22 +119,28 @@ impl ListHeader {
|
|||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||
.flex()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
h_stack()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.children(self.left_icon.map(|i| {
|
||||
IconElement::new(i)
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small)
|
||||
}))
|
||||
.child(Label::new(self.label.clone()).color(LabelColor::Muted)),
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.children(self.left_icon.map(|i| {
|
||||
IconElement::new(i)
|
||||
.color(IconColor::Muted)
|
||||
.size(IconSize::Small)
|
||||
}))
|
||||
.child(Label::new(self.label.clone()).color(LabelColor::Muted)),
|
||||
)
|
||||
.child(disclosure_control),
|
||||
)
|
||||
.child(disclosure_control),
|
||||
.child(meta),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -473,42 +489,63 @@ impl<V: 'static> ListDetailsEntry<V> {
|
|||
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let settings = user_settings(cx);
|
||||
|
||||
let (item_bg, item_bg_hover, item_bg_active) = match self.seen {
|
||||
true => (
|
||||
cx.theme().colors().ghost_element,
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
),
|
||||
false => (
|
||||
cx.theme().colors().element,
|
||||
cx.theme().colors().element_hover,
|
||||
cx.theme().colors().element_active,
|
||||
),
|
||||
};
|
||||
let (item_bg, item_bg_hover, item_bg_active) = (
|
||||
cx.theme().colors().ghost_element,
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
);
|
||||
|
||||
let label_color = match self.seen {
|
||||
true => LabelColor::Muted,
|
||||
false => LabelColor::Default,
|
||||
};
|
||||
|
||||
v_stack()
|
||||
div()
|
||||
.relative()
|
||||
.group("")
|
||||
.bg(item_bg)
|
||||
.px_1()
|
||||
.py_1_5()
|
||||
.px_2()
|
||||
.py_1p5()
|
||||
.w_full()
|
||||
.line_height(relative(1.2))
|
||||
.child(Label::new(self.label.clone()).color(label_color))
|
||||
.children(
|
||||
self.meta
|
||||
.map(|meta| Label::new(meta).color(LabelColor::Muted)),
|
||||
)
|
||||
.z_index(1)
|
||||
.when(!self.seen, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.absolute()
|
||||
.left(px(3.0))
|
||||
.top_3()
|
||||
.rounded_full()
|
||||
.border_2()
|
||||
.border_color(cx.theme().colors().surface)
|
||||
.w(px(9.0))
|
||||
.h(px(9.0))
|
||||
.z_index(2)
|
||||
.bg(cx.theme().status().info),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_stack()
|
||||
v_stack()
|
||||
.w_full()
|
||||
.line_height(relative(1.2))
|
||||
.gap_1()
|
||||
.justify_end()
|
||||
.children(self.actions.unwrap_or_default()),
|
||||
.child(
|
||||
div()
|
||||
.w_5()
|
||||
.h_5()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().colors().icon_accent),
|
||||
)
|
||||
.child(Label::new(self.label.clone()).color(label_color))
|
||||
.children(
|
||||
self.meta
|
||||
.map(|meta| Label::new(meta).color(LabelColor::Muted)),
|
||||
)
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_1()
|
||||
.justify_end()
|
||||
.children(self.actions.unwrap_or_default()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -522,7 +559,7 @@ impl ListSeparator {
|
|||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div().h_px().w_full().bg(cx.theme().colors().border)
|
||||
div().h_px().w_full().bg(cx.theme().colors().border_variant)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -564,14 +601,15 @@ impl<V: 'static> List<V> {
|
|||
let is_toggled = Toggleable::is_toggled(&self.toggleable);
|
||||
|
||||
let list_content = match (self.items.is_empty(), is_toggled) {
|
||||
(_, false) => div(),
|
||||
(false, _) => div().children(self.items),
|
||||
(true, _) => {
|
||||
(true, false) => div(),
|
||||
(true, true) => {
|
||||
div().child(Label::new(self.empty_message.clone()).color(LabelColor::Muted))
|
||||
}
|
||||
};
|
||||
|
||||
v_stack()
|
||||
.w_full()
|
||||
.py_1()
|
||||
.children(self.header.map(|header| header.toggleable(self.toggleable)))
|
||||
.child(list_content)
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
use crate::{prelude::*, static_new_notification_items, static_read_notification_items};
|
||||
use crate::{List, ListHeader};
|
||||
use crate::utils::naive_format_distance_from_now;
|
||||
use crate::{
|
||||
h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, Button, Icon,
|
||||
IconButton, IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator,
|
||||
UnreadIndicator,
|
||||
};
|
||||
use crate::{ClickHandler, ListHeader};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct NotificationsPanel {
|
||||
|
@ -16,31 +21,348 @@ impl NotificationsPanel {
|
|||
.id(self.id.clone())
|
||||
.flex()
|
||||
.flex_col()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.size_full()
|
||||
.bg(cx.theme().colors().surface)
|
||||
.child(
|
||||
div()
|
||||
.id("header")
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
ListHeader::new("Notifications").meta(Some(ListHeaderMeta::Tools(vec![
|
||||
Icon::AtSign,
|
||||
Icon::BellOff,
|
||||
Icon::MailOpen,
|
||||
]))),
|
||||
)
|
||||
.child(ListSeparator::new())
|
||||
.child(
|
||||
v_stack()
|
||||
.id("notifications-panel-scroll-view")
|
||||
.py_1()
|
||||
.overflow_y_scroll()
|
||||
.flex_1()
|
||||
.child(
|
||||
List::new(static_new_notification_items())
|
||||
.header(ListHeader::new("NEW").toggle(ToggleState::Toggled))
|
||||
.toggle(ToggleState::Toggled),
|
||||
div()
|
||||
.mx_2()
|
||||
.p_1()
|
||||
// TODO: Add cursor style
|
||||
// .cursor(Cursor::IBeam)
|
||||
.bg(cx.theme().colors().element)
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
Label::new("Search...")
|
||||
.color(LabelColor::Placeholder)
|
||||
.line_height_style(LineHeightStyle::UILabel),
|
||||
),
|
||||
)
|
||||
.child(v_stack().px_1().children(static_new_notification_items_2())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ButtonOrIconButton<V: 'static> {
|
||||
Button(Button<V>),
|
||||
IconButton(IconButton<V>),
|
||||
}
|
||||
|
||||
impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
|
||||
fn from(value: Button<V>) -> Self {
|
||||
Self::Button(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
|
||||
fn from(value: IconButton<V>) -> Self {
|
||||
Self::IconButton(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NotificationAction<V: 'static> {
|
||||
button: ButtonOrIconButton<V>,
|
||||
tooltip: SharedString,
|
||||
/// Shows after action is chosen
|
||||
///
|
||||
/// For example, if the action is "Accept" the taken message could be:
|
||||
///
|
||||
/// - `(None,"Accepted")` - "Accepted"
|
||||
///
|
||||
/// - `(Some(Icon::Check),"Accepted")` - ✓ "Accepted"
|
||||
taken_message: (Option<Icon>, SharedString),
|
||||
}
|
||||
|
||||
impl<V: 'static> NotificationAction<V> {
|
||||
pub fn new(
|
||||
button: impl Into<ButtonOrIconButton<V>>,
|
||||
tooltip: impl Into<SharedString>,
|
||||
(icon, taken_message): (Option<Icon>, impl Into<SharedString>),
|
||||
) -> Self {
|
||||
Self {
|
||||
button: button.into(),
|
||||
tooltip: tooltip.into(),
|
||||
taken_message: (icon, taken_message.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ActorOrIcon {
|
||||
Actor(PublicActor),
|
||||
Icon(Icon),
|
||||
}
|
||||
|
||||
pub struct NotificationMeta<V: 'static> {
|
||||
items: Vec<(Option<Icon>, SharedString, Option<ClickHandler<V>>)>,
|
||||
}
|
||||
|
||||
struct NotificationHandlers<V: 'static> {
|
||||
click: Option<ClickHandler<V>>,
|
||||
}
|
||||
|
||||
impl<V: 'static> Default for NotificationHandlers<V> {
|
||||
fn default() -> Self {
|
||||
Self { click: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Notification<V: 'static> {
|
||||
id: ElementId,
|
||||
slot: ActorOrIcon,
|
||||
message: SharedString,
|
||||
date_received: NaiveDateTime,
|
||||
meta: Option<NotificationMeta<V>>,
|
||||
actions: Option<[NotificationAction<V>; 2]>,
|
||||
unread: bool,
|
||||
new: bool,
|
||||
action_taken: Option<NotificationAction<V>>,
|
||||
handlers: NotificationHandlers<V>,
|
||||
}
|
||||
|
||||
impl<V> Notification<V> {
|
||||
fn new(
|
||||
id: ElementId,
|
||||
message: SharedString,
|
||||
date_received: NaiveDateTime,
|
||||
slot: ActorOrIcon,
|
||||
click_action: Option<ClickHandler<V>>,
|
||||
) -> Self {
|
||||
let handlers = if click_action.is_some() {
|
||||
NotificationHandlers {
|
||||
click: click_action,
|
||||
}
|
||||
} else {
|
||||
NotificationHandlers::default()
|
||||
};
|
||||
|
||||
Self {
|
||||
id,
|
||||
date_received,
|
||||
message,
|
||||
meta: None,
|
||||
slot,
|
||||
actions: None,
|
||||
unread: true,
|
||||
new: false,
|
||||
action_taken: None,
|
||||
handlers,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new notification with an actor slot.
|
||||
///
|
||||
/// Requires a click action.
|
||||
pub fn new_actor_message(
|
||||
id: impl Into<ElementId>,
|
||||
message: impl Into<SharedString>,
|
||||
date_received: NaiveDateTime,
|
||||
actor: PublicActor,
|
||||
click_action: ClickHandler<V>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
id.into(),
|
||||
message.into(),
|
||||
date_received,
|
||||
ActorOrIcon::Actor(actor),
|
||||
Some(click_action),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new notification with an icon slot.
|
||||
///
|
||||
/// Requires a click action.
|
||||
pub fn new_icon_message(
|
||||
id: impl Into<ElementId>,
|
||||
message: impl Into<SharedString>,
|
||||
date_received: NaiveDateTime,
|
||||
icon: Icon,
|
||||
click_action: ClickHandler<V>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
id.into(),
|
||||
message.into(),
|
||||
date_received,
|
||||
ActorOrIcon::Icon(icon),
|
||||
Some(click_action),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new notification with an actor slot
|
||||
/// and a Call To Action row.
|
||||
///
|
||||
/// Cannot take a click action due to required actions.
|
||||
pub fn new_actor_with_actions(
|
||||
id: impl Into<ElementId>,
|
||||
message: impl Into<SharedString>,
|
||||
date_received: NaiveDateTime,
|
||||
actor: PublicActor,
|
||||
actions: [NotificationAction<V>; 2],
|
||||
) -> Self {
|
||||
Self::new(
|
||||
id.into(),
|
||||
message.into(),
|
||||
date_received,
|
||||
ActorOrIcon::Actor(actor),
|
||||
None,
|
||||
)
|
||||
.actions(actions)
|
||||
}
|
||||
|
||||
/// Creates a new notification with an icon slot
|
||||
/// and a Call To Action row.
|
||||
///
|
||||
/// Cannot take a click action due to required actions.
|
||||
pub fn new_icon_with_actions(
|
||||
id: impl Into<ElementId>,
|
||||
message: impl Into<SharedString>,
|
||||
date_received: NaiveDateTime,
|
||||
icon: Icon,
|
||||
actions: [NotificationAction<V>; 2],
|
||||
) -> Self {
|
||||
Self::new(
|
||||
id.into(),
|
||||
message.into(),
|
||||
date_received,
|
||||
ActorOrIcon::Icon(icon),
|
||||
None,
|
||||
)
|
||||
.actions(actions)
|
||||
}
|
||||
|
||||
fn on_click(mut self, handler: ClickHandler<V>) -> Self {
|
||||
self.handlers.click = Some(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn actions(mut self, actions: [NotificationAction<V>; 2]) -> Self {
|
||||
self.actions = Some(actions);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn meta(mut self, meta: NotificationMeta<V>) -> Self {
|
||||
self.meta = Some(meta);
|
||||
self
|
||||
}
|
||||
|
||||
fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
if let Some(meta) = &self.meta {
|
||||
h_stack().children(
|
||||
meta.items
|
||||
.iter()
|
||||
.map(|(icon, text, _)| {
|
||||
let mut meta_el = div();
|
||||
if let Some(icon) = icon {
|
||||
meta_el = meta_el.child(IconElement::new(icon.clone()));
|
||||
}
|
||||
meta_el.child(Label::new(text.clone()).color(LabelColor::Muted))
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
} else {
|
||||
div()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
match &self.slot {
|
||||
ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(),
|
||||
ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.relative()
|
||||
.id(self.id.clone())
|
||||
.p_1()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.w_full()
|
||||
.children(
|
||||
Some(
|
||||
div()
|
||||
.absolute()
|
||||
.left(px(3.0))
|
||||
.top_3()
|
||||
.z_index(2)
|
||||
.child(UnreadIndicator::new()),
|
||||
)
|
||||
.filter(|_| self.unread),
|
||||
)
|
||||
.child(
|
||||
v_stack()
|
||||
.z_index(1)
|
||||
.gap_1()
|
||||
.w_full()
|
||||
.child(
|
||||
h_stack()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.child(self.render_slot(cx))
|
||||
.child(div().flex_1().child(Label::new(self.message.clone()))),
|
||||
)
|
||||
.child(
|
||||
List::new(static_read_notification_items())
|
||||
.header(ListHeader::new("EARLIER").toggle(ToggleState::Toggled))
|
||||
.empty_message("No new notifications")
|
||||
.toggle(ToggleState::Toggled),
|
||||
h_stack()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(naive_format_distance_from_now(
|
||||
self.date_received,
|
||||
true,
|
||||
true,
|
||||
))
|
||||
.color(LabelColor::Muted),
|
||||
)
|
||||
.child(self.render_meta_items(cx)),
|
||||
)
|
||||
.child(match (self.actions, self.action_taken) {
|
||||
// Show nothing
|
||||
(None, _) => div(),
|
||||
// Show the taken_message
|
||||
(Some(_), Some(action_taken)) => h_stack()
|
||||
.children(action_taken.taken_message.0.map(|icon| {
|
||||
IconElement::new(icon).color(crate::IconColor::Muted)
|
||||
}))
|
||||
.child(
|
||||
Label::new(action_taken.taken_message.1.clone())
|
||||
.color(LabelColor::Muted),
|
||||
),
|
||||
// Show the actions
|
||||
(Some(actions), None) => {
|
||||
h_stack().children(actions.map(|action| match action.button {
|
||||
ButtonOrIconButton::Button(button) => {
|
||||
Component::render(button)
|
||||
}
|
||||
ButtonOrIconButton::IconButton(icon_button) => {
|
||||
Component::render(icon_button)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
use chrono::NaiveDateTime;
|
||||
use gpui2::{px, Styled};
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{Icon, IconColor, IconElement, Label, LabelColor};
|
||||
use gpui2::{black, red, Div, ElementId, Render, View, VisualContext};
|
||||
use gpui2::{red, Div, ElementId, Render, View, VisualContext};
|
||||
|
||||
#[derive(Component, Clone)]
|
||||
pub struct Tab {
|
||||
|
@ -108,13 +108,13 @@ impl Tab {
|
|||
let close_icon = || IconElement::new(Icon::Close).color(IconColor::Muted);
|
||||
|
||||
let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current {
|
||||
true => (
|
||||
cx.theme().colors().ghost_element,
|
||||
false => (
|
||||
cx.theme().colors().tab_inactive,
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
),
|
||||
false => (
|
||||
cx.theme().colors().element,
|
||||
true => (
|
||||
cx.theme().colors().tab_active,
|
||||
cx.theme().colors().element_hover,
|
||||
cx.theme().colors().element_active,
|
||||
),
|
||||
|
@ -127,7 +127,7 @@ impl Tab {
|
|||
div()
|
||||
.id(self.id.clone())
|
||||
.on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone()))
|
||||
.drag_over::<TabDragState>(|d| d.bg(black()))
|
||||
.drag_over::<TabDragState>(|d| d.bg(cx.theme().colors().element_drop_target))
|
||||
.on_drop(|_view, state: View<TabDragState>, cx| {
|
||||
eprintln!("{:?}", state.read(cx));
|
||||
})
|
||||
|
@ -144,7 +144,7 @@ impl Tab {
|
|||
.px_1()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.gap_1p5()
|
||||
.children(has_fs_conflict.then(|| {
|
||||
IconElement::new(Icon::ExclamationTriangle)
|
||||
.size(crate::IconSize::Small)
|
||||
|
|
|
@ -27,6 +27,7 @@ impl TabBar {
|
|||
let (can_navigate_back, can_navigate_forward) = self.can_navigate;
|
||||
|
||||
div()
|
||||
.group("tab_bar")
|
||||
.id(self.id.clone())
|
||||
.w_full()
|
||||
.flex()
|
||||
|
@ -34,6 +35,7 @@ impl TabBar {
|
|||
// Left Side
|
||||
.child(
|
||||
div()
|
||||
.relative()
|
||||
.px_1()
|
||||
.flex()
|
||||
.flex_none()
|
||||
|
@ -41,6 +43,7 @@ impl TabBar {
|
|||
// Nav Buttons
|
||||
.child(
|
||||
div()
|
||||
.right_0()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
|
@ -67,10 +70,15 @@ impl TabBar {
|
|||
// Right Side
|
||||
.child(
|
||||
div()
|
||||
// We only use absolute here since we don't
|
||||
// have opacity or `hidden()` yet
|
||||
.absolute()
|
||||
.neg_top_7()
|
||||
.px_1()
|
||||
.flex()
|
||||
.flex_none()
|
||||
.gap_2()
|
||||
.group_hover("tab_bar", |this| this.top_0())
|
||||
// Nav Buttons
|
||||
.child(
|
||||
div()
|
||||
|
|
|
@ -2,6 +2,7 @@ mod avatar;
|
|||
mod button;
|
||||
mod details;
|
||||
mod icon;
|
||||
mod indicator;
|
||||
mod input;
|
||||
mod label;
|
||||
mod player;
|
||||
|
@ -12,6 +13,7 @@ pub use avatar::*;
|
|||
pub use button::*;
|
||||
pub use details::*;
|
||||
pub use icon::*;
|
||||
pub use indicator::*;
|
||||
pub use input::*;
|
||||
pub use label::*;
|
||||
pub use player::*;
|
||||
|
|
|
@ -26,23 +26,21 @@ pub enum IconColor {
|
|||
|
||||
impl IconColor {
|
||||
pub fn color(self, cx: &WindowContext) -> Hsla {
|
||||
let theme_colors = cx.theme().colors();
|
||||
|
||||
match self {
|
||||
IconColor::Default => theme_colors.icon,
|
||||
IconColor::Muted => theme_colors.icon_muted,
|
||||
IconColor::Disabled => theme_colors.icon_disabled,
|
||||
IconColor::Placeholder => theme_colors.icon_placeholder,
|
||||
IconColor::Accent => theme_colors.icon_accent,
|
||||
IconColor::Error => gpui2::red(),
|
||||
IconColor::Warning => gpui2::red(),
|
||||
IconColor::Success => gpui2::red(),
|
||||
IconColor::Info => gpui2::red(),
|
||||
IconColor::Default => cx.theme().colors().icon,
|
||||
IconColor::Muted => cx.theme().colors().icon_muted,
|
||||
IconColor::Disabled => cx.theme().colors().icon_disabled,
|
||||
IconColor::Placeholder => cx.theme().colors().icon_placeholder,
|
||||
IconColor::Accent => cx.theme().colors().icon_accent,
|
||||
IconColor::Error => cx.theme().status().error,
|
||||
IconColor::Warning => cx.theme().status().warning,
|
||||
IconColor::Success => cx.theme().status().success,
|
||||
IconColor::Info => cx.theme().status().info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Copy, Clone, EnumIter)]
|
||||
#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
|
||||
pub enum Icon {
|
||||
Ai,
|
||||
ArrowLeft,
|
||||
|
@ -51,6 +49,7 @@ pub enum Icon {
|
|||
AudioOff,
|
||||
AudioOn,
|
||||
Bolt,
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
|
@ -69,7 +68,6 @@ pub enum Icon {
|
|||
Folder,
|
||||
FolderOpen,
|
||||
FolderX,
|
||||
#[default]
|
||||
Hash,
|
||||
InlayHint,
|
||||
MagicWand,
|
||||
|
@ -91,6 +89,11 @@ pub enum Icon {
|
|||
XCircle,
|
||||
Copilot,
|
||||
Envelope,
|
||||
Bell,
|
||||
BellOff,
|
||||
BellRing,
|
||||
MailOpen,
|
||||
AtSign,
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
|
@ -103,6 +106,7 @@ impl Icon {
|
|||
Icon::AudioOff => "icons/speaker-off.svg",
|
||||
Icon::AudioOn => "icons/speaker-loud.svg",
|
||||
Icon::Bolt => "icons/bolt.svg",
|
||||
Icon::Check => "icons/check.svg",
|
||||
Icon::ChevronDown => "icons/chevron_down.svg",
|
||||
Icon::ChevronLeft => "icons/chevron_left.svg",
|
||||
Icon::ChevronRight => "icons/chevron_right.svg",
|
||||
|
@ -142,6 +146,11 @@ impl Icon {
|
|||
Icon::XCircle => "icons/error.svg",
|
||||
Icon::Copilot => "icons/copilot.svg",
|
||||
Icon::Envelope => "icons/feedback.svg",
|
||||
Icon::Bell => "icons/bell.svg",
|
||||
Icon::BellOff => "icons/bell-off.svg",
|
||||
Icon::BellRing => "icons/bell-ring.svg",
|
||||
Icon::MailOpen => "icons/mail-open.svg",
|
||||
Icon::AtSign => "icons/at-sign.svg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
crates/ui2/src/elements/indicator.rs
Normal file
23
crates/ui2/src/elements/indicator.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use gpui2::px;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct UnreadIndicator;
|
||||
|
||||
impl UnreadIndicator {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
div()
|
||||
.rounded_full()
|
||||
.border_2()
|
||||
.border_color(cx.theme().colors().surface)
|
||||
.w(px(9.0))
|
||||
.h(px(9.0))
|
||||
.z_index(2)
|
||||
.bg(cx.theme().status().info)
|
||||
}
|
||||
}
|
|
@ -21,11 +21,11 @@ impl LabelColor {
|
|||
match self {
|
||||
Self::Default => cx.theme().colors().text,
|
||||
Self::Muted => cx.theme().colors().text_muted,
|
||||
Self::Created => gpui2::red(),
|
||||
Self::Modified => gpui2::red(),
|
||||
Self::Deleted => gpui2::red(),
|
||||
Self::Created => cx.theme().status().created,
|
||||
Self::Modified => cx.theme().status().modified,
|
||||
Self::Deleted => cx.theme().status().deleted,
|
||||
Self::Disabled => cx.theme().colors().text_disabled,
|
||||
Self::Hidden => gpui2::red(),
|
||||
Self::Hidden => cx.theme().status().hidden,
|
||||
Self::Placeholder => cx.theme().colors().text_placeholder,
|
||||
Self::Accent => cx.theme().colors().text_accent,
|
||||
}
|
||||
|
@ -79,8 +79,7 @@ impl Label {
|
|||
this.relative().child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_px()
|
||||
.my_auto()
|
||||
.top_1_2()
|
||||
.w_full()
|
||||
.h_px()
|
||||
.bg(LabelColor::Hidden.hsla(cx)),
|
||||
|
|
|
@ -23,6 +23,7 @@ mod elevation;
|
|||
pub mod prelude;
|
||||
pub mod settings;
|
||||
mod static_data;
|
||||
pub mod utils;
|
||||
|
||||
pub use components::*;
|
||||
pub use elements::*;
|
||||
|
|
|
@ -10,6 +10,24 @@ pub use theme2::ActiveTheme;
|
|||
use gpui2::Hsla;
|
||||
use strum::EnumIter;
|
||||
|
||||
/// Represents a person with a Zed account's public profile.
|
||||
/// All data in this struct should be considered public.
|
||||
pub struct PublicActor {
|
||||
pub username: SharedString,
|
||||
pub avatar: SharedString,
|
||||
pub is_contact: bool,
|
||||
}
|
||||
|
||||
impl PublicActor {
|
||||
pub fn new(username: impl Into<SharedString>, avatar: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
username: username.into(),
|
||||
avatar: avatar.into(),
|
||||
is_contact: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||
pub enum FileSystemStatus {
|
||||
#[default]
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::DateTime;
|
||||
use gpui2::{AppContext, ViewContext};
|
||||
use rand::Rng;
|
||||
use theme2::ActiveTheme;
|
||||
|
||||
use crate::{
|
||||
Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
|
||||
HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListItem,
|
||||
Livestream, MicStatus, ModifierKeys, PaletteItem, Player, PlayerCallStatus,
|
||||
PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus,
|
||||
HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListSubHeader,
|
||||
Livestream, MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus,
|
||||
PlayerWithCallStatus, PublicActor, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus,
|
||||
};
|
||||
use crate::{HighlightedText, ListDetailsEntry};
|
||||
use crate::{ListItem, NotificationAction};
|
||||
|
||||
pub fn static_tabs_example() -> Vec<Tab> {
|
||||
vec![
|
||||
|
@ -325,27 +328,227 @@ pub fn static_players_with_call_status() -> Vec<PlayerWithCallStatus> {
|
|||
]
|
||||
}
|
||||
|
||||
pub fn static_new_notification_items<V: 'static>() -> Vec<ListItem<V>> {
|
||||
pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
|
||||
vec![
|
||||
ListDetailsEntry::new("maxdeviant invited you to join a stream in #design.")
|
||||
.meta("4 people in stream."),
|
||||
ListDetailsEntry::new("nathansobo accepted your contact request."),
|
||||
Notification::new_icon_message(
|
||||
"notif-1",
|
||||
"You were mentioned in a note.",
|
||||
DateTime::parse_from_rfc3339("2023-11-02T11:59:57Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
Icon::AtSign,
|
||||
Arc::new(|_, _| {}),
|
||||
),
|
||||
Notification::new_actor_with_actions(
|
||||
"notif-2",
|
||||
"as-cii sent you a contact request.",
|
||||
DateTime::parse_from_rfc3339("2023-11-02T12:09:07Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"),
|
||||
[
|
||||
NotificationAction::new(
|
||||
Button::new("Decline"),
|
||||
"Decline Request",
|
||||
(Some(Icon::XCircle), "Declined"),
|
||||
),
|
||||
NotificationAction::new(
|
||||
Button::new("Accept").variant(crate::ButtonVariant::Filled),
|
||||
"Accept Request",
|
||||
(Some(Icon::Check), "Accepted"),
|
||||
),
|
||||
],
|
||||
),
|
||||
Notification::new_icon_message(
|
||||
"notif-3",
|
||||
"You were mentioned #design.",
|
||||
DateTime::parse_from_rfc3339("2023-11-02T12:09:07Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
Icon::MessageBubbles,
|
||||
Arc::new(|_, _| {}),
|
||||
),
|
||||
Notification::new_actor_with_actions(
|
||||
"notif-4",
|
||||
"as-cii sent you a contact request.",
|
||||
DateTime::parse_from_rfc3339("2023-11-01T12:09:07Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"),
|
||||
[
|
||||
NotificationAction::new(
|
||||
Button::new("Decline"),
|
||||
"Decline Request",
|
||||
(Some(Icon::XCircle), "Declined"),
|
||||
),
|
||||
NotificationAction::new(
|
||||
Button::new("Accept").variant(crate::ButtonVariant::Filled),
|
||||
"Accept Request",
|
||||
(Some(Icon::Check), "Accepted"),
|
||||
),
|
||||
],
|
||||
),
|
||||
Notification::new_icon_message(
|
||||
"notif-5",
|
||||
"You were mentioned in a note.",
|
||||
DateTime::parse_from_rfc3339("2023-10-28T12:09:07Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
Icon::AtSign,
|
||||
Arc::new(|_, _| {}),
|
||||
),
|
||||
Notification::new_actor_with_actions(
|
||||
"notif-6",
|
||||
"as-cii sent you a contact request.",
|
||||
DateTime::parse_from_rfc3339("2022-10-25T12:09:07Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"),
|
||||
[
|
||||
NotificationAction::new(
|
||||
Button::new("Decline"),
|
||||
"Decline Request",
|
||||
(Some(Icon::XCircle), "Declined"),
|
||||
),
|
||||
NotificationAction::new(
|
||||
Button::new("Accept").variant(crate::ButtonVariant::Filled),
|
||||
"Accept Request",
|
||||
(Some(Icon::Check), "Accepted"),
|
||||
),
|
||||
],
|
||||
),
|
||||
Notification::new_icon_message(
|
||||
"notif-7",
|
||||
"You were mentioned in a note.",
|
||||
DateTime::parse_from_rfc3339("2022-10-14T12:09:07Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
Icon::AtSign,
|
||||
Arc::new(|_, _| {}),
|
||||
),
|
||||
Notification::new_actor_with_actions(
|
||||
"notif-8",
|
||||
"as-cii sent you a contact request.",
|
||||
DateTime::parse_from_rfc3339("2021-10-12T12:09:07Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"),
|
||||
[
|
||||
NotificationAction::new(
|
||||
Button::new("Decline"),
|
||||
"Decline Request",
|
||||
(Some(Icon::XCircle), "Declined"),
|
||||
),
|
||||
NotificationAction::new(
|
||||
Button::new("Accept").variant(crate::ButtonVariant::Filled),
|
||||
"Accept Request",
|
||||
(Some(Icon::Check), "Accepted"),
|
||||
),
|
||||
],
|
||||
),
|
||||
Notification::new_icon_message(
|
||||
"notif-9",
|
||||
"You were mentioned in a note.",
|
||||
DateTime::parse_from_rfc3339("2021-02-02T12:09:07Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
Icon::AtSign,
|
||||
Arc::new(|_, _| {}),
|
||||
),
|
||||
Notification::new_actor_with_actions(
|
||||
"notif-10",
|
||||
"as-cii sent you a contact request.",
|
||||
DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"),
|
||||
[
|
||||
NotificationAction::new(
|
||||
Button::new("Decline"),
|
||||
"Decline Request",
|
||||
(Some(Icon::XCircle), "Declined"),
|
||||
),
|
||||
NotificationAction::new(
|
||||
Button::new("Accept").variant(crate::ButtonVariant::Filled),
|
||||
"Accept Request",
|
||||
(Some(Icon::Check), "Accepted"),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn static_read_notification_items<V: 'static>() -> Vec<ListItem<V>> {
|
||||
pub fn static_new_notification_items<V: 'static>() -> Vec<ListItem<V>> {
|
||||
vec![
|
||||
ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![
|
||||
Button::new("Decline"),
|
||||
Button::new("Accept").variant(crate::ButtonVariant::Filled),
|
||||
]),
|
||||
ListDetailsEntry::new("maxdeviant invited you to a stream in #design.")
|
||||
.seen(true)
|
||||
.meta("This stream has ended."),
|
||||
ListDetailsEntry::new("as-cii accepted your contact request."),
|
||||
ListItem::Header(ListSubHeader::new("New")),
|
||||
ListItem::Details(
|
||||
ListDetailsEntry::new("maxdeviant invited you to join a stream in #design.")
|
||||
.meta("4 people in stream."),
|
||||
),
|
||||
ListItem::Details(ListDetailsEntry::new(
|
||||
"nathansobo accepted your contact request.",
|
||||
)),
|
||||
ListItem::Header(ListSubHeader::new("Earlier")),
|
||||
ListItem::Details(
|
||||
ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![
|
||||
Button::new("Decline"),
|
||||
Button::new("Accept").variant(crate::ButtonVariant::Filled),
|
||||
]),
|
||||
),
|
||||
ListItem::Details(
|
||||
ListDetailsEntry::new("maxdeviant invited you to a stream in #design.")
|
||||
.seen(true)
|
||||
.meta("This stream has ended."),
|
||||
),
|
||||
ListItem::Details(ListDetailsEntry::new(
|
||||
"as-cii accepted your contact request.",
|
||||
)),
|
||||
ListItem::Details(
|
||||
ListDetailsEntry::new("You were added as an admin on the #gpui2 channel.").seen(true),
|
||||
),
|
||||
ListItem::Details(ListDetailsEntry::new(
|
||||
"osiewicz accepted your contact request.",
|
||||
)),
|
||||
ListItem::Details(ListDetailsEntry::new(
|
||||
"ConradIrwin accepted your contact request.",
|
||||
)),
|
||||
ListItem::Details(
|
||||
ListDetailsEntry::new("nathansobo invited you to a stream in #gpui2.")
|
||||
.seen(true)
|
||||
.meta("This stream has ended."),
|
||||
),
|
||||
ListItem::Details(ListDetailsEntry::new(
|
||||
"nathansobo accepted your contact request.",
|
||||
)),
|
||||
ListItem::Header(ListSubHeader::new("Earlier")),
|
||||
ListItem::Details(
|
||||
ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![
|
||||
Button::new("Decline"),
|
||||
Button::new("Accept").variant(crate::ButtonVariant::Filled),
|
||||
]),
|
||||
),
|
||||
ListItem::Details(
|
||||
ListDetailsEntry::new("maxdeviant invited you to a stream in #design.")
|
||||
.seen(true)
|
||||
.meta("This stream has ended."),
|
||||
),
|
||||
ListItem::Details(ListDetailsEntry::new(
|
||||
"as-cii accepted your contact request.",
|
||||
)),
|
||||
ListItem::Details(
|
||||
ListDetailsEntry::new("You were added as an admin on the #gpui2 channel.").seen(true),
|
||||
),
|
||||
ListItem::Details(ListDetailsEntry::new(
|
||||
"osiewicz accepted your contact request.",
|
||||
)),
|
||||
ListItem::Details(ListDetailsEntry::new(
|
||||
"ConradIrwin accepted your contact request.",
|
||||
)),
|
||||
ListItem::Details(
|
||||
ListDetailsEntry::new("nathansobo invited you to a stream in #gpui2.")
|
||||
.seen(true)
|
||||
.meta("This stream has ended."),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
|
|
3
crates/ui2/src/utils.rs
Normal file
3
crates/ui2/src/utils.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod format_distance;
|
||||
|
||||
pub use format_distance::*;
|
173
crates/ui2/src/utils/format_distance.rs
Normal file
173
crates/ui2/src/utils/format_distance.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
use chrono::NaiveDateTime;
|
||||
|
||||
fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
|
||||
let duration = date.signed_duration_since(base_date);
|
||||
-duration.num_seconds()
|
||||
}
|
||||
|
||||
fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String {
|
||||
let suffix = if distance < 0 { " from now" } else { " ago" };
|
||||
|
||||
let d = distance.abs();
|
||||
|
||||
let minutes = d / 60;
|
||||
let hours = d / 3600;
|
||||
let days = d / 86400;
|
||||
let months = d / 2592000;
|
||||
let years = d / 31536000;
|
||||
|
||||
let string = if d < 5 && include_seconds {
|
||||
"less than 5 seconds".to_string()
|
||||
} else if d < 10 && include_seconds {
|
||||
"less than 10 seconds".to_string()
|
||||
} else if d < 20 && include_seconds {
|
||||
"less than 20 seconds".to_string()
|
||||
} else if d < 40 && include_seconds {
|
||||
"half a minute".to_string()
|
||||
} else if d < 60 && include_seconds {
|
||||
"less than a minute".to_string()
|
||||
} else if d < 90 && include_seconds {
|
||||
"1 minute".to_string()
|
||||
} else if d < 30 {
|
||||
"less than a minute".to_string()
|
||||
} else if d < 90 {
|
||||
"1 minute".to_string()
|
||||
} else if d < 2700 {
|
||||
format!("{} minutes", minutes)
|
||||
} else if d < 5400 {
|
||||
"about 1 hour".to_string()
|
||||
} else if d < 86400 {
|
||||
format!("about {} hours", hours)
|
||||
} else if d < 172800 {
|
||||
"1 day".to_string()
|
||||
} else if d < 2592000 {
|
||||
format!("{} days", days)
|
||||
} else if d < 5184000 {
|
||||
"about 1 month".to_string()
|
||||
} else if d < 7776000 {
|
||||
"about 2 months".to_string()
|
||||
} else if d < 31540000 {
|
||||
format!("{} months", months)
|
||||
} else if d < 39425000 {
|
||||
"about 1 year".to_string()
|
||||
} else if d < 55195000 {
|
||||
"over 1 year".to_string()
|
||||
} else if d < 63080000 {
|
||||
"almost 2 years".to_string()
|
||||
} else {
|
||||
let years = d / 31536000;
|
||||
let remaining_months = (d % 31536000) / 2592000;
|
||||
|
||||
if remaining_months < 3 {
|
||||
format!("about {} years", years)
|
||||
} else if remaining_months < 9 {
|
||||
format!("over {} years", years)
|
||||
} else {
|
||||
format!("almost {} years", years + 1)
|
||||
}
|
||||
};
|
||||
|
||||
if add_suffix {
|
||||
return format!("{}{}", string, suffix);
|
||||
} else {
|
||||
string
|
||||
}
|
||||
}
|
||||
|
||||
pub fn naive_format_distance(
|
||||
date: NaiveDateTime,
|
||||
base_date: NaiveDateTime,
|
||||
include_seconds: bool,
|
||||
add_suffix: bool,
|
||||
) -> String {
|
||||
let distance = distance_in_seconds(date, base_date);
|
||||
|
||||
distance_string(distance, include_seconds, add_suffix)
|
||||
}
|
||||
|
||||
pub fn naive_format_distance_from_now(
|
||||
datetime: NaiveDateTime,
|
||||
include_seconds: bool,
|
||||
add_suffix: bool,
|
||||
) -> String {
|
||||
let now = chrono::offset::Local::now().naive_local();
|
||||
|
||||
naive_format_distance(datetime, now, include_seconds, add_suffix)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
#[test]
|
||||
fn test_naive_format_distance() {
|
||||
let date =
|
||||
NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date");
|
||||
let base_date =
|
||||
NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date");
|
||||
|
||||
assert_eq!(
|
||||
"about 2 hours",
|
||||
naive_format_distance(date, base_date, false, false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naive_format_distance_with_suffix() {
|
||||
let date =
|
||||
NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date");
|
||||
let base_date =
|
||||
NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date");
|
||||
|
||||
assert_eq!(
|
||||
"about 2 hours from now",
|
||||
naive_format_distance(date, base_date, false, true)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naive_format_distance_from_now() {
|
||||
let date = NaiveDateTime::parse_from_str("1969-07-20T00:00:00Z", "%Y-%m-%dT%H:%M:%SZ")
|
||||
.expect("Invalid NaiveDateTime for date");
|
||||
|
||||
assert_eq!(
|
||||
"over 54 years ago",
|
||||
naive_format_distance_from_now(date, false, true)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naive_format_distance_string() {
|
||||
assert_eq!(distance_string(3, false, false), "less than a minute");
|
||||
assert_eq!(distance_string(7, false, false), "less than a minute");
|
||||
assert_eq!(distance_string(13, false, false), "less than a minute");
|
||||
assert_eq!(distance_string(21, false, false), "less than a minute");
|
||||
assert_eq!(distance_string(45, false, false), "1 minute");
|
||||
assert_eq!(distance_string(61, false, false), "1 minute");
|
||||
assert_eq!(distance_string(1920, false, false), "32 minutes");
|
||||
assert_eq!(distance_string(3902, false, false), "about 1 hour");
|
||||
assert_eq!(distance_string(18002, false, false), "about 5 hours");
|
||||
assert_eq!(distance_string(86470, false, false), "1 day");
|
||||
assert_eq!(distance_string(345880, false, false), "4 days");
|
||||
assert_eq!(distance_string(2764800, false, false), "about 1 month");
|
||||
assert_eq!(distance_string(5184000, false, false), "about 2 months");
|
||||
assert_eq!(distance_string(10368000, false, false), "4 months");
|
||||
assert_eq!(distance_string(34694000, false, false), "about 1 year");
|
||||
assert_eq!(distance_string(47310000, false, false), "over 1 year");
|
||||
assert_eq!(distance_string(61503000, false, false), "almost 2 years");
|
||||
assert_eq!(distance_string(160854000, false, false), "about 5 years");
|
||||
assert_eq!(distance_string(236550000, false, false), "over 7 years");
|
||||
assert_eq!(distance_string(249166000, false, false), "almost 8 years");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naive_format_distance_string_include_seconds() {
|
||||
assert_eq!(distance_string(3, true, false), "less than 5 seconds");
|
||||
assert_eq!(distance_string(7, true, false), "less than 10 seconds");
|
||||
assert_eq!(distance_string(13, true, false), "less than 20 seconds");
|
||||
assert_eq!(distance_string(21, true, false), "half a minute");
|
||||
assert_eq!(distance_string(45, true, false), "less than a minute");
|
||||
assert_eq!(distance_string(61, true, false), "1 minute");
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue