@motion-proto/live-tokens 0.67.1 → 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 +32 -0
- 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-J2JT4UEA.js → chunk-2UX6EVVA.js} +25 -25
- 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 +27 -27
- 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 +25 -25
- 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/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,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);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { derived, get, writable } from 'svelte/store';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
SHIPPED_SKETCH_SETTINGS,
|
|
4
4
|
DEFAULT_SKETCH_STYLE,
|
|
5
5
|
THEME_SKETCH_ID,
|
|
6
|
-
|
|
7
|
-
type
|
|
6
|
+
hydrateSketchSettings,
|
|
7
|
+
type SketchStyleSettings,
|
|
8
8
|
} from './sketchStyles';
|
|
9
|
-
import {
|
|
9
|
+
import { sketchStyleById, replaceRegisteredSketchStyles, sketchStyles } from './sketchRegistry';
|
|
10
10
|
import {
|
|
11
11
|
applySketchLayer,
|
|
12
12
|
hostRoot,
|
|
@@ -26,7 +26,9 @@ import {
|
|
|
26
26
|
|
|
27
27
|
const ENABLED_KEY = 'lt.sketchEnabled';
|
|
28
28
|
const SETTINGS_KEY = 'lt.sketchSettings';
|
|
29
|
-
|
|
29
|
+
/** The stored key keeps its old spelling. It is a browser's saved selection,
|
|
30
|
+
so renaming the string would drop every visitor's pick on upgrade. */
|
|
31
|
+
const SELECTED_ID_KEY = 'lt.sketchStyleName';
|
|
30
32
|
const BASELINE_KEY = 'lt.sketchBaseline';
|
|
31
33
|
/** Set only by a genuine user decision (`markSketchTouched`), never by the
|
|
32
34
|
`subscribe` write-back below. That write-back plants the four keys above
|
|
@@ -66,7 +68,7 @@ export function hasPersistedSketchState(): boolean {
|
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
/** Called from every control a user can act on: a dial, a sketchstyle pick, the
|
|
69
|
-
on/off switch. Never from `
|
|
71
|
+
on/off switch. Never from `openThemeSketchSettings` or the storage-sync `adopt`
|
|
70
72
|
path, both of which write the same four keys as a side effect of state
|
|
71
73
|
this browser did not decide on its own. */
|
|
72
74
|
function markSketchTouched(): void {
|
|
@@ -83,20 +85,20 @@ function readEnabled(): boolean {
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
function readSettings():
|
|
88
|
+
function readSettings(): SketchStyleSettings {
|
|
87
89
|
try {
|
|
88
90
|
const raw = localStorage.getItem(SETTINGS_KEY);
|
|
89
|
-
if (raw) return
|
|
91
|
+
if (raw) return hydrateSketchSettings(JSON.parse(raw));
|
|
90
92
|
} catch {
|
|
91
93
|
// fall through to the default sketchstyle
|
|
92
94
|
}
|
|
93
|
-
return { ...
|
|
95
|
+
return { ...SHIPPED_SKETCH_SETTINGS[DEFAULT_SKETCH_STYLE] };
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
function readBaseline():
|
|
98
|
+
function readBaseline(): SketchStyleSettings | null {
|
|
97
99
|
try {
|
|
98
100
|
const raw = localStorage.getItem(BASELINE_KEY);
|
|
99
|
-
if (raw) return
|
|
101
|
+
if (raw) return hydrateSketchSettings(JSON.parse(raw));
|
|
100
102
|
} catch {
|
|
101
103
|
// fall through
|
|
102
104
|
}
|
|
@@ -108,14 +110,14 @@ function readBaseline(): SketchStyle | null {
|
|
|
108
110
|
it. Stripped on read below; delete a release after that ships. */
|
|
109
111
|
const RETIRED_USER_PREFIX = 'user:';
|
|
110
112
|
|
|
111
|
-
/** Deliberately unvalidated.
|
|
113
|
+
/** Deliberately unvalidated. Sketchstyles are registered after this module is
|
|
112
114
|
imported, so an id it has never heard of is the normal case rather than a
|
|
113
115
|
fault: `selectSketchStyle` no-ops on one, and `sketchPick` already reports
|
|
114
|
-
a
|
|
116
|
+
a style nothing names as `adjusted`. Only a browser that has stored nothing
|
|
115
117
|
falls back. */
|
|
116
|
-
function
|
|
118
|
+
function readSelectedId(): string {
|
|
117
119
|
try {
|
|
118
|
-
const name = localStorage.getItem(
|
|
120
|
+
const name = localStorage.getItem(SELECTED_ID_KEY);
|
|
119
121
|
if (name !== null) {
|
|
120
122
|
return name.startsWith(RETIRED_USER_PREFIX) ? name.slice(RETIRED_USER_PREFIX.length) : name;
|
|
121
123
|
}
|
|
@@ -128,39 +130,39 @@ function readStyleName(): string {
|
|
|
128
130
|
/** Off by default: the effect paints nothing until the open theme's own
|
|
129
131
|
sketchstyle, or a decision this browser already recorded, says otherwise. */
|
|
130
132
|
export const sketchEnabled = writable<boolean>(readEnabled());
|
|
131
|
-
export const sketchSettings = writable<
|
|
132
|
-
/**
|
|
133
|
-
keeps showing what the current
|
|
134
|
-
`THEME_SKETCH_ID` for
|
|
135
|
-
nothing was picked, or the picked file was deleted. */
|
|
136
|
-
export const
|
|
133
|
+
export const sketchSettings = writable<SketchStyleSettings>(readSettings());
|
|
134
|
+
/** Id of the sketchstyle the dials started from. It survives dial moves, so the
|
|
135
|
+
grid keeps showing what the current settings are closest to. Any id in the
|
|
136
|
+
pool, or `THEME_SKETCH_ID` for settings the open theme carries that no file
|
|
137
|
+
holds; empty only when nothing was picked, or the picked file was deleted. */
|
|
138
|
+
export const selectedSketchStyleId = writable<string>(readSelectedId());
|
|
137
139
|
|
|
138
140
|
/** The settings as the selected sketchstyle defined them. Kept beside the live
|
|
139
141
|
settings so "modified" is derived by comparison rather than tracked as a
|
|
140
142
|
flag: dial a value back to where it started and the tab stops claiming a
|
|
141
143
|
change, with no bookkeeping to get wrong. */
|
|
142
|
-
export const sketchBaseline = writable<
|
|
144
|
+
export const sketchBaseline = writable<SketchStyleSettings | null>(readBaseline());
|
|
143
145
|
|
|
144
146
|
/** Dial-set fields only. `label` and `blurb` name the sketchstyle rather than
|
|
145
|
-
describe the
|
|
146
|
-
export function
|
|
147
|
-
return (Object.keys(a) as (keyof
|
|
147
|
+
describe the style, and no dial writes them. */
|
|
148
|
+
export function sameSketchStyle(a: SketchStyleSettings, b: SketchStyleSettings): boolean {
|
|
149
|
+
return (Object.keys(a) as (keyof SketchStyleSettings)[])
|
|
148
150
|
.filter((k) => k !== 'label' && k !== 'blurb')
|
|
149
151
|
.every((k) => a[k] === b[k]);
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
export const sketchDirty = derived(
|
|
153
155
|
[sketchSettings, sketchBaseline],
|
|
154
|
-
([settings, baseline]) => baseline !== null && !
|
|
156
|
+
([settings, baseline]) => baseline !== null && !sameSketchStyle(settings, baseline),
|
|
155
157
|
);
|
|
156
158
|
|
|
157
159
|
export const sketchBlurb = derived(sketchSettings, (s) => s.blurb);
|
|
158
160
|
|
|
159
|
-
/** The live
|
|
161
|
+
/** The live style as a theme would carry it: the dials when the effect is
|
|
160
162
|
on, nothing when it is off (RJC 1). A copy, not the store's own object,
|
|
161
163
|
so a caller holding onto the result can never observe a later dial move
|
|
162
164
|
through it. */
|
|
163
|
-
export function
|
|
165
|
+
export function liveSketchSettings(): SketchStyleSettings | undefined {
|
|
164
166
|
return get(sketchEnabled) ? { ...get(sketchSettings) } : undefined;
|
|
165
167
|
}
|
|
166
168
|
|
|
@@ -175,25 +177,25 @@ export function setSketchEnabled(enabled: boolean): void {
|
|
|
175
177
|
|
|
176
178
|
/** What the open theme holds, so "unsaved" is a comparison rather than a
|
|
177
179
|
flag (RJC 5). Set by every path that opens or saves a theme. */
|
|
178
|
-
export const
|
|
180
|
+
export const themeSketchSettings = writable<SketchStyleSettings | undefined>(undefined);
|
|
179
181
|
|
|
180
182
|
/** Open a theme's sketchstyle: the dials, the on/off state, and the name
|
|
181
183
|
recovered by comparison (RJC 3). Overwrites the live buffer, which
|
|
182
184
|
is what opening a theme means everywhere else (RJC 6). The name is recovered
|
|
183
|
-
over the whole pool, so a theme carrying a
|
|
185
|
+
over the whole pool, so a theme carrying a style a saved file also holds is
|
|
184
186
|
named by that file rather than falling back to `THEME_SKETCH_ID`. */
|
|
185
|
-
export function
|
|
186
|
-
|
|
187
|
-
if (!
|
|
187
|
+
export function openThemeSketchSettings(settings: SketchStyleSettings | undefined): void {
|
|
188
|
+
themeSketchSettings.set(settings);
|
|
189
|
+
if (!settings) {
|
|
188
190
|
sketchEnabled.set(false);
|
|
189
191
|
sketchBaseline.set(null);
|
|
190
|
-
|
|
192
|
+
selectedSketchStyleId.set('');
|
|
191
193
|
return;
|
|
192
194
|
}
|
|
193
|
-
const matched = get(
|
|
194
|
-
sketchSettings.set({ ...
|
|
195
|
-
sketchBaseline.set({ ...
|
|
196
|
-
|
|
195
|
+
const matched = get(sketchStyles).find((style) => sameSketchStyle(style.settings, settings))?.id;
|
|
196
|
+
sketchSettings.set({ ...settings });
|
|
197
|
+
sketchBaseline.set({ ...settings });
|
|
198
|
+
selectedSketchStyleId.set(matched ?? THEME_SKETCH_ID);
|
|
197
199
|
sketchEnabled.set(true);
|
|
198
200
|
}
|
|
199
201
|
|
|
@@ -202,40 +204,40 @@ export function openThemeSketchStyle(sketchStyle: SketchStyle | undefined): void
|
|
|
202
204
|
* browser has already decided for itself.
|
|
203
205
|
*
|
|
204
206
|
* The rule boot has always followed in dev, and the only one a built site has:
|
|
205
|
-
* a visitor who picked a
|
|
207
|
+
* a visitor who picked a style, or picked None, keeps it, and `themeSketchSettings`
|
|
206
208
|
* still learns what the theme holds so the panel can call the difference
|
|
207
|
-
* unsaved. Both branches set it, so a picker can offer the theme's
|
|
209
|
+
* unsaved. Both branches set it, so a picker can offer the theme's style as a
|
|
208
210
|
* row either way.
|
|
209
211
|
*
|
|
210
|
-
* Takes the raw field rather than a `
|
|
212
|
+
* Takes the raw field rather than a `SketchStyleSettings`, and hydrates it here: a
|
|
211
213
|
* built site reads its theme JSON straight off disk with no dev server to run
|
|
212
|
-
* `normalizeTheme` over it first, so this is the only place a
|
|
214
|
+
* `normalizeTheme` over it first, so this is the only place a style stored under
|
|
213
215
|
* a retired dial name gets carried forward. Anything that is not an object is
|
|
214
216
|
* the absent case, which is off (invariant 3).
|
|
215
217
|
*/
|
|
216
|
-
export function seedSketchFromTheme(
|
|
218
|
+
export function seedSketchFromTheme(raw: unknown): void {
|
|
217
219
|
const style =
|
|
218
|
-
typeof
|
|
219
|
-
?
|
|
220
|
+
typeof raw === 'object' && raw !== null && !Array.isArray(raw)
|
|
221
|
+
? hydrateSketchSettings(raw)
|
|
220
222
|
: undefined;
|
|
221
223
|
if (hasPersistedSketchState()) {
|
|
222
|
-
|
|
224
|
+
themeSketchSettings.set(style);
|
|
223
225
|
return;
|
|
224
226
|
}
|
|
225
|
-
|
|
227
|
+
openThemeSketchSettings(style);
|
|
226
228
|
}
|
|
227
229
|
|
|
228
|
-
/** Go back to the
|
|
229
|
-
theme's is the one
|
|
230
|
+
/** Go back to the style the theme carries, after picking something else. The
|
|
231
|
+
theme's is the one style a picker can offer that this module did not ship, so
|
|
230
232
|
it needs a door of its own beside `selectSketchStyle`; `setSketch` gives the
|
|
231
233
|
two the same face. Silent when the theme carries none, the way
|
|
232
234
|
`selectSketchStyle` is for a name it does not know. */
|
|
233
|
-
export function
|
|
234
|
-
const style = get(
|
|
235
|
+
export function selectUnsavedSketchStyle(): void {
|
|
236
|
+
const style = get(themeSketchSettings);
|
|
235
237
|
if (!style) return;
|
|
236
238
|
markSketchTouched();
|
|
237
239
|
if (get(sketchEnabled)) liveMovedSinceBake.set(true);
|
|
238
|
-
|
|
240
|
+
selectedSketchStyleId.set(THEME_SKETCH_ID);
|
|
239
241
|
sketchBaseline.set({ ...style });
|
|
240
242
|
sketchSettings.set({ ...style });
|
|
241
243
|
}
|
|
@@ -243,13 +245,13 @@ export function selectThemeSketchStyle(): void {
|
|
|
243
245
|
/** The live sketch differs from what the open theme carries. Presence is
|
|
244
246
|
half the comparison: on with dials the theme does not hold, or off while
|
|
245
247
|
the theme holds a layer, are both off the theme. */
|
|
246
|
-
export const
|
|
247
|
-
[sketchEnabled, sketchSettings,
|
|
248
|
+
export const sketchOffTheme = derived(
|
|
249
|
+
[sketchEnabled, sketchSettings, themeSketchSettings],
|
|
248
250
|
([enabled, settings, saved]) => {
|
|
249
251
|
const live = enabled ? settings : undefined;
|
|
250
252
|
if (!live && !saved) return false;
|
|
251
253
|
if (!live || !saved) return true;
|
|
252
|
-
return !
|
|
254
|
+
return !sameSketchStyle(live, saved);
|
|
253
255
|
},
|
|
254
256
|
);
|
|
255
257
|
|
|
@@ -257,13 +259,13 @@ export const sketchOffLook = derived(
|
|
|
257
259
|
Silent for an id nothing knows, the way it has always been for an unknown
|
|
258
260
|
shipped name. */
|
|
259
261
|
export function selectSketchStyle(id: string): void {
|
|
260
|
-
const
|
|
261
|
-
if (!
|
|
262
|
+
const style = sketchStyleById(id);
|
|
263
|
+
if (!style) return;
|
|
262
264
|
markSketchTouched();
|
|
263
265
|
if (get(sketchEnabled)) liveMovedSinceBake.set(true);
|
|
264
|
-
|
|
265
|
-
sketchBaseline.set({ ...
|
|
266
|
-
sketchSettings.set({ ...
|
|
266
|
+
selectedSketchStyleId.set(id);
|
|
267
|
+
sketchBaseline.set({ ...style.settings });
|
|
268
|
+
sketchSettings.set({ ...style.settings });
|
|
267
269
|
}
|
|
268
270
|
|
|
269
271
|
/** Saved sketchstyles, listed from the data tree. Empty until
|
|
@@ -279,7 +281,7 @@ export async function refreshSavedSketchStyles(): Promise<void> {
|
|
|
279
281
|
const files = await listSketchStyles();
|
|
280
282
|
const loaded = await Promise.all(files.map((f) => loadSketchStyle(f.fileName)));
|
|
281
283
|
savedSketchStyles.set(files);
|
|
282
|
-
|
|
284
|
+
replaceRegisteredSketchStyles(
|
|
283
285
|
files.map((file, i) => ({
|
|
284
286
|
id: file.fileName,
|
|
285
287
|
label: file.name || file.fileName,
|
|
@@ -303,23 +305,36 @@ export async function saveCurrentSketchStyle(name: string): Promise<string> {
|
|
|
303
305
|
await refreshSavedSketchStyles();
|
|
304
306
|
sketchSettings.set(settings);
|
|
305
307
|
sketchBaseline.set({ ...settings });
|
|
306
|
-
|
|
308
|
+
selectedSketchStyleId.set(fileName);
|
|
307
309
|
return fileName;
|
|
308
310
|
}
|
|
309
311
|
|
|
310
|
-
/** Overwrite the selected
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
312
|
+
/** Overwrite the selected sketchstyle, so a style can be put down and picked up
|
|
313
|
+
again across sessions.
|
|
314
|
+
|
|
315
|
+
The file name comes from the selection rather than from re-slugifying the
|
|
316
|
+
label, because the two can disagree: `dry` is labelled "Dry marker", which
|
|
317
|
+
re-slugified writes `dry-marker.json` and leaves the selected file untouched.
|
|
318
|
+
The label comes from the style for the same reason, so the name the grid shows
|
|
319
|
+
survives.
|
|
320
|
+
|
|
321
|
+
A shipped style saves the same way. Its file lives in the package, so the
|
|
322
|
+
write lands in this project under the same id and shadows it, which is how a
|
|
323
|
+
project comes to own its Pencil; deleting that file uncovers the packaged one
|
|
324
|
+
again. A registered style owns no file at all, so it has none to write over.
|
|
315
325
|
|
|
316
326
|
No `markSketchTouched`: the button only lights once a dial has moved, and
|
|
317
327
|
every dial goes through `updateSketchSettings`, which marks it. */
|
|
318
328
|
export async function saveSelectedSketchStyle(): Promise<void> {
|
|
319
|
-
const
|
|
320
|
-
if (
|
|
329
|
+
const style = sketchStyleById(get(selectedSketchStyleId));
|
|
330
|
+
if (!style) throw new Error('No sketchstyle is selected');
|
|
331
|
+
if (style.source === 'registered') {
|
|
332
|
+
throw new Error(
|
|
333
|
+
`${style.label} was registered in code rather than saved as a file. Use Save As to keep these dials.`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
321
336
|
const settings = { ...get(sketchSettings) };
|
|
322
|
-
await saveSketchStyle(
|
|
337
|
+
await saveSketchStyle(style.id, style.label, settings);
|
|
323
338
|
await refreshSavedSketchStyles();
|
|
324
339
|
// Re-baselining is what disables the button again and returns the readout
|
|
325
340
|
// from "Modified from X" to the saved blurb.
|
|
@@ -330,16 +345,16 @@ export async function deleteSavedSketchStyle(fileName: string): Promise<void> {
|
|
|
330
345
|
await deleteSketchStyle(fileName);
|
|
331
346
|
await refreshSavedSketchStyles();
|
|
332
347
|
// The dials keep their values; only the name stops naming a file that exists.
|
|
333
|
-
if (get(
|
|
334
|
-
|
|
348
|
+
if (get(selectedSketchStyleId) === fileName) {
|
|
349
|
+
selectedSketchStyleId.set('');
|
|
335
350
|
sketchBaseline.set(null);
|
|
336
351
|
}
|
|
337
352
|
}
|
|
338
353
|
|
|
339
354
|
/** Every dial goes through here. The selection deliberately survives: the grid
|
|
340
|
-
keeps naming the sketchstyle this
|
|
355
|
+
keeps naming the sketchstyle this style came from, and `sketchDirty` reports
|
|
341
356
|
the drift. Save writes a new sketchstyle rather than overwriting the base. */
|
|
342
|
-
export function updateSketchSettings(patch: Partial<
|
|
357
|
+
export function updateSketchSettings(patch: Partial<SketchStyleSettings>): void {
|
|
343
358
|
markSketchTouched();
|
|
344
359
|
if (get(sketchEnabled)) liveMovedSinceBake.set(true);
|
|
345
360
|
sketchSettings.update((s) => ({ ...s, ...patch }));
|
|
@@ -361,7 +376,7 @@ function persist(key: string, value: string): void {
|
|
|
361
376
|
itself between import and first render. */
|
|
362
377
|
let pageRoot: HTMLElement | null = null;
|
|
363
378
|
|
|
364
|
-
function render(enabled: boolean, settings:
|
|
379
|
+
function render(enabled: boolean, settings: SketchStyleSettings): void {
|
|
365
380
|
if (typeof document === 'undefined') return;
|
|
366
381
|
if (!enabled) {
|
|
367
382
|
if (sketchLayerInstalled()) removeSketchLayer();
|
|
@@ -375,14 +390,14 @@ function render(enabled: boolean, settings: SketchStyle): void {
|
|
|
375
390
|
setSketchScope(pageRoot, settings);
|
|
376
391
|
}
|
|
377
392
|
|
|
378
|
-
/** Paint one
|
|
393
|
+
/** Paint one style on the two roots `render` owns, and on nothing else.
|
|
379
394
|
|
|
380
395
|
`render(false, ...)` reaches further: it clears every `[data-sketch]` in
|
|
381
396
|
the document. The scopes it takes with it belong to `$effect`s that paint
|
|
382
397
|
from the live stores, which a preview deliberately never ticks, so those
|
|
383
398
|
never come back. Previewing a theme carrying no sketchstyle left the
|
|
384
399
|
Sketchstyle view's own stage crisp for good, Cancel included. */
|
|
385
|
-
function paintPreviewRoots(style:
|
|
400
|
+
function paintPreviewRoots(style: SketchStyleSettings | undefined): void {
|
|
386
401
|
if (typeof document === 'undefined') return;
|
|
387
402
|
if (style) applySketchLayer(style);
|
|
388
403
|
setSketchScope(hostRoot(), style ?? null);
|
|
@@ -397,7 +412,7 @@ function paintPreviewRoots(style: SketchStyle | undefined): void {
|
|
|
397
412
|
host page across the iframe boundary the same way Apply's does; only the
|
|
398
413
|
store write and the persistence are skipped. Pass `undefined` for a theme
|
|
399
414
|
that carries no sketchstyle. */
|
|
400
|
-
export function previewSketchStyle(style:
|
|
415
|
+
export function previewSketchStyle(style: SketchStyleSettings | undefined): void {
|
|
401
416
|
paintPreviewRoots(style);
|
|
402
417
|
}
|
|
403
418
|
|
|
@@ -457,7 +472,7 @@ if (typeof document !== 'undefined') {
|
|
|
457
472
|
render(get(sketchEnabled), settings);
|
|
458
473
|
});
|
|
459
474
|
|
|
460
|
-
|
|
475
|
+
selectedSketchStyleId.subscribe((name) => share(SELECTED_ID_KEY, name));
|
|
461
476
|
sketchBaseline.subscribe((b) => share(BASELINE_KEY, b ? JSON.stringify(b) : ''));
|
|
462
477
|
|
|
463
478
|
/* The overlay editor runs in an iframe, so the Sketchstyle view and a
|
|
@@ -477,10 +492,10 @@ if (typeof document !== 'undefined') {
|
|
|
477
492
|
adopt(SETTINGS_KEY, JSON.stringify(next));
|
|
478
493
|
sketchSettings.set(next);
|
|
479
494
|
}
|
|
480
|
-
} else if (event.key ===
|
|
481
|
-
const next =
|
|
482
|
-
adopt(
|
|
483
|
-
if (next !== get(
|
|
495
|
+
} else if (event.key === SELECTED_ID_KEY) {
|
|
496
|
+
const next = readSelectedId();
|
|
497
|
+
adopt(SELECTED_ID_KEY, next);
|
|
498
|
+
if (next !== get(selectedSketchStyleId)) selectedSketchStyleId.set(next);
|
|
484
499
|
} else if (event.key === BASELINE_KEY) {
|
|
485
500
|
const current = get(sketchBaseline);
|
|
486
501
|
if ((event.newValue || '') !== (current ? JSON.stringify(current) : '')) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { API_BASE } from '../storage/apiBase';
|
|
2
|
-
import {
|
|
2
|
+
import { hydrateSketchSettings, type SketchStyleSettings } from './sketchStyles';
|
|
3
3
|
|
|
4
4
|
export interface SketchStyleFile {
|
|
5
5
|
name: string;
|
|
6
6
|
createdAt?: string;
|
|
7
7
|
updatedAt?: string;
|
|
8
|
-
settings:
|
|
8
|
+
settings: SketchStyleSettings;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export interface SketchStyleMeta {
|
|
@@ -40,13 +40,13 @@ export async function loadSketchStyle(fileName: string): Promise<SketchStyleFile
|
|
|
40
40
|
const res = await fetch(`${BASE}/${encodeURIComponent(fileName)}`);
|
|
41
41
|
if (!res.ok) throw new Error(`Failed to load sketchstyle: ${fileName}`);
|
|
42
42
|
const body = await res.json();
|
|
43
|
-
return { ...body, settings:
|
|
43
|
+
return { ...body, settings: hydrateSketchSettings(body.settings) };
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export async function saveSketchStyle(
|
|
47
47
|
fileName: string,
|
|
48
48
|
name: string,
|
|
49
|
-
settings:
|
|
49
|
+
settings: SketchStyleSettings,
|
|
50
50
|
): Promise<void> {
|
|
51
51
|
const res = await fetch(`${BASE}/${encodeURIComponent(fileName)}`, {
|
|
52
52
|
method: 'PUT',
|