mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Build: landing/learn shed eager Monaco+Vega; post-build gate + immutable asset caching
This commit is contained in:
@@ -20,13 +20,8 @@
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import type { JsonObject } from '@core/spec-config';
|
||||
import {
|
||||
type ConfigPath,
|
||||
countSet,
|
||||
getConfigValue,
|
||||
schemeColors,
|
||||
schemesByKind,
|
||||
} from '@core/theme-controls';
|
||||
import { type ConfigPath, countSet, getConfigValue, schemesByKind } from '@core/theme-controls';
|
||||
import { schemeColors } from '@core/scheme-colors';
|
||||
import { Button } from './Button';
|
||||
import { ColorField } from './ColorField';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { schemeColors } from './scheme-colors';
|
||||
|
||||
describe('schemeColors', () => {
|
||||
it('returns the full fixed palette for a categorical scheme (count ignored)', () => {
|
||||
const colors = schemeColors('tableau10', 3);
|
||||
expect(colors).toHaveLength(10);
|
||||
expect(colors.every((c) => /^#[0-9a-f]{6}$/i.test(c))).toBe(true);
|
||||
});
|
||||
|
||||
it('samples a continuous scheme at count stops, normalized to hex', () => {
|
||||
const colors = schemeColors('viridis', 5);
|
||||
expect(colors).toHaveLength(5);
|
||||
expect(colors.every((c) => /^#[0-9a-f]{6}$/.test(c))).toBe(true);
|
||||
});
|
||||
|
||||
it('samples the midpoint for a single continuous stop', () => {
|
||||
expect(schemeColors('viridis', 1)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('returns [] for an unknown scheme', () => {
|
||||
expect(schemeColors('not-a-scheme')).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Named-scheme resolution to hex swatches (docs/exploration/chart-theming-scope.md §5).
|
||||
*
|
||||
* Split from theme-controls.ts because of the `vega-scale` import below:
|
||||
* theme-controls is reachable from the landing/learn entry graphs (via
|
||||
* `vega-themes`), and a static vega-scale edge there drags the vega vendor
|
||||
* chunk into the marketing pages' eager JS (`scripts/check-light-entries.mjs`
|
||||
* guards this after every build). Only the app's color controls resolve
|
||||
* schemes to swatches, so the edge lives here, app-side of the split.
|
||||
*/
|
||||
|
||||
import { scheme } from 'vega-scale';
|
||||
|
||||
/**
|
||||
* Resolve a named Vega scheme to hex swatches. A categorical scheme returns its
|
||||
* fixed palette in full; a continuous scheme (sequential/diverging) is sampled
|
||||
* at `count` evenly-spaced stops (`count` applies to continuous schemes only).
|
||||
* Continuous interpolators yield `rgb(...)`, normalized to hex here. An unknown
|
||||
* name returns `[]` — the picker shows that scheme without a preview rather than
|
||||
* throwing. Used for the swatch/gradient preview and "materialize to swatches".
|
||||
*/
|
||||
export function schemeColors(name: string, count = 9): string[] {
|
||||
const resolved: unknown = scheme(name);
|
||||
if (Array.isArray(resolved)) return resolved.map((c) => toHex(String(c)));
|
||||
if (typeof resolved === 'function' && count > 0) {
|
||||
const interp = resolved as (t: number) => string;
|
||||
if (count === 1) return [toHex(interp(0.5))];
|
||||
return Array.from({ length: count }, (_, i) => toHex(interp(i / (count - 1))));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/** Normalize a CSS color to `#rrggbb`; passes through existing hex and unknowns. */
|
||||
function toHex(color: string): string {
|
||||
if (color.startsWith('#')) return color;
|
||||
const m = /rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i.exec(color);
|
||||
if (!m) return color;
|
||||
const h = (n: string) => Math.round(Number(n)).toString(16).padStart(2, '0');
|
||||
return `#${h(m[1])}${h(m[2])}${h(m[3])}`;
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
enumValue,
|
||||
getConfigValue,
|
||||
normalizeRangeSchemes,
|
||||
schemeColors,
|
||||
schemesByKind,
|
||||
setConfigValue,
|
||||
} from './theme-controls';
|
||||
import { schemeColors } from './scheme-colors';
|
||||
|
||||
describe('getConfigValue', () => {
|
||||
const config = { range: { category: ['#111', '#222'] }, mark: { color: '#333' } };
|
||||
@@ -163,28 +163,6 @@ describe('leaf coercion', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('schemeColors', () => {
|
||||
it('returns the full fixed palette for a categorical scheme (count ignored)', () => {
|
||||
const colors = schemeColors('tableau10', 3);
|
||||
expect(colors).toHaveLength(10);
|
||||
expect(colors.every((c) => /^#[0-9a-f]{6}$/i.test(c))).toBe(true);
|
||||
});
|
||||
|
||||
it('samples a continuous scheme at count stops, normalized to hex', () => {
|
||||
const colors = schemeColors('viridis', 5);
|
||||
expect(colors).toHaveLength(5);
|
||||
expect(colors.every((c) => /^#[0-9a-f]{6}$/.test(c))).toBe(true);
|
||||
});
|
||||
|
||||
it('samples the midpoint for a single continuous stop', () => {
|
||||
expect(schemeColors('viridis', 1)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('returns [] for an unknown scheme', () => {
|
||||
expect(schemeColors('not-a-scheme')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('enumValue', () => {
|
||||
const options = [{ value: '' }, { value: 'start' }, { value: 'end' }] as const;
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/**
|
||||
* Theme Builder structured-control primitives (docs/chart-theming-scope.md §5).
|
||||
* Theme Builder structured-control primitives (docs/exploration/chart-theming-scope.md §5).
|
||||
*
|
||||
* Pure core: the read/write transforms the builder's structured controls run on
|
||||
* a draft config, plus the named-color-scheme catalog and its resolution. Three
|
||||
* concerns:
|
||||
* a draft config, plus the named-color-scheme catalog. Two concerns:
|
||||
*
|
||||
* - **Path get/set** — read a value at a nested config path, and set one back
|
||||
* immutably while preserving every sibling key at each level. That guarantee
|
||||
@@ -15,16 +14,15 @@
|
||||
* writes a minimal diff, never a default dump.
|
||||
* - **Scheme catalog** — the named Vega color schemes the color controls offer,
|
||||
* grouped by kind (categorical / sequential / diverging).
|
||||
* - **Scheme resolution** (`schemeColors`) — a scheme name to hex swatches, for
|
||||
* the picker preview and the "materialize to an editable array" action.
|
||||
*
|
||||
* `vega-scale` (a focused vega sub-package, like core's `vega-expression`) owns
|
||||
* the scheme registry; importing it here keeps the umbrella `vega` out of core.
|
||||
* The one control transform that predates this module, `applyFontToConfig`,
|
||||
* stays in custom-theme.ts with the record entity.
|
||||
* Scheme *resolution* (name → hex swatches) lives in `scheme-colors.ts`: it
|
||||
* imports the `vega-scale` registry, and this module is reachable from the
|
||||
* landing/learn entry graphs (via `vega-themes`), which must stay free of
|
||||
* static edges into the vega vendor chunk. The one control transform that
|
||||
* predates this module, `applyFontToConfig`, stays in custom-theme.ts with the
|
||||
* record entity.
|
||||
*/
|
||||
|
||||
import { scheme } from 'vega-scale';
|
||||
import { isJsonObject, type JsonObject } from './spec-config';
|
||||
|
||||
// ── Path get/set ──────────────────────────────────────────────────────────
|
||||
@@ -213,33 +211,3 @@ export const THEME_SCHEMES: ReadonlyArray<ThemeScheme> = [
|
||||
export function schemesByKind(kind: SchemeKind): ThemeScheme[] {
|
||||
return THEME_SCHEMES.filter((s) => s.kind === kind);
|
||||
}
|
||||
|
||||
// ── Scheme resolution ───────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Resolve a named Vega scheme to hex swatches. A categorical scheme returns its
|
||||
* fixed palette in full; a continuous scheme (sequential/diverging) is sampled
|
||||
* at `count` evenly-spaced stops (`count` applies to continuous schemes only).
|
||||
* Continuous interpolators yield `rgb(...)`, normalized to hex here. An unknown
|
||||
* name returns `[]` — the picker shows that scheme without a preview rather than
|
||||
* throwing. Used for the swatch/gradient preview and "materialize to swatches".
|
||||
*/
|
||||
export function schemeColors(name: string, count = 9): string[] {
|
||||
const resolved: unknown = scheme(name);
|
||||
if (Array.isArray(resolved)) return resolved.map((c) => toHex(String(c)));
|
||||
if (typeof resolved === 'function' && count > 0) {
|
||||
const interp = resolved as (t: number) => string;
|
||||
if (count === 1) return [toHex(interp(0.5))];
|
||||
return Array.from({ length: count }, (_, i) => toHex(interp(i / (count - 1))));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/** Normalize a CSS color to `#rrggbb`; passes through existing hex and unknowns. */
|
||||
function toHex(color: string): string {
|
||||
if (color.startsWith('#')) return color;
|
||||
const m = /rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i.exec(color);
|
||||
if (!m) return color;
|
||||
const h = (n: string) => Math.round(Number(n)).toString(16).padStart(2, '0');
|
||||
return `#${h(m[1])}${h(m[2])}${h(m[3])}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user