perspective_viewer/
presentation.rs1mod column_locator;
14mod props;
15mod sheets;
16
17use std::cell::RefCell;
18use std::collections::{HashMap, HashSet};
19use std::ops::Deref;
20use std::rc::Rc;
21
22use async_lock::Mutex;
23use perspective_js::utils::{ApiFuture, ApiResult};
24use web_sys::*;
25use yew::html::ImplicitClone;
26use yew::prelude::*;
27
28pub use self::column_locator::{ColumnLocator, ColumnSettingsTab, ColumnTab, OpenColumnSettings};
29pub use self::props::PresentationProps;
30use crate::config::{ColumnConfigUpdate, ColumnConfigValueUpdate, ColumnConfigValues};
31use crate::utils::*;
32
33pub type ColumnConfigMap = HashMap<String, ColumnConfigValues>;
34
35#[derive(Default)]
40struct ThemeData {
41 themes: Option<Vec<String>>,
42}
43
44pub struct PresentationHandle {
46 viewer_elem: HtmlElement,
47 theme_data: Mutex<ThemeData>,
48 is_settings_open: RefCell<bool>,
49 open_column_settings: RefCell<OpenColumnSettings>,
50 columns_config: RefCell<ColumnConfigMap>,
51 is_workspace: RefCell<Option<bool>>,
52 pub settings_open_changed: PubSub<bool>,
53
54 pub on_is_workspace_changed: RefCell<Option<Callback<bool>>>,
57 pub settings_before_open_changed: PubSub<bool>,
58 pub column_settings_open_changed: PubSub<(bool, Option<String>)>,
59 pub theme_config_updated: PubSub<(PtrEqRc<Vec<String>>, Option<usize>)>,
60 pub on_eject: PubSub<()>,
61}
62
63#[derive(Clone)]
66pub struct Presentation(Rc<PresentationHandle>);
67
68impl PartialEq for Presentation {
69 fn eq(&self, other: &Self) -> bool {
70 Rc::ptr_eq(&self.0, &other.0)
71 }
72}
73
74impl Deref for Presentation {
75 type Target = PresentationHandle;
76
77 fn deref(&self) -> &Self::Target {
78 &self.0
79 }
80}
81
82impl ImplicitClone for Presentation {}
83
84impl Presentation {
85 pub fn new(elem: &HtmlElement) -> Self {
86 let theme = Self(Rc::new(PresentationHandle {
87 viewer_elem: elem.clone(),
88 theme_data: Default::default(),
89 is_workspace: Default::default(),
90 settings_open_changed: Default::default(),
91 settings_before_open_changed: Default::default(),
92 column_settings_open_changed: Default::default(),
93 on_is_workspace_changed: Default::default(),
94 columns_config: Default::default(),
95 is_settings_open: Default::default(),
96 open_column_settings: Default::default(),
97 theme_config_updated: PubSub::default(),
98 on_eject: PubSub::default(),
99 }));
100
101 ApiFuture::spawn(theme.clone().init());
102 theme
103 }
104
105 pub fn is_visible(&self) -> bool {
106 self.viewer_elem
107 .offset_parent()
108 .map(|x| !x.is_null())
109 .unwrap_or(false)
110 }
111
112 pub fn is_active(&self, elem: &Option<Element>) -> bool {
113 elem.is_some() && &self.viewer_elem.shadow_root().unwrap().active_element() == elem
114 }
115
116 pub fn reset_attached(&self) {
117 *self.0.is_workspace.borrow_mut() = None;
118 if let Some(cb) = self.on_is_workspace_changed.borrow().as_ref() {
119 cb.emit(self.get_is_workspace());
120 }
121 }
122
123 pub fn get_is_workspace(&self) -> bool {
124 if self.is_workspace.borrow().is_none() {
125 if !self.viewer_elem.is_connected() {
126 return false;
127 }
128
129 let is_workspace = self
130 .viewer_elem
131 .parent_element()
132 .map(|x| x.tag_name() == "PERSPECTIVE-WORKSPACE")
133 .unwrap_or_default();
134
135 *self.is_workspace.borrow_mut() = Some(is_workspace);
136 }
137
138 self.is_workspace.borrow().unwrap()
139 }
140
141 pub fn set_settings_attribute(&self, opt: bool) {
142 self.viewer_elem
143 .toggle_attribute_with_force("settings", opt)
144 .unwrap();
145 }
146
147 pub fn is_settings_open(&self) -> bool {
148 *self.is_settings_open.borrow()
149 }
150
151 pub fn set_settings_before_open(&self, open: bool) {
152 if *self.is_settings_open.borrow() != open {
153 *self.is_settings_open.borrow_mut() = open;
154 self.set_settings_attribute(open);
155 self.settings_before_open_changed.emit(open);
156 }
157 }
158
159 pub fn set_settings_open(&self, open: bool) {
160 self.settings_open_changed.emit(open);
161 }
162
163 pub fn set_open_column_settings(&self, settings: Option<OpenColumnSettings>) {
167 let settings = settings.unwrap_or_default();
168 if *(self.open_column_settings.borrow()) != settings {
169 settings.clone_into(&mut *self.open_column_settings.borrow_mut());
170 self.column_settings_open_changed
171 .emit((true, settings.name()));
172 }
173 }
174
175 pub fn get_open_column_settings(&self) -> OpenColumnSettings {
177 self.open_column_settings.borrow().deref().clone()
178 }
179
180 async fn init(self) -> ApiResult<()> {
181 self.set_theme_attribute(self.get_selected_theme_name().await.as_deref())
182 }
183
184 pub async fn get_available_themes(&self) -> ApiResult<PtrEqRc<Vec<String>>> {
188 let mut data = self.0.theme_data.lock().await;
189 if data.themes.is_none() {
190 await_dom_loaded().await?;
191 let themes = sheets::get_theme_names(&self.0.viewer_elem)?;
192 data.themes = Some(themes);
193 }
194
195 Ok(data.themes.clone().unwrap().into())
196 }
197
198 pub async fn reset_available_themes(&self, themes: Option<Vec<String>>) -> bool {
204 fn as_set(x: &Option<Vec<String>>) -> HashSet<&'_ String> {
205 x.as_ref()
206 .map(|x| x.iter().collect::<HashSet<_>>())
207 .unwrap_or_default()
208 }
209
210 let mut mutex = self.0.theme_data.lock().await;
211 let changed = as_set(&mutex.themes) != as_set(&themes);
212 mutex.themes = themes;
213 changed
214 }
215
216 pub async fn get_selected_theme_config(
217 &self,
218 ) -> ApiResult<(PtrEqRc<Vec<String>>, Option<usize>)> {
219 let themes = self.get_available_themes().await?;
220 let name = self.0.viewer_elem.get_attribute("theme");
221 let index = name
222 .and_then(|x| themes.iter().position(|y| y == &x))
223 .or(if !themes.is_empty() { Some(0) } else { None });
224
225 Ok((themes, index))
226 }
227
228 pub async fn get_selected_theme_name(&self) -> Option<String> {
232 let (themes, index) = self.get_selected_theme_config().await.ok()?;
233 index.and_then(|x| themes.get(x).cloned())
234 }
235
236 fn set_theme_attribute(&self, theme: Option<&str>) -> ApiResult<()> {
237 if let Some(theme) = theme {
238 Ok(self.0.viewer_elem.set_attribute("theme", theme)?)
239 } else {
240 Ok(self.0.viewer_elem.remove_attribute("theme")?)
241 }
242 }
243
244 pub async fn reset_theme(&self) -> ApiResult<()> {
245 *self.0.is_workspace.borrow_mut() = None;
246 let themes = self.get_available_themes().await?;
247 let default_theme = themes.first().map(|x| x.as_str());
248 self.set_theme_name(default_theme).await?;
249 Ok(())
250 }
251
252 pub async fn set_theme_name(&self, theme: Option<&str>) -> ApiResult<bool> {
257 let (themes, selected) = self.get_selected_theme_config().await?;
258 if let Some(x) = selected
259 && themes.get(x).map(|x| x.as_str()) == theme
260 {
261 return Ok(false);
262 }
263
264 let index = if let Some(theme) = theme {
265 self.set_theme_attribute(Some(theme))?;
266 themes.iter().position(|x| x == theme)
267 } else if !themes.is_empty() {
268 self.set_theme_attribute(themes.first().map(|x| x.as_str()))?;
269 Some(0)
270 } else {
271 self.set_theme_attribute(None)?;
272 None
273 };
274
275 self.theme_config_updated.emit((themes, index));
276 Ok(true)
277 }
278
279 pub fn all_columns_configs(&self) -> ColumnConfigMap {
281 self.columns_config.borrow().clone()
282 }
283
284 pub fn reset_columns_configs(&self) {
285 *self.columns_config.borrow_mut() = ColumnConfigMap::new();
286 }
287
288 pub fn get_columns_config(&self, column_name: &str) -> Option<ColumnConfigValues> {
290 self.columns_config.borrow().get(column_name).cloned()
291 }
292
293 pub fn update_columns_configs(&self, update: ColumnConfigUpdate) {
295 match update {
296 crate::config::OptionalUpdate::SetDefault => {
297 let mut config = self.columns_config.borrow_mut();
298 *config = HashMap::default()
299 },
300 crate::config::OptionalUpdate::Missing => {},
301 crate::config::OptionalUpdate::Update(update) => {
302 for (col_name, new_config) in update.into_iter() {
303 self.columns_config
304 .borrow_mut()
305 .insert(col_name, new_config);
306 }
307 },
308 }
309 }
310
311 pub fn update_columns_config_value(
312 &self,
313 column_name: String,
314 update: ColumnConfigValueUpdate,
315 ) {
316 let mut config = self.columns_config.borrow_mut();
317 let value = config.remove(&column_name).unwrap_or_default();
318 let update = value.update(update);
319 if !update.is_empty() {
320 config.insert(column_name, update);
321 }
322 }
323
324 pub fn to_props(&self, available_themes: PtrEqRc<Vec<String>>) -> PresentationProps {
331 let theme_attr = self.0.viewer_elem.get_attribute("theme");
332 let selected_theme = theme_attr.as_deref().and_then(|name| {
333 available_themes
334 .iter()
335 .find(|x| x.as_str() == name)
336 .cloned()
337 });
338
339 PresentationProps {
340 is_settings_open: self.is_settings_open(),
341 available_themes,
342 selected_theme,
343 open_column_settings: self.get_open_column_settings(),
344 is_workspace: self.get_is_workspace(),
345 }
346 }
347}