mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
import {
|
|
memoIsoLightFloorRooms, type IsoLightFloorMemo, type Rgb,
|
|
} from './iso-materials';
|
|
|
|
export interface IsoPaperContext {
|
|
readonly key: string;
|
|
readonly imagePlan: boolean;
|
|
}
|
|
|
|
interface ThemeIdentity {
|
|
readonly darkMode?: unknown;
|
|
readonly default_theme?: unknown;
|
|
readonly default_dark_theme?: unknown;
|
|
readonly theme?: unknown;
|
|
}
|
|
|
|
/** All non-DOM inputs that can change the computed plan-paper colour. */
|
|
export function isoPaperContext(
|
|
space: string, mode: string, imagePlan: boolean, themes?: ThemeIdentity,
|
|
): IsoPaperContext {
|
|
return {
|
|
imagePlan,
|
|
key: JSON.stringify([
|
|
space, mode, imagePlan, themes?.darkMode ?? null, themes?.default_theme ?? null,
|
|
themes?.default_dark_theme ?? null, themes?.theme ?? null,
|
|
]),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* #654 cold-start barrier and memo ownership. Lifecycle callers may invalidate
|
|
* here before render, but the theme resolver is invoked only after DOM commit.
|
|
*/
|
|
export class IsoFirstFrameState {
|
|
private runtimeFailed = false;
|
|
private paperRgb: Rgb = [255, 255, 255];
|
|
private paperContext = '';
|
|
private paperReady = false;
|
|
private floorMemo: IsoLightFloorMemo | null = null;
|
|
|
|
public runtimeLoading(): void { this.runtimeFailed = false; }
|
|
public runtimeReady(): void { this.runtimeFailed = false; }
|
|
public runtimeFailure(): void { this.runtimeFailed = true; }
|
|
|
|
public prepare(desired: 'flat' | 'iso', context: IsoPaperContext): void {
|
|
if (context.key === this.paperContext) return;
|
|
this.paperReady = false;
|
|
if (desired === 'iso' && !context.imagePlan) this.commitPaper(context.key, [255, 255, 255]);
|
|
}
|
|
|
|
/** Returns true when a committed render must consume a newly resolved paper. */
|
|
public sync(
|
|
desired: 'flat' | 'iso', context: IsoPaperContext, resolveThemePaper: () => Rgb,
|
|
): boolean {
|
|
if (desired !== 'iso' || this.paperReady && context.key === this.paperContext) return false;
|
|
this.commitPaper(context.key, context.imagePlan ? resolveThemePaper() : [255, 255, 255]);
|
|
return true;
|
|
}
|
|
|
|
public pending(desired: 'flat' | 'iso', runtimeReady: boolean): boolean {
|
|
return desired === 'iso' && !this.runtimeFailed && (!runtimeReady || !this.paperReady);
|
|
}
|
|
|
|
public readiness(
|
|
desired: 'flat' | 'iso', effective: 'flat' | 'iso', runtimeReady: boolean,
|
|
): 'pending' | 'ready' | 'fallback' | null {
|
|
if (this.pending(desired, runtimeReady)) return 'pending';
|
|
if (effective === 'iso') return 'ready';
|
|
return desired === 'iso' && this.runtimeFailed ? 'fallback' : null;
|
|
}
|
|
|
|
public lightFloors(
|
|
fills: ReadonlyMap<string, { color: string; opacity: number } | null>,
|
|
): ReadonlySet<string> {
|
|
this.floorMemo = memoIsoLightFloorRooms(this.floorMemo, fills, this.paperRgb);
|
|
return this.floorMemo.rooms;
|
|
}
|
|
|
|
private commitPaper(key: string, rgb: Rgb): void {
|
|
const changed = !this.paperReady || this.paperContext !== key
|
|
|| rgb.some((channel, index) => channel !== this.paperRgb[index]);
|
|
this.paperContext = key;
|
|
this.paperRgb = rgb;
|
|
this.paperReady = true;
|
|
if (changed) this.floorMemo = null;
|
|
}
|
|
}
|