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>
This commit is contained in:
StroepWafel
2026-04-29 09:54:26 +09:30
committed by GitHub
parent 01cd5d1428
commit 7a4fa1e5bf
7 changed files with 110 additions and 24 deletions
+2 -1
View File
@@ -22,4 +22,5 @@ betterseqtaplus-safari/
.parcel-cache
.env
.env.submit
dependency-graph.svg
dependency-graph.svg
+1 -1
View File
@@ -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",
+3 -2
View File
@@ -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;
+3 -1
View File
@@ -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 {
+45 -3
View File
@@ -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);
}
}
@@ -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<string>();
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 = [];
}
+7 -1
View File
@@ -34,9 +34,14 @@ export function OpenWhatsNewPopup(onDismissed?: () => void) {
const text = stringToHTML(/* html */ `
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: auto;">
<h1>3.6.4 - Fix for alpine theme & Assement wieghting improvement</h1>
<li>Added advanced colour adjustments variables for theme customisation.</li>
<li>Improved logic for assement weightings to improve compatibility.</li>
<h1>3.6.3 - Assessment overview fix</h1>
<li>Fixed assessments overview failing to load.</li>
<h1>3.6.2 - Cloud backup, various fixes & SEQTA Engage support</h1>
<li>BetterSEQTA Cloud: back up and restore extension settings from your account (General settings).</li>
<li>Optional automatic cloud sync if signed in (on by default).</li>
@@ -50,6 +55,7 @@ export function OpenWhatsNewPopup(onDismissed?: () => void) {
<li>Updated outdated in-app links and update some under the hood code (Vite 8).</li>
<li>Added a notifications panel animation to work like settings.</li>
<li>Fix timetable edit plugin not working correctly.</li>
<h1>3.5.3 - Adaptive theme updates</h1>
<li>Fixed adaptive theming on current-year course and assessment pages.</li>