From 41406f2d1ef54d1c11a926fb01c162f5b1add1d3 Mon Sep 17 00:00:00 2001 From: timurgordon Date: Wed, 25 Jun 2025 03:54:49 +0300 Subject: [PATCH] add theme data to circle and fix slideshow --- heromodels/src/models/circle/circle.rs | 41 +++-- heromodels/src/models/circle/mod.rs | 2 +- heromodels/src/models/circle/rhai.rs | 201 ++++++++++++++++++++----- heromodels/src/models/library/items.rs | 65 ++++---- heromodels/src/models/library/rhai.rs | 68 +++++---- heromodels/src/models/mod.rs | 2 +- 6 files changed, 270 insertions(+), 109 deletions(-) diff --git a/heromodels/src/models/circle/circle.rs b/heromodels/src/models/circle/circle.rs index 450444b..b9a0696 100644 --- a/heromodels/src/models/circle/circle.rs +++ b/heromodels/src/models/circle/circle.rs @@ -1,10 +1,33 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; -// Temporarily removed to fix compilation issues -// use rhai_autobind_macros::rhai_model_export; use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; + +/// Represents the visual theme for a circle. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct ThemeData { + pub primary_color: String, + pub background_color: String, + pub background_pattern: String, + pub logo_symbol: String, + pub logo_url: String, + pub nav_dashboard_visible: bool, + pub nav_timeline_visible: bool, +} + +impl Default for ThemeData { + fn default() -> Self { + Self { + primary_color: "#3b82f6".to_string(), + background_color: "#0a0a0a".to_string(), + background_pattern: "none".to_string(), + logo_symbol: "◯".to_string(), + logo_url: "".to_string(), + nav_dashboard_visible: true, + nav_timeline_visible: true, + } + } +} /// Represents an event in a calendar #[model] @@ -24,7 +47,7 @@ pub struct Circle { /// Logo URL or symbol for the circle pub logo: Option, /// Theme settings for the circle (colors, styling, etc.) - pub theme: HashMap, + pub theme: ThemeData, } impl Circle { @@ -38,7 +61,7 @@ impl Circle { circles: Vec::new(), logo: None, members: Vec::new(), - theme: HashMap::new(), + theme: ThemeData::default(), } } @@ -66,14 +89,8 @@ impl Circle { self } - /// Sets a theme property for the circle - pub fn theme_property(mut self, key: impl ToString, value: impl ToString) -> Self { - self.theme.insert(key.to_string(), value.to_string()); - self - } - /// Sets the entire theme for the circle - pub fn theme(mut self, theme: HashMap) -> Self { + pub fn theme(mut self, theme: ThemeData) -> Self { self.theme = theme; self } diff --git a/heromodels/src/models/circle/mod.rs b/heromodels/src/models/circle/mod.rs index cb5c353..864b982 100644 --- a/heromodels/src/models/circle/mod.rs +++ b/heromodels/src/models/circle/mod.rs @@ -3,5 +3,5 @@ pub mod circle; pub mod rhai; // Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs -pub use self::circle::Circle; +pub use self::circle::{Circle, ThemeData}; pub use rhai::register_circle_rhai_module; diff --git a/heromodels/src/models/circle/rhai.rs b/heromodels/src/models/circle/rhai.rs index 3bab892..d51c1cb 100644 --- a/heromodels/src/models/circle/rhai.rs +++ b/heromodels/src/models/circle/rhai.rs @@ -4,28 +4,23 @@ use rhai::{Array, CustomType, Dynamic, Engine, EvalAltResult, INT, Module, Posit use std::mem; use std::sync::Arc; -use super::circle::Circle; +use super::circle::{Circle, ThemeData}; type RhaiCircle = Circle; +type RhaiThemeData = ThemeData; + use crate::db::Collection; use crate::db::hero::OurDB; use serde::Serialize; use serde_json; -use std::collections::HashMap; /// Registers a `.json()` method for any type `T` that implements the required traits. fn register_json_method(engine: &mut Engine) where - // The type must be: - T: CustomType + Clone + Serialize, // A clonable, serializable, custom type for Rhai + T: CustomType + Clone + Serialize, { - // This is the function that will be called when a script runs '.json()' let to_json_fn = |obj: &mut T| -> Result> { - // Use serde_json to serialize the object to a pretty-formatted string. - // The '?' will automatically convert any serialization error into a Rhai error. serde_json::to_string(obj).map_err(|e| e.to_string().into()) }; - - // Register the function as a method named "json" for the type 'T'. engine.build_type::().register_fn("json", to_json_fn); } @@ -39,6 +34,128 @@ fn id_from_i64_to_u32(id_i64: i64) -> Result> { }) } +#[export_module] +mod rhai_theme_data_module { + #[rhai_fn(name = "new_theme_data")] + pub fn new_theme_data() -> RhaiThemeData { + ThemeData::default() + } + + // --- Setters for ThemeData --- + #[rhai_fn(name = "primary_color", return_raw, global, pure)] + pub fn set_primary_color( + theme: &mut RhaiThemeData, + color: String, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.primary_color = color; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "background_color", return_raw, global, pure)] + pub fn set_background_color( + theme: &mut RhaiThemeData, + color: String, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.background_color = color; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "background_pattern", return_raw, global, pure)] + pub fn set_background_pattern( + theme: &mut RhaiThemeData, + pattern: String, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.background_pattern = pattern; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "logo_symbol", return_raw, global, pure)] + pub fn set_logo_symbol( + theme: &mut RhaiThemeData, + symbol: String, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.logo_symbol = symbol; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "logo_url", return_raw, global, pure)] + pub fn set_logo_url( + theme: &mut RhaiThemeData, + url: String, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.logo_url = url; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "nav_dashboard_visible", return_raw, global, pure)] + pub fn set_nav_dashboard_visible( + theme: &mut RhaiThemeData, + visible: bool, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.nav_dashboard_visible = visible; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "nav_timeline_visible", return_raw, global, pure)] + pub fn set_nav_timeline_visible( + theme: &mut RhaiThemeData, + visible: bool, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.nav_timeline_visible = visible; + *theme = owned_theme; + Ok(theme.clone()) + } + + // --- Getters for ThemeData --- + #[rhai_fn(name = "get_primary_color", pure)] + pub fn get_primary_color(theme: &mut RhaiThemeData) -> String { + theme.primary_color.clone() + } + + #[rhai_fn(name = "get_background_color", pure)] + pub fn get_background_color(theme: &mut RhaiThemeData) -> String { + theme.background_color.clone() + } + + #[rhai_fn(name = "get_background_pattern", pure)] + pub fn get_background_pattern(theme: &mut RhaiThemeData) -> String { + theme.background_pattern.clone() + } + + #[rhai_fn(name = "get_logo_symbol", pure)] + pub fn get_logo_symbol(theme: &mut RhaiThemeData) -> String { + theme.logo_symbol.clone() + } + + #[rhai_fn(name = "get_logo_url", pure)] + pub fn get_logo_url(theme: &mut RhaiThemeData) -> String { + theme.logo_url.clone() + } + + #[rhai_fn(name = "get_nav_dashboard_visible", pure)] + pub fn get_nav_dashboard_visible(theme: &mut RhaiThemeData) -> bool { + theme.nav_dashboard_visible + } + + #[rhai_fn(name = "get_nav_timeline_visible", pure)] + pub fn get_nav_timeline_visible(theme: &mut RhaiThemeData) -> bool { + theme.nav_timeline_visible + } +} + #[export_module] mod rhai_circle_module { // --- Circle Functions --- @@ -95,7 +212,7 @@ mod rhai_circle_module { #[rhai_fn(name = "theme", return_raw, global, pure)] pub fn circle_theme( circle: &mut RhaiCircle, - theme: HashMap, + theme: RhaiThemeData, ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.theme(theme); @@ -108,7 +225,6 @@ mod rhai_circle_module { circle: &mut RhaiCircle, added_circle: String, ) -> Result> { - // Use take to get ownership of the circle let owned_circle = mem::take(circle); *circle = owned_circle.add_circle(added_circle); Ok(circle.clone()) @@ -120,93 +236,89 @@ mod rhai_circle_module { circle: &mut RhaiCircle, added_member: String, ) -> Result> { - // Use take to get ownership of the circle let owned_circle = mem::take(circle); *circle = owned_circle.add_member(added_member); Ok(circle.clone()) } // Circle Getters - #[rhai_fn(get = "id", pure)] + #[rhai_fn(name = "get_id", pure)] pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 { circle.base_data.id as i64 } - #[rhai_fn(get = "created_at", pure)] + #[rhai_fn(name = "get_created_at", pure)] pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.created_at } - #[rhai_fn(get = "modified_at", pure)] + #[rhai_fn(name = "get_modified_at", pure)] pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.modified_at } - #[rhai_fn(get = "title", pure)] + #[rhai_fn(name = "get_title", pure)] pub fn get_circle_title(circle: &mut RhaiCircle) -> String { circle.title.clone() } - #[rhai_fn(get = "description", pure)] + #[rhai_fn(name = "get_description", pure)] pub fn get_circle_description(circle: &mut RhaiCircle) -> Option { circle.description.clone() } - #[rhai_fn(get = "circles", pure)] + #[rhai_fn(name = "get_circles", pure)] pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec { circle.circles.clone() } - #[rhai_fn(get = "ws_url", pure)] + #[rhai_fn(name = "get_ws_url", pure)] pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String { circle.ws_url.clone() } - #[rhai_fn(get = "logo", pure)] + #[rhai_fn(name = "get_logo", pure)] pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option { circle.logo.clone() } - #[rhai_fn(get = "theme", pure)] - pub fn get_circle_theme(circle: &mut RhaiCircle) -> HashMap { + #[rhai_fn(name = "get_theme", pure)] + pub fn get_circle_theme(circle: &mut RhaiCircle) -> RhaiThemeData { circle.theme.clone() } } pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { - // Register the exported module globally - let module = exported_module!(rhai_circle_module); - engine.register_global_module(module.into()); + engine.build_type::(); + engine.build_type::(); - // Create a module for database functions let mut db_module = Module::new(); + let circle_module = exported_module!(rhai_circle_module); + let theme_data_module = exported_module!(rhai_theme_data_module); + + engine.register_global_module(circle_module.into()); + engine.register_global_module(theme_data_module.into()); + + register_json_method::(engine); + register_json_method::(engine); // Manually register database functions as they need to capture 'db' let db_clone_set_circle = db.clone(); db_module.set_native_fn( "save_circle", move |circle: Circle| -> Result> { - // Use the Collection trait method directly let result = db_clone_set_circle.set(&circle).map_err(|e| { Box::new(EvalAltResult::ErrorRuntime( format!("DB Error set_circle: {}", e).into(), Position::NONE, )) })?; - - // Return the updated circle with the correct ID Ok(result.1) }, ); - register_json_method::(engine); - - // Manually register database functions as they need to capture 'db' let db_clone_delete_circle = db.clone(); db_module.set_native_fn( "delete_circle", move |circle: Circle| -> Result<(), Box> { - // Use the Collection trait method directly let result = db_clone_delete_circle .collection::() .expect("can open circle collection") .delete_by_id(circle.base_data.id) .expect("can delete circle"); - - // Return the updated circle with the correct ID Ok(result) }, ); @@ -215,7 +327,6 @@ pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { db_module.set_native_fn( "get_circle", move || -> Result> { - // Use the Collection trait method directly let all_circles: Vec = db_clone_get_circle.get_all().map_err(|e| { Box::new(EvalAltResult::ErrorRuntime( format!("DB Error get_circle: {}", e).into(), @@ -234,12 +345,26 @@ pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { }, ); + // --- Collection DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn( + "save_circle", + move |circle: RhaiCircle| -> Result> { + let result = db_clone.set(&circle).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); + let db_clone_get_circle_by_id = db.clone(); db_module.set_native_fn( "get_circle_by_id", move |id_i64: INT| -> Result> { let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly db_clone_get_circle_by_id .get_by_id(id_u32) .map_err(|e| { @@ -257,7 +382,6 @@ pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { }, ); - // Add list_circles function to get all circles let db_clone_list_circles = db.clone(); db_module.set_native_fn( "list_circles", @@ -282,7 +406,6 @@ pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { }, ); - // Register the database module globally engine.register_global_module(db_module.into()); println!("Successfully registered circle Rhai module using export_module approach."); diff --git a/heromodels/src/models/library/items.rs b/heromodels/src/models/library/items.rs index aacd182..0743be4 100644 --- a/heromodels/src/models/library/items.rs +++ b/heromodels/src/models/library/items.rs @@ -301,10 +301,10 @@ impl Book { } } -/// Represents a Slides library item (collection of images for slideshow). +/// Represents a Slideshow library item (collection of images for slideshow). #[model] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] -pub struct Slides { +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Slideshow { /// Base model data pub base_data: BaseModelData, /// Title of the slideshow @@ -312,26 +312,44 @@ pub struct Slides { pub title: String, /// Optional description of the slideshow pub description: Option, - /// List of slide image URLs - pub slide_urls: Vec, - /// Optional slide titles/captions - pub slide_titles: Vec>, + /// List of slides + pub slides: Vec, } -impl Default for Slides { - fn default() -> Self { +#[derive(Debug, Clone, Serialize, Default, Deserialize, PartialEq, CustomType)] +pub struct Slide { + pub image_url: String, + pub title: Option, + pub description: Option, +} + +impl Slide { + pub fn new() -> Self { Self { - base_data: BaseModelData::new(), - title: String::new(), + image_url: String::new(), + title: None, description: None, - slide_urls: Vec::new(), - slide_titles: Vec::new(), } } + + pub fn url(mut self, url: impl Into) -> Self { + self.image_url = url.into(); + self + } + + pub fn title(mut self, title: impl Into) -> Self { + self.title = Some(title.into()); + self + } + + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } } -impl Slides { - /// Creates a new `Slides` with default values. +impl Slideshow { + /// Creates a new `Slideshow` with default values. pub fn new() -> Self { Self::default() } @@ -349,21 +367,8 @@ impl Slides { } /// Adds a slide with URL and optional title. - pub fn add_slide(mut self, url: impl Into, title: Option) -> Self { - self.slide_urls.push(url.into()); - self.slide_titles.push(title); - self - } - - /// Sets all slide URLs at once. - pub fn slide_urls(mut self, urls: Vec) -> Self { - self.slide_urls = urls; - self - } - - /// Sets all slide titles at once. - pub fn slide_titles(mut self, titles: Vec>) -> Self { - self.slide_titles = titles; + pub fn add_slide(mut self, slide: Slide) -> Self { + self.slides.push(slide); self } } diff --git a/heromodels/src/models/library/rhai.rs b/heromodels/src/models/library/rhai.rs index 734f04b..192e98e 100644 --- a/heromodels/src/models/library/rhai.rs +++ b/heromodels/src/models/library/rhai.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use super::collection::Collection as RhaiCollection; use super::items::{ Book as RhaiBook, Image as RhaiImage, Markdown as RhaiMarkdown, Pdf as RhaiPdf, - Slides as RhaiSlides, TocEntry as RhaiTocEntry, + Slide as RhaiSlide, Slideshow as RhaiSlides, TocEntry as RhaiTocEntry, }; use crate::db::Collection as DbCollectionTrait; use crate::db::hero::OurDB; @@ -565,7 +565,40 @@ mod rhai_library_module { book.pages.clone() } - // --- Slides Functions --- + // --- Slideshow Functions --- + #[rhai_fn(name = "new_slide")] + pub fn new_slide() -> RhaiSlide { + RhaiSlide::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn slide_title( + slide: &mut RhaiSlide, + title: String, + ) -> Result> { + let owned = mem::take(slide); + *slide = owned.title(title); + Ok(slide.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn slide_description( + slide: &mut RhaiSlide, + description: String, + ) -> Result> { + let owned = mem::take(slide); + *slide = owned.description(description); + Ok(slide.clone()) + } + + #[rhai_fn(name = "url", return_raw, global, pure)] + pub fn slide_url(slide: &mut RhaiSlide, url: String) -> Result> { + let owned = mem::take(slide); + *slide = owned.url(url); + Ok(slide.clone()) + } + + // --- Slideshow Functions --- #[rhai_fn(name = "new_slides")] pub fn new_slides() -> RhaiSlides { RhaiSlides::new() @@ -594,22 +627,10 @@ mod rhai_library_module { #[rhai_fn(name = "add_slide", return_raw, global, pure)] pub fn slides_add_slide( slides: &mut RhaiSlides, - url: String, - title: String, + slide: RhaiSlide, ) -> Result> { let owned = mem::take(slides); - let title_opt = if title.is_empty() { None } else { Some(title) }; - *slides = owned.add_slide(url, title_opt); - Ok(slides.clone()) - } - - #[rhai_fn(name = "add_slide", return_raw, global, pure)] - pub fn slides_add_slide_no_title( - slides: &mut RhaiSlides, - url: String, - ) -> Result> { - let owned = mem::take(slides); - *slides = owned.add_slide(url, None); + *slides = owned.add_slide(slide); Ok(slides.clone()) } @@ -638,14 +659,9 @@ mod rhai_library_module { slides.description.clone() } - #[rhai_fn(get = "slide_urls", pure)] - pub fn get_slides_slide_urls(slides: &mut RhaiSlides) -> Vec { - slides.slide_urls.clone() - } - - #[rhai_fn(get = "slide_titles", pure)] - pub fn get_slides_slide_titles(slides: &mut RhaiSlides) -> Vec> { - slides.slide_titles.clone() + #[rhai_fn(get = "slides", pure)] + pub fn get_slides_slides(slides: &mut RhaiSlides) -> Vec { + slides.slides.clone() } } @@ -969,7 +985,7 @@ pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { }, ); - // --- Slides DB Functions --- + // --- Slideshow DB Functions --- let db_clone = db.clone(); db_module.set_native_fn( "save_slides", @@ -999,7 +1015,7 @@ pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { })? .ok_or_else(|| { Box::new(EvalAltResult::ErrorRuntime( - format!("Slides with ID {} not found", slides_id).into(), + format!("Slideshow with ID {} not found", slides_id).into(), Position::NONE, )) }) diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index 7763482..9a121ff 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -20,7 +20,7 @@ pub use userexample::User; // pub use productexample::Product; // Temporarily remove pub use biz::{Sale, SaleItem, SaleStatus}; pub use calendar::{AttendanceStatus, Attendee, Calendar, Event}; -pub use circle::Circle; +pub use circle::{Circle, ThemeData}; pub use finance::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; pub use finance::{Account, Asset, AssetType}; pub use flow::{Flow, FlowStep, SignatureRequirement};