perspective_viewer/config/
viewer_config.rs1use 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#[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 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 pub fn decode(update: &JsValue) -> ApiResult<Self> {
122 Ok(update.into_serde_ext()?)
123 }
124
125 pub fn migrate(&self) -> ApiResult<Self> {
126 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)]
143pub enum OptionalUpdate<T: Clone> {
145 #[ts(skip)]
146 SetDefault,
147
148 Missing,
151
152 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
166impl<T: Clone> Default for OptionalUpdate<T> {
168 fn default() -> Self {
169 Self::Missing
170 }
171}
172
173impl<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
184impl<'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}