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::*;
24
25use super::modal::{ModalLink, SetModalLink};
26use super::style::LocalStyle;
27use crate::components::datetime_column_style::custom::DatetimeStyleCustom;
28use crate::components::datetime_column_style::simple::DatetimeStyleSimple;
29use crate::components::form::select_value_field::SelectValueField;
30use crate::config::*;
31use crate::css;
32use crate::utils::WeakScope;
33
34#[derive(Properties, Derivative)]
38#[derivative(Debug)]
39pub struct DatetimeColumnStyleProps {
40 pub enable_time_config: bool,
41 pub config: Option<DatetimeColumnStyleConfig>,
42
43 #[prop_or_default]
44 pub on_change: Callback<ColumnConfigFieldUpdate>,
45
46 #[prop_or_default]
47 pub keys: Vec<String>,
48
49 #[prop_or_default]
50 #[derivative(Debug = "ignore")]
51 weak_link: WeakScope<DatetimeColumnStyle>,
52}
53
54impl ModalLink<DatetimeColumnStyle> for DatetimeColumnStyleProps {
55 fn weak_link(&self) -> &'_ WeakScope<DatetimeColumnStyle> {
56 &self.weak_link
57 }
58}
59
60impl PartialEq for DatetimeColumnStyleProps {
61 fn eq(&self, other: &Self) -> bool {
62 self.enable_time_config == other.enable_time_config && self.config == other.config
63 }
64}
65
66pub enum DatetimeColumnStyleMsg {
67 SimpleDatetimeStyleConfigChanged(SimpleDatetimeStyleConfig),
68 CustomDatetimeStyleConfigChanged(CustomDatetimeStyleConfig),
69 TimezoneChanged(Option<String>),
70}
71
72#[derive(Debug)]
73pub struct DatetimeColumnStyle {
74 config: DatetimeColumnStyleConfig,
75}
76
77impl Component for DatetimeColumnStyle {
78 type Message = DatetimeColumnStyleMsg;
79 type Properties = DatetimeColumnStyleProps;
80
81 fn create(ctx: &Context<Self>) -> Self {
82 ctx.set_modal_link();
83 Self {
84 config: ctx.props().config.clone().unwrap_or_default(),
85 }
86 }
87
88 fn changed(&mut self, ctx: &Context<Self>, old: &Self::Properties) -> bool {
89 let mut rerender = false;
90 let mut new_config = ctx.props().config.clone().unwrap_or_default();
91 if self.config != new_config {
92 std::mem::swap(&mut self.config, &mut new_config);
93 rerender = true;
94 }
95 if old.enable_time_config != ctx.props().enable_time_config {
96 rerender = true;
97 }
98 rerender
99 }
100
101 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
102 match msg {
103 DatetimeColumnStyleMsg::TimezoneChanged(val) => {
104 if Some(&*USER_TIMEZONE) != val.as_ref() {
105 *self.config.date_format.time_zone_mut() = val;
106 } else {
107 *self.config.date_format.time_zone_mut() = None;
108 }
109
110 self.dispatch_config(ctx);
111 true
112 },
113 DatetimeColumnStyleMsg::SimpleDatetimeStyleConfigChanged(simple) => {
114 self.config.date_format = DatetimeFormatType::Simple(simple);
115 self.dispatch_config(ctx);
116 true
117 },
118 DatetimeColumnStyleMsg::CustomDatetimeStyleConfigChanged(custom) => {
119 self.config.date_format = DatetimeFormatType::Custom(custom);
120 self.dispatch_config(ctx);
121 true
122 },
123 }
124 }
125
126 fn view(&self, ctx: &Context<Self>) -> Html {
127 html! {
128 <>
129 <LocalStyle href={css!("column-style")} />
130 <div id="column-style-container" class="datetime-column-style-container">
131 if ctx.props().enable_time_config {
132 <SelectValueField<String>
133 label="timezone"
134 values={ALL_TIMEZONES.with(|x| (*x).clone())}
135 default_value={(*USER_TIMEZONE).clone()}
136 on_change={ctx.link().callback(DatetimeColumnStyleMsg::TimezoneChanged)}
137 current_value={self.config.date_format.time_zone().as_ref().unwrap_or(&*USER_TIMEZONE).clone()}
138 />
139 }
140 if let DatetimeFormatType::Simple(config) = &self.config.date_format {
141 if ctx.props().enable_time_config {
142 <div class="row">
143 <button
144 id="datetime_format"
145 data-title="Simple"
146 data-title-hover="Switch to Custom"
147 onclick={ctx.link().callback(|_| DatetimeColumnStyleMsg::CustomDatetimeStyleConfigChanged(CustomDatetimeStyleConfig::default()))}
148 />
149 </div>
150 }
151 <DatetimeStyleSimple
152 enable_time_config={ctx.props().enable_time_config}
153 on_change={ctx.link().callback(DatetimeColumnStyleMsg::SimpleDatetimeStyleConfigChanged)}
154 config={config.clone()}
155 />
156 } else if let DatetimeFormatType::Custom(config) = &self.config.date_format {
157 if ctx.props().enable_time_config {
158 <div class="row">
159 <button
160 id="datetime_format"
161 data-title="Custom"
162 data-title-hover="Switch to Simple"
163 onclick={ctx.link().callback(|_| DatetimeColumnStyleMsg::SimpleDatetimeStyleConfigChanged(SimpleDatetimeStyleConfig::default()))}
164 />
165 </div>
166 }
167 <DatetimeStyleCustom
168 enable_time_config={ctx.props().enable_time_config}
169 on_change={ctx.link().callback(DatetimeColumnStyleMsg::CustomDatetimeStyleConfigChanged)}
170 config={config.clone()}
171 />
172 }
173 </div>
174 </>
175 }
176 }
177}
178
179#[wasm_bindgen]
180extern "C" {
181 #[wasm_bindgen(js_name = supportedValuesOf, js_namespace = Intl)]
182 pub fn supported_values_of(s: &JsValue) -> js_sys::Array;
183}
184
185thread_local! {
186 static ALL_TIMEZONES: LazyLock<Rc<Vec<String>>> = LazyLock::new(|| {
187 Rc::new(
188 supported_values_of(&JsValue::from("timeZone"))
189 .iter()
190 .map(|x| x.as_string().unwrap())
191 .collect(),
192 )
193 });
194}
195
196static USER_TIMEZONE: LazyLock<String> = LazyLock::new(|| {
197 js_sys::Reflect::get(
198 &js_sys::Intl::DateTimeFormat::new(&navigator().languages(), &json!({})).resolved_options(),
199 &JsValue::from("timeZone"),
200 )
201 .unwrap()
202 .as_string()
203 .unwrap()
204});
205
206impl DatetimeColumnStyle {
207 fn dispatch_config(&self, ctx: &Context<Self>) {
209 let value = if self.config == DatetimeColumnStyleConfig::default() {
210 serde_json::Map::new()
211 } else {
212 match serde_json::to_value(&self.config) {
213 Ok(serde_json::Value::Object(m)) => m,
214 _ => serde_json::Map::new(),
215 }
216 };
217
218 ctx.props().on_change.emit(ColumnConfigFieldUpdate {
219 keys: ctx.props().keys.clone(),
220 value,
221 });
222 }
223}