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