Skip to main content

liora_components/
operation.rs

1use crate::Label;
2use gpui::{
3    AnyElement, App, Component, Hsla, IntoElement, Pixels, RenderOnce, SharedString, Window, div,
4    prelude::*, px,
5};
6use liora_core::Config;
7
8pub struct Operation {
9    label: AnyElement,
10    action: AnyElement,
11    description: Option<SharedString>,
12    status: Option<SharedString>,
13    status_color: Option<Hsla>,
14    gap: Pixels,
15    padded: bool,
16    disabled: bool,
17}
18
19impl Operation {
20    pub fn new(label: impl IntoElement, action: impl IntoElement) -> Self {
21        Self {
22            label: label.into_any_element(),
23            action: action.into_any_element(),
24            description: None,
25            status: None,
26            status_color: None,
27            gap: px(16.0),
28            padded: true,
29            disabled: false,
30        }
31    }
32
33    pub fn with_text(text: impl Into<gpui::SharedString>, action: impl IntoElement) -> Self {
34        Self::new(Label::new(text), action)
35    }
36    pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
37        self.gap = gap.into().max(px(0.0));
38        self
39    }
40    pub fn description(mut self, description: impl Into<SharedString>) -> Self {
41        self.description = Some(description.into());
42        self
43    }
44    pub fn status(mut self, status: impl Into<SharedString>) -> Self {
45        self.status = Some(status.into());
46        self
47    }
48    pub fn status_color(mut self, color: Hsla) -> Self {
49        self.status_color = Some(color);
50        self
51    }
52    pub fn success(self) -> Self {
53        self.status("正常").status_color(gpui::green())
54    }
55    pub fn warning(self) -> Self {
56        self.status("注意").status_color(gpui::yellow())
57    }
58    pub fn danger(self) -> Self {
59        self.status("异常").status_color(gpui::red())
60    }
61    pub fn disabled(mut self, disabled: bool) -> Self {
62        self.disabled = disabled;
63        self
64    }
65    pub fn no_padding(mut self) -> Self {
66        self.padded = false;
67        self
68    }
69}
70
71impl RenderOnce for Operation {
72    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
73        let theme = cx.global::<Config>().theme.clone();
74        let status_color = self.status_color.unwrap_or(theme.primary.base);
75        div()
76            .flex()
77            .items_center()
78            .justify_between()
79            .gap(self.gap)
80            .w_full()
81            .when(self.disabled, |s| s.opacity(0.52))
82            .when(self.padded, |s| {
83                s.p_3()
84                    .rounded_md()
85                    .border_1()
86                    .border_color(theme.neutral.border)
87                    .bg(theme.neutral.card)
88            })
89            .child(
90                div()
91                    .min_w_0()
92                    .flex()
93                    .flex_col()
94                    .gap_1()
95                    .child(
96                        div()
97                            .flex()
98                            .items_center()
99                            .gap_2()
100                            .child(self.label)
101                            .when_some(self.status, |s, status| {
102                                s.child(
103                                    div()
104                                        .rounded_full()
105                                        .px_2()
106                                        .py(px(1.0))
107                                        .text_xs()
108                                        .bg(status_color.opacity(0.12))
109                                        .text_color(status_color)
110                                        .child(status),
111                                )
112                            }),
113                    )
114                    .when_some(self.description, |s, description| {
115                        s.child(
116                            div()
117                                .text_sm()
118                                .text_color(theme.neutral.text_3)
119                                .child(description),
120                        )
121                    }),
122            )
123            .child(div().flex_none().child(self.action))
124    }
125}
126
127impl IntoElement for Operation {
128    type Element = Component<Self>;
129    fn into_element(self) -> Self::Element {
130        Component::new(self)
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    #[test]
138    fn operation_tracks_layout_options() {
139        let op = Operation::with_text("Auto save", div())
140            .gap(px(20.0))
141            .description("Save changes automatically")
142            .status("Enabled")
143            .disabled(true)
144            .no_padding();
145        assert_eq!(op.gap, px(20.0));
146        assert!(!op.padded);
147        assert_eq!(
148            op.description.as_ref().map(|text| text.as_ref()),
149            Some("Save changes automatically")
150        );
151        assert_eq!(
152            op.status.as_ref().map(|text| text.as_ref()),
153            Some("Enabled")
154        );
155        assert!(op.disabled);
156    }
157}