mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-15 10:43:36 -06:00
The logo/title/theme/favicon are static 'ScreenTinker' in index.html, and applyBranding() only overrode them AFTER an async /api/white-label fetch - that network delay was the flash, on every load and on switch (which reloads). Now applyBranding caches the resolved white-label per workspace (keyed by the JWT's current_workspace_id), and a tiny same-origin brand-prime.js loads render-blocking right after the logo - so it applies the cached colors/name/ title/favicon/custom-css BEFORE first paint. CSP-safe (external 'self' script, not inline). applyBranding still runs to refresh + re-cache. First-ever visit to an uncached branded workspace still shows the default once; every load after is flash-free.
72 lines
2.3 KiB
JavaScript
72 lines
2.3 KiB
JavaScript
// Applies the current user's saved white-label config to the DOM.
|
|
// Runs once after login/route bootstrap. Without this, saved values in the
|
|
// white_labels table are read into the Settings form but never applied to
|
|
// the actual page — so users see "ScreenTinker" and default colors after
|
|
// every reload, as if their save reverted.
|
|
|
|
let applied = false;
|
|
|
|
// Current workspace id from the JWT, so the branding cache (read render-blocking by
|
|
// brand-prime.js) is keyed per workspace — a switch shows the right brand. (#38)
|
|
function currentWorkspaceId() {
|
|
try {
|
|
const seg = localStorage.getItem('token').split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
return (JSON.parse(atob(seg)) || {}).current_workspace_id || 'none';
|
|
} catch { return 'none'; }
|
|
}
|
|
|
|
export async function applyBranding() {
|
|
if (applied) return;
|
|
applied = true;
|
|
|
|
const token = localStorage.getItem('token');
|
|
if (!token) return;
|
|
|
|
let wl;
|
|
try {
|
|
const res = await fetch('/api/white-label', { headers: { Authorization: `Bearer ${token}` } });
|
|
if (!res.ok) return;
|
|
wl = await res.json();
|
|
} catch { return; }
|
|
if (!wl) return;
|
|
|
|
// Cache for the next load/switch so brand-prime.js can apply it before paint.
|
|
try { localStorage.setItem('rd_branding_' + currentWorkspaceId(), JSON.stringify(wl)); } catch {}
|
|
|
|
const root = document.documentElement;
|
|
if (wl.primary_color) root.style.setProperty('--accent', wl.primary_color);
|
|
if (wl.bg_color) {
|
|
root.style.setProperty('--bg-primary', wl.bg_color);
|
|
const meta = document.querySelector('meta[name="theme-color"]');
|
|
if (meta) meta.setAttribute('content', wl.bg_color);
|
|
}
|
|
|
|
if (wl.brand_name) {
|
|
document.title = wl.brand_name;
|
|
const span = document.getElementById('brandName');
|
|
if (span) span.textContent = wl.brand_name;
|
|
}
|
|
|
|
if (wl.favicon_url) {
|
|
document.querySelectorAll('link[rel="icon"], link[rel="apple-touch-icon"]').forEach(l => {
|
|
l.setAttribute('href', wl.favicon_url);
|
|
});
|
|
}
|
|
|
|
if (wl.custom_css) {
|
|
let style = document.getElementById('wl-custom-css');
|
|
if (!style) {
|
|
style = document.createElement('style');
|
|
style.id = 'wl-custom-css';
|
|
document.head.appendChild(style);
|
|
}
|
|
style.textContent = wl.custom_css;
|
|
}
|
|
}
|
|
|
|
// Force a re-apply (called from settings.js after save)
|
|
export function resetBranding() {
|
|
applied = false;
|
|
return applyBranding();
|
|
}
|