Something went wrong. Try again.
Something went wrong. Try again.
15 kB · 399 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400//! Sync notebook theme to ColourScheme and Theme records.
use jacquard::client::AgentSessionExt;use jacquard::types::aturi::AtUri;use jacquard::types::string::{Cid, RecordKey};use jacquard::CowStr;use jacquard::IntoStatic;use weaver_api::com_atproto::repo::strong_ref::StrongRef;use weaver_api::sh_weaver::notebook::colour_scheme::ColourScheme;use weaver_api::sh_weaver::notebook::theme::{ Theme, ThemeDarkCodeTheme, ThemeFonts, ThemeLightCodeTheme, ThemeSpacing,};use weaver_common::WeaverError;use weaver_renderer::colour_gen::{ detect_variant, generate_counterpart_palette, generate_palette, ThemeVariant,};
use crate::components::inline_theme_editor::InlineThemeValues;use crate::fetch::Fetcher;
/// Result of syncing theme records.pub struct ThemeSyncResult { pub theme_uri: AtUri<'static>, pub theme_cid: Cid<'static>,}
/// Create or update theme records for a notebook.////// Returns the Theme record's URI and CID for updating Book.theme.pub async fn sync_theme( fetcher: &Fetcher, existing_theme_ref: Option<&StrongRef<'_>>, values: &InlineThemeValues,) -> Result<ThemeSyncResult, WeaverError> { // Determine primary variant. let primary_variant = match values.default_mode.as_str() { "light" => ThemeVariant::Light, "dark" => ThemeVariant::Dark, _ => detect_variant(&values.background) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?, };
// Generate palettes. let light_inputs = values.light_inputs(); let dark_inputs = values.dark_inputs();
let (light_palette, dark_palette) = match primary_variant { ThemeVariant::Light => { let light = generate_palette(&light_inputs, ThemeVariant::Light) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?; let dark = if values.dark_background.is_some() { generate_palette(&dark_inputs, ThemeVariant::Dark) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? } else { generate_counterpart_palette(&light_inputs, ThemeVariant::Light) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? }; (light, dark) } ThemeVariant::Dark => { let dark = generate_palette(&dark_inputs, ThemeVariant::Dark) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?; let light = if values.light_background.is_some() { generate_palette(&light_inputs, ThemeVariant::Light) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? } else { generate_counterpart_palette(&dark_inputs, ThemeVariant::Dark) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? }; (light, dark) } };
// Code theme values. let light_code = ThemeLightCodeTheme::CodeThemeName(Box::new(CowStr::from( values.light_code_theme.clone(), ))); let dark_code = ThemeDarkCodeTheme::CodeThemeName(Box::new(CowStr::from(values.dark_code_theme.clone()))); let default_theme: Option<CowStr<'static>> = match values.default_mode.as_str() { "light" => Some(CowStr::from("light")), "dark" => Some(CowStr::from("dark")), _ => None, };
if let Some(theme_ref) = existing_theme_ref { // UPDATE existing records. let theme_uri = &theme_ref.uri; let existing_theme: Theme<'static> = fetcher .fetch_record(&Theme::uri(theme_uri.as_ref()) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?) .await .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? .value .into_static();
// Update light ColourScheme. let light_scheme_rkey = existing_theme .light_scheme .uri .rkey() .ok_or_else(|| WeaverError::InvalidNotebook("Light scheme URI missing rkey".into()))?; let light_result = fetcher .put_record( RecordKey::any(light_scheme_rkey.as_ref()) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? .into_static(), ColourScheme::new() .name(CowStr::from("Custom Light")) .variant(CowStr::from("light")) .colours(light_palette) .build(), ) .await .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?;
// Update dark ColourScheme. let dark_scheme_rkey = existing_theme .dark_scheme .uri .rkey() .ok_or_else(|| WeaverError::InvalidNotebook("Dark scheme URI missing rkey".into()))?; let dark_result = fetcher .put_record( RecordKey::any(dark_scheme_rkey.as_ref()) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? .into_static(), ColourScheme::new() .name(CowStr::from("Custom Dark")) .variant(CowStr::from("dark")) .colours(dark_palette) .build(), ) .await .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?;
// Update Theme with new CIDs. let theme_rkey = theme_uri .rkey() .ok_or_else(|| WeaverError::InvalidNotebook("Theme URI missing rkey".into()))?; let theme_result = fetcher .put_record( RecordKey::any(theme_rkey.as_ref()) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? .into_static(), Theme::new() .light_scheme( StrongRef::new() .uri(light_result.uri.clone().into_static()) .cid(light_result.cid.clone().into_static()) .build(), ) .dark_scheme( StrongRef::new() .uri(dark_result.uri.clone().into_static()) .cid(dark_result.cid.clone().into_static()) .build(), ) .light_code_theme(light_code) .dark_code_theme(dark_code) .fonts( ThemeFonts::new() .body(vec![]) .heading(vec![]) .monospace(vec![]) .build(), ) .spacing(ThemeSpacing { base_size: CowStr::from("1rem"), line_height: CowStr::from("1.6"), scale: CowStr::from("1.25"), extra_data: None, }) .maybe_default_theme(default_theme) .build(), ) .await .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?;
Ok(ThemeSyncResult { theme_uri: theme_result.uri.into_static(), theme_cid: theme_result.cid.into_static(), }) } else { // CREATE new records with fresh TIDs. let light_scheme = ColourScheme::new() .name(CowStr::from("Custom Light")) .variant(CowStr::from("light")) .colours(light_palette) .build();
let light_result = fetcher .create_record(light_scheme, None) .await .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?;
let dark_scheme = ColourScheme::new() .name(CowStr::from("Custom Dark")) .variant(CowStr::from("dark")) .colours(dark_palette) .build();
let dark_result = fetcher .create_record(dark_scheme, None) .await .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?;
let theme = Theme::new() .light_scheme( StrongRef::new() .uri(light_result.uri.clone().into_static()) .cid(light_result.cid.clone().into_static()) .build(), ) .dark_scheme( StrongRef::new() .uri(dark_result.uri.clone().into_static()) .cid(dark_result.cid.clone().into_static()) .build(), ) .light_code_theme(light_code) .dark_code_theme(dark_code) .fonts( ThemeFonts::new() .body(vec![]) .heading(vec![]) .monospace(vec![]) .build(), ) .spacing(ThemeSpacing { base_size: CowStr::from("1rem"), line_height: CowStr::from("1.6"), scale: CowStr::from("1.25"), extra_data: None, }) .maybe_default_theme(default_theme) .build();
let theme_result = fetcher .create_record(theme, None) .await .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?;
Ok(ThemeSyncResult { theme_uri: theme_result.uri.into_static(), theme_cid: theme_result.cid.into_static(), }) }}
/// Check if theme values have been customized from defaults.pub fn has_theme_customizations(values: &InlineThemeValues) -> bool { !values.background.is_empty() || !values.text.is_empty() || !values.primary.is_empty() || !values.link.is_empty() || values.light_background.is_some() || values.dark_background.is_some()}
/// Load theme values from an existing theme reference.////// Fetches Theme and ColourScheme records, extracts the 4 base colours/// (base→background, text→text, primary→primary, link→link).pub async fn load_theme_values( fetcher: &Fetcher, theme_ref: &StrongRef<'_>,) -> Result<InlineThemeValues, WeaverError> { // Fetch Theme record. let theme: Theme<'static> = fetcher .fetch_record( &Theme::uri(theme_ref.uri.as_ref()) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?, ) .await .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? .value .into_static();
// Fetch light ColourScheme. let light_scheme: ColourScheme<'static> = fetcher .fetch_record( &ColourScheme::uri(theme.light_scheme.uri.as_ref()) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?, ) .await .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? .value .into_static();
// Fetch dark ColourScheme. let dark_scheme: ColourScheme<'static> = fetcher .fetch_record( &ColourScheme::uri(theme.dark_scheme.uri.as_ref()) .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))?, ) .await .map_err(|e| WeaverError::InvalidNotebook(e.to_string().into()))? .value .into_static();
// Determine default mode. let default_mode = theme .default_theme .as_ref() .map(|s| s.to_string()) .unwrap_or_else(|| "auto".to_string());
// Extract code themes. let light_code_theme = match &theme.light_code_theme { ThemeLightCodeTheme::CodeThemeName(name) => name.as_ref().to_string(), ThemeLightCodeTheme::CodeThemeFile(file) => file.name.as_ref().to_string(), ThemeLightCodeTheme::Unknown(_) => "base16-ocean.light".to_string(), }; let dark_code_theme = match &theme.dark_code_theme { ThemeDarkCodeTheme::CodeThemeName(name) => name.as_ref().to_string(), ThemeDarkCodeTheme::CodeThemeFile(file) => file.name.as_ref().to_string(), ThemeDarkCodeTheme::Unknown(_) => "base16-ocean.dark".to_string(), };
// Determine primary variant and extract base colours. let (background, text, primary, link) = match default_mode.as_str() { "dark" => ( dark_scheme.colours.base.to_string(), dark_scheme.colours.text.to_string(), dark_scheme.colours.primary.to_string(), dark_scheme.colours.link.to_string(), ), _ => ( light_scheme.colours.base.to_string(), light_scheme.colours.text.to_string(), light_scheme.colours.primary.to_string(), light_scheme.colours.link.to_string(), ), };
// Check if variants differ (user customized per-variant colours). let light_differs = default_mode == "dark" && (light_scheme.colours.base.as_ref() != dark_scheme.colours.base.as_ref() || light_scheme.colours.text.as_ref() != dark_scheme.colours.text.as_ref() || light_scheme.colours.primary.as_ref() != dark_scheme.colours.primary.as_ref() || light_scheme.colours.link.as_ref() != dark_scheme.colours.link.as_ref());
let dark_differs = default_mode != "dark" && (dark_scheme.colours.base.as_ref() != light_scheme.colours.base.as_ref() || dark_scheme.colours.text.as_ref() != light_scheme.colours.text.as_ref() || dark_scheme.colours.primary.as_ref() != light_scheme.colours.primary.as_ref() || dark_scheme.colours.link.as_ref() != light_scheme.colours.link.as_ref());
Ok(InlineThemeValues { background, text, primary, link, light_background: if light_differs { Some(light_scheme.colours.base.to_string()) } else { None }, light_text: if light_differs { Some(light_scheme.colours.text.to_string()) } else { None }, light_primary: if light_differs { Some(light_scheme.colours.primary.to_string()) } else { None }, light_link: if light_differs { Some(light_scheme.colours.link.to_string()) } else { None }, dark_background: if dark_differs { Some(dark_scheme.colours.base.to_string()) } else { None }, dark_text: if dark_differs { Some(dark_scheme.colours.text.to_string()) } else { None }, dark_primary: if dark_differs { Some(dark_scheme.colours.primary.to_string()) } else { None }, dark_link: if dark_differs { Some(dark_scheme.colours.link.to_string()) } else { None }, light_code_theme, dark_code_theme, default_mode, })}