@motion-proto/live-tokens 0.67.0 → 0.68.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/README.md +67 -9
- package/bin/generate-theme.mjs +5 -4
- package/dist-plugin/adjust/index.d.cts +1 -1
- package/dist-plugin/adjust/index.d.ts +1 -1
- package/dist-plugin/{chunk-T4PMCFJN.js → chunk-2UX6EVVA.js} +30 -30
- package/dist-plugin/{dataPaths-DZUzVv8H.d.cts → dataPaths-bJTCEO4H.d.cts} +1 -1
- package/dist-plugin/{dataPaths-DZUzVv8H.d.ts → dataPaths-bJTCEO4H.d.ts} +1 -1
- package/dist-plugin/fontPairing/index.d.cts +1 -1
- package/dist-plugin/fontPairing/index.d.ts +1 -1
- package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
- package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
- package/dist-plugin/index.cjs +32 -32
- package/dist-plugin/index.d.cts +1 -1
- package/dist-plugin/index.d.ts +1 -1
- package/dist-plugin/index.js +5 -5
- package/dist-plugin/migrateData/index.cjs +30 -30
- package/dist-plugin/migrateData/index.d.cts +1 -1
- package/dist-plugin/migrateData/index.d.ts +1 -1
- package/dist-plugin/migrateData/index.js +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
- package/package.json +4 -4
- package/src/editor/bootstrap.ts +6 -6
- package/src/editor/component-editor/ImageLightboxEditor.svelte +1 -1
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +4 -4
- package/src/editor/core/fonts/fontLoader.ts +2 -2
- package/src/editor/core/preview/{lookPreview.ts → themePreview.ts} +27 -27
- package/src/editor/core/productionPulse.ts +3 -3
- package/src/editor/core/sketch/index.ts +52 -44
- package/src/editor/core/sketch/maskField.ts +9 -9
- package/src/editor/core/sketch/sketchLayer.ts +9 -9
- package/src/editor/core/sketch/sketchRegistry.ts +34 -34
- package/src/editor/core/sketch/sketchStore.ts +98 -83
- package/src/editor/core/sketch/sketchStyleService.ts +4 -4
- package/src/editor/core/sketch/sketchStyles.ts +28 -28
- package/src/editor/core/themes/colorsAndTypeService.ts +1 -1
- package/src/editor/core/themes/loadRows.ts +8 -8
- package/src/editor/core/themes/themeDocumentSync.ts +2 -2
- package/src/editor/core/themes/themeInit.ts +2 -2
- package/src/editor/core/themes/themeService.ts +17 -17
- package/src/editor/core/themes/{lookSummary.ts → themeSummary.ts} +9 -9
- package/src/editor/core/themes/themeTypes.ts +10 -6
- package/src/editor/docs/content/getting-started.md +1 -1
- package/src/editor/docs/content/sketch-mode.md +22 -17
- package/src/editor/docs/content/themes-workflow.md +11 -11
- package/src/editor/docs/content/where-themes-live.md +6 -6
- package/src/editor/docs/content.generated.ts +4 -4
- package/src/editor/index.ts +2 -2
- package/src/editor/ui/ThemePanel.svelte +52 -52
- package/src/editor/ui/sketch/SketchPreview.svelte +2 -2
- package/src/editor/ui/sketch/SketchTab.svelte +64 -38
- package/src/live-tokens/data/sketch-styles/dry.json +5 -5
- package/src/live-tokens/data/sketch-styles/hatched.json +9 -9
- package/src/live-tokens/data/sketch-styles/napkin.json +4 -4
- package/src/live-tokens/data/sketch-styles/pencil.json +2 -2
- package/src/live-tokens/data/themes/autumn.json +1 -1
- package/src/live-tokens/data/themes/halloween.json +1 -1
- package/src/live-tokens/data/themes/midnight-study.json +1 -1
- package/src/live-tokens/data/themes/ocean.json +1 -1
- package/src/live-tokens/data/themes/royal-velvet.json +1 -1
- package/src/live-tokens/data/themes/sketchy.json +1 -1
- package/src/live-tokens/data/themes/spring-meadow.json +1 -1
- package/src/live-tokens/data/themes/sunset.json +1 -1
- package/src/system/backdrop/backdrop.ts +1 -1
- package/src/system/components/SectionDivider.svelte +1 -1
|
@@ -1,70 +1,78 @@
|
|
|
1
1
|
import { derived, get, type Readable } from 'svelte/store';
|
|
2
2
|
import { THEME_SKETCH_ID } from './sketchStyles';
|
|
3
|
-
import {
|
|
3
|
+
import { sketchStyleById, sketchStyles, type SketchStyle } from './sketchRegistry';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
sameSketchStyle,
|
|
6
6
|
selectSketchStyle,
|
|
7
|
-
|
|
7
|
+
selectUnsavedSketchStyle,
|
|
8
8
|
setSketchEnabled,
|
|
9
9
|
sketchEnabled,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
selectedSketchStyleId,
|
|
11
|
+
themeSketchSettings,
|
|
12
12
|
} from './sketchStore';
|
|
13
13
|
|
|
14
|
-
export {
|
|
15
|
-
export type {
|
|
14
|
+
export { sketchStyles, registerSketchStyle } from './sketchRegistry';
|
|
15
|
+
export type { SketchStyle, SketchStyleSource, RegisterSketchStyleInput } from './sketchRegistry';
|
|
16
16
|
|
|
17
|
-
/** A
|
|
17
|
+
/** A style as one picker row. The theme's own style is offered this way too, and
|
|
18
18
|
it belongs to no pool entry, so the fields only a pool entry has are off. */
|
|
19
|
-
export type
|
|
19
|
+
export type SketchStyleRow = Omit<SketchStyle, 'settings' | 'source'>;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* Sketch settings the open theme carries that no sketchstyle file holds, as one
|
|
23
|
+
* more row for a picker: same shape as a shipped sketchstyle, and `setSketch`
|
|
24
|
+
* takes its id like any other. Null when the theme carries no settings, and
|
|
25
|
+
* null when what it carries matches a sketchstyle, since that file's own row
|
|
26
|
+
* already names it.
|
|
26
27
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* A theme embeds its sketch settings by value, so it can hold a set that
|
|
29
|
+
* belongs to no file: Save As is what turns one into a sketchstyle. Until then
|
|
30
|
+
* this is the only row that can offer it. Without it the settings are a one-way
|
|
31
|
+
* door, a visitor picks Pencil and nothing can take them back.
|
|
32
|
+
*
|
|
33
|
+
* Labelled "Unsaved" rather than by the settings' own label, which lies: a theme
|
|
34
|
+
* tuned off `marker` still carries the label "Marker", so a row built from it
|
|
35
|
+
* would sit beside the shipped Marker claiming to be it.
|
|
32
36
|
*/
|
|
33
|
-
export const
|
|
34
|
-
[
|
|
35
|
-
([
|
|
36
|
-
if (!
|
|
37
|
-
if (
|
|
38
|
-
return {
|
|
37
|
+
export const unsavedSketchStyle: Readable<SketchStyleRow | null> = derived(
|
|
38
|
+
[themeSketchSettings, sketchStyles],
|
|
39
|
+
([settings, styles]) => {
|
|
40
|
+
if (!settings) return null;
|
|
41
|
+
if (styles.some((style) => sameSketchStyle(style.settings, settings))) return null;
|
|
42
|
+
return {
|
|
43
|
+
id: THEME_SKETCH_ID,
|
|
44
|
+
label: 'Unsaved',
|
|
45
|
+
blurb: 'Sketch settings this theme carries that no sketchstyle holds.',
|
|
46
|
+
};
|
|
39
47
|
},
|
|
40
48
|
);
|
|
41
49
|
|
|
42
50
|
/**
|
|
43
51
|
* What the page is drawing with. Three states, not two: the effect can be on
|
|
44
|
-
* under a
|
|
52
|
+
* under a style no shipped sketchstyle names — one saved to a file, or one a
|
|
45
53
|
* theme carried — and a picker that collapses that into `off` tells the
|
|
46
54
|
* visitor the page is crisp while it is visibly drawn.
|
|
47
55
|
*
|
|
48
|
-
* A dial moved off a shipped
|
|
49
|
-
* own rule: the pick says where the
|
|
56
|
+
* A dial moved off a shipped style keeps naming it, which is `selectSketchStyle`'s
|
|
57
|
+
* own rule: the pick says where the style came from, and `sketchDirty` says it
|
|
50
58
|
* has since drifted.
|
|
51
59
|
*/
|
|
52
60
|
export type SketchPick =
|
|
53
61
|
| { state: 'off' }
|
|
54
|
-
| { state: '
|
|
62
|
+
| { state: 'style'; style: SketchStyleRow }
|
|
55
63
|
| { state: 'adjusted' };
|
|
56
64
|
|
|
57
65
|
export const sketchPick: Readable<SketchPick> = derived(
|
|
58
|
-
[sketchEnabled,
|
|
59
|
-
([on, name,
|
|
66
|
+
[sketchEnabled, selectedSketchStyleId, sketchStyles, unsavedSketchStyle],
|
|
67
|
+
([on, name, styles, unsaved]): SketchPick => {
|
|
60
68
|
if (!on) return { state: 'off' };
|
|
61
|
-
const
|
|
62
|
-
return
|
|
69
|
+
const style = styles.find((s) => s.id === name) ?? (unsaved?.id === name ? unsaved : undefined);
|
|
70
|
+
return style ? { state: 'style', style: { id: style.id, label: style.label, blurb: style.blurb } } : { state: 'adjusted' };
|
|
63
71
|
},
|
|
64
72
|
);
|
|
65
73
|
|
|
66
74
|
/**
|
|
67
|
-
* Draw the page with one of the
|
|
75
|
+
* Draw the page with one of the styles in the pool, or `null` for none.
|
|
68
76
|
*
|
|
69
77
|
* The only supported way for a consumer to drive the effect. Reaching for
|
|
70
78
|
* `applySketchLayer` instead paints a stylesheet the store does not know it
|
|
@@ -77,15 +85,15 @@ export function setSketch(id: string | null): void {
|
|
|
77
85
|
return;
|
|
78
86
|
}
|
|
79
87
|
if (id === THEME_SKETCH_ID) {
|
|
80
|
-
if (!get(
|
|
81
|
-
throw new Error('No theme sketchstyle to draw with. `
|
|
88
|
+
if (!get(themeSketchSettings)) {
|
|
89
|
+
throw new Error('No theme sketchstyle to draw with. `unsavedSketchStyle` is null unless a theme carries one.');
|
|
82
90
|
}
|
|
83
|
-
|
|
91
|
+
selectUnsavedSketchStyle();
|
|
84
92
|
setSketchEnabled(true);
|
|
85
93
|
return;
|
|
86
94
|
}
|
|
87
|
-
if (!
|
|
88
|
-
throw new Error(`Unknown sketchstyle "${id}". Ids come from
|
|
95
|
+
if (!sketchStyleById(id)) {
|
|
96
|
+
throw new Error(`Unknown sketchstyle "${id}". Ids come from sketchStyles and unsavedSketchStyle.`);
|
|
89
97
|
}
|
|
90
98
|
selectSketchStyle(id);
|
|
91
99
|
setSketchEnabled(true);
|
|
@@ -106,22 +114,22 @@ export { hasPersistedSketchState } from './sketchStore';
|
|
|
106
114
|
* already decided for itself.
|
|
107
115
|
*
|
|
108
116
|
* The route from a saved theme to a built page. Hand it the theme's
|
|
109
|
-
* `
|
|
117
|
+
* `sketchSettings` field, raw: a built site has no theme API, so it reads its own
|
|
110
118
|
* theme JSON and this hydrates what it finds. Absent, `null`, or anything that
|
|
111
119
|
* is not an object all mean the same thing, which is no sketch.
|
|
112
120
|
*
|
|
113
121
|
* Call it before mounting, the way dev boot does (`bootstrap.ts` awaits
|
|
114
|
-
* `initializeTheme` first), so the
|
|
122
|
+
* `initializeTheme` first), so the style is up on the first frame rather than
|
|
115
123
|
* arriving over a crisp page.
|
|
116
124
|
*
|
|
117
125
|
* A visitor who has recorded a pick of their own keeps it, None included: this
|
|
118
126
|
* seeds an undecided browser and never overwrites a decided one, so it is safe
|
|
119
|
-
* to call on every boot. `
|
|
120
|
-
* can offer the theme's
|
|
127
|
+
* to call on every boot. `unsavedSketchStyle` is populated either way, so a picker
|
|
128
|
+
* can offer the theme's style as a row whether or not this painted it.
|
|
121
129
|
*/
|
|
122
130
|
export { seedSketchFromTheme } from './sketchStore';
|
|
123
131
|
|
|
124
|
-
/** The dial set a theme's `
|
|
132
|
+
/** The dial set a theme's `sketchSettings` field holds, for a consumer typing the
|
|
125
133
|
value it pulled out of its own theme JSON. `seedSketchFromTheme` takes it
|
|
126
134
|
raw, so nothing has to be cast to hand it over. */
|
|
127
|
-
export type {
|
|
135
|
+
export type { SketchStyleSettings } from './sketchStyles';
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Everything is integer maths on a Float32Array with no DOM, so what a test
|
|
20
20
|
* asserts is what the browser paints.
|
|
21
21
|
*/
|
|
22
|
-
import type {
|
|
22
|
+
import type { SketchStyleSettings } from './sketchStyles';
|
|
23
23
|
|
|
24
24
|
/** Samples across the tile, on both axes. Fixed, whatever the blobs measure:
|
|
25
25
|
the field is blobs and gradients, so the browser stretching the tile back to
|
|
@@ -69,7 +69,7 @@ export interface MaskLattice {
|
|
|
69
69
|
angle: number;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export function maskLattice(s:
|
|
72
|
+
export function maskLattice(s: SketchStyleSettings): MaskLattice {
|
|
73
73
|
const ask = s.maskBlobX === s.maskBlobY ? 0 : s.maskAngle;
|
|
74
74
|
const rad = ask * (Math.PI / 180);
|
|
75
75
|
const cos = Math.cos(rad), sin = Math.sin(rad);
|
|
@@ -78,7 +78,7 @@ export function maskLattice(s: SketchStyle): MaskLattice {
|
|
|
78
78
|
const vx = fit(dx, ask, (v) => turnOf(-v[1] * s.maskBlobY, v[0] * s.maskBlobX));
|
|
79
79
|
let vy = fit(dy, ask, (v) => turnOf(v[0] * s.maskBlobX, v[1] * s.maskBlobY));
|
|
80
80
|
// Two vectors along one line describe no tile at all. The turn of the first
|
|
81
|
-
//
|
|
81
|
+
// fixes the tile, so the second is the one that gives way.
|
|
82
82
|
if (vx[0] * vy[1] - vx[1] * vy[0] === 0) vy = [-vx[1], vx[0]];
|
|
83
83
|
return {
|
|
84
84
|
vx,
|
|
@@ -131,7 +131,7 @@ function fit(d: Vec, ask: number, turn: (v: Vec) => number): Vec {
|
|
|
131
131
|
* fitted to it, which meant the dial could only reach the sizes that divide
|
|
132
132
|
* 600: it said 250px and painted 300px, and there was nothing above that.
|
|
133
133
|
*/
|
|
134
|
-
export function maskTile(s:
|
|
134
|
+
export function maskTile(s: SketchStyleSettings): { w: number; h: number } {
|
|
135
135
|
const { w, h } = maskLattice(s);
|
|
136
136
|
return { w, h };
|
|
137
137
|
}
|
|
@@ -228,11 +228,11 @@ function makePerlin(seed: number): (x: number, y: number, w: Wrap) => number {
|
|
|
228
228
|
* the one before, stretched onto its own measured range.
|
|
229
229
|
*
|
|
230
230
|
* `veined` folds each octave at zero before summing. The fold puts a crease
|
|
231
|
-
* wherever the octave crossed zero, which is the marbled
|
|
231
|
+
* wherever the octave crossed zero, which is the marbled effect; plain sum is
|
|
232
232
|
* cloud.
|
|
233
233
|
*/
|
|
234
234
|
function rawField(
|
|
235
|
-
s:
|
|
235
|
+
s: SketchStyleSettings, seed: number, raster: number, lattice: MaskLattice,
|
|
236
236
|
): Float32Array {
|
|
237
237
|
const noise = makePerlin(seed);
|
|
238
238
|
const veined = s.maskGrain === 'turbulence';
|
|
@@ -388,7 +388,7 @@ function boxPass(f: Float32Array, n: number, radius: number, stride: number): Fl
|
|
|
388
388
|
all four; the levels, the posterising and the blur are one pass each. */
|
|
389
389
|
let rawCache: { key: string; field: Float32Array } | null = null;
|
|
390
390
|
|
|
391
|
-
function cachedRaw(s:
|
|
391
|
+
function cachedRaw(s: SketchStyleSettings, seed: number): Float32Array {
|
|
392
392
|
const lattice = maskLattice(s);
|
|
393
393
|
// Keyed on the tile vectors rather than the blob sizes: most of the Scale
|
|
394
394
|
// dial's travel paints the same lattice at another size, and the field is
|
|
@@ -407,7 +407,7 @@ function cachedRaw(s: SketchStyle, seed: number): Float32Array {
|
|
|
407
407
|
* pass did.
|
|
408
408
|
*/
|
|
409
409
|
export function buildMaskField(
|
|
410
|
-
s:
|
|
410
|
+
s: SketchStyleSettings, seed = 9, through?: MaskStage,
|
|
411
411
|
): { field: Float32Array; raster: number } {
|
|
412
412
|
const raw = cachedRaw(s, seed);
|
|
413
413
|
if (through === 'noise') return { field: raw, raster: RASTER };
|
|
@@ -544,7 +544,7 @@ const CACHE_MAX = 6;
|
|
|
544
544
|
const cache = new Map<string, string>();
|
|
545
545
|
|
|
546
546
|
/** The field as a `url(...)` for `mask-image`. */
|
|
547
|
-
export function buildMaskUri(s:
|
|
547
|
+
export function buildMaskUri(s: SketchStyleSettings, seed = 9, through?: MaskStage): string {
|
|
548
548
|
const key = [...KEYS.map((k) => s[k]), seed, through ?? 'all'].join('|');
|
|
549
549
|
const hit = cache.get(key);
|
|
550
550
|
if (hit) return hit;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sketch effect layer.
|
|
3
3
|
*
|
|
4
|
-
* Builds an SVG filter bank and a stylesheet from one
|
|
4
|
+
* Builds an SVG filter bank and a stylesheet from one SketchStyleSettings and
|
|
5
5
|
* injects both into every document cssVarSync tracks, so the host page behind
|
|
6
6
|
* the overlay iframe gets the same effect the editor's preview shows.
|
|
7
7
|
*
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { getSyncedDocuments } from '../cssVarSync';
|
|
18
18
|
import { buildMaskUri, maskTile } from './maskField';
|
|
19
|
-
import type {
|
|
19
|
+
import type { SketchStyleSettings } from './sketchStyles';
|
|
20
20
|
|
|
21
21
|
const DEFS_ATTR = 'data-sketch-defs';
|
|
22
22
|
const STYLE_ATTR = 'data-sketch-style';
|
|
@@ -325,7 +325,7 @@ const UNCLIPPED = PART_SPECS.filter((p) => !p.clips).map((p) => p.sel).join(', '
|
|
|
325
325
|
const CLIPPED = PART_SPECS.filter((p) => p.clips).map((p) => p.sel).join(', ');
|
|
326
326
|
const UNMASKED = PART_SPECS.filter((p) => p.unmasked).map((p) => p.sel).join(', ');
|
|
327
327
|
|
|
328
|
-
export function buildDefsMarkup(s:
|
|
328
|
+
export function buildDefsMarkup(s: SketchStyleSettings): string {
|
|
329
329
|
/**
|
|
330
330
|
* `warp` is the shape stage: one wave of noise whose wavelength spans a whole
|
|
331
331
|
* component, so the four corners sample different parts of the field and the
|
|
@@ -559,7 +559,7 @@ const MASK_POS = ['0 0', '-137px -211px', '-311px -97px', '-73px -389px', '-419p
|
|
|
559
559
|
* A hand holds a rough size and misses it by a bit each time, so half to full
|
|
560
560
|
* is the shape of the error. The four land in a different order row to row.
|
|
561
561
|
*
|
|
562
|
-
* Added, never subtracted, because a hand-drawn
|
|
562
|
+
* Added, never subtracted, because a hand-drawn line tends to sit on a theme
|
|
563
563
|
* whose corners are already tight: a coefficient set that swung both ways
|
|
564
564
|
* spent half its range clamped at zero.
|
|
565
565
|
*/
|
|
@@ -584,7 +584,7 @@ const swing = (travel: number) => String(Number((travel * 2).toFixed(4)));
|
|
|
584
584
|
/** Squares the displacement wave off around 0.5, its zero, so full amplitude is
|
|
585
585
|
spent along the whole edge rather than only where the wave peaks. Both
|
|
586
586
|
channels take it: the map reads x from R and y from G. */
|
|
587
|
-
function squareOff(s:
|
|
587
|
+
function squareOff(s: SketchStyleSettings, from: string, to: string): string {
|
|
588
588
|
if (s.waveform <= 1) return '';
|
|
589
589
|
const slope = s.waveform.toFixed(2);
|
|
590
590
|
const intercept = ((1 - s.waveform) / 2).toFixed(3);
|
|
@@ -594,7 +594,7 @@ function squareOff(s: SketchStyle, from: string, to: string): string {
|
|
|
594
594
|
`</feComponentTransfer>`;
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
-
const squaredResult = (s:
|
|
597
|
+
const squaredResult = (s: SketchStyleSettings, from: string, to: string) =>
|
|
598
598
|
(s.waveform > 1 ? to : from);
|
|
599
599
|
|
|
600
600
|
/** Along-stroke pressure wavelength. Low, with a high floor in the transfer
|
|
@@ -650,7 +650,7 @@ const RETRACE_SEED = 53;
|
|
|
650
650
|
const WARP_FREQUENCY = 0.08;
|
|
651
651
|
|
|
652
652
|
|
|
653
|
-
export function buildStylesheet(s:
|
|
653
|
+
export function buildStylesheet(s: SketchStyleSettings): string {
|
|
654
654
|
const on = '[data-sketch]';
|
|
655
655
|
const parts = `:is(${PARTS})`;
|
|
656
656
|
const el = `${on} ${parts}`;
|
|
@@ -1089,7 +1089,7 @@ function defsNode(doc: Document): SVGSVGElement {
|
|
|
1089
1089
|
* This only makes the effect *available*. An element opts in by carrying
|
|
1090
1090
|
* data-sketch, which is what `setSketchScope` writes.
|
|
1091
1091
|
*/
|
|
1092
|
-
export function applySketchLayer(settings:
|
|
1092
|
+
export function applySketchLayer(settings: SketchStyleSettings): void {
|
|
1093
1093
|
const defs = buildDefsMarkup(settings);
|
|
1094
1094
|
const css = buildStylesheet(settings);
|
|
1095
1095
|
for (const doc of getSyncedDocuments()) {
|
|
@@ -1132,7 +1132,7 @@ export function removeSketchLayer(): void {
|
|
|
1132
1132
|
* The host page's root and the editor's own preview container are both scopes,
|
|
1133
1133
|
* which is why this takes an element rather than assuming documentElement.
|
|
1134
1134
|
*/
|
|
1135
|
-
export function setSketchScope(el: HTMLElement | null, settings:
|
|
1135
|
+
export function setSketchScope(el: HTMLElement | null, settings: SketchStyleSettings | null): void {
|
|
1136
1136
|
if (!el) return;
|
|
1137
1137
|
if (!settings) {
|
|
1138
1138
|
el.removeAttribute('data-sketch');
|
|
@@ -1,97 +1,97 @@
|
|
|
1
1
|
import { derived, get, writable, type Readable } from 'svelte/store';
|
|
2
|
-
import {
|
|
2
|
+
import { SHIPPED_SKETCH_SETTINGS, hydrateSketchSettings, type SketchStyleSettings } from './sketchStyles';
|
|
3
3
|
|
|
4
|
-
/** Where a
|
|
4
|
+
/** Where a style came from, which is what decides the affordances on offer.
|
|
5
5
|
`shipped` is served by the package: it has a file, but not one in this
|
|
6
6
|
project, so there is nothing here to save over or delete until the editor
|
|
7
7
|
writes a copy. `file` lives in this project's data tree, so the editor can
|
|
8
8
|
save over it and delete it. `registered` was handed to `bootLiveTokens`, so
|
|
9
9
|
it is real on a built site but owns no file the editor could touch. */
|
|
10
|
-
export type
|
|
10
|
+
export type SketchStyleSource = 'shipped' | 'file' | 'registered';
|
|
11
11
|
|
|
12
|
-
export interface
|
|
12
|
+
export interface SketchStyle {
|
|
13
13
|
/** What `setSketch` takes. A saved sketchstyle's file slug is its id, so a
|
|
14
|
-
|
|
14
|
+
style picked in dev keeps working once the site is built. */
|
|
15
15
|
id: string;
|
|
16
16
|
label: string;
|
|
17
17
|
blurb: string;
|
|
18
|
-
settings:
|
|
19
|
-
source:
|
|
18
|
+
settings: SketchStyleSettings;
|
|
19
|
+
source: SketchStyleSource;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export interface
|
|
22
|
+
export interface RegisterSketchStyleInput {
|
|
23
23
|
id: string;
|
|
24
24
|
label: string;
|
|
25
25
|
blurb?: string;
|
|
26
26
|
settings: unknown;
|
|
27
|
-
/** Only `
|
|
27
|
+
/** Only `replaceRegisteredSketchStyles` sets this. A listing carries the package's
|
|
28
28
|
own sketchstyles as well as the project's, and the two differ in what the
|
|
29
29
|
editor may do to them, so the row's origin has to survive the trip. */
|
|
30
|
-
source?:
|
|
30
|
+
source?: SketchStyleSource;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const SHIPPED: ReadonlyMap<string,
|
|
34
|
-
Object.entries(
|
|
33
|
+
const SHIPPED: ReadonlyMap<string, SketchStyle> = new Map(
|
|
34
|
+
Object.entries(SHIPPED_SKETCH_SETTINGS).map(([id, settings]) => [
|
|
35
35
|
id,
|
|
36
|
-
{ id, label:
|
|
36
|
+
{ id, label: settings.label, blurb: settings.blurb, settings, source: 'shipped' as const },
|
|
37
37
|
]),
|
|
38
38
|
);
|
|
39
39
|
|
|
40
|
-
/** Seeded with the shipped
|
|
40
|
+
/** Seeded with the shipped styles, then written by `registerSketchStyle`. A Map
|
|
41
41
|
because re-setting an existing key keeps its position, so a project's own
|
|
42
42
|
`pencil` lands where the shipped Pencil sat rather than at the end of the
|
|
43
43
|
grid. */
|
|
44
|
-
const
|
|
44
|
+
const styles = writable(new Map(SHIPPED));
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Every sketchstyle on offer: the shipped
|
|
47
|
+
* Every sketchstyle on offer: the shipped styles, plus whatever this project
|
|
48
48
|
* registered over and beside them. A store rather than a constant, because
|
|
49
49
|
* registration happens after this module is imported.
|
|
50
50
|
*
|
|
51
51
|
* A picker adds its own "None" row, since off is a state of the effect rather
|
|
52
|
-
* than one of the
|
|
52
|
+
* than one of the styles. A theme's own style is not here either; `unsavedSketchStyle`
|
|
53
53
|
* carries that one row.
|
|
54
54
|
*/
|
|
55
|
-
export const
|
|
55
|
+
export const sketchStyles: Readable<SketchStyle[]> = derived(styles, (m) => [...m.values()]);
|
|
56
56
|
|
|
57
|
-
export function
|
|
58
|
-
return get(
|
|
57
|
+
export function sketchStyleById(id: string): SketchStyle | undefined {
|
|
58
|
+
return get(styles).get(id);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
function put(m: Map<string,
|
|
61
|
+
function put(m: Map<string, SketchStyle>, style: RegisterSketchStyleInput, source: SketchStyleSource) {
|
|
62
62
|
// Hydrated on the way in: a consumer hands over raw JSON it imported, so this
|
|
63
|
-
// is the only place a
|
|
63
|
+
// is the only place a style stored under a retired dial name gets carried
|
|
64
64
|
// forward, the same job `seedSketchFromTheme` does for a theme.
|
|
65
|
-
const settings =
|
|
66
|
-
return m.set(
|
|
67
|
-
id:
|
|
68
|
-
label:
|
|
69
|
-
blurb:
|
|
65
|
+
const settings = hydrateSketchSettings(style.settings);
|
|
66
|
+
return m.set(style.id, {
|
|
67
|
+
id: style.id,
|
|
68
|
+
label: style.label,
|
|
69
|
+
blurb: style.blurb ?? settings.blurb ?? '',
|
|
70
70
|
settings,
|
|
71
71
|
source,
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
export function
|
|
76
|
-
|
|
75
|
+
export function registerSketchStyle(style: RegisterSketchStyleInput): void {
|
|
76
|
+
styles.update((m) => put(new Map(m), style, 'registered'));
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/** Ids the last file listing claimed. The sweep below removes these and nothing
|
|
80
|
-
else, so a
|
|
80
|
+
else, so a style handed to `bootLiveTokens` survives the editor opening. */
|
|
81
81
|
let fromFiles: string[] = [];
|
|
82
82
|
|
|
83
83
|
/** The editor re-lists its files after every save and delete, so a file removed
|
|
84
|
-
on disk has to leave the pool too. A file that had shadowed a shipped
|
|
84
|
+
on disk has to leave the pool too. A file that had shadowed a shipped style
|
|
85
85
|
hands the id back on its way out. */
|
|
86
|
-
export function
|
|
87
|
-
|
|
86
|
+
export function replaceRegisteredSketchStyles(next: RegisterSketchStyleInput[]): void {
|
|
87
|
+
styles.update((m) => {
|
|
88
88
|
const out = new Map(m);
|
|
89
89
|
for (const id of fromFiles) {
|
|
90
90
|
const shipped = SHIPPED.get(id);
|
|
91
91
|
if (shipped) out.set(id, shipped);
|
|
92
92
|
else out.delete(id);
|
|
93
93
|
}
|
|
94
|
-
for (const
|
|
94
|
+
for (const style of next) put(out, style, style.source ?? 'file');
|
|
95
95
|
return out;
|
|
96
96
|
});
|
|
97
97
|
fromFiles = next.map((l) => l.id);
|