From 7a4fa1e5bf44745a06a5653edd9af6fa7182563f Mon Sep 17 00:00:00 2001 From: StroepWafel <109832156+StroepWafel@users.noreply.github.com> Date: Wed, 29 Apr 2026 09:54:26 +0930 Subject: [PATCH] feat: add RGB handler for alpine theme (#427) * add handlers for individual Channels * add notes * patch fix theme overrides for adaptive colour * idk * Update package.json * fix issue spelling * Update OpenWhatsNewPopup.ts * Update OpenWhatsNewPopup.ts * fix: remove debug line from .gitignore * chore: fix up patch notes to be a bit more user friendly * chore: finalise patch notes and fix grammer * Add empty line to .gitignore --------- Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com> --- .gitignore | 3 +- package.json | 2 +- src/css/injected.scss | 5 +- src/plugins/built-in/themes/theme-manager.ts | 4 +- src/seqta/ui/colors/Manager.ts | 48 +++++++++++++- .../ui/colors/customThemeAdaptiveBindings.ts | 64 ++++++++++++++----- src/seqta/utils/Openers/OpenWhatsNewPopup.ts | 8 ++- 7 files changed, 110 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index e24ea199..47ffcf73 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ betterseqtaplus-safari/ .parcel-cache .env .env.submit -dependency-graph.svg \ No newline at end of file +dependency-graph.svg + diff --git a/package.json b/package.json index 72bdfc2a..27826d94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "betterseqtaplus", - "version": "3.6.3", + "version": "3.6.4", "type": "module", "description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development and add heaps more features!", "browserslist": "> 0.5%, last 2 versions, not dead", diff --git a/src/css/injected.scss b/src/css/injected.scss index 261552c2..9c5fe51c 100644 --- a/src/css/injected.scss +++ b/src/css/injected.scss @@ -1,3 +1,4 @@ + @use "sass:meta"; @import url("https://fonts.googleapis.com/css?family=Rubik:300,400,500,600,700"); @@ -2609,7 +2610,7 @@ body { [class*="MessageList__unread___"] { position: relative; - background: rgb(228 225 225); + background: var(--background-secondary, rgb(228 225 225)); } .dark [class*="MessageList__unread___"] { @@ -2735,7 +2736,7 @@ body { [class*="MessageList__MessageList___"] > ol > li[class*="MessageList__selected___"] { - background: rgb(228 225 225); + background: var(--background-secondary, rgb(228 225 225)); color: var(--text-primary); box-shadow: none !important; position: relative; diff --git a/src/plugins/built-in/themes/theme-manager.ts b/src/plugins/built-in/themes/theme-manager.ts index b3bc490a..e37f90e5 100644 --- a/src/plugins/built-in/themes/theme-manager.ts +++ b/src/plugins/built-in/themes/theme-manager.ts @@ -25,7 +25,9 @@ type ThemeContent = { CanChangeColour?: boolean; CustomCSS?: string; hideThemeName?: boolean; + forceTheme?: boolean; forceDark?: boolean; + adaptiveCssVariables?: string[]; images?: { id: string; variableName: string; data: string }[]; // data: base64 }; @@ -35,7 +37,7 @@ export type InstallThemeMeta = { serverUpdatedAtSec?: number; forceTheme?: boolean; adaptiveCssVariables?: string[]; - images: { id: string; variableName: string; data: string }[]; // data: base64 + images?: { id: string; variableName: string; data: string }[]; // data: base64 }; export class ThemeManager { diff --git a/src/seqta/ui/colors/Manager.ts b/src/seqta/ui/colors/Manager.ts index d72c8844..71740da9 100644 --- a/src/seqta/ui/colors/Manager.ts +++ b/src/seqta/ui/colors/Manager.ts @@ -5,7 +5,7 @@ import { lightenAndPaleColor } from "./lightenAndPaleColor"; import ColorLuminance from "./ColorLuminance"; import { settingsState } from "@/seqta/utils/listeners/SettingsState"; import { getAdaptiveColour } from "@/seqta/utils/adaptiveThemeColour"; -import { getCustomThemeAdaptiveCssVariables } from "@/seqta/ui/colors/customThemeAdaptiveBindings"; +import { getCustomThemeAdaptiveCssVariableBindings } from "@/seqta/ui/colors/customThemeAdaptiveBindings"; import darkLogo from "@/resources/icons/betterseqta-light-full.png"; import lightLogo from "@/resources/icons/betterseqta-dark-full.png"; @@ -84,6 +84,21 @@ function cancelColorTransition() { } } +function getRepresentativeRgbChannels(s: string): { r: number; g: number; b: number } | null { + const parsedHex = parseRepresentativeHex(s); + if (!parsedHex) return null; + try { + const [r, g, b] = Color(parsedHex).rgb().array(); + return { + r: Math.round(r), + g: Math.round(g), + b: Math.round(b), + }; + } catch { + return null; + } +} + function applyColorsWith(selectedColor: string) { if (settingsState.transparencyEffects) { document.documentElement.classList.add("transparencyEffects"); @@ -129,8 +144,35 @@ function applyColorsWith(selectedColor: string) { applyProperties({ ...commonProps, ...modeProps, ...dynamicProps }); if (settingsState.selectedTheme) { - for (const name of getCustomThemeAdaptiveCssVariables()) { - setCSSVar(name, selectedColor); + const channels = getRepresentativeRgbChannels(selectedColor); + for (const binding of getCustomThemeAdaptiveCssVariableBindings()) { + if (!binding.channel) { + setCSSVar(binding.cssVarName, selectedColor); + continue; + } + + if (!channels) { + continue; + } + + if (binding.channel === "r") { + setCSSVar(binding.cssVarName, String(channels.r)); + } else if (binding.channel === "g") { + setCSSVar(binding.cssVarName, String(channels.g)); + } else { + setCSSVar(binding.cssVarName, String(channels.b)); + } + } + } + + // Let themes opt-in to overriding only adaptive accent output. + // A theme can define `--adaptive-better-main` from adaptive channel bindings. + if (settingsState.selectedTheme && settingsState.adaptiveThemeColour) { + const adaptiveOverride = getComputedStyle(document.documentElement) + .getPropertyValue("--adaptive-better-main") + .trim(); + if (adaptiveOverride) { + setCSSVar("--better-main", adaptiveOverride); } } diff --git a/src/seqta/ui/colors/customThemeAdaptiveBindings.ts b/src/seqta/ui/colors/customThemeAdaptiveBindings.ts index dba35e3f..cdd0c823 100644 --- a/src/seqta/ui/colors/customThemeAdaptiveBindings.ts +++ b/src/seqta/ui/colors/customThemeAdaptiveBindings.ts @@ -1,20 +1,49 @@ /** Tracks which author-declared CSS variables mirror the effective accent; not persisted in settings storage. */ const VALID_CUSTOM_PROP = /^--[a-zA-Z0-9_-]{1,120}$/; +const VALID_CHANNEL = /^(r|g|b)$/; -let boundNames: string[] = []; +export type AdaptiveChannel = "r" | "g" | "b"; -export function normalizeAdaptiveCssVariableNames( +export type AdaptiveCssVariableBinding = { + cssVarName: string; + channel?: AdaptiveChannel; +}; + +let boundBindings: AdaptiveCssVariableBinding[] = []; + +function parseAdaptiveBinding( + rawBinding: string, +): AdaptiveCssVariableBinding | null { + const trimmed = rawBinding.trim(); + if (!trimmed) return null; + + const [rawName, rawChannel] = trimmed.split(":", 2); + const cssVarName = rawName?.trim() ?? ""; + if (!VALID_CUSTOM_PROP.test(cssVarName)) return null; + + if (!rawChannel) return { cssVarName }; + + const channel = rawChannel.trim().toLowerCase(); + if (!VALID_CHANNEL.test(channel)) return null; + + return { cssVarName, channel: channel as AdaptiveChannel }; +} + +export function normalizeAdaptiveCssVariableBindings( names: string[] | undefined, -): string[] { +): AdaptiveCssVariableBinding[] { if (!names?.length) return []; - const out: string[] = []; + const out: AdaptiveCssVariableBinding[] = []; const seen = new Set(); + for (const raw of names) { - const s = raw.trim(); - if (!VALID_CUSTOM_PROP.test(s) || seen.has(s)) continue; - seen.add(s); - out.push(s); + const parsed = parseAdaptiveBinding(raw); + if (!parsed) continue; + const key = `${parsed.cssVarName}:${parsed.channel ?? "full"}`; + if (seen.has(key)) continue; + seen.add(key); + out.push(parsed); } return out; } @@ -22,19 +51,24 @@ export function normalizeAdaptiveCssVariableNames( export function setCustomThemeAdaptiveCssVariables( names: string[] | undefined, ): void { - for (const n of boundNames) { - document.documentElement.style.removeProperty(n); + for (const binding of boundBindings) { + document.documentElement.style.removeProperty(binding.cssVarName); } - boundNames = normalizeAdaptiveCssVariableNames(names); + boundBindings = normalizeAdaptiveCssVariableBindings(names); } +export function getCustomThemeAdaptiveCssVariableBindings(): AdaptiveCssVariableBinding[] { + return boundBindings; +} + +// Backward-compatible helper for existing callsites. export function getCustomThemeAdaptiveCssVariables(): string[] { - return boundNames; + return boundBindings.map((b) => b.cssVarName); } export function clearCustomThemeAdaptiveCssVariables(): void { - for (const n of boundNames) { - document.documentElement.style.removeProperty(n); + for (const binding of boundBindings) { + document.documentElement.style.removeProperty(binding.cssVarName); } - boundNames = []; + boundBindings = []; } diff --git a/src/seqta/utils/Openers/OpenWhatsNewPopup.ts b/src/seqta/utils/Openers/OpenWhatsNewPopup.ts index 71c51032..a8366943 100644 --- a/src/seqta/utils/Openers/OpenWhatsNewPopup.ts +++ b/src/seqta/utils/Openers/OpenWhatsNewPopup.ts @@ -34,9 +34,14 @@ export function OpenWhatsNewPopup(onDismissed?: () => void) { const text = stringToHTML(/* html */ `
+ +

3.6.4 - Fix for alpine theme & Assement wieghting improvement

+
  • Added advanced colour adjustments variables for theme customisation.
  • +
  • Improved logic for assement weightings to improve compatibility.
  • +

    3.6.3 - Assessment overview fix

  • Fixed assessments overview failing to load.
  • - +

    3.6.2 - Cloud backup, various fixes & SEQTA Engage support

  • BetterSEQTA Cloud: back up and restore extension settings from your account (General settings).
  • Optional automatic cloud sync if signed in (on by default).
  • @@ -50,6 +55,7 @@ export function OpenWhatsNewPopup(onDismissed?: () => void) {
  • Updated outdated in-app links and update some under the hood code (Vite 8).
  • Added a notifications panel animation to work like settings.
  • Fix timetable edit plugin not working correctly.
  • +

    3.5.3 - Adaptive theme updates

  • Fixed adaptive theming on current-year course and assessment pages.