use chrono::NaiveDate; use core::fmt; use egui::ScrollArea; use serde::{Deserialize, Serialize}; use std::path::Path; use crate::{ RightPanelContent, editors::object_editor::ObjectInstance, filesystem::{FILESYSTEM, LegacyFileSystem}, util::{self, Error}, }; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum FieldType { Image, SingleLine, MultiLine, Date, Number, Link { template_id: Option }, Links, } impl Default for FieldType { fn default() -> Self { Self::SingleLine } } impl FieldType { fn types() -> Vec { vec![ FieldType::Image, FieldType::SingleLine, FieldType::MultiLine, FieldType::Date, FieldType::Number, FieldType::Link { template_id: None }, FieldType::Links, ] } } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum FieldValue { Image(String), SingleLine(String), MultiLine(String), Date(NaiveDate), Number(f64), Link(String), Links(Vec), } impl FieldValue { pub fn from_type(_type: &FieldType) -> Self { match _type { FieldType::Image => Self::Image(String::new()), FieldType::SingleLine => Self::SingleLine(String::new()), FieldType::MultiLine => Self::MultiLine(String::new()), FieldType::Date => Self::Date(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()), FieldType::Number => Self::Number(0.0), FieldType::Link { template_id: None } => Self::Link(String::new()), FieldType::Link { template_id: Some(template_id), } => Self::Link(template_id.clone()), FieldType::Links => Self::Links(Vec::new()), } } } impl Default for FieldValue { fn default() -> Self { Self::SingleLine(String::new()) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FieldDefinition { pub name: String, pub field_type: FieldType, pub required: bool, #[serde(default)] pub on_preview: bool, pub description: Option, } #[derive(Serialize, Deserialize)] pub struct Template { pub name: String, pub id: String, pub description: Option, pub fields: Vec, #[serde(skip)] pub saved: bool, #[serde(skip)] pub error: Option, #[serde(skip)] pub new_field_name: String, #[serde(skip)] pub new_field_type: FieldType, #[serde(skip)] pub new_field_required: bool, #[serde(skip)] pub new_field_description: String, #[serde(skip)] pub new_field_on_preview: bool, } impl fmt::Debug for Template { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Template") .field("name", &self.name) .field("id", &self.id) .field("description", &self.description) .field("fields", &self.fields) .field("saved", &self.saved) .field("new_field_name", &self.new_field_name) .field("new_field_type", &self.new_field_type) .field("new_field_required", &self.new_field_required) .field("new_field_description", &self.new_field_description) .finish() } } impl Clone for Template { fn clone(&self) -> Self { Self { name: self.name.clone(), id: self.id.clone(), description: self.description.clone(), fields: self.fields.clone(), saved: self.saved, error: None, new_field_name: "".to_string(), new_field_type: FieldType::default(), new_field_required: false, new_field_description: "".to_string(), new_field_on_preview: false, } } } impl Default for Template { fn default() -> Self { Self { name: "New Template".to_string(), id: uuid::Uuid::new_v4().to_string(), description: Some(String::from("Placeholder description")), fields: Vec::new(), saved: false, error: None, new_field_name: "".to_string(), new_field_type: FieldType::default(), new_field_required: false, new_field_description: "".to_string(), new_field_on_preview: false, } } } impl Template { pub fn load(id: &str) -> Result> { let mut template = FILESYSTEM.read::(Path::new(&format!("templates/{id}.json")))?; template.saved = true; Ok(template) } pub fn save(&mut self) -> Result<(), Box> { let id = &self.id; FILESYSTEM.write(Path::new(&format!("templates/{id}.json")), self.clone())?; self.saved = true; Ok(()) } pub fn ui(&mut self, ui: &mut egui::Ui, new_instance: &mut Option) { if let Some(error) = &mut self.error { error.show(ui); } if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { if let Err(e) = self.save() { eprintln!("Failed to save: {e}"); } } ScrollArea::vertical().show(ui, |ui| { ui.vertical(|ui| { util::saved_status(ui, self.saved, &self.id, &self.name); // Save/Cancel buttons ui.horizontal(|ui| { if ui.button("Save").clicked() { if let Err(e) = self.save() { eprintln!("Failed to save: {e}"); } } if ui.button("Create Copy").clicked() { let mut copy = self.clone(); copy.id = uuid::Uuid::new_v4().to_string(); copy.name = format!("{} (Copy)", self.name); copy.save().unwrap(); } if ui.button("Delete").clicked() { let id = &self.id; FILESYSTEM .delete(Path::new(&format!("templates/{id}.json"))) .unwrap(); *new_instance = Some(RightPanelContent::None); } if ui.button("Cancel").clicked() { // load default state *self = Self::load(&self.id).unwrap(); } if ui.button("Use Template").clicked() { if self.saved { *new_instance = Some(RightPanelContent::Object(Box::new( ObjectInstance::new(self), ))); } else { self.error = Some(Error::new( "You must save the template before creating a new instance!" .to_string(), )); } } }); self.editor_ui(ui); }); }); } pub fn editor_ui(&mut self, ui: &mut egui::Ui) { egui::Grid::new("template_grid") .num_columns(2) .striped(true) .show(ui, |ui| { ui.label("Template Name:"); if ui .add(egui::TextEdit::singleline(&mut self.name).frame(false)) .changed() { self.saved = false; } ui.end_row(); ui.label("Description:"); if ui .add( egui::TextEdit::multiline(self.description.get_or_insert_with(String::new)) .desired_rows(1) .frame(false), ) .changed() { self.saved = false; } ui.end_row(); }); ui.separator(); egui::CollapsingHeader::new("Name").show(ui, |ui: &mut egui::Ui| { egui::Grid::new("field_grid") .num_columns(2) .striped(true) .show(ui, |ui| { ui.label("Name:"); ui.label("Name"); ui.end_row(); ui.label("Type:"); ui.label("SingleLine"); ui.end_row(); ui.label("Required"); ui.label("✓"); ui.end_row(); ui.label("Description:"); ui.label("Object name"); ui.end_row(); }); }); // List of fields let mut to_remove = None; for (i, field) in self.fields.iter_mut().enumerate() { let id = ui.make_persistent_id(format!("field_{i}")); egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, true) .show_header(ui, |ui| { if ui.button("❌").clicked() { to_remove = Some(i); } ui.strong(field.name.clone()); }) .body(|ui| { ui.separator(); egui::Grid::new("field_grid") .num_columns(2) .striped(true) .show(ui, |ui| { ui.label("Name:"); if ui.text_edit_singleline(&mut field.name).changed() { self.saved = false; } ui.end_row(); ui.label("Type:"); egui::ComboBox::from_id_salt(format!("field_type_{i}")) .selected_text(format!("{:?}", field.field_type)) .show_ui(ui, |ui| { for variant in FieldType::types() { if ui .selectable_value( &mut field.field_type, variant.clone(), format!("{variant:?}"), ) .changed() { self.saved = false; } } }); ui.end_row(); ui.label("Required:"); if ui.checkbox(&mut field.required, "").clicked() { self.saved = false; } ui.end_row(); ui.label("On Preview:"); if ui.checkbox(&mut field.on_preview, "").clicked() { self.saved = false; } ui.end_row(); ui.label("Description:"); if ui .text_edit_singleline( field.description.get_or_insert_with(String::new), ) .changed() { self.saved = false; } ui.end_row(); ui.separator(); }); }); } // Remove field if needed if let Some(index) = to_remove { self.fields.remove(index); self.saved = false; } // Add new field ui.separator(); ui.heading("Add New Field"); ui.horizontal(|ui| { egui::Grid::new("field_grid") .num_columns(2) .striped(true) .show(ui, |ui| { ui.label("Name:"); ui.text_edit_singleline(&mut self.new_field_name); ui.end_row(); ui.label("Type:"); egui::ComboBox::from_id_salt("new_field_type") .selected_text(format!("{:?}", self.new_field_type)) .show_ui(ui, |ui| { for variant in FieldType::types() { ui.selectable_value( &mut self.new_field_type, variant.clone(), format!("{variant:?}"), ); } }); ui.end_row(); ui.label("Required:"); ui.checkbox(&mut self.new_field_required, ""); ui.end_row(); ui.label("On Preview:"); ui.checkbox(&mut self.new_field_on_preview, ""); ui.end_row(); ui.label("Description:"); ui.text_edit_singleline(&mut self.new_field_description); ui.end_row(); if ui.button("Add Field").clicked() && !self.new_field_name.is_empty() { self.fields.push(FieldDefinition { name: self.new_field_name.clone(), field_type: self.new_field_type.clone(), on_preview: self.new_field_on_preview, required: self.new_field_required, description: if self.new_field_description.is_empty() { None } else { Some(self.new_field_description.clone()) }, }); self.saved = false; // Reset new field form self.new_field_name.clear(); self.new_field_required = false; self.new_field_description.clear(); } }); }); } }