@1agh/maude 0.45.1 → 0.46.0
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/apps/studio/acp/bridge.ts +628 -74
- package/apps/studio/acp/index.ts +172 -23
- package/apps/studio/acp/probe.ts +31 -7
- package/apps/studio/acp/transcript.ts +91 -5
- package/apps/studio/annotations-snap.ts +50 -10
- package/apps/studio/api.ts +344 -14
- package/apps/studio/artboard-guides-overlay.tsx +270 -0
- package/apps/studio/bin/_import-asset.mjs +35 -5
- package/apps/studio/bin/_pdf-playwright.mjs +35 -4
- package/apps/studio/bin/_png-playwright.mjs +38 -4
- package/apps/studio/bin/_pw-launch.mjs +52 -0
- package/apps/studio/bin/_pw-launch.test.mjs +90 -0
- package/apps/studio/bin/_smart-frames.mjs +419 -0
- package/apps/studio/bin/_smart-frames.test.mjs +140 -0
- package/apps/studio/bin/smart-frames.sh +30 -0
- package/apps/studio/canvas-edit.ts +302 -3
- package/apps/studio/canvas-lib.tsx +203 -11
- package/apps/studio/canvas-shell.tsx +167 -28
- package/apps/studio/client/app.jsx +678 -176
- package/apps/studio/client/panels/CapabilityBar.jsx +101 -0
- package/apps/studio/client/panels/ChatPanel.jsx +846 -133
- package/apps/studio/client/panels/ElicitationPrompt.jsx +429 -0
- package/apps/studio/client/panels/PermissionPrompt.jsx +119 -0
- package/apps/studio/client/panels/ReadinessList.jsx +17 -3
- package/apps/studio/client/panels/SettingsPanel.jsx +211 -0
- package/apps/studio/client/panels/ToolGroup.jsx +79 -0
- package/apps/studio/client/panels/acp-capabilities.js +82 -0
- package/apps/studio/client/panels/acp-elicitation.js +194 -0
- package/apps/studio/client/panels/acp-runtime.js +248 -9
- package/apps/studio/client/panels/acp-usage.js +65 -0
- package/apps/studio/client/panels/transcript-view.js +36 -0
- package/apps/studio/client/styles/6-acp-chat.css +689 -11
- package/apps/studio/dist/client.bundle.js +1738 -1736
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +20 -0
- package/apps/studio/export-dialog.tsx +138 -18
- package/apps/studio/exporters/pdf.ts +332 -18
- package/apps/studio/exporters/png.ts +45 -3
- package/apps/studio/footage/schema.ts +17 -1
- package/apps/studio/generation/gemma-models.ts +224 -0
- package/apps/studio/generation/prefs.ts +43 -0
- package/apps/studio/generation/whisper-models.test.ts +28 -1
- package/apps/studio/generation/whisper-models.ts +22 -7
- package/apps/studio/http.ts +246 -9
- package/apps/studio/print/marks.ts +113 -0
- package/apps/studio/print/units.ts +269 -0
- package/apps/studio/print-overlay-content.tsx +132 -0
- package/apps/studio/test/acp-bridge.test.ts +11 -6
- package/apps/studio/test/acp-capabilities.test.ts +123 -0
- package/apps/studio/test/acp-caps-bridge.test.ts +274 -0
- package/apps/studio/test/acp-elicitation-bridge.test.ts +475 -0
- package/apps/studio/test/acp-elicitation.test.ts +251 -0
- package/apps/studio/test/acp-mode-banner.test.ts +45 -0
- package/apps/studio/test/acp-permission-prompt.test.ts +77 -0
- package/apps/studio/test/acp-permission.test.ts +262 -0
- package/apps/studio/test/acp-session-allowed-tools.test.ts +91 -0
- package/apps/studio/test/acp-toolgroup.test.ts +76 -0
- package/apps/studio/test/acp-transcript-view.test.ts +71 -0
- package/apps/studio/test/acp-transcript.test.ts +75 -2
- package/apps/studio/test/acp-usage-bridge.test.ts +136 -0
- package/apps/studio/test/acp-usage.test.ts +143 -0
- package/apps/studio/test/annotations-snap.test.ts +56 -0
- package/apps/studio/test/artboard-guides-overlay.test.tsx +152 -0
- package/apps/studio/test/artboard-kinds.test.tsx +83 -0
- package/apps/studio/test/artboard-selection-attrs.test.ts +69 -0
- package/apps/studio/test/canvas-meta-api.test.ts +167 -0
- package/apps/studio/test/element-structural-edit.test.ts +250 -0
- package/apps/studio/test/exporters/png.test.ts +49 -1
- package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +158 -0
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +46 -0
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +42 -0
- package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +63 -0
- package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +51 -0
- package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +50 -0
- package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +56 -0
- package/apps/studio/test/import-asset.test.ts +31 -3
- package/apps/studio/test/pdf-print-boxes.test.ts +272 -0
- package/apps/studio/test/print-marks.test.ts +113 -0
- package/apps/studio/test/print-units.test.ts +173 -0
- package/apps/studio/test/use-snap-guides.test.ts +81 -0
- package/apps/studio/use-chrome-visibility.tsx +19 -0
- package/apps/studio/use-element-resize.tsx +21 -2
- package/apps/studio/use-snap-guides.tsx +73 -5
- package/apps/studio/use-spacing-handles.tsx +9 -5
- package/apps/studio/whats-new.json +62 -0
- package/cli/commands/design.mjs +116 -3
- package/cli/lib/pkg-root.mjs +42 -10
- package/cli/lib/pkg-root.test.mjs +33 -1
- package/package.json +11 -9
- package/plugins/design/dependencies.json +35 -0
|
@@ -3598,12 +3598,16 @@ export async function setArtboardHug(
|
|
|
3598
3598
|
}
|
|
3599
3599
|
|
|
3600
3600
|
/** Patch shape for {@link applySetArtboardStyle} — `null` resets/removes that
|
|
3601
|
-
* prop back to the engine default; `undefined`/absent leaves it untouched.
|
|
3601
|
+
* prop back to the engine default; `undefined`/absent leaves it untouched.
|
|
3602
|
+
* padding/gap accept a `var(--token)` STRING alongside raw px numbers
|
|
3603
|
+
* (dogfood: the Inspector's artboard panel binds space tokens the same way
|
|
3604
|
+
* the CSS panel's ValueTokenField does) — validation of the string shape
|
|
3605
|
+
* lives at the api.ts layer, same split as every other artboard writer. */
|
|
3602
3606
|
export interface ArtboardStylePatch {
|
|
3603
3607
|
background?: string | null;
|
|
3604
|
-
padding?: number | null;
|
|
3608
|
+
padding?: number | string | null;
|
|
3605
3609
|
layout?: string | null;
|
|
3606
|
-
gap?: number | null;
|
|
3610
|
+
gap?: number | string | null;
|
|
3607
3611
|
}
|
|
3608
3612
|
|
|
3609
3613
|
/**
|
|
@@ -3647,10 +3651,14 @@ export function applySetArtboardStyle(
|
|
|
3647
3651
|
}
|
|
3648
3652
|
if ('padding' in patch) {
|
|
3649
3653
|
if (patch.padding == null) removeStringAttr(s, opening, 'padding', source);
|
|
3654
|
+
else if (typeof patch.padding === 'string')
|
|
3655
|
+
editStringAttr(s, opening, 'padding', patch.padding, canvasAbsPath, artboardId);
|
|
3650
3656
|
else writeNumericAttr(s, opening, 'padding', patch.padding, 0);
|
|
3651
3657
|
}
|
|
3652
3658
|
if ('gap' in patch) {
|
|
3653
3659
|
if (patch.gap == null) removeStringAttr(s, opening, 'gap', source);
|
|
3660
|
+
else if (typeof patch.gap === 'string')
|
|
3661
|
+
editStringAttr(s, opening, 'gap', patch.gap, canvasAbsPath, artboardId);
|
|
3654
3662
|
else writeNumericAttr(s, opening, 'gap', patch.gap, 0);
|
|
3655
3663
|
}
|
|
3656
3664
|
const out = s.toString();
|
|
@@ -3689,6 +3697,297 @@ export async function setArtboardStyle(
|
|
|
3689
3697
|
});
|
|
3690
3698
|
}
|
|
3691
3699
|
|
|
3700
|
+
const ARTBOARD_KIND_VALUES = new Set(['digital', 'print', 'web', 'video']);
|
|
3701
|
+
|
|
3702
|
+
/**
|
|
3703
|
+
* Write/clear the DCArtboard `kind` prop (feature-1-artboard-kinds-foundation
|
|
3704
|
+
* T5/T8) — kind-switch surfaces (context menu, Inspector) route here. Plain
|
|
3705
|
+
* string prop, so this reuses the same `editStringAttr`/`removeStringAttr`
|
|
3706
|
+
* toolkit + id-prop addressing as `applySetArtboardStyle`. `kind: null` clears
|
|
3707
|
+
* back to the implicit default (`digital`, or the `subtreeHasVideoComp`
|
|
3708
|
+
* fallback). Pure; reparse-gated.
|
|
3709
|
+
*/
|
|
3710
|
+
export function applySetArtboardKind(
|
|
3711
|
+
canvasAbsPath: string,
|
|
3712
|
+
source: string,
|
|
3713
|
+
artboardId: string,
|
|
3714
|
+
kind: string | null
|
|
3715
|
+
): { source: string } {
|
|
3716
|
+
if (kind !== null && !ARTBOARD_KIND_VALUES.has(kind)) {
|
|
3717
|
+
throw new CanvasEditError(`invalid artboard kind "${kind}"`, {
|
|
3718
|
+
canvas: canvasAbsPath,
|
|
3719
|
+
id: artboardId,
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
const parsed = parseSync(canvasAbsPath, source, { sourceType: 'module' });
|
|
3723
|
+
if (parsed.errors && parsed.errors.length > 0) {
|
|
3724
|
+
throw new CanvasEditError(
|
|
3725
|
+
`oxc-parser failed on ${canvasAbsPath}: ${parsed.errors[0]?.message ?? 'unknown'}`,
|
|
3726
|
+
{ canvas: canvasAbsPath, id: artboardId }
|
|
3727
|
+
);
|
|
3728
|
+
}
|
|
3729
|
+
const artboards = collectJsxByTag(parsed.program, 'DCArtboard');
|
|
3730
|
+
const target = artboards.find((a) => getStringAttr(a.openingElement, 'id') === artboardId);
|
|
3731
|
+
if (!target) {
|
|
3732
|
+
throw new CanvasEditError(`<DCArtboard id="${artboardId}"> not found in ${canvasAbsPath}`, {
|
|
3733
|
+
canvas: canvasAbsPath,
|
|
3734
|
+
id: artboardId,
|
|
3735
|
+
});
|
|
3736
|
+
}
|
|
3737
|
+
const s = new MagicString(source);
|
|
3738
|
+
const opening = target.openingElement;
|
|
3739
|
+
if (kind === null) removeStringAttr(s, opening, 'kind', source);
|
|
3740
|
+
else editStringAttr(s, opening, 'kind', kind, canvasAbsPath, artboardId);
|
|
3741
|
+
const out = s.toString();
|
|
3742
|
+
const check = parseSync(canvasAbsPath, out, { sourceType: 'module' });
|
|
3743
|
+
if (check.errors && check.errors.length > 0) {
|
|
3744
|
+
throw new CanvasEditError(
|
|
3745
|
+
`artboard kind edit produced invalid source (${check.errors[0]?.message ?? 'parse error'})`,
|
|
3746
|
+
{ canvas: canvasAbsPath, id: artboardId }
|
|
3747
|
+
);
|
|
3748
|
+
}
|
|
3749
|
+
return { source: out };
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
/** Set an artboard's `kind` on disk (atomic write + cross-process lock). */
|
|
3753
|
+
export async function setArtboardKind(
|
|
3754
|
+
canvasAbsPath: string,
|
|
3755
|
+
artboardId: string,
|
|
3756
|
+
kind: string | null
|
|
3757
|
+
): Promise<{ source: string }> {
|
|
3758
|
+
return withLock(canvasAbsPath, async () => {
|
|
3759
|
+
const file = Bun.file(canvasAbsPath);
|
|
3760
|
+
if (!(await file.exists())) {
|
|
3761
|
+
throw new CanvasEditError(`Canvas not found: ${canvasAbsPath}`, {
|
|
3762
|
+
canvas: canvasAbsPath,
|
|
3763
|
+
id: artboardId,
|
|
3764
|
+
});
|
|
3765
|
+
}
|
|
3766
|
+
const source = await file.text();
|
|
3767
|
+
const next = applySetArtboardKind(canvasAbsPath, source, artboardId, kind);
|
|
3768
|
+
if (next.source === source) return next;
|
|
3769
|
+
const tmp = `${canvasAbsPath}.tmp.${Math.random().toString(36).slice(2, 10)}`;
|
|
3770
|
+
await Bun.write(tmp, next.source);
|
|
3771
|
+
const { rename } = await import('node:fs/promises');
|
|
3772
|
+
await rename(tmp, canvasAbsPath);
|
|
3773
|
+
return next;
|
|
3774
|
+
});
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
/**
|
|
3778
|
+
* Write/clear an object-VALUED DCArtboard prop (`guides={{ columns: {...} }}`,
|
|
3779
|
+
* `print={{ paper: 'a4' }}`), unlike every other artboard writer above.
|
|
3780
|
+
* Per the plan's own gotcha, object-prop editing via AST is the hard part;
|
|
3781
|
+
* this scopes to REPLACE-WHOLE-PROP (stringify the given object and overwrite
|
|
3782
|
+
* the entire `{{...}}` span), not a deep merge — a caller that wants to keep
|
|
3783
|
+
* one key while adding another must read the current value back (Inspector
|
|
3784
|
+
* pre-fill reads `data-dc-*`-style attrs same as every other artboard knob)
|
|
3785
|
+
* and send the full merged object. `value: null` removes the prop entirely.
|
|
3786
|
+
* `JSON.stringify` output is valid as a JS object-literal expression (quoted
|
|
3787
|
+
* keys are legal JS, not just JSON), so no separate JS-literal serializer is
|
|
3788
|
+
* needed. Shape validation lives at the api.ts layer; this is pure AST
|
|
3789
|
+
* surgery. Pure; reparse-gated. Shared by `applySetArtboardGuides` and
|
|
3790
|
+
* `applySetArtboardPrint` (feature-2-print-artboards T2) below.
|
|
3791
|
+
*/
|
|
3792
|
+
function applySetArtboardObjectProp(
|
|
3793
|
+
canvasAbsPath: string,
|
|
3794
|
+
source: string,
|
|
3795
|
+
artboardId: string,
|
|
3796
|
+
propName: string,
|
|
3797
|
+
value: Record<string, unknown> | null
|
|
3798
|
+
): { source: string } {
|
|
3799
|
+
const parsed = parseSync(canvasAbsPath, source, { sourceType: 'module' });
|
|
3800
|
+
if (parsed.errors && parsed.errors.length > 0) {
|
|
3801
|
+
throw new CanvasEditError(
|
|
3802
|
+
`oxc-parser failed on ${canvasAbsPath}: ${parsed.errors[0]?.message ?? 'unknown'}`,
|
|
3803
|
+
{ canvas: canvasAbsPath, id: artboardId }
|
|
3804
|
+
);
|
|
3805
|
+
}
|
|
3806
|
+
const artboards = collectJsxByTag(parsed.program, 'DCArtboard');
|
|
3807
|
+
const target = artboards.find((a) => getStringAttr(a.openingElement, 'id') === artboardId);
|
|
3808
|
+
if (!target) {
|
|
3809
|
+
throw new CanvasEditError(`<DCArtboard id="${artboardId}"> not found in ${canvasAbsPath}`, {
|
|
3810
|
+
canvas: canvasAbsPath,
|
|
3811
|
+
id: artboardId,
|
|
3812
|
+
});
|
|
3813
|
+
}
|
|
3814
|
+
const s = new MagicString(source);
|
|
3815
|
+
const opening = target.openingElement;
|
|
3816
|
+
if (value === null) {
|
|
3817
|
+
removeStringAttr(s, opening, propName, source);
|
|
3818
|
+
} else {
|
|
3819
|
+
const literal = JSON.stringify(value);
|
|
3820
|
+
const attr = findAttribute(opening, propName);
|
|
3821
|
+
if (!attr) {
|
|
3822
|
+
const insertAt: number | undefined = opening?.name?.end;
|
|
3823
|
+
if (typeof insertAt !== 'number') {
|
|
3824
|
+
throw new Error('Opening element has no resolvable name range');
|
|
3825
|
+
}
|
|
3826
|
+
s.appendLeft(insertAt, ` ${propName}={${literal}}`);
|
|
3827
|
+
} else if (attr.value?.type === 'JSXExpressionContainer') {
|
|
3828
|
+
s.overwrite(attr.value.start, attr.value.end, `{${literal}}`);
|
|
3829
|
+
} else {
|
|
3830
|
+
throw new CanvasEditError(
|
|
3831
|
+
`${propName} attribute is not a {{...}} expression — refusing to edit`,
|
|
3832
|
+
{
|
|
3833
|
+
canvas: canvasAbsPath,
|
|
3834
|
+
id: artboardId,
|
|
3835
|
+
}
|
|
3836
|
+
);
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
const out = s.toString();
|
|
3840
|
+
const check = parseSync(canvasAbsPath, out, { sourceType: 'module' });
|
|
3841
|
+
if (check.errors && check.errors.length > 0) {
|
|
3842
|
+
throw new CanvasEditError(
|
|
3843
|
+
`artboard ${propName} edit produced invalid source (${check.errors[0]?.message ?? 'parse error'})`,
|
|
3844
|
+
{ canvas: canvasAbsPath, id: artboardId }
|
|
3845
|
+
);
|
|
3846
|
+
}
|
|
3847
|
+
return { source: out };
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
/** Set an artboard's object-valued prop on disk (atomic write + cross-process lock). */
|
|
3851
|
+
async function setArtboardObjectProp(
|
|
3852
|
+
canvasAbsPath: string,
|
|
3853
|
+
artboardId: string,
|
|
3854
|
+
propName: string,
|
|
3855
|
+
value: Record<string, unknown> | null
|
|
3856
|
+
): Promise<{ source: string }> {
|
|
3857
|
+
return withLock(canvasAbsPath, async () => {
|
|
3858
|
+
const file = Bun.file(canvasAbsPath);
|
|
3859
|
+
if (!(await file.exists())) {
|
|
3860
|
+
throw new CanvasEditError(`Canvas not found: ${canvasAbsPath}`, {
|
|
3861
|
+
canvas: canvasAbsPath,
|
|
3862
|
+
id: artboardId,
|
|
3863
|
+
});
|
|
3864
|
+
}
|
|
3865
|
+
const source = await file.text();
|
|
3866
|
+
const next = applySetArtboardObjectProp(canvasAbsPath, source, artboardId, propName, value);
|
|
3867
|
+
if (next.source === source) return next;
|
|
3868
|
+
const tmp = `${canvasAbsPath}.tmp.${Math.random().toString(36).slice(2, 10)}`;
|
|
3869
|
+
await Bun.write(tmp, next.source);
|
|
3870
|
+
const { rename } = await import('node:fs/promises');
|
|
3871
|
+
await rename(tmp, canvasAbsPath);
|
|
3872
|
+
return next;
|
|
3873
|
+
});
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
export function applySetArtboardGuides(
|
|
3877
|
+
canvasAbsPath: string,
|
|
3878
|
+
source: string,
|
|
3879
|
+
artboardId: string,
|
|
3880
|
+
guides: Record<string, unknown> | null
|
|
3881
|
+
): { source: string } {
|
|
3882
|
+
return applySetArtboardObjectProp(canvasAbsPath, source, artboardId, 'guides', guides);
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
/** Set an artboard's `guides` on disk (atomic write + cross-process lock). */
|
|
3886
|
+
export function setArtboardGuides(
|
|
3887
|
+
canvasAbsPath: string,
|
|
3888
|
+
artboardId: string,
|
|
3889
|
+
guides: Record<string, unknown> | null
|
|
3890
|
+
): Promise<{ source: string }> {
|
|
3891
|
+
return setArtboardObjectProp(canvasAbsPath, artboardId, 'guides', guides);
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
/**
|
|
3895
|
+
* Write/clear the DCArtboard `print` prop (feature-2-print-artboards T2) —
|
|
3896
|
+
* see `applySetArtboardObjectProp`'s doc comment for the shared shape.
|
|
3897
|
+
* `print: null` removes the prop entirely (e.g. reverting an artboard to a
|
|
3898
|
+
* non-print kind).
|
|
3899
|
+
*/
|
|
3900
|
+
export function applySetArtboardPrint(
|
|
3901
|
+
canvasAbsPath: string,
|
|
3902
|
+
source: string,
|
|
3903
|
+
artboardId: string,
|
|
3904
|
+
print: Record<string, unknown> | null
|
|
3905
|
+
): { source: string } {
|
|
3906
|
+
return applySetArtboardObjectProp(canvasAbsPath, source, artboardId, 'print', print);
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
/** Set an artboard's `print` on disk (atomic write + cross-process lock). */
|
|
3910
|
+
export function setArtboardPrint(
|
|
3911
|
+
canvasAbsPath: string,
|
|
3912
|
+
artboardId: string,
|
|
3913
|
+
print: Record<string, unknown> | null
|
|
3914
|
+
): Promise<{ source: string }> {
|
|
3915
|
+
return setArtboardObjectProp(canvasAbsPath, artboardId, 'print', print);
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
/**
|
|
3919
|
+
* Read a JS object-literal AST node into a plain object WITHOUT eval —
|
|
3920
|
+
* `print` (like every other canvas-authored JSX prop) is part of the
|
|
3921
|
+
* DDR-054 untrusted-canvas surface, so evaluating its source text
|
|
3922
|
+
* (`Function('return ' + src)()`) would be a code-injection hole. Handles
|
|
3923
|
+
* string/numeric/boolean/null literals and nested object literals; any other
|
|
3924
|
+
* value shape (identifiers, calls, template strings, arrays) is silently
|
|
3925
|
+
* skipped — conservative by design, since a print-geometry reader should
|
|
3926
|
+
* degrade to "field absent" rather than guess. Depth-capped defensively,
|
|
3927
|
+
* though `print`'s own shape never nests past marginsMm (depth 2).
|
|
3928
|
+
*/
|
|
3929
|
+
function objectExpressionToPlain(node: AnyNode, depth = 0): Record<string, unknown> {
|
|
3930
|
+
const out: Record<string, unknown> = {};
|
|
3931
|
+
if (depth > 6 || node?.type !== 'ObjectExpression') return out;
|
|
3932
|
+
for (const p of node.properties ?? []) {
|
|
3933
|
+
if ((p?.type !== 'Property' && p?.type !== 'ObjectProperty') || p.computed) continue;
|
|
3934
|
+
const key =
|
|
3935
|
+
p.key?.type === 'Identifier'
|
|
3936
|
+
? (p.key.name as string)
|
|
3937
|
+
: isStringLit(p.key)
|
|
3938
|
+
? String(p.key.value)
|
|
3939
|
+
: null;
|
|
3940
|
+
if (key === null) continue;
|
|
3941
|
+
const v = p.value;
|
|
3942
|
+
if (!v) continue;
|
|
3943
|
+
if (v.type === 'ObjectExpression') {
|
|
3944
|
+
out[key] = objectExpressionToPlain(v, depth + 1);
|
|
3945
|
+
} else if (
|
|
3946
|
+
(v.type === 'Literal' ||
|
|
3947
|
+
v.type === 'StringLiteral' ||
|
|
3948
|
+
v.type === 'NumericLiteral' ||
|
|
3949
|
+
v.type === 'BooleanLiteral') &&
|
|
3950
|
+
(typeof v.value === 'string' ||
|
|
3951
|
+
typeof v.value === 'number' ||
|
|
3952
|
+
typeof v.value === 'boolean' ||
|
|
3953
|
+
v.value === null)
|
|
3954
|
+
) {
|
|
3955
|
+
out[key] = v.value;
|
|
3956
|
+
}
|
|
3957
|
+
// else: unsupported value shape — key silently omitted.
|
|
3958
|
+
}
|
|
3959
|
+
return out;
|
|
3960
|
+
}
|
|
3961
|
+
|
|
3962
|
+
/**
|
|
3963
|
+
* Read an artboard's `print` JSX prop as a plain object — used by the PDF
|
|
3964
|
+
* exporter (T5) to resolve bleed/paper geometry for the artboard being
|
|
3965
|
+
* exported. Read-only (no lock, no write). Returns null when the canvas/
|
|
3966
|
+
* artboard/prop doesn't exist, `print` isn't a `{{...}}` object-expression,
|
|
3967
|
+
* or `kind` isn't the literal string `"print"` — a `print` prop left over
|
|
3968
|
+
* from a prior kind switch (or hand-authored on a non-print artboard) must
|
|
3969
|
+
* NOT leak bleed/marks into an export; `kind="print"` is the sole gate
|
|
3970
|
+
* (unlike `video`, print has no implicit structural-fallback resolution, so
|
|
3971
|
+
* checking the explicit attr is sufficient — see DDR-181/canvas-lib.tsx).
|
|
3972
|
+
*/
|
|
3973
|
+
export function readArtboardPrintProp(
|
|
3974
|
+
canvasAbsPath: string,
|
|
3975
|
+
source: string,
|
|
3976
|
+
artboardId: string
|
|
3977
|
+
): Record<string, unknown> | null {
|
|
3978
|
+
const parsed = parseSync(canvasAbsPath, source, { sourceType: 'module' });
|
|
3979
|
+
if (parsed.errors && parsed.errors.length > 0) return null;
|
|
3980
|
+
const artboards = collectJsxByTag(parsed.program, 'DCArtboard');
|
|
3981
|
+
const target = artboards.find((a) => getStringAttr(a.openingElement, 'id') === artboardId);
|
|
3982
|
+
if (!target) return null;
|
|
3983
|
+
if (getStringAttr(target.openingElement, 'kind') !== 'print') return null;
|
|
3984
|
+
const attr = findAttribute(target.openingElement, 'print');
|
|
3985
|
+
if (attr?.value?.type !== 'JSXExpressionContainer') return null;
|
|
3986
|
+
const expr = attr.value.expression as AnyNode | undefined;
|
|
3987
|
+
if (expr?.type !== 'ObjectExpression') return null;
|
|
3988
|
+
return objectExpressionToPlain(expr);
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3692
3991
|
/**
|
|
3693
3992
|
* Delete the `<DCArtboard id="…">` whose `id` prop equals `artboardId` — the
|
|
3694
3993
|
* artboard counterpart of applyDeleteElement (an artboard is addressed by its id
|