diff options
Diffstat (limited to 'src/preferences/models.rs')
| -rw-r--r-- | src/preferences/models.rs | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/src/preferences/models.rs b/src/preferences/models.rs new file mode 100644 index 0000000..2cf4665 --- /dev/null +++ b/src/preferences/models.rs @@ -0,0 +1,266 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. + +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// Stage labels for custom stages (matches Swift Stage struct) +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct Stage { + pub i: String, + pub ii: String, + pub iii: String, + pub iv: String, +} + +/// Custom stage definition (matches Swift CustomStage) +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct CustomStage { + pub id: Uuid, + pub name: String, + pub stage: Stage, +} + +impl Default for CustomStage { + fn default() -> Self { + Self { + id: Uuid::new_v4(), + name: "Custom Stage".to_string(), + stage: Stage::default(), + } + } +} + +impl CustomStage { + pub fn cynefin_default() -> Self { + Self { + id: Uuid::new_v4(), + name: "Cynefin".to_string(), + stage: Stage { + i: "Chaotic".to_string(), + ii: "Complex".to_string(), + iii: "Complicated".to_string(), + iv: "Clear".to_string(), + }, + } + } + + pub fn placeholder_default() -> Self { + Self { + id: Uuid::new_v4(), + name: "New Stage".to_string(), + stage: Stage { + i: "Stage I".to_string(), + ii: "Stage II".to_string(), + iii: "Stage III".to_string(), + iv: "Stage IV".to_string(), + }, + } + } +} + +/// Template for new maps (matches Swift Template) +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Template { + pub id: Uuid, + pub name: String, + pub content: String, + #[serde(default)] + pub is_default: bool, +} + +impl Default for Template { + fn default() -> Self { + Self { + id: Uuid::new_v4(), + name: "Untitled".to_string(), + content: String::new(), + is_default: false, + } + } +} + +impl Template { + pub fn new(name: String, content: String, is_default: bool) -> Self { + Self { + id: Uuid::new_v4(), + name, + content, + is_default, + } + } +} + +/// JSON import/export format (matches Swift UserPreferencesJSON) +/// All fields optional to support partial imports +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct UserPreferencesJson { + // Map preferences + pub show_map_background: Option<bool>, + pub use_custom_font: Option<bool>, + pub custom_font_name: Option<String>, + pub default_export_format: Option<String>, + pub use_smart_label_positioning: Option<bool>, + + // Editor preferences + pub use_custom_editor_font: Option<bool>, + pub custom_editor_font_name: Option<String>, + pub editor_font_size: Option<f64>, + pub soft_wrap_lines: Option<bool>, + + // Templates and stages + pub map_templates: Option<Vec<Template>>, + pub custom_stages: Option<Vec<CustomStage>>, + + // UI preferences + pub view_style: Option<String>, + pub zoom: Option<f64>, +} + +/// Main preferences struct stored in app +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct UserPreferences { + // Map preferences + pub show_map_background: bool, + pub use_custom_font: bool, + pub custom_font_name: String, + pub default_export_format: String, + pub use_smart_label_positioning: bool, + + // Editor preferences + pub use_custom_editor_font: bool, + pub custom_editor_font_name: String, + pub editor_font_size: f64, + pub soft_wrap_lines: bool, + + // Templates and stages + pub map_templates: Vec<Template>, + pub custom_stages: Vec<CustomStage>, + + // UI preferences + pub view_style: String, + pub zoom: f64, +} + +impl Default for UserPreferences { + fn default() -> Self { + Self { + show_map_background: true, + use_custom_font: false, + custom_font_name: "Sans".to_string(), + default_export_format: "png".to_string(), + use_smart_label_positioning: true, + + use_custom_editor_font: false, + custom_editor_font_name: "Monospace".to_string(), + editor_font_size: 14.0, + soft_wrap_lines: false, + + map_templates: vec![ + Template::new( + "Default".to_string(), + "title My Map\n\nanchor Business [0.95, 0.63]\nanchor Public [0.95, 0.78]\ncomponent User Needs [0.9, 0.50]\n".to_string(), + true, + ), + Template::new("Empty".to_string(), String::new(), false), + ], + custom_stages: vec![CustomStage::cynefin_default()], + + view_style: "horizontal".to_string(), + zoom: 1.0, + } + } +} + +impl UserPreferences { + /// Convert to JSON export format + pub fn to_json(&self) -> UserPreferencesJson { + UserPreferencesJson { + show_map_background: Some(self.show_map_background), + use_custom_font: Some(self.use_custom_font), + custom_font_name: Some(self.custom_font_name.clone()), + default_export_format: Some(self.default_export_format.clone()), + use_smart_label_positioning: Some(self.use_smart_label_positioning), + use_custom_editor_font: Some(self.use_custom_editor_font), + custom_editor_font_name: Some(self.custom_editor_font_name.clone()), + editor_font_size: Some(self.editor_font_size), + soft_wrap_lines: Some(self.soft_wrap_lines), + map_templates: Some(self.map_templates.clone()), + custom_stages: Some(self.custom_stages.clone()), + view_style: Some(self.view_style.clone()), + zoom: Some(self.zoom), + } + } + + /// Update from JSON import (partial updates supported) + pub fn update_from_json(&mut self, json: &UserPreferencesJson) { + if let Some(v) = json.show_map_background { + self.show_map_background = v; + } + if let Some(ref v) = json.use_custom_font { + self.use_custom_font = *v; + } + if let Some(ref v) = json.custom_font_name { + self.custom_font_name = v.clone(); + } + if let Some(ref v) = json.default_export_format { + self.default_export_format = v.clone(); + } + if let Some(v) = json.use_smart_label_positioning { + self.use_smart_label_positioning = v; + } + if let Some(ref v) = json.use_custom_editor_font { + self.use_custom_editor_font = *v; + } + if let Some(ref v) = json.custom_editor_font_name { + self.custom_editor_font_name = v.clone(); + } + if let Some(v) = json.editor_font_size { + self.editor_font_size = v; + } + if let Some(v) = json.soft_wrap_lines { + self.soft_wrap_lines = v; + } + if let Some(ref v) = json.map_templates { + self.map_templates = v.clone(); + } + if let Some(ref v) = json.custom_stages { + self.custom_stages = v.clone(); + } + if let Some(ref v) = json.view_style { + self.view_style = v.clone(); + } + if let Some(v) = json.zoom { + self.zoom = v; + } + } + + /// Get the default template + #[allow(dead_code)] + pub fn default_template(&self) -> Option<&Template> { + self.map_templates.iter().find(|t| t.is_default) + } + + /// Set a template as default (unsets others) + pub fn set_default_template(&mut self, id: Uuid) { + for template in &mut self.map_templates { + template.is_default = template.id == id; + } + } +} |