Skip to main content

perspective_viewer/config/
viewer_config.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use std::collections::HashMap;
14use std::ops::Deref;
15use std::sync::LazyLock;
16
17use perspective_client::config::*;
18use perspective_js::utils::*;
19use serde::{Deserialize, Deserializer, Serialize};
20use serde_json::Value;
21use ts_rs::TS;
22use wasm_bindgen::prelude::*;
23
24use super::ColumnConfigValues;
25use crate::presentation::ColumnConfigMap;
26
27/// The state of an entire `custom_elements::PerspectiveViewerElement` component
28/// and its `Plugin`.
29#[derive(Debug, Default, Serialize, PartialEq)]
30#[serde(deny_unknown_fields)]
31pub struct ViewerConfig<V = String> {
32    pub version: V,
33    pub columns_config: ColumnConfigMap,
34    pub plugin: String,
35    pub plugin_config: Value,
36    pub settings: bool,
37    pub table: Option<String>,
38    pub theme: Option<String>,
39    pub title: Option<String>,
40
41    #[serde(flatten)]
42    pub view_config: ViewConfig,
43}
44
45pub static API_VERSION: LazyLock<&'static str> = LazyLock::new(|| {
46    #[derive(Deserialize)]
47    struct Package {
48        version: &'static str,
49    }
50    let pkg: &'static str = include_str!("../../../package.json");
51    let pkg: Package = serde_json::from_str(pkg).unwrap();
52    pkg.version
53});
54
55impl ViewerConfig {
56    /// Encode a `ViewerConfig` to a `JsValue` in a supported type.
57    pub fn encode(&self) -> ApiResult<JsValue> {
58        Ok(JsValue::from_serde_ext(self)?)
59    }
60}
61
62#[derive(Clone, Debug, TS, Deserialize, PartialEq, Serialize)]
63#[serde(transparent)]
64pub struct PluginConfig(serde_json::Value);
65
66impl Deref for PluginConfig {
67    type Target = Value;
68
69    fn deref(&self) -> &Self::Target {
70        &self.0
71    }
72}
73
74#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, TS)]
75pub struct ViewerConfigUpdate {
76    #[serde(default)]
77    #[ts(as = "Option<_>")]
78    #[ts(optional)]
79    pub version: VersionUpdate,
80
81    #[serde(default)]
82    #[ts(as = "Option<_>")]
83    #[ts(optional)]
84    pub plugin: PluginUpdate,
85
86    #[serde(default)]
87    #[ts(as = "Option<_>")]
88    #[ts(optional)]
89    pub title: TitleUpdate,
90
91    #[serde(default)]
92    #[ts(as = "Option<_>")]
93    #[ts(optional)]
94    pub table: TableUpdate,
95
96    #[serde(default)]
97    #[ts(as = "Option<_>")]
98    #[ts(optional)]
99    pub theme: ThemeUpdate,
100
101    #[serde(default)]
102    #[ts(as = "Option<_>")]
103    #[ts(optional)]
104    pub settings: SettingsUpdate,
105
106    #[serde(default)]
107    #[ts(as = "Option<_>")]
108    #[ts(optional)]
109    pub plugin_config: Option<PluginConfig>,
110
111    #[serde(default)]
112    #[ts(as = "Option<_>")]
113    #[ts(optional)]
114    pub columns_config: ColumnConfigUpdate,
115
116    #[serde(flatten)]
117    pub view_config: ViewConfigUpdate,
118}
119
120impl ViewerConfigUpdate {
121    /// Decode a `JsValue` into a `ViewerConfigUpdate` by auto-detecting format
122    /// from JavaScript type.
123    pub fn decode(update: &JsValue) -> ApiResult<Self> {
124        Ok(update.into_serde_ext()?)
125    }
126
127    pub fn migrate(&self) -> ApiResult<Self> {
128        // TODO: Call the migrate script from js
129        Ok(self.clone())
130    }
131}
132
133impl std::fmt::Display for ViewerConfigUpdate {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        write!(
136            f,
137            "{}",
138            serde_json::to_string(self).map_err(|_| std::fmt::Error)?
139        )
140    }
141}
142
143#[derive(Clone, Debug, Serialize, PartialEq, TS)]
144#[serde(untagged)]
145// #[ts(untagged)]
146pub enum OptionalUpdate<T: Clone> {
147    #[ts(skip)]
148    SetDefault,
149
150    // #[ts(skip)]
151    // #[ts(type = "undefined")]
152    Missing,
153
154    // #[ts(type = "_")]
155    // #[ts(untagged)]
156    Update(T),
157}
158
159pub type PluginUpdate = OptionalUpdate<String>;
160pub type SettingsUpdate = OptionalUpdate<bool>;
161pub type ThemeUpdate = OptionalUpdate<String>;
162pub type TitleUpdate = OptionalUpdate<String>;
163pub type TableUpdate = OptionalUpdate<String>;
164pub type VersionUpdate = OptionalUpdate<String>;
165pub type ColumnConfigUpdate = OptionalUpdate<HashMap<String, ColumnConfigValues>>;
166
167/// Handles `{}` when included as a field with `#[serde(default)]`.
168impl<T: Clone> Default for OptionalUpdate<T> {
169    fn default() -> Self {
170        Self::Missing
171    }
172}
173
174/// Handles `{plugin: null}` and `{plugin: val}` by treating this type as an
175/// option.
176impl<T: Clone> From<Option<T>> for OptionalUpdate<T> {
177    fn from(opt: Option<T>) -> Self {
178        match opt {
179            Some(v) => Self::Update(v),
180            None => Self::SetDefault,
181        }
182    }
183}
184
185/// Treats `PluginUpdate` enum as an `Option<T>` when present during
186/// deserialization.
187impl<'a, T> Deserialize<'a> for OptionalUpdate<T>
188where
189    T: Deserialize<'a> + Clone,
190{
191    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192    where
193        D: Deserializer<'a>,
194    {
195        Option::deserialize(deserializer).map(Into::into)
196    }
197}