perspective_viewer/components/
datetime_column_style.rs1mod custom;
14mod simple;
15
16use std::rc::Rc;
17use std::sync::LazyLock;
18
19use derivative::Derivative;
20use perspective_js::json;
21use perspective_js::utils::global::navigator;
22use wasm_bindgen::prelude::*;
23use yew::prelude::*;
24use yew::*;
25
26use super::form::color_selector::*;
27use super::modal::{ModalLink, SetModalLink};
28use super::style::LocalStyle;
29use crate::components::datetime_column_style::custom::DatetimeStyleCustom;
30use crate::components::datetime_column_style::simple::DatetimeStyleSimple;
31use crate::components::form::select_field::{SelectEnumField, SelectValueField};
32use crate::config::*;
33use crate::utils::WeakScope;
34use crate::*;
35
36#[wasm_bindgen]
37extern "C" {
38 #[wasm_bindgen(js_name = supportedValuesOf, js_namespace = Intl)]
39 pub fn supported_values_of(s: &JsValue) -> js_sys::Array;
40}
41
42thread_local! {
43 static ALL_TIMEZONES: LazyLock<Rc<Vec<String>>> = LazyLock::new(|| {
44 Rc::new(
45 supported_values_of(&JsValue::from("timeZone"))
46 .iter()
47 .map(|x| x.as_string().unwrap())
48 .collect(),
49 )
50 });
51}
52
53static USER_TIMEZONE: LazyLock<String> = LazyLock::new(|| {
54 js_sys::Reflect::get(
55 &js_sys::Intl::DateTimeFormat::new(&navigator().languages(), &json!({})).resolved_options(),
56 &JsValue::from("timeZone"),
57 )
58 .unwrap()
59 .as_string()
60 .unwrap()
61});
62
63pub enum DatetimeColumnStyleMsg {
64 SimpleDatetimeStyleConfigChanged(SimpleDatetimeStyleConfig),
65 CustomDatetimeStyleConfigChanged(CustomDatetimeStyleConfig),
66 TimezoneChanged(Option<String>),
67 ColorModeChanged(Option<DatetimeColorMode>),
68 ColorChanged(String),
69 ColorReset,
70}
71
72#[derive(Properties, Derivative)]
73#[derivative(Debug)]
74pub struct DatetimeColumnStyleProps {
75 pub enable_time_config: bool,
76
77 pub config: Option<DatetimeColumnStyleConfig>,
78
79 pub default_config: DatetimeColumnStyleDefaultConfig,
80
81 #[prop_or_default]
82 pub on_change: Callback<ColumnConfigValueUpdate>,
83
84 #[prop_or_default]
85 #[derivative(Debug = "ignore")]
86 weak_link: WeakScope<DatetimeColumnStyle>,
87}
88
89impl ModalLink<DatetimeColumnStyle> for DatetimeColumnStyleProps {
90 fn weak_link(&self) -> &'_ WeakScope<DatetimeColumnStyle> {
91 &self.weak_link
92 }
93}
94
95impl PartialEq for DatetimeColumnStyleProps {
96 fn eq(&self, _other: &Self) -> bool {
97 false
98 }
99}
100
101#[derive(Debug)]
103pub struct DatetimeColumnStyle {
104 config: DatetimeColumnStyleConfig,
105 default_config: DatetimeColumnStyleDefaultConfig,
106}
107
108impl DatetimeColumnStyle {
109 fn dispatch_config(&self, ctx: &Context<Self>) {
111 let update =
112 Some(self.config.clone()).filter(|x| x != &DatetimeColumnStyleConfig::default());
113 ctx.props()
114 .on_change
115 .emit(ColumnConfigValueUpdate::DatagridDatetimeStyle(update));
116 }
117
118 fn color_select_row(&self, ctx: &Context<Self>, mode: &DatetimeColorMode, title: &str) -> Html {
121 let on_color = ctx.link().callback(DatetimeColumnStyleMsg::ColorChanged);
122 let color = self
123 .config
124 .color
125 .clone()
126 .unwrap_or_else(|| self.default_config.color.to_owned());
127
128 let color_props = props!(ColorProps {
129 title: title.to_owned(),
130 on_color,
131 is_modified: color != self.default_config.color,
132 color,
133 on_reset: ctx.link().callback(|_| DatetimeColumnStyleMsg::ColorReset)
134 });
135
136 if &self.config.datetime_color_mode == mode {
137 html! { <div class="row"><ColorSelector ..color_props /></div> }
138 } else {
139 html! {}
140 }
141 }
142}
143
144impl Component for DatetimeColumnStyle {
145 type Message = DatetimeColumnStyleMsg;
146 type Properties = DatetimeColumnStyleProps;
147
148 fn create(ctx: &Context<Self>) -> Self {
149 ctx.set_modal_link();
150 Self {
151 config: ctx.props().config.clone().unwrap_or_default(),
152 default_config: ctx.props().default_config.clone(),
153 }
154 }
155
156 fn changed(&mut self, ctx: &Context<Self>, old: &Self::Properties) -> bool {
158 let mut rerender = false;
159 let mut new_config = ctx.props().config.clone().unwrap_or_default();
160 if self.config != new_config {
161 std::mem::swap(&mut self.config, &mut new_config);
162 rerender = true;
163 }
164 if old.enable_time_config != ctx.props().enable_time_config {
165 rerender = true;
166 }
167 rerender
168 }
169
170 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
172 match msg {
173 DatetimeColumnStyleMsg::TimezoneChanged(val) => {
174 if Some(&*USER_TIMEZONE) != val.as_ref() {
175 *self.config.date_format.time_zone_mut() = val;
176 } else {
177 *self.config.date_format.time_zone_mut() = None;
178 }
179
180 self.dispatch_config(ctx);
181 true
182 },
183 DatetimeColumnStyleMsg::ColorModeChanged(mode) => {
184 self.config.datetime_color_mode = mode.unwrap_or_default();
185 self.dispatch_config(ctx);
186 true
187 },
188 DatetimeColumnStyleMsg::ColorChanged(color) => {
189 self.config.color = Some(color);
190 self.dispatch_config(ctx);
191 true
192 },
193
194 DatetimeColumnStyleMsg::SimpleDatetimeStyleConfigChanged(simple) => {
195 self.config.date_format = DatetimeFormatType::Simple(simple);
196 self.dispatch_config(ctx);
197 true
198 },
199 DatetimeColumnStyleMsg::CustomDatetimeStyleConfigChanged(custom) => {
200 self.config.date_format = DatetimeFormatType::Custom(custom);
201 self.dispatch_config(ctx);
202 true
203 },
204 DatetimeColumnStyleMsg::ColorReset => {
205 self.config.color = Some(self.default_config.color.clone());
206 self.dispatch_config(ctx);
207 true
208 },
209 }
210 }
211
212 fn view(&self, ctx: &Context<Self>) -> Html {
213 let selected_color_mode = self.config.datetime_color_mode;
214 let color_mode_changed = ctx
215 .link()
216 .callback(DatetimeColumnStyleMsg::ColorModeChanged);
217
218 let color_controls = match selected_color_mode {
219 DatetimeColorMode::None => html! {},
220 DatetimeColorMode::Foreground => {
221 self.color_select_row(ctx, &DatetimeColorMode::Foreground, "foreground-label")
222 },
223 DatetimeColorMode::Background => {
224 self.color_select_row(ctx, &DatetimeColorMode::Background, "background-label")
225 },
226 };
227
228 html! {
229 <>
230 <LocalStyle href={css!("column-style")} />
231 <div id="column-style-container" class="datetime-column-style-container">
232 <SelectEnumField<DatetimeColorMode>
233 label="color"
234 on_change={color_mode_changed}
235 current_value={selected_color_mode}
236 />
237 { color_controls }
238 if ctx.props().enable_time_config {
239 <SelectValueField<String>
240 label="timezone"
241 values={ALL_TIMEZONES.with(|x| (*x).clone())}
242 default_value={(*USER_TIMEZONE).clone()}
243 on_change={ctx.link().callback(DatetimeColumnStyleMsg::TimezoneChanged)}
244 current_value={self.config.date_format.time_zone().as_ref().unwrap_or(&*USER_TIMEZONE).clone()}
245 />
246 }
247 if let DatetimeFormatType::Simple(config) = &self.config.date_format {
248 if ctx.props().enable_time_config {
249 <div class="row">
250 <button
251 id="datetime_format"
252 data-title="Simple"
253 data-title-hover="Switch to Custom"
254 onclick={ctx.link().callback(|_| DatetimeColumnStyleMsg::CustomDatetimeStyleConfigChanged(CustomDatetimeStyleConfig::default()))}
255 />
256 </div>
257 }
258 <DatetimeStyleSimple
259 enable_time_config={ctx.props().enable_time_config}
260 on_change={ctx.link().callback(DatetimeColumnStyleMsg::SimpleDatetimeStyleConfigChanged)}
261 config={config.clone()}
262 />
263 } else if let DatetimeFormatType::Custom(config) = &self.config.date_format {
264 if ctx.props().enable_time_config {
265 <div class="row">
266 <button
267 id="datetime_format"
268 data-title="Custom"
269 data-title-hover="Switch to Simple"
270 onclick={ctx.link().callback(|_| DatetimeColumnStyleMsg::SimpleDatetimeStyleConfigChanged(SimpleDatetimeStyleConfig::default()))}
271 />
272 </div>
273 }
274 <DatetimeStyleCustom
275 enable_time_config={ctx.props().enable_time_config}
276 on_change={ctx.link().callback(DatetimeColumnStyleMsg::CustomDatetimeStyleConfigChanged)}
277 config={config.clone()}
278 />
279 }
280 </div>
281 </>
282 }
283 }
284}