diff options
| -rw-r--r-- | common/src/user.rs | 9 | ||||
| -rw-r--r-- | locale/en.ini | 4 | ||||
| -rw-r--r-- | server/src/ui/account/settings.rs | 31 | ||||
| -rw-r--r-- | ui/client-style/src/node_card.css | 3 | ||||
| -rw-r--r-- | ui/client-style/src/themes.css | 59 | ||||
| -rw-r--r-- | ui/src/components/user.rs | 11 | ||||
| -rw-r--r-- | ui/src/lib.rs | 14 | ||||
| -rw-r--r-- | ui/src/scaffold.rs | 7 |
8 files changed, 66 insertions, 72 deletions
diff --git a/common/src/user.rs b/common/src/user.rs index ccd9c5c..0a4111d 100644 --- a/common/src/user.rs +++ b/common/src/user.rs @@ -4,15 +4,20 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use jellyobject::{enums, fields}; +use jellyobject::{Tag, enums, fields}; fields! { USER_LOGIN: &str = b"Ulgn"; USER_PASSWORD: &[u8] = b"Upwd"; USER_NAME: &str = b"Unam"; - USER_THEME: &str = b"Uthm"; USER_ADMIN: () = b"Uadm"; + USER_THEME_PRESET: Tag = b"Utpr"; + USER_THEME_ACCENT: u32 = b"Utac"; UDATA_WATCHED: () = b"Dwat"; UDATA_RATING: f64 = b"Drat"; } +enums! { + THEME_DARK = b"dark"; + THEME_LIGHT = b"ligh"; +} diff --git a/locale/en.ini b/locale/en.ini index 15658ae..8ec8c32 100644 --- a/locale/en.ini +++ b/locale/en.ini @@ -148,6 +148,10 @@ tag.Uadm=Admin tag.Ulgn=Login tag.Unam=Name tag.Upwd=Password +tag.Utac=Accent Color +tag.Utpr.dark=Dark +tag.Utpr.ligh=Light +tag.Utpr=Theme Preset theme.dark=Dark theme.light=Light diff --git a/server/src/ui/account/settings.rs b/server/src/ui/account/settings.rs index 2e9fcbe..2052c5e 100644 --- a/server/src/ui/account/settings.rs +++ b/server/src/ui/account/settings.rs @@ -9,10 +9,9 @@ use crate::{ }; use anyhow::anyhow; use jellycommon::{ - MESSAGE_KIND, MESSAGE_TEXT, USER_LOGIN, USER_NAME, USER_PASSWORD, VIEW_MESSAGE, - VIEW_USER_SETTINGS, - jellyobject::{Object, ObjectBuffer, ObjectBufferBuilder, Path}, + jellyobject::{Object, ObjectBuffer, ObjectBufferBuilder, Path, Tag}, routes::u_account_settings, + *, }; use jellydb::{Filter, Query, Sort}; use jellyui::tr; @@ -31,6 +30,8 @@ pub struct SettingsForm { password: Option<String>, #[field(validate = option_len(4..32))] name: Option<String>, + theme_accent: Option<u32>, + theme_preset: Option<String>, } fn option_len<'v>(value: &Option<String>, range: Range<usize>) -> form::Result<'v, ()> { @@ -87,21 +88,25 @@ pub fn r_account_settings_post( out += &*tr(ri.lang, "settings.account.display_name.changed"); out += "\n"; } - // if let Some(theme) = form.theme { - // update_user_theme(&ri.session, theme.0)?; - // out += &*tr(ri.lang, "settings.account.theme.changed"); - // out += "\n"; - // } + if let Some(preset) = &form.theme_preset { + let tag = Tag::new( + preset + .as_bytes() + .try_into() + .map_err(|_| anyhow!("invalid theme preset"))?, + ); + update_user(&ri, |user| user.insert(USER_THEME_PRESET, tag))?; + out += &*tr(ri.lang, "settings.appearance.theme.changed"); + out += "\n"; + } + if let Some(accent) = form.theme_accent { + update_user(&ri, |user| user.insert(USER_THEME_ACCENT, accent))?; + } // if let Some(player_preference) = form.player_preference { // update_user_player_preference(&ri.session, player_preference.0)?; // out += &*tr(ri.lang, "settings.player_preference.changed"); // out += "\n"; // } - // if let Some(native_secret) = &form.native_secret { - // update_user_native_secret(&ri.session, native_secret)?; - // out += &*tr(ri.lang, "settings.native_secret.changed"); - // out += "\n"; - // } let out = if out.is_empty() { tr(ri.lang, "settings.no_change").to_string() } else { diff --git a/ui/client-style/src/node_card.css b/ui/client-style/src/node_card.css index 8148845..f9197c5 100644 --- a/ui/client-style/src/node_card.css +++ b/ui/client-style/src/node_card.css @@ -158,6 +158,9 @@ transparent 100% ); } +.card.highlight .inner { + border-radius: 1em; +} .card.highlight .overview { flex-grow: 1; } diff --git a/ui/client-style/src/themes.css b/ui/client-style/src/themes.css index 18a79b5..2e8e2df 100644 --- a/ui/client-style/src/themes.css +++ b/ui/client-style/src/themes.css @@ -5,20 +5,20 @@ */ body { --video-brackground: black; - --c-danger: rgb(177, 36, 36); + --accent-hue: 277; } body.theme-dark { - --accent-light: rgb(255, 163, 87); - --accent-dark: rgb(199, 90, 0); + --accent-light: hsl(var(--accent-hue), 100%, 67%); + --accent-dark: hsl(var(--accent-hue), 65%, 49%); --c-error: rgb(255, 117, 117); --c-warn: rgb(252, 255, 78); --c-success: rgb(117, 255, 117); --c-nav: #1c1c1c9a; --c-nav-hover: #ffffff10; --c-fade: black; - --overlay-poster: #0005; - --overlay: #0006; - --overlay-hover: #0009; + --c-danger: rgb(177, 36, 36); + --overlay: #0005; + --overlay-hover: #0008; --background-dark: #070707; --background-light: #1c1c1c; --background-very-light: #323232; @@ -30,14 +30,15 @@ body.theme-dark { --image-loading-placeholder: rgb(50, 50, 50); } body.theme-light { - --accent-light: #e46600; - --accent-dark: #ff9036; + --accent-light: hsl(var(--accent-hue), 100%, 45%); + --accent-dark: hsl(var(--accent-hue), 100%, 61%); --c-error: rgb(255, 117, 117); --c-warn: rgb(252, 255, 78); --c-success: rgb(117, 255, 117); --c-nav: #c4c4c4d7; --c-nav-hover: #ffffff10; --c-fade: white; + --c-danger: rgb(177, 36, 36); --overlay: rgba(255, 255, 255, 0.267); --overlay-hover: rgba(255, 255, 255, 0.533); --background-dark: #ffffff; @@ -50,45 +51,3 @@ body.theme-light { --font-highlight: black; --image-loading-placeholder: rgb(200, 200, 200); } -body.theme-purple { - --accent-light: rgb(191, 87, 255); - --accent-dark: rgb(143, 43, 205); - --c-error: rgb(255, 117, 117); - --c-warn: rgb(252, 255, 78); - --c-success: rgb(117, 255, 117); - --c-nav: #1c1c1c9a; - --c-nav-hover: #ffffff10; - --c-fade: black; - --overlay: #0005; - --overlay-hover: #0008; - --background-dark: #070707; - --background-light: #1c1c1c; - --background-very-light: #323232; - --background-disable: rgb(128, 128, 128); - --background-prop: rgba(50, 50, 50, 0.8); - --font: #f1f1f1; - --font-dark: rgb(122, 122, 122); - --font-highlight: white; - --image-loading-placeholder: rgb(50, 50, 50); -} -body.theme-black { - --accent-light: hsl(250, 100%, 67%); - --accent-dark: hsl(250, 60%, 42%); - --c-error: rgb(255, 117, 117); - --c-warn: rgb(252, 255, 78); - --c-success: rgb(117, 255, 117); - --c-nav: #0000009a; - --c-nav-hover: #ffffff10; - --c-fade: black; - --overlay: rgba(114, 114, 114, 0.333); - --overlay-hover: rgba(255, 255, 255, 0.533); - --background-dark: #000000; - --background-light: #000000; - --background-very-light: #323232; - --background-disable: rgb(128, 128, 128); - --background-prop: rgba(50, 50, 50, 0.8); - --font: #e8e8e8; - --font-dark: rgb(102, 102, 102); - --font-highlight: white; - --image-loading-placeholder: rgb(20, 20, 20); -} diff --git a/ui/src/components/user.rs b/ui/src/components/user.rs index 600af22..cf1c123 100644 --- a/ui/src/components/user.rs +++ b/ui/src/components/user.rs @@ -30,6 +30,17 @@ markup::define! { input[type="password", id="password", name="password"]; input[type="submit", value=tr(ri.lang, "settings.update")]; } + form[method="POST", action=u_account_settings()] { + fieldset { + legend { @tr(ri.lang, "tag.Utpr") } + @for preset in [THEME_DARK, THEME_LIGHT] { + label { input[type="radio", name="theme_preset", value=preset.to_string(), checked=user.get(USER_THEME_PRESET) == Some(preset)]; @tr(ri.lang, &format!("tag.Utpr.{preset}")) } br; + } + } + label[for="accent"] { @tr(ri.lang, "tag.Utac") } + input[type="range", id="accent", name="theme_accent", min=0, max=360, step=1, value=user.get(USER_THEME_ACCENT)]; + input[type="submit", value=tr(ri.lang, "settings.update")]; + } // h2 { @tr(ri.lang, "settings.appearance") } // form[method="POST", action=u_account_settings()] { diff --git a/ui/src/lib.rs b/ui/src/lib.rs index f32657d..d74df51 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -35,14 +35,20 @@ pub struct RenderInfo<'a> { } pub fn render_view(ri: RenderInfo<'_>, view: Object<'_>) -> String { + let theme = ri + .user + .and_then(|u| u.get(USER_THEME_PRESET)) + .unwrap_or(THEME_DARK); Scaffold { ri: &ri, main: View { ri: &ri, view }, title: view.get(VIEW_TITLE).unwrap_or_default(), - class: if view.has(VIEW_PLAYER.0) { - "theme-purple player" - } else { - "theme-purple" + class: match (theme, view.has(VIEW_PLAYER.0)) { + (THEME_DARK, true) => "theme-dark player", + (THEME_DARK, false) => "theme-dark", + (THEME_LIGHT, true) => "theme-light player", + (THEME_LIGHT, false) => "theme-light", + _ => "theme-dark", }, } .to_string() diff --git a/ui/src/scaffold.rs b/ui/src/scaffold.rs index 4fc7d60..c563ee4 100644 --- a/ui/src/scaffold.rs +++ b/ui/src/scaffold.rs @@ -6,9 +6,10 @@ use crate::RenderInfo; use jellycommon::{ + USER_THEME_ACCENT, routes::{ - u_account_login, u_account_logout, u_account_settings, u_admin_dashboard, u_home, u_items, - u_node_slug, u_search, u_stats, + u_account_login, u_account_settings, u_admin_dashboard, u_home, u_items, u_node_slug, + u_search, u_stats, }, user::{USER_ADMIN, USER_NAME}, }; @@ -25,7 +26,7 @@ markup::define! { link[rel="stylesheet", href="/assets/bundle.css"]; script[src="/assets/bundle.js"] {} } - body[class=class] { + body[class=class, style=format!("--accent-hue: {}", ri.user.and_then(|u|u.get(USER_THEME_ACCENT)).unwrap_or(277))] { @Navbar { ri } #main { @main } footer { |