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`.
27#[derive(Debug, Default, Serialize, PartialEq, TS)]
28#[serde(deny_unknown_fields)]
29pub struct ViewerConfig<V: TS = String> {
30    pub version: V,
31    pub columns_config: ColumnConfigMap,
32    pub plugin: String,
33    pub plugin_config: serde_json::Map<String, Value>,
34    pub settings: bool,
35    pub table: Option<String>,
36    pub theme: Option<String>,
37    pub title: Option<String>,
38
39    #[serde(flatten)]
40    pub view_config: ViewConfig,
41}
42
43pub static API_VERSION: LazyLock<&'static str> = LazyLock::new(|| {
44    #[derive(Deserialize)]
45    struct Package {
46        version: &'static str,
47    }
48    let pkg: &'static str = include_str!("../../../package.json");
49    let pkg: Package = serde_json::from_str(pkg).unwrap();
50    pkg.version
51});
52
53impl ViewerConfig {
54    /// Encode a `ViewerConfig` to a `JsValue` in a supported type.
55    pub fn encode(&self) -> ApiResult<JsValue> {
56        Ok(JsValue::from_serde_ext(self)?)
57    }
58}
59
60#[derive(Clone, Debug, TS, Deserialize, PartialEq, Serialize)]
61#[serde(transparent)]
62pub struct PluginConfig(serde_json::Value);
63
64impl Deref for PluginConfig {
65    type Target = Value;
66
67    fn deref(&self) -> &Self::Target {
68        &self.0
69    }
70}
71
72#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, TS)]
73pub struct ViewerConfigUpdate {
74    #[serde(default)]
75    #[ts(as = "Option<_>")]
76    #[ts(optional)]
77    pub version: VersionUpdate,
78
79    #[serde(default)]
80    #[ts(as = "Option<_>")]
81    #[ts(optional)]
82    pub plugin: PluginUpdate,
83
84    #[serde(default)]
85    #[ts(as = "Option<_>")]
86    #[ts(optional)]
87    pub title: TitleUpdate,
88
89    #[serde(default)]
90    #[ts(as = "Option<_>")]
91    #[ts(optional)]
92    pub table: TableUpdate,
93
94    #[serde(default)]
95    #[ts(as = "Option<_>")]
96    #[ts(optional)]
97    pub theme: ThemeUpdate,
98
99    #[serde(default)]
100    #[ts(as = "Option<_>")]
101    #[ts(optional)]
102    pub settings: SettingsUpdate,
103
104    #[serde(default)]
105    #[ts(as = "Option<_>")]
106    #[ts(optional)]
107    pub plugin_config: PluginConfigUpdate,
108
109    #[serde(default)]
110    #[ts(as = "Option<_>")]
111    #[ts(optional)]
112    pub columns_config: ColumnConfigUpdate,
113
114    #[serde(flatten)]
115    pub view_config: ViewConfigUpdate,
116}
117
118impl ViewerConfigUpdate {
119    /// Decode a `JsValue` into a `ViewerConfigUpdate` by auto-detecting format
120    /// from JavaScript type.
121    pub fn decode(update: &JsValue) -> ApiResult<Self> {
122        Ok(update.into_serde_ext()?)
123    }
124
125    pub fn migrate(&self) -> ApiResult<Self> {
126        // TODO: Call the migrate script from js
127        Ok(self.clone())
128    }
129}
130
131impl std::fmt::Display for ViewerConfigUpdate {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        write!(
134            f,
135            "{}",
136            serde_json::to_string(self).map_err(|_| std::fmt::Error)?
137        )
138    }
139}
140
141#[derive(Clone, Debug, Serialize, PartialEq, TS)]
142#[serde(untagged)]
143// #[ts(untagged)]
144pub enum OptionalUpdate<T: Clone> {
145    #[ts(skip)]
146    SetDefault,
147
148    // #[ts(skip)]
149    // #[ts(type = "undefined")]
150    Missing,
151
152    // #[ts(type = "_")]
153    // #[ts(untagged)]
154    Update(T),
155}
156
157pub type PluginUpdate = OptionalUpdate<String>;
158pub type SettingsUpdate = OptionalUpdate<bool>;
159pub type ThemeUpdate = OptionalUpdate<String>;
160pub type TitleUpdate = OptionalUpdate<String>;
161pub type TableUpdate = OptionalUpdate<String>;
162pub type VersionUpdate = OptionalUpdate<String>;
163pub type ColumnConfigUpdate = OptionalUpdate<ColumnConfigMap>;
164pub type PluginConfigUpdate = OptionalUpdate<serde_json::Map<String, Value>>;
165
166/// Handles `{}` when included as a field with `#[serde(default)]`.
167impl<T: Clone> Default for OptionalUpdate<T> {
168    fn default() -> Self {
169        Self::Missing
170    }
171}
172
173/// Handles `{plugin: null}` and `{plugin: val}` by treating this type as an
174/// option.
175impl<T: Clone> From<Option<T>> for OptionalUpdate<T> {
176    fn from(opt: Option<T>) -> Self {
177        match opt {
178            Some(v) => Self::Update(v),
179            None => Self::SetDefault,
180        }
181    }
182}
183
184/// Treats `PluginUpdate` enum as an `Option<T>` when present during
185/// deserialization.
186impl<'a, T> Deserialize<'a> for OptionalUpdate<T>
187where
188    T: Deserialize<'a> + Clone,
189{
190    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
191    where
192        D: Deserializer<'a>,
193    {
194        Option::deserialize(deserializer).map(Into::into)
195    }
196}