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::ops::Deref;
14use std::sync::LazyLock;
15
16use perspective_client::config::*;
17use perspective_js::utils::*;
18use serde::{Deserialize, Deserializer, Serialize};
19use serde_json::Value;
20use ts_rs::TS;
21use wasm_bindgen::prelude::*;
22
23use crate::renderer::ColumnConfigMap;
24
25/// The state of an entire `custom_elements::PerspectiveViewerElement` component
26/// and its `Plugin`: the element-level `settings` flag plus the per-panel
27/// [`PanelViewerConfig`]. The split exists so the whole-element config format
28/// can serialize panel entries *without* a `settings` key (it is element-level
29/// state there, carried by the top-level `active` field instead), while the
30/// single-panel format flattens back to the legacy shape.
31#[derive(Debug, Default, Serialize, PartialEq, TS)]
32#[serde(deny_unknown_fields)]
33pub struct ViewerConfig<V: TS = String> {
34    pub settings: bool,
35
36    #[serde(flatten)]
37    pub panel: PanelViewerConfig<V>,
38}
39
40/// The per-panel state of a [`ViewerConfig`] — everything except the
41/// element-level `settings` flag. This is the `panels` entry type of the
42/// whole-element config format.
43#[derive(Debug, Default, Serialize, PartialEq, TS)]
44pub struct PanelViewerConfig<V: TS = String> {
45    pub version: V,
46    pub columns_config: ColumnConfigMap,
47    pub plugin: String,
48    pub plugin_config: serde_json::Map<String, Value>,
49    pub table: Option<String>,
50    pub theme: Option<String>,
51    pub title: Option<String>,
52
53    #[serde(flatten)]
54    pub view_config: ViewConfig,
55}
56
57impl<V: TS> Deref for ViewerConfig<V> {
58    type Target = PanelViewerConfig<V>;
59
60    fn deref(&self) -> &Self::Target {
61        &self.panel
62    }
63}
64
65pub static API_VERSION: LazyLock<&'static str> = LazyLock::new(|| {
66    #[derive(Deserialize)]
67    struct Package {
68        version: &'static str,
69    }
70    let pkg: &'static str = include_str!("../../../package.json");
71    let pkg: Package = serde_json::from_str(pkg).unwrap();
72    pkg.version
73});
74
75impl ViewerConfig {
76    /// Encode a `ViewerConfig` to a `JsValue` in a supported type.
77    pub fn encode(&self) -> ApiResult<JsValue> {
78        Ok(JsValue::from_serde_ext(self)?)
79    }
80}
81
82#[derive(Clone, Debug, TS, Deserialize, PartialEq, Serialize)]
83#[serde(transparent)]
84pub struct PluginConfig(serde_json::Value);
85
86impl Deref for PluginConfig {
87    type Target = Value;
88
89    fn deref(&self) -> &Self::Target {
90        &self.0
91    }
92}
93
94#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, TS)]
95pub struct ViewerConfigUpdate {
96    #[serde(default)]
97    #[ts(as = "Option<_>")]
98    #[ts(optional)]
99    pub version: VersionUpdate,
100
101    #[serde(default)]
102    #[ts(as = "Option<_>")]
103    #[ts(optional)]
104    pub plugin: PluginUpdate,
105
106    #[serde(default)]
107    #[ts(as = "Option<_>")]
108    #[ts(optional)]
109    pub title: TitleUpdate,
110
111    #[serde(default)]
112    #[ts(as = "Option<_>")]
113    #[ts(optional)]
114    pub table: TableUpdate,
115
116    #[serde(default)]
117    #[ts(as = "Option<_>")]
118    #[ts(optional)]
119    pub theme: ThemeUpdate,
120
121    #[serde(default)]
122    #[ts(as = "Option<_>")]
123    #[ts(optional)]
124    pub settings: SettingsUpdate,
125
126    #[serde(default)]
127    #[ts(as = "Option<_>")]
128    #[ts(optional)]
129    pub plugin_config: PluginConfigUpdate,
130
131    #[serde(default)]
132    #[ts(as = "Option<_>")]
133    #[ts(optional)]
134    pub columns_config: ColumnConfigUpdate,
135
136    #[serde(flatten)]
137    pub view_config: ViewConfigUpdate,
138}
139
140impl ViewerConfigUpdate {
141    /// Decode a `JsValue` into a `ViewerConfigUpdate` by auto-detecting format
142    /// from JavaScript type.
143    pub fn decode(update: &JsValue) -> ApiResult<Self> {
144        Ok(update.into_serde_ext()?)
145    }
146
147    pub fn migrate(&self) -> ApiResult<Self> {
148        // TODO: Call the migrate script from js
149        Ok(self.clone())
150    }
151}
152
153impl std::fmt::Display for ViewerConfigUpdate {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        write!(
156            f,
157            "{}",
158            serde_json::to_string(self).map_err(|_| std::fmt::Error)?
159        )
160    }
161}
162
163#[derive(Clone, Debug, Serialize, PartialEq, TS)]
164#[serde(untagged)]
165// #[ts(untagged)]
166pub enum OptionalUpdate<T: Clone> {
167    #[ts(skip)]
168    SetDefault,
169
170    // #[ts(skip)]
171    // #[ts(type = "undefined")]
172    Missing,
173
174    // #[ts(type = "_")]
175    // #[ts(untagged)]
176    Update(T),
177}
178
179pub type PluginUpdate = OptionalUpdate<String>;
180pub type SettingsUpdate = OptionalUpdate<bool>;
181pub type ThemeUpdate = OptionalUpdate<String>;
182pub type TitleUpdate = OptionalUpdate<String>;
183pub type TableUpdate = OptionalUpdate<String>;
184pub type VersionUpdate = OptionalUpdate<String>;
185pub type ColumnConfigUpdate = OptionalUpdate<ColumnConfigMap>;
186pub type PluginConfigUpdate = OptionalUpdate<serde_json::Map<String, Value>>;
187
188/// Handles `{}` when included as a field with `#[serde(default)]`.
189impl<T: Clone> Default for OptionalUpdate<T> {
190    fn default() -> Self {
191        Self::Missing
192    }
193}
194
195/// Handles `{plugin: null}` and `{plugin: val}` by treating this type as an
196/// option.
197impl<T: Clone> From<Option<T>> for OptionalUpdate<T> {
198    fn from(opt: Option<T>) -> Self {
199        match opt {
200            Some(v) => Self::Update(v),
201            None => Self::SetDefault,
202        }
203    }
204}
205
206/// Treats `PluginUpdate` enum as an `Option<T>` when present during
207/// deserialization.
208impl<'a, T> Deserialize<'a> for OptionalUpdate<T>
209where
210    T: Deserialize<'a> + Clone,
211{
212    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
213    where
214        D: Deserializer<'a>,
215    {
216        Option::deserialize(deserializer).map(Into::into)
217    }
218}