feat(banner): per-campaign colour overrides and urgent alerts
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m50s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m50s
Non-default campaigns gain an optional colour override (background / text / link) in two sets — one for every theme, one for the dark theme only — and an "urgent" flag. - Colours ride profile.get as six trailing FlatBuffers strings on BannerCampaign (backward-compatible). The client resolves the cascade (dark <- dark ?? all, light <- all) per rendered theme and derives the strip border from the background in JS (no CSS color-mix, for the old Android WebView floor); AdBanner applies them as inline vars scoped to the strip. - Urgent is resolved entirely server-side: while any enabled, in-window urgent campaign exists, computeActiveSet returns only the urgent campaigns and bannerFor skips the eligibility gate — so a system notice reaches every viewer (paid / hint-holding / no_banner included) and preempts the ordinary feed. No wire field; it appears on each viewer's next profile.get. - Admin console (/_gm/banners): native colour pickers + a live light/dark preview of the strip, and an urgent toggle. The default campaign stays plain, enforced by the service and a DB CHECK. Migration 00009 is additive (nullable colour columns + a bool default + all-or-nothing / hex / default-plain CHECKs) — expand-contract, rollback-safe. Docs: ARCHITECTURE §10, UI_DESIGN, FUNCTIONAL (+ru). Tests: ads unit (urgent preempt + colour validation), codec + resolver unit, gateway transcode, and integration (colour round-trip + urgent bypass against real Postgres).
This commit is contained in:
@@ -4,11 +4,14 @@
|
||||
// independent of route transitions (the cycle is not restarted on navigation).
|
||||
|
||||
import { createBannerRotator, type BannerHost, type Rotator } from './banner';
|
||||
import type { BannerCampaign, BannerTimings } from './model';
|
||||
import type { BannerCampaign, BannerColors, BannerTimings } from './model';
|
||||
|
||||
const noColors: BannerColors = { all: null, dark: null };
|
||||
|
||||
let rotator: Rotator | null = null;
|
||||
let mounted: BannerHost | null = null;
|
||||
let current = '';
|
||||
let currentColors: BannerColors = noColors;
|
||||
let key = '';
|
||||
// The in-flight horizontal scroll of the current message, so a view mounted by navigation can
|
||||
// resume it from the same offset instead of restarting at the left. Null when not scrolling.
|
||||
@@ -17,10 +20,11 @@ let activeScroll: { toPx: number; dur: number; start: number } | null = null;
|
||||
// proxy is the rotator's host: it records the current message + scroll (so a freshly-mounted view
|
||||
// can resume them) and forwards every effect to the currently-attached DOM host, if any.
|
||||
const proxy: BannerHost = {
|
||||
show(md) {
|
||||
show(md, colors) {
|
||||
current = md;
|
||||
currentColors = colors;
|
||||
activeScroll = null;
|
||||
mounted?.show(md);
|
||||
mounted?.show(md, colors);
|
||||
},
|
||||
resetScroll() {
|
||||
activeScroll = null;
|
||||
@@ -58,6 +62,7 @@ export function configureBanner(campaigns: BannerCampaign[], timings: BannerTimi
|
||||
key = k;
|
||||
rotator?.stop();
|
||||
current = '';
|
||||
currentColors = noColors;
|
||||
rotator = createBannerRotator(campaigns, proxy, timings);
|
||||
rotator.start();
|
||||
}
|
||||
@@ -71,6 +76,15 @@ export function bannerCurrent(): string {
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* bannerCurrentColors returns the colour override of the campaign whose message is currently
|
||||
* displayed (neutral tokens — both sets null — before the first message). A freshly-mounted view
|
||||
* reads it alongside bannerCurrent so the strip paints the right colours immediately on mount.
|
||||
*/
|
||||
export function bannerCurrentColors(): BannerColors {
|
||||
return currentColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* attachBannerHost connects a freshly-mounted view as the DOM host. It does NOT re-show the
|
||||
* current message (the view renders it itself from bannerCurrent on mount): re-showing here would
|
||||
|
||||
Reference in New Issue
Block a user