@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,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
// The root of the
|
|
2
|
+
// The root of the theme hierarchy: one Theme panel that owns the open theme,
|
|
3
3
|
// with the colors and type and the components as parts that report rather
|
|
4
4
|
// than manage.
|
|
5
5
|
//
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
getProductionTheme,
|
|
16
16
|
loadTheme,
|
|
17
17
|
applyTheme,
|
|
18
|
-
|
|
18
|
+
adoptTheme,
|
|
19
19
|
freshName,
|
|
20
20
|
saveAsTheme,
|
|
21
21
|
saveActiveTheme,
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
THEME_APPLIED_EVENT,
|
|
28
28
|
type AppliedThemeDetail,
|
|
29
29
|
} from '../core/themes/themeDocumentSync';
|
|
30
|
-
import {
|
|
31
|
-
import { commitPreview, previewTheme, previewColorsAndType, revertPreview } from '../core/preview/
|
|
30
|
+
import { countComponentsOffTheme, themeProductionState } from '../core/themes/themeSummary';
|
|
31
|
+
import { commitPreview, previewTheme, previewColorsAndType, revertPreview } from '../core/preview/themePreview';
|
|
32
32
|
import {
|
|
33
33
|
deleteColorsAndType,
|
|
34
34
|
hydrateColorsAndType,
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
markComponentSaved,
|
|
59
59
|
} from '../core/store/editorStore';
|
|
60
60
|
import { openThemeSlug } from '../core/store/editorConfigStore';
|
|
61
|
-
import {
|
|
61
|
+
import { sketchOffTheme, themeSketchSettings } from '../core/sketch/sketchStore';
|
|
62
62
|
import { editorView } from '../core/store/editorViewStore';
|
|
63
63
|
import {
|
|
64
64
|
componentActiveRevision,
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
|
|
101
101
|
let currentDisplayName = $state('Motion Proto');
|
|
102
102
|
let activeIsProtected = $derived($openThemeSlug === 'default');
|
|
103
|
-
let activeRowId = $derived(loadRowId('
|
|
103
|
+
let activeRowId = $derived(loadRowId('theme', $openThemeSlug));
|
|
104
104
|
|
|
105
105
|
type SaveState = 'idle' | 'saving' | 'saved' | 'error';
|
|
106
106
|
const setSaveStatus = (s: SaveState) => (saveStatus = s);
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
let dirtyComponentCount = $derived(
|
|
109
109
|
Object.values($componentDirty).filter(Boolean).length,
|
|
110
110
|
);
|
|
111
|
-
let unsavedEdits = $derived($colorsAndTypeDirty || dirtyComponentCount > 0 || $
|
|
111
|
+
let unsavedEdits = $derived($colorsAndTypeDirty || dirtyComponentCount > 0 || $sketchOffTheme);
|
|
112
112
|
|
|
113
113
|
async function refreshFiles() {
|
|
114
114
|
try {
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
// Deleting the open theme repoints active without going through Load
|
|
128
128
|
// or Save, both of which already set this; this is the path that
|
|
129
129
|
// makes sketchStore's "set by every path that opens a theme" true.
|
|
130
|
-
|
|
130
|
+
themeSketchSettings.set(active.sketchSettings);
|
|
131
131
|
}
|
|
132
132
|
} catch {
|
|
133
133
|
// silent
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
|
|
162
162
|
let components: ComponentSummary[] = $state([]);
|
|
163
163
|
|
|
164
|
-
let
|
|
164
|
+
let componentsOffTheme = $derived(countComponentsOffTheme(components));
|
|
165
165
|
|
|
166
166
|
async function refreshComponents() {
|
|
167
167
|
try {
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
// ── Adopt: ship the whole
|
|
317
|
+
// ── Adopt: ship the whole theme ────────────────────────────────────────
|
|
318
318
|
//
|
|
319
319
|
// One action at the root, because production is one saved theme: Adopt saves
|
|
320
320
|
// the open one and publishes it whole. The component editors' Adopt runs the
|
|
@@ -325,7 +325,7 @@
|
|
|
325
325
|
const setAdoptStatus = (s: AdoptStatus) => (adoptStatus = s);
|
|
326
326
|
|
|
327
327
|
let production = $derived(
|
|
328
|
-
|
|
328
|
+
themeProductionState({
|
|
329
329
|
openTheme: $openThemeSlug,
|
|
330
330
|
productionTheme: $productionTheme?._fileName ?? null,
|
|
331
331
|
unpublished: unsavedEdits || $liveMovedSinceBake,
|
|
@@ -346,7 +346,7 @@
|
|
|
346
346
|
/**
|
|
347
347
|
* Adopt publishes what is saved, so the flow is flush, save, ship. The
|
|
348
348
|
* protected Default theme cannot record what shipped: the user clicked
|
|
349
|
-
* Adopt, and which file holds the
|
|
349
|
+
* Adopt, and which file holds the theme is bookkeeping they should not have
|
|
350
350
|
* to think about, so it forks to a theme of their own first.
|
|
351
351
|
*/
|
|
352
352
|
async function runAdopt(saveComponents = false) {
|
|
@@ -362,7 +362,7 @@
|
|
|
362
362
|
} else {
|
|
363
363
|
await saveActiveTheme(currentDisplayName);
|
|
364
364
|
}
|
|
365
|
-
await
|
|
365
|
+
await adoptTheme();
|
|
366
366
|
// The panel's own production pulse re-reads identity, the production
|
|
367
367
|
// theme and the component summary.
|
|
368
368
|
bumpProductionRevision();
|
|
@@ -388,7 +388,7 @@
|
|
|
388
388
|
let previewRow: LoadRow | null = $state(null);
|
|
389
389
|
// `$state.raw`: the preview engine structuredClones these, and a deep `$state`
|
|
390
390
|
// proxy is not cloneable. Both are only ever reassigned whole.
|
|
391
|
-
let
|
|
391
|
+
let previewedTheme: Theme | null = $state.raw(null);
|
|
392
392
|
let previewLayer: ColorsAndType | null = $state.raw(null);
|
|
393
393
|
let colorsOnly = $state(false);
|
|
394
394
|
let effectiveColorsOnly = $derived(isColorsOnly(previewRow, colorsOnly));
|
|
@@ -397,47 +397,47 @@
|
|
|
397
397
|
function cancelPreview() {
|
|
398
398
|
revertPreview();
|
|
399
399
|
previewRow = null;
|
|
400
|
-
|
|
400
|
+
previewedTheme = null;
|
|
401
401
|
previewLayer = null;
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
function acceptPreview() {
|
|
405
405
|
commitPreview();
|
|
406
406
|
previewRow = null;
|
|
407
|
-
|
|
407
|
+
previewedTheme = null;
|
|
408
408
|
previewLayer = null;
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
/** Paint whatever is selected, through the engine the mode calls for. A
|
|
412
|
-
* theme's colors and type are the slice embedded in it, so one
|
|
412
|
+
* theme's colors and type are the slice embedded in it, so one theme
|
|
413
413
|
* previews either way. */
|
|
414
414
|
async function repaint(): Promise<void> {
|
|
415
415
|
if (previewLayer) {
|
|
416
416
|
previewColorsAndType(previewLayer);
|
|
417
417
|
return;
|
|
418
418
|
}
|
|
419
|
-
if (!
|
|
420
|
-
if (effectiveColorsOnly) previewColorsAndType(
|
|
421
|
-
else await previewTheme(
|
|
419
|
+
if (!previewedTheme) return;
|
|
420
|
+
if (effectiveColorsOnly) previewColorsAndType(previewedTheme.colorsAndType);
|
|
421
|
+
else await previewTheme(previewedTheme);
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
async function handleSelect(row: LoadRow) {
|
|
425
425
|
if (row.fileName === previewRow?.fileName) return;
|
|
426
|
-
// The open theme in whole-
|
|
426
|
+
// The open theme in whole-theme mode is what the page already shows. In
|
|
427
427
|
// colors-only mode it is a real operation: it puts the theme's own colors
|
|
428
428
|
// back over whatever the user has edited.
|
|
429
|
-
if (row.kind === '
|
|
429
|
+
if (row.kind === 'theme' && row.slug === $openThemeSlug && !isColorsOnly(row, colorsOnly)) {
|
|
430
430
|
cancelPreview();
|
|
431
431
|
return;
|
|
432
432
|
}
|
|
433
433
|
try {
|
|
434
|
-
const
|
|
434
|
+
const theme = row.kind === 'theme' ? await loadTheme(row.slug) : null;
|
|
435
435
|
const layer = row.kind === 'layer' ? await loadColorsAndType(row.slug) : null;
|
|
436
436
|
// The window may have closed during the fetch; painting then would leave
|
|
437
437
|
// a preview on screen with no Save or Cancel in sight.
|
|
438
438
|
if (!showFileList) return;
|
|
439
439
|
previewRow = row;
|
|
440
|
-
|
|
440
|
+
previewedTheme = theme;
|
|
441
441
|
previewLayer = layer;
|
|
442
442
|
await repaint();
|
|
443
443
|
} catch (err) {
|
|
@@ -457,7 +457,7 @@
|
|
|
457
457
|
await commitColorsOnly();
|
|
458
458
|
return;
|
|
459
459
|
}
|
|
460
|
-
await
|
|
460
|
+
await commitWholeTheme();
|
|
461
461
|
}
|
|
462
462
|
|
|
463
463
|
function filledNote(filled: ThemeFillReport): string | null {
|
|
@@ -467,7 +467,7 @@
|
|
|
467
467
|
+ `The editor filled them from the current defaults.`;
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
-
async function
|
|
470
|
+
async function commitWholeTheme() {
|
|
471
471
|
const row = previewRow;
|
|
472
472
|
if (!row) return;
|
|
473
473
|
if (unsavedEdits) {
|
|
@@ -477,7 +477,7 @@
|
|
|
477
477
|
if (!ok) return;
|
|
478
478
|
}
|
|
479
479
|
// The exact theme being opened is already painted. Hand that preview to
|
|
480
|
-
// the store load without restoring the old
|
|
480
|
+
// the store load without restoring the old theme across the request.
|
|
481
481
|
acceptPreview();
|
|
482
482
|
let result: Awaited<ReturnType<typeof applyTheme>>;
|
|
483
483
|
try {
|
|
@@ -501,8 +501,8 @@
|
|
|
501
501
|
// The selection remains visible after a failed request; put its preview
|
|
502
502
|
// back so the dialog and page continue to agree.
|
|
503
503
|
previewRow = row;
|
|
504
|
-
|
|
505
|
-
if (
|
|
504
|
+
previewedTheme = row.kind === 'theme' ? await loadTheme(row.slug).catch(() => null) : null;
|
|
505
|
+
if (previewedTheme) await repaint();
|
|
506
506
|
return;
|
|
507
507
|
}
|
|
508
508
|
|
|
@@ -517,7 +517,7 @@
|
|
|
517
517
|
await saveAsTheme(freshName('my-theme', taken), 'My Theme');
|
|
518
518
|
await refreshActive();
|
|
519
519
|
}
|
|
520
|
-
await
|
|
520
|
+
await adoptTheme();
|
|
521
521
|
bumpProductionRevision();
|
|
522
522
|
flashStatus(setAdoptStatus, 'done');
|
|
523
523
|
} catch (err) {
|
|
@@ -537,7 +537,7 @@
|
|
|
537
537
|
const row = previewRow;
|
|
538
538
|
// Read the payload before the revert clears it. The read doors mark where
|
|
539
539
|
// they answered from; the buffer holds content alone.
|
|
540
|
-
const picked = previewLayer ??
|
|
540
|
+
const picked = previewLayer ?? previewedTheme?.colorsAndType ?? null;
|
|
541
541
|
if (!row || !picked) return;
|
|
542
542
|
const { _fileName, _source, ...buffer } = picked;
|
|
543
543
|
if (unsavedEdits) {
|
|
@@ -548,7 +548,7 @@
|
|
|
548
548
|
}
|
|
549
549
|
// Hand the page back to the store before loading. The renderer diffs
|
|
550
550
|
// against its own last-applied set, which never saw the preview's direct
|
|
551
|
-
// writes; starting from the live
|
|
551
|
+
// writes; starting from the live theme makes the load land exactly as it
|
|
552
552
|
// does with no preview in play.
|
|
553
553
|
cancelPreview();
|
|
554
554
|
showFileList = false;
|
|
@@ -633,14 +633,14 @@
|
|
|
633
633
|
if (row.isProtected) return;
|
|
634
634
|
if (row.kind === 'layer') return deleteColorsFile(row);
|
|
635
635
|
// Deleting the open theme is legal: the server materialises only the
|
|
636
|
-
// deltas needed to keep the
|
|
636
|
+
// deltas needed to keep the theme on screen. Where open lands depends on
|
|
637
637
|
// something the client can't see — deleting a local copy that shadows a
|
|
638
638
|
// shipped theme restores the shipped one and keeps naming it, while
|
|
639
639
|
// deleting a local-only theme sends open back to Motion Proto.
|
|
640
640
|
const wasActive = row.slug === $openThemeSlug;
|
|
641
641
|
const ok = window.confirm(
|
|
642
642
|
wasActive
|
|
643
|
-
? `Delete theme "${row.name}"? The editor moves to the version shipped with the package if there is one, otherwise Motion Proto.
|
|
643
|
+
? `Delete theme "${row.name}"? The editor moves to the version shipped with the package if there is one, otherwise Motion Proto. What is on screen stays as it is.`
|
|
644
644
|
: `Delete theme "${row.name}"?`,
|
|
645
645
|
);
|
|
646
646
|
if (!ok) return;
|
|
@@ -687,18 +687,18 @@
|
|
|
687
687
|
}
|
|
688
688
|
</script>
|
|
689
689
|
|
|
690
|
-
<div class="
|
|
690
|
+
<div class="theme-panel">
|
|
691
691
|
<div class="mfm-header">
|
|
692
692
|
<span class="mfm-header-label">Theme</span>
|
|
693
693
|
<UIInfoPopover title="Theme" ariaLabel="About the theme">
|
|
694
694
|
<p>
|
|
695
|
-
A <strong>theme</strong> is a whole
|
|
695
|
+
A <strong>theme</strong> is a whole theme in one file: the colors and type plus a setting for every component.
|
|
696
696
|
</p>
|
|
697
697
|
<p>
|
|
698
|
-
It holds its own copy of that data, so the theme you open is the whole
|
|
698
|
+
It holds its own copy of that data, so the theme you open is the whole theme, and one theme can never break another.
|
|
699
699
|
</p>
|
|
700
700
|
<p>
|
|
701
|
-
<strong>Load</strong> opens the list. Picking a theme shows it on the page as a preview, so you can try each
|
|
701
|
+
<strong>Load</strong> opens the list. Picking a theme shows it on the page as a preview, so you can try each theme with nothing written to disk. Pick another to compare, or <strong>Cancel</strong> to go back to where you were.
|
|
702
702
|
</p>
|
|
703
703
|
<p>
|
|
704
704
|
<strong>Save</strong> opens and adopts the previewed theme: the editor works on it from then on and production ships it immediately. Previewing and cancelling never change what your site ships.
|
|
@@ -781,7 +781,7 @@
|
|
|
781
781
|
disabled={activeIsProtected || saveStatus === 'saving'}
|
|
782
782
|
title={activeIsProtected
|
|
783
783
|
? 'Motion Proto is read-only. Use Save As to capture under a new name.'
|
|
784
|
-
: 'Update this theme from the
|
|
784
|
+
: 'Update this theme from the theme on screen'}
|
|
785
785
|
>
|
|
786
786
|
<i
|
|
787
787
|
class="fas"
|
|
@@ -794,7 +794,7 @@
|
|
|
794
794
|
{#if saveStatus === 'idle'}Save{:else if saveStatus === 'saving'}Saving{:else if saveStatus === 'saved'}Saved{:else}Error{/if}
|
|
795
795
|
</span>
|
|
796
796
|
</button>
|
|
797
|
-
<button class="mfm-btn mfm-btn-row" onclick={openSaveAs} title="Save the
|
|
797
|
+
<button class="mfm-btn mfm-btn-row" onclick={openSaveAs} title="Save the theme on screen as a new theme">
|
|
798
798
|
<i class="fas fa-copy"></i>
|
|
799
799
|
<span>Save As…</span>
|
|
800
800
|
</button>
|
|
@@ -818,8 +818,8 @@
|
|
|
818
818
|
</div>
|
|
819
819
|
</div>
|
|
820
820
|
|
|
821
|
-
<!-- Parts of the
|
|
822
|
-
<div class="
|
|
821
|
+
<!-- Parts of the theme: what each holds now, no lifecycle of its own. -->
|
|
822
|
+
<div class="theme-parts">
|
|
823
823
|
<div class="part-head part-static">
|
|
824
824
|
<span class="part-label">Colors & Type</span>
|
|
825
825
|
<span class="part-summary">
|
|
@@ -845,8 +845,8 @@
|
|
|
845
845
|
<span class="part-label">Components</span>
|
|
846
846
|
<span class="part-summary">
|
|
847
847
|
<span class="part-summary-sep">·</span>
|
|
848
|
-
{#if
|
|
849
|
-
<span class="part-summary-count">{
|
|
848
|
+
{#if componentsOffTheme > 0}
|
|
849
|
+
<span class="part-summary-count">{componentsOffTheme}</span>
|
|
850
850
|
<span>off the theme</span>
|
|
851
851
|
{:else}
|
|
852
852
|
<span>in sync</span>
|
|
@@ -868,10 +868,10 @@
|
|
|
868
868
|
<span class="part-label">Sketchstyle</span>
|
|
869
869
|
<span class="part-summary">
|
|
870
870
|
<span class="part-summary-sep">·</span>
|
|
871
|
-
{#if $
|
|
871
|
+
{#if $sketchOffTheme}
|
|
872
872
|
<span>off the theme</span>
|
|
873
|
-
{:else if $
|
|
874
|
-
<span class="part-summary-text">{$
|
|
873
|
+
{:else if $themeSketchSettings}
|
|
874
|
+
<span class="part-summary-text">{$themeSketchSettings.label}</span>
|
|
875
875
|
{:else}
|
|
876
876
|
<span>none</span>
|
|
877
877
|
{/if}
|
|
@@ -892,7 +892,7 @@
|
|
|
892
892
|
It travels with the theme when you save, load or share it.
|
|
893
893
|
</p>
|
|
894
894
|
<p>
|
|
895
|
-
It does not reach a production build. Adopt publishes the crisp
|
|
895
|
+
It does not reach a production build. Adopt publishes the crisp theme; the drawing
|
|
896
896
|
stays a preview.
|
|
897
897
|
</p>
|
|
898
898
|
</UIInfoPopover>
|
|
@@ -939,7 +939,7 @@
|
|
|
939
939
|
onload={handleSelect}
|
|
940
940
|
ondelete={handleDelete}
|
|
941
941
|
onexport={(row) => handleExport(row)}
|
|
942
|
-
canExport={(row) => row.kind === '
|
|
942
|
+
canExport={(row) => row.kind === 'theme'}
|
|
943
943
|
exportTitle={(row) => `Export "${row.name}" as a file you can share`}
|
|
944
944
|
>
|
|
945
945
|
{#snippet options()}
|
|
@@ -979,7 +979,7 @@
|
|
|
979
979
|
line-height: 1.5;
|
|
980
980
|
}
|
|
981
981
|
|
|
982
|
-
.
|
|
982
|
+
.theme-panel {
|
|
983
983
|
--mfm-active: #5aa85e;
|
|
984
984
|
--mfm-rail-neutral: var(--ui-border);
|
|
985
985
|
--mfm-rail-active: var(--mfm-active);
|
|
@@ -1081,7 +1081,7 @@
|
|
|
1081
1081
|
font-size: 0.8em;
|
|
1082
1082
|
}
|
|
1083
1083
|
|
|
1084
|
-
/* Production state of the whole
|
|
1084
|
+
/* Production state of the whole theme, under the name it ships as. */
|
|
1085
1085
|
.mfm-prod-status {
|
|
1086
1086
|
display: inline-flex;
|
|
1087
1087
|
align-items: center;
|
|
@@ -1214,7 +1214,7 @@
|
|
|
1214
1214
|
|
|
1215
1215
|
/* Parts. Each reads as a row belonging to the card above, so they sit inside
|
|
1216
1216
|
the panel with the same left inset the card's rail gives its content. */
|
|
1217
|
-
.
|
|
1217
|
+
.theme-parts {
|
|
1218
1218
|
display: flex;
|
|
1219
1219
|
flex-direction: column;
|
|
1220
1220
|
gap: var(--ui-space-4);
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
import Callout from '../../../system/components/Callout.svelte';
|
|
7
7
|
import Toggle from '../../../system/components/Toggle.svelte';
|
|
8
8
|
import { setSketchScope } from '../../core/sketch/sketchLayer';
|
|
9
|
-
import type {
|
|
9
|
+
import type { SketchStyleSettings } from '../../core/sketch/sketchStyles';
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
|
-
settings:
|
|
12
|
+
settings: SketchStyleSettings;
|
|
13
13
|
enabled: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -14,18 +14,19 @@
|
|
|
14
14
|
import {
|
|
15
15
|
sketchEnabled,
|
|
16
16
|
setSketchEnabled,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
sketchOffTheme,
|
|
18
|
+
selectedSketchStyleId,
|
|
19
19
|
sketchDirty,
|
|
20
20
|
sketchSettings,
|
|
21
|
-
selectSketchStyle,
|
|
22
21
|
updateSketchSettings,
|
|
23
22
|
refreshSavedSketchStyles,
|
|
24
23
|
saveCurrentSketchStyle,
|
|
25
24
|
saveSelectedSketchStyle,
|
|
26
25
|
deleteSavedSketchStyle,
|
|
27
26
|
} from '../../core/sketch/sketchStore';
|
|
28
|
-
import {
|
|
27
|
+
import { sketchStyles } from '../../core/sketch/sketchRegistry';
|
|
28
|
+
import { setSketch, unsavedSketchStyle } from '../../core/sketch';
|
|
29
|
+
import { THEME_SKETCH_ID } from '../../core/sketch/sketchStyles';
|
|
29
30
|
|
|
30
31
|
interface Props {
|
|
31
32
|
/** The rail's last jump. A fresh object per click, not a bare id, so
|
|
@@ -38,8 +39,13 @@
|
|
|
38
39
|
|
|
39
40
|
let s = $derived($sketchSettings);
|
|
40
41
|
|
|
42
|
+
/** The theme's own style sits in the grid like any other row, but it is the
|
|
43
|
+
one row with no file behind it, so Save has nothing to write over. Save As
|
|
44
|
+
is what gives it one, after which it is an ordinary sketchstyle. */
|
|
45
|
+
let onUnsavedSketchStyle = $derived($selectedSketchStyleId === THEME_SKETCH_ID);
|
|
46
|
+
|
|
41
47
|
// Save is disabled for the protected Default theme (ThemePanel.svelte);
|
|
42
|
-
// Save As is the gesture actually on offer there, so the off-
|
|
48
|
+
// Save As is the gesture actually on offer there, so the off-style copy has
|
|
43
49
|
// to name it instead.
|
|
44
50
|
let activeIsProtected = $derived($openThemeSlug === 'default');
|
|
45
51
|
|
|
@@ -50,7 +56,7 @@
|
|
|
50
56
|
|
|
51
57
|
/** All closed to start: twenty-odd dials in one scroll is a wall, and the
|
|
52
58
|
summary on each trigger row answers most questions without opening
|
|
53
|
-
anything. Each section then stands alone, because shaping a
|
|
59
|
+
anything. Each section then stands alone, because shaping a style means
|
|
54
60
|
reaching between them — the border's detail against the noise it
|
|
55
61
|
multiplies, the fill's travel against the border's.
|
|
56
62
|
|
|
@@ -124,43 +130,49 @@
|
|
|
124
130
|
});
|
|
125
131
|
|
|
126
132
|
/** Shipped and saved sketchstyles are one choice, so they share one radio
|
|
127
|
-
group: picking a saved one clears the shipped selection natively.
|
|
133
|
+
group: picking a saved one clears the shipped selection natively.
|
|
134
|
+
|
|
135
|
+
Picks go through `setSketch`, the same door a page picker uses, so one is
|
|
136
|
+
the whole gesture: the style is applied, the effect comes on, and the
|
|
137
|
+
page's own picker follows. Selecting without applying would leave the two
|
|
138
|
+
pickers disagreeing about what the page is drawn with. */
|
|
128
139
|
const STYLE_RADIO_GROUP = 'sketchstyle';
|
|
129
140
|
|
|
130
141
|
/** What the readout under the grid says. The selection survives a dial move,
|
|
131
142
|
so the base keeps its name here and the blurb reports the drift instead. */
|
|
132
143
|
let active = $derived(
|
|
133
144
|
$sketchDirty
|
|
134
|
-
? {
|
|
145
|
+
? {
|
|
146
|
+
name: s.label,
|
|
147
|
+
blurb: onUnsavedSketchStyle
|
|
148
|
+
? 'Modified from settings this theme carries. Save As keeps them as a sketchstyle.'
|
|
149
|
+
: `Modified from ${s.label}. Save it to keep it.`,
|
|
150
|
+
}
|
|
135
151
|
: { name: s.label, blurb: s.blurb || 'Your saved sketchstyle.' },
|
|
136
152
|
);
|
|
137
153
|
|
|
138
|
-
let
|
|
154
|
+
let selectedSketchStyle = $derived($sketchStyles.find((l) => l.id === $selectedSketchStyleId));
|
|
139
155
|
/** Offered whenever the dials have moved off the sketchstyle they name. A
|
|
140
|
-
|
|
156
|
+
style backed by a project file is written over. A shipped one's file lives
|
|
141
157
|
in the package, so saving it writes this project's own under the same
|
|
142
158
|
name, which is how a project comes to own its Pencil: the id is the same,
|
|
143
159
|
so the new file takes the shipped one's place in the grid rather than
|
|
144
160
|
sitting beside it. Nothing is lost either way, since deleting that file
|
|
145
161
|
uncovers the packaged one again. */
|
|
146
|
-
let canSave = $derived(!!
|
|
162
|
+
let canSave = $derived(!!selectedSketchStyle && $sketchDirty);
|
|
147
163
|
let saveTitle = $derived(
|
|
148
|
-
|
|
149
|
-
? '
|
|
150
|
-
:
|
|
151
|
-
? '
|
|
152
|
-
:
|
|
153
|
-
?
|
|
154
|
-
: `
|
|
164
|
+
onUnsavedSketchStyle
|
|
165
|
+
? 'These settings belong to no sketchstyle file. Save As keeps them as one.'
|
|
166
|
+
: !selectedSketchStyle
|
|
167
|
+
? 'Pick a sketchstyle to save over'
|
|
168
|
+
: !$sketchDirty
|
|
169
|
+
? 'No changes to save'
|
|
170
|
+
: `Save over ${selectedSketchStyle.label}`,
|
|
155
171
|
);
|
|
156
172
|
|
|
157
173
|
function saveInPlace() {
|
|
158
|
-
if (!
|
|
159
|
-
runStyleAction(
|
|
160
|
-
selectedLook.source === 'file'
|
|
161
|
-
? saveSelectedSketchStyle()
|
|
162
|
-
: saveCurrentSketchStyle(selectedLook.label),
|
|
163
|
-
);
|
|
174
|
+
if (!selectedSketchStyle) return;
|
|
175
|
+
runStyleAction(saveSelectedSketchStyle());
|
|
164
176
|
}
|
|
165
177
|
|
|
166
178
|
let naming = $state(false);
|
|
@@ -324,9 +336,9 @@
|
|
|
324
336
|
<div class="readout" aria-live="polite">
|
|
325
337
|
<span class="readout-name">{active.name}</span>
|
|
326
338
|
<p class="readout-blurb">{active.blurb}</p>
|
|
327
|
-
{#if $
|
|
339
|
+
{#if $sketchOffTheme}
|
|
328
340
|
{#if $sketchEnabled}
|
|
329
|
-
<p class="readout-off-
|
|
341
|
+
<p class="readout-off-style">
|
|
330
342
|
{#if activeIsProtected}
|
|
331
343
|
Motion Proto is read-only, and these dials are ahead of it. Use Save As in the
|
|
332
344
|
Theme panel to keep them in a new theme.
|
|
@@ -336,7 +348,7 @@
|
|
|
336
348
|
{/if}
|
|
337
349
|
</p>
|
|
338
350
|
{:else}
|
|
339
|
-
<p class="readout-off-
|
|
351
|
+
<p class="readout-off-style">
|
|
340
352
|
The theme carries a sketchstyle this page is not painting. Saving now would drop
|
|
341
353
|
it.
|
|
342
354
|
</p>
|
|
@@ -345,25 +357,39 @@
|
|
|
345
357
|
</div>
|
|
346
358
|
|
|
347
359
|
<div class="presets">
|
|
348
|
-
{#
|
|
360
|
+
{#if $unsavedSketchStyle}
|
|
361
|
+
<div class="saved-item">
|
|
362
|
+
<label class="preset">
|
|
363
|
+
<input
|
|
364
|
+
type="radio"
|
|
365
|
+
name={STYLE_RADIO_GROUP}
|
|
366
|
+
value={$unsavedSketchStyle.id}
|
|
367
|
+
checked={onUnsavedSketchStyle}
|
|
368
|
+
onchange={() => setSketch(THEME_SKETCH_ID)}
|
|
369
|
+
/>
|
|
370
|
+
<span class="preset-name">{$unsavedSketchStyle.label}</span>
|
|
371
|
+
</label>
|
|
372
|
+
</div>
|
|
373
|
+
{/if}
|
|
374
|
+
{#each $sketchStyles as style (style.id)}
|
|
349
375
|
<div class="saved-item">
|
|
350
376
|
<label class="preset">
|
|
351
377
|
<input
|
|
352
378
|
type="radio"
|
|
353
379
|
name={STYLE_RADIO_GROUP}
|
|
354
|
-
value={
|
|
355
|
-
checked={$
|
|
356
|
-
onchange={() =>
|
|
380
|
+
value={style.id}
|
|
381
|
+
checked={$selectedSketchStyleId === style.id}
|
|
382
|
+
onchange={() => setSketch(style.id)}
|
|
357
383
|
/>
|
|
358
|
-
<span class="preset-name">{
|
|
384
|
+
<span class="preset-name">{style.label}</span>
|
|
359
385
|
</label>
|
|
360
|
-
{#if
|
|
386
|
+
{#if style.source === 'file'}
|
|
361
387
|
<button
|
|
362
388
|
type="button"
|
|
363
389
|
class="saved-delete"
|
|
364
|
-
title="Delete {
|
|
365
|
-
aria-label="Delete {
|
|
366
|
-
onclick={() => runStyleAction(deleteSavedSketchStyle(
|
|
390
|
+
title="Delete {style.label}"
|
|
391
|
+
aria-label="Delete {style.label}"
|
|
392
|
+
onclick={() => runStyleAction(deleteSavedSketchStyle(style.id))}
|
|
367
393
|
><i class="fas fa-xmark"></i></button>
|
|
368
394
|
{/if}
|
|
369
395
|
</div>
|
|
@@ -665,7 +691,7 @@
|
|
|
665
691
|
/>
|
|
666
692
|
</div>
|
|
667
693
|
</div>
|
|
668
|
-
{#if
|
|
694
|
+
{#if s.maskBlobX !== s.maskBlobY}
|
|
669
695
|
<SketchDial
|
|
670
696
|
label="Rotation" value={s.maskAngle} min={0} max={180} step={1}
|
|
671
697
|
readout={`${blobAngle}°`}
|
|
@@ -1097,7 +1123,7 @@
|
|
|
1097
1123
|
color: var(--ui-text-secondary);
|
|
1098
1124
|
}
|
|
1099
1125
|
|
|
1100
|
-
.readout-off-
|
|
1126
|
+
.readout-off-style {
|
|
1101
1127
|
margin: var(--ui-space-2) 0 0;
|
|
1102
1128
|
font-size: var(--ui-font-size-sm);
|
|
1103
1129
|
font-weight: var(--ui-font-weight-medium);
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"pooling": 1.5,
|
|
22
22
|
"strokeInk": 0.4,
|
|
23
23
|
"maskOn": true,
|
|
24
|
-
"maskBlobX":
|
|
25
|
-
"maskBlobY":
|
|
24
|
+
"maskBlobX": 170,
|
|
25
|
+
"maskBlobY": 170,
|
|
26
26
|
"maskBlobLinked": true,
|
|
27
27
|
"maskAngle": 0,
|
|
28
|
-
"maskOutputMin": 0.
|
|
28
|
+
"maskOutputMin": 0.57,
|
|
29
29
|
"maskOutputMax": 0.91,
|
|
30
30
|
"maskOctaves": 2,
|
|
31
31
|
"maskGrain": "fractal",
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
"iconMaskOn": true,
|
|
43
43
|
"iconMaskScale": 3.8
|
|
44
44
|
},
|
|
45
|
-
"updatedAt": "2026-08-
|
|
45
|
+
"updatedAt": "2026-08-28T21:19:51.383Z",
|
|
46
46
|
"createdAt": "2026-08-28T15:47:36.934Z"
|
|
47
|
-
}
|
|
47
|
+
}
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
"pressureMod": 0.45,
|
|
21
21
|
"pooling": 0.8,
|
|
22
22
|
"strokeInk": 1,
|
|
23
|
-
"maskOn":
|
|
24
|
-
"maskBlobX":
|
|
25
|
-
"maskBlobY":
|
|
26
|
-
"maskBlobLinked":
|
|
27
|
-
"maskAngle":
|
|
28
|
-
"maskOutputMin": 0.
|
|
23
|
+
"maskOn": true,
|
|
24
|
+
"maskBlobX": 10,
|
|
25
|
+
"maskBlobY": 600,
|
|
26
|
+
"maskBlobLinked": false,
|
|
27
|
+
"maskAngle": 150,
|
|
28
|
+
"maskOutputMin": 0.35,
|
|
29
29
|
"maskOutputMax": 1,
|
|
30
30
|
"maskOctaves": 2,
|
|
31
|
-
"maskGrain": "
|
|
31
|
+
"maskGrain": "turbulence",
|
|
32
32
|
"maskPosterize": 1,
|
|
33
33
|
"maskSoftness": 1.5,
|
|
34
34
|
"jitterX": 1.5,
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
"iconMaskOn": false,
|
|
43
43
|
"iconMaskScale": 1
|
|
44
44
|
},
|
|
45
|
-
"updatedAt": "2026-08-
|
|
45
|
+
"updatedAt": "2026-08-28T21:17:18.232Z",
|
|
46
46
|
"createdAt": "2026-08-28T15:47:36.934Z"
|
|
47
|
-
}
|
|
47
|
+
}
|