Files
windmill/frontend/src/lib/components/InstanceUiSync.svelte
T
Ruben FiszelandClaude Opus 5.5 1fd729ac1a feat: add an instance-wide accent color setting with sidebar tint (#11335)
* feat: add an instance-wide accent color setting with sidebar tint

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: paint the cached accent before the license resolves

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat: serve the banner and accent color from one cached endpoint

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: invalidate the instance ui cache and bound it with a ttl

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: keep the instance ui ttl under the client poll period

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor: load the banner and accent color once per page load

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor: read the banner and accent color without a server cache

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: show a cleared accent color as off

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 11:23:30 +02:00

56 lines
2.3 KiB
Svelte

<script lang="ts">
import { get } from 'svelte/store'
import { SettingService, SettingsService } from '$lib/gen'
import { enterpriseLicense } from '$lib/stores'
import { useLocalStorageValue } from '$lib/svelte5Utils.svelte'
import { applyAccentColor, parseAccentColor } from '$lib/accentColor'
import { instanceUi } from '$lib/instanceUi'
import { instanceSettingsSaved } from './instanceSettings'
// The only fetcher of `/settings/instance_ui`: the banner renders from the store this
// fills. Loaded once per full page load (and after a settings save), not polled: a
// change reaches other sessions on their next reload.
// Painted on mount, before the license and settings requests return, so a reload does
// not flash the default blue. Only written on a licensed instance, and cleared once the
// license check says there is none.
const cached = useLocalStorageValue<string>('instance_accent_color', '', 'string')
let color = $state(parseAccentColor(cached.val))
// Successive saves can refetch concurrently, and responses are not ordered: without
// this a slow earlier fetch lands last and puts a retracted announcement back on screen.
let latestLoad = 0
async function load() {
const generation = ++latestLoad
try {
// `enterpriseLicense` stays unset both while loading and on CE, so ask directly.
// Not through `setLicense()`: it swallows a failed request, which would read as CE
// and retract the banner and accent of a licensed instance.
const license = get(enterpriseLicense) || (await SettingsService.getLicenseId())
if (license && !get(enterpriseLicense)) enterpriseLicense.set(license)
const next = license ? await SettingService.getInstanceUi() : undefined
if (generation !== latestLoad) return
instanceUi.set(next)
color = parseAccentColor(next?.accent_color)
cached.val = color ?? ''
} catch (e) {
// Keep whatever is on screen: a transient failure must not silently retract
// an announcement that is still in force.
console.warn('Could not fetch the instance banner and accent color', e)
}
}
$effect(() => {
// Re-runs on save so a superadmin sees their change without reloading.
$instanceSettingsSaved
load()
})
$effect(() => {
applyAccentColor(color)
return () => applyAccentColor(undefined)
})
</script>