mirror of
https://github.com/TautvydasDerzinskas/Thingport.git
synced 2026-10-21 22:22:24 -05:00
fix: keep accent per account and readable in light mode
Ignores a theme answer for a token that's since changed, resets the accent to the default on sign-out or when none is saved, only lets the latest accent pick revert on a failed save, and gives amber a darker selected-menu text in light mode.
This commit is contained in:
1 parent
d26f730bee
commit
14126dee8c
3 files changed
+24
-8
No files matched your search
+14
-4
@@ -47,7 +47,7 @@ import { clearToken, clearUser, readToken, readUser, storeToken, storeUser } fro
|
||||
import { type AppSettings, type MakerWorldSettings, loadSettings, saveSettings } from "./utils/settings";
|
||||
import { useResolvedTheme } from "./hooks/useResolvedTheme";
|
||||
import { setCachedAccentColor, useAccentColor } from "./hooks/useAccentColor";
|
||||
import type { ResolvedTheme, ThemeSelection } from "./constants/settingsOptions";
|
||||
import { DEFAULT_ACCENT, type ResolvedTheme, type ThemeSelection } from "./constants/settingsOptions";
|
||||
import { buildTheme } from "./theme";
|
||||
|
||||
const DEFAULT_REFRESH_SECONDS = 6 * 60 * 60; // 6 hours
|
||||
@@ -332,15 +332,25 @@ export default function App() {
|
||||
})();
|
||||
}, [token]);
|
||||
React.useEffect(() => {
|
||||
if (!token) return;
|
||||
// The accent is the account's, so another account signing in on this tab doesn't inherit it.
|
||||
if (!token) {
|
||||
setCachedAccentColor(DEFAULT_ACCENT);
|
||||
return;
|
||||
}
|
||||
let active = true;
|
||||
(async () => {
|
||||
try {
|
||||
const saved = await settingsApi.getTheme();
|
||||
// Null means never set; keep the current theme.
|
||||
// An answer for a token that's since changed belongs to someone else's session.
|
||||
if (!active) return;
|
||||
// Null means never set: the theme stays as it is, the accent is the default.
|
||||
if (saved.theme) setThemeSelection(saved.theme);
|
||||
if (saved.accent) setCachedAccentColor(saved.accent);
|
||||
setCachedAccentColor(saved.accent ?? DEFAULT_ACCENT);
|
||||
} catch {}
|
||||
})();
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [token]);
|
||||
const handleThemeChange = React.useCallback((selected: ThemeSelection) => {
|
||||
setThemeSelection(selected);
|
||||
|
||||
@@ -34,15 +34,19 @@ export default function AppearancePanel({ theme, onThemeChange, onUnauthorized }
|
||||
const language = matchLanguage(i18n.resolvedLanguage ?? i18n.language);
|
||||
const accent = useAccentColor();
|
||||
const [accentError, setAccentError] = React.useState<string | null>(null);
|
||||
// Picks can overlap; only the latest one's failure may put the accent back.
|
||||
const latestAccentSave = React.useRef(0);
|
||||
|
||||
const changeAccent = async (next: AccentColor) => {
|
||||
const previous = accent;
|
||||
const save = ++latestAccentSave.current;
|
||||
setAccentError(null);
|
||||
// The whole app recolours straight away; put back if the save fails.
|
||||
setCachedAccentColor(next);
|
||||
try {
|
||||
await settingsApi.updateTheme({ accent: next });
|
||||
} catch (err) {
|
||||
if (save !== latestAccentSave.current) return;
|
||||
setCachedAccentColor(previous);
|
||||
if (err instanceof UnauthorizedError) onUnauthorized?.();
|
||||
else setAccentError(t("appearance.accentFailed"));
|
||||
|
||||
@@ -18,7 +18,7 @@ declare module "@mui/material/styles" {
|
||||
subnavBackground: string;
|
||||
modelColor: string;
|
||||
modelEmissive: string;
|
||||
/** A secondary accent that doesn't compete with the primary green. */
|
||||
/** A fixed secondary accent, whichever accent is picked. */
|
||||
altText: string;
|
||||
/** Separate from text.secondary so recoloring nav rows can't leak into unrelated UI. */
|
||||
navInactiveText: string;
|
||||
@@ -118,6 +118,8 @@ type AccentDef = {
|
||||
light?: string;
|
||||
/** Text on a filled accent: white unless the accent is too pale for it. */
|
||||
contrastText?: string;
|
||||
/** Light mode's selected-menu text, for an accent too pale to read as text on white. */
|
||||
lightText?: string;
|
||||
/** Light mode's sub-menu background: a shade under the page, faintly tinted, so it's neither the white sidebar
|
||||
* nor the content. Defaults to a little of the accent in grey. */
|
||||
lightSubnav?: string;
|
||||
@@ -129,7 +131,7 @@ const ACCENTS: Record<AccentColor, AccentDef> = {
|
||||
green: { main: "#00b800", light: "#5be584", lightSubnav: "#ecf1ec" },
|
||||
blue: { main: "#248bfb" }, // Thingiverse
|
||||
orange: { main: "#fa6831" }, // Printables
|
||||
amber: { main: "#f2a900", contrastText: "#1f1f1f" }, // Thangs, a shade deeper to read on white
|
||||
amber: { main: "#f2a900", contrastText: "#1f1f1f", lightText: "#8a6000" }, // Thangs, a shade deeper to read on white
|
||||
teal: { main: "#00b39a" }, // MyMiniFactory, likewise
|
||||
purple: { main: "#822ef5" }, // Cults3D
|
||||
};
|
||||
@@ -166,7 +168,7 @@ function withAccent(d: BaseThemeDef, accent: AccentColor): ThemeDef {
|
||||
accentStrong: darken(a.main, 0.15),
|
||||
accentSoft: alpha(a.main, 0.16),
|
||||
accentContrast: accentSwatch(accent).contrast,
|
||||
selectedNavText: a.main,
|
||||
selectedNavText: d.mode === "light" ? (a.lightText ?? a.main) : a.main,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -212,7 +214,7 @@ export function buildTheme(id: ResolvedTheme, accent: AccentColor = DEFAULT_ACCE
|
||||
MuiButton: { styleOverrides: { root: { borderRadius: 8 } } },
|
||||
MuiChip: { styleOverrides: { root: { borderRadius: 6 } } },
|
||||
MuiTooltip: { styleOverrides: { tooltip: { backgroundColor: d.panelStrong, color: d.text } } },
|
||||
// Success toasts always use the brand green, not MUI's success palette.
|
||||
// Success toasts use the accent, not MUI's success palette.
|
||||
MuiAlert: {
|
||||
styleOverrides: {
|
||||
filledSuccess: { backgroundColor: d.accent, color: d.accentContrast },
|
||||
|
||||
Reference in new issue
Block a user