@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
|
@@ -12,6 +12,10 @@ import { createRoot } from 'react-dom/client';
|
|
|
12
12
|
// import that Bun erases), so this pulls only string constants into the client
|
|
13
13
|
// bundle — no React, no input-router. See the tool-cursor handler below.
|
|
14
14
|
import { resolveToolCursor } from '../canvas-cursors.ts';
|
|
15
|
+
// feature-2-print-artboards T2 — the single source (print/units.ts) for the
|
|
16
|
+
// Inspector's paper-preset picker, same "pull only pure math/data" shape as
|
|
17
|
+
// canvas-cursors.ts above.
|
|
18
|
+
import { PAPER_PRESETS, resolvePrintArtboard } from '../print/units.ts';
|
|
15
19
|
import { sizingModeOf, sizingModePatch } from '../sizing-mode.ts';
|
|
16
20
|
import { canvasUrl } from './canvas-url.js';
|
|
17
21
|
import ChatPanel from './panels/ChatPanel.jsx';
|
|
@@ -29,7 +33,7 @@ import GenerateDialog from './generate-dialog.jsx';
|
|
|
29
33
|
import RepoBranchSwitcher from './panels/RepoBranchSwitcher.jsx';
|
|
30
34
|
import SettingsPanel from './panels/SettingsPanel.jsx';
|
|
31
35
|
import StickerPicker from './panels/StickerPicker.jsx';
|
|
32
|
-
import { AlignPad, AngleDial, ColorField, IconButtonGroup, IconToggleGroup, makeScrubHandler, NumberField, RadiusControl, Segmented, SliderField, Toggle, UnitSelect, ValueTokenField } from './inspector-controls.jsx';
|
|
36
|
+
import { AlignPad, AngleDial, ColorField, IconButtonGroup, IconToggleGroup, makeScrubHandler, NumberField, RadiusControl, Segmented, Select, SliderField, Toggle, UnitSelect, ValueTokenField } from './inspector-controls.jsx';
|
|
33
37
|
import {
|
|
34
38
|
ALargeSmall as LuALargeSmall,
|
|
35
39
|
AlignCenter as LuAlignCenter,
|
|
@@ -960,6 +964,37 @@ const PNG_SCALES = [
|
|
|
960
964
|
{ value: 3, label: '3× (max)' },
|
|
961
965
|
];
|
|
962
966
|
|
|
967
|
+
// feature-2-print-artboards T6 — the PNG card's resolution picker folds the
|
|
968
|
+
// legacy 1×/2×/3× multiplier and the new physical-DPI ladder (T4) into ONE
|
|
969
|
+
// select: `kind:'scale'` entries send `options.scale`, `kind:'dpi'` entries
|
|
970
|
+
// send `options.dpi` (exporters/png.ts resolveDeviceScale — dpi wins over
|
|
971
|
+
// scale when both could apply). Default stays 2× (unchanged UX for anyone
|
|
972
|
+
// not exporting for print).
|
|
973
|
+
const PNG_RESOLUTIONS = [
|
|
974
|
+
{ id: '1x', label: '1× (native)', kind: 'scale', value: 1 },
|
|
975
|
+
{ id: '2x', label: '2× (retina)', kind: 'scale', value: 2 },
|
|
976
|
+
{ id: '3x', label: '3× (max)', kind: 'scale', value: 3 },
|
|
977
|
+
{ id: 'dpi150', label: '150 dpi', kind: 'dpi', value: 150 },
|
|
978
|
+
{ id: 'dpi300', label: '300 dpi (print)', kind: 'dpi', value: 300 },
|
|
979
|
+
{ id: 'dpi600', label: '600 dpi (high-res print)', kind: 'dpi', value: 600 },
|
|
980
|
+
];
|
|
981
|
+
const PNG_RESOLUTION_DEFAULT = '2x';
|
|
982
|
+
|
|
983
|
+
// feature-2-print-artboards T5/T6 (dogfood follow-up) — the PDF page itself
|
|
984
|
+
// is always vector (text/shapes never rasterize), but any RASTER content ON
|
|
985
|
+
// the artboard — a dropped photo, a large-format piece authored at a
|
|
986
|
+
// fraction of its real physical size (e.g. a billboard at 1:10 scale) —
|
|
987
|
+
// still embeds as a bitmap whose density this controls. Default "Auto (1×)"
|
|
988
|
+
// is today's unchanged behavior; PDF has no legacy scale concept, so unlike
|
|
989
|
+
// PNG_RESOLUTIONS this is DPI-only.
|
|
990
|
+
const PDF_DPI_OPTIONS = [
|
|
991
|
+
{ id: 'auto', label: 'Auto (1×)', value: undefined },
|
|
992
|
+
{ id: 'dpi150', label: '150 dpi', value: 150 },
|
|
993
|
+
{ id: 'dpi300', label: '300 dpi (print)', value: 300 },
|
|
994
|
+
{ id: 'dpi600', label: '600 dpi (high-res print)', value: 600 },
|
|
995
|
+
];
|
|
996
|
+
const PDF_DPI_DEFAULT = 'auto';
|
|
997
|
+
|
|
963
998
|
// feature-bulk-media-insert — best-effort MIME guess from a filename extension.
|
|
964
999
|
// Only used to classify a natively-picked file (Rust returns `{name, bytes}`,
|
|
965
1000
|
// no type) for the destination-toggle's image/video/audio split; the actual
|
|
@@ -1288,6 +1323,18 @@ function ExportDialog({
|
|
|
1288
1323
|
initialScope && EXPORT_SCOPE_LABELS[initialScope] ? initialScope : 'artboard'
|
|
1289
1324
|
);
|
|
1290
1325
|
const [scale, setScale] = useState(2);
|
|
1326
|
+
// feature-2-print-artboards T4/T6 — PNG resolution (folds scale + dpi, see
|
|
1327
|
+
// PNG_RESOLUTIONS above).
|
|
1328
|
+
const [pngResId, setPngResId] = useState(PNG_RESOLUTION_DEFAULT);
|
|
1329
|
+
// feature-2-print-artboards T5/T6 — PDF print options. Sent unconditionally
|
|
1330
|
+
// on every PDF export; the server no-ops them for a non-print artboard (no
|
|
1331
|
+
// `print` JSX prop → applyPrintBoxesAndMarks never runs), so there's no
|
|
1332
|
+
// need to detect the artboard's kind client-side before showing these.
|
|
1333
|
+
const [pdfIncludeBleed, setPdfIncludeBleed] = useState(true);
|
|
1334
|
+
const [pdfMarksOpen, setPdfMarksOpen] = useState(false);
|
|
1335
|
+
const [pdfMarksCrop, setPdfMarksCrop] = useState(false);
|
|
1336
|
+
const [pdfMarksRegistration, setPdfMarksRegistration] = useState(false);
|
|
1337
|
+
const [pdfDpiId, setPdfDpiId] = useState(PDF_DPI_DEFAULT);
|
|
1291
1338
|
// DDR-148 addendum — mp4/webm of a registered video-comp render through
|
|
1292
1339
|
// renderMediaOnWeb, which produces real audio (Remotion owns the
|
|
1293
1340
|
// TransitionSeries/volume-closure timeline). gif has no audio track at all
|
|
@@ -1338,12 +1385,31 @@ function ExportDialog({
|
|
|
1338
1385
|
}
|
|
1339
1386
|
setBusy(true);
|
|
1340
1387
|
setStatus(null);
|
|
1341
|
-
// `scale` drives
|
|
1342
|
-
//
|
|
1343
|
-
|
|
1388
|
+
// `scale` drives video resolution (deviceScaleFactor → encoder dims);
|
|
1389
|
+
// temporal formats were previously fixed at the tiny native size. PNG's
|
|
1390
|
+
// resolution now comes from pngResId (T4/T6 — scale OR dpi).
|
|
1391
|
+
const options = card.temporal ? { scale } : {};
|
|
1392
|
+
if (card.format === 'png') {
|
|
1393
|
+
const res = PNG_RESOLUTIONS.find((r) => r.id === pngResId) || PNG_RESOLUTIONS[1];
|
|
1394
|
+
if (res.kind === 'dpi') options.dpi = res.value;
|
|
1395
|
+
else options.scale = res.value;
|
|
1396
|
+
}
|
|
1344
1397
|
// Export-with-audio (DDR-148 addendum) — mp4/webm only; gif is silent by
|
|
1345
1398
|
// format, so the checkbox never renders for it (see hasAudioToggle below).
|
|
1346
1399
|
if (card.format === 'mp4' || card.format === 'webm') options.audio = audio;
|
|
1400
|
+
// feature-2-print-artboards T5/T6 — always sent on a PDF export; the
|
|
1401
|
+
// adapter no-ops includeBleed/marks for a non-print artboard (T5).
|
|
1402
|
+
if (card.format === 'pdf') {
|
|
1403
|
+
options.pdfPrint = {
|
|
1404
|
+
includeBleed: pdfIncludeBleed,
|
|
1405
|
+
marks: { crop: pdfMarksCrop, registration: pdfMarksRegistration },
|
|
1406
|
+
};
|
|
1407
|
+
// Dogfood follow-up — raster CONTENT on the artboard (a dropped photo,
|
|
1408
|
+
// large-format art) still needs a real capture density; the page
|
|
1409
|
+
// itself stays vector regardless of this.
|
|
1410
|
+
const pdfDpi = PDF_DPI_OPTIONS.find((d) => d.id === pdfDpiId)?.value;
|
|
1411
|
+
if (pdfDpi !== undefined) options.dpi = pdfDpi;
|
|
1412
|
+
}
|
|
1347
1413
|
// Scope targeting hints (resolveScope reads these): `artboardId` makes
|
|
1348
1414
|
// "Active artboard" export the right screen instead of `:first-of-type`;
|
|
1349
1415
|
// `selection` makes "Current selection" export the selected element. Mirrors
|
|
@@ -1428,14 +1494,42 @@ function ExportDialog({
|
|
|
1428
1494
|
</select>
|
|
1429
1495
|
</div>
|
|
1430
1496
|
)}
|
|
1431
|
-
{!card.handoff &&
|
|
1497
|
+
{!card.handoff && card.format === 'png' && (
|
|
1432
1498
|
<div className="st-dialog-row">
|
|
1433
1499
|
<label className="st-dialog-lbl" htmlFor="st-export-size">
|
|
1434
|
-
|
|
1500
|
+
Resolution
|
|
1435
1501
|
</label>
|
|
1436
1502
|
<select
|
|
1437
1503
|
id="st-export-size"
|
|
1438
1504
|
className="st-select"
|
|
1505
|
+
value={pngResId}
|
|
1506
|
+
onChange={(e) => setPngResId(e.target.value)}
|
|
1507
|
+
>
|
|
1508
|
+
{PNG_RESOLUTIONS.map((r) => (
|
|
1509
|
+
<option key={r.id} value={r.id}>
|
|
1510
|
+
{r.label}
|
|
1511
|
+
</option>
|
|
1512
|
+
))}
|
|
1513
|
+
</select>
|
|
1514
|
+
</div>
|
|
1515
|
+
)}
|
|
1516
|
+
{!card.handoff && card.format === 'png' && (
|
|
1517
|
+
<div className="st-mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>
|
|
1518
|
+
{(() => {
|
|
1519
|
+
const res = PNG_RESOLUTIONS.find((r) => r.id === pngResId) || PNG_RESOLUTIONS[1];
|
|
1520
|
+
const factor = res.kind === 'dpi' ? res.value / 96 : res.value;
|
|
1521
|
+
return `${res.label} ≈ ${Math.round(1440 * factor)}×${Math.round(900 * factor)} for a 1440×900 artboard.`;
|
|
1522
|
+
})()}
|
|
1523
|
+
</div>
|
|
1524
|
+
)}
|
|
1525
|
+
{!card.handoff && card.temporal && (
|
|
1526
|
+
<div className="st-dialog-row">
|
|
1527
|
+
<label className="st-dialog-lbl" htmlFor="st-export-temporal-size">
|
|
1528
|
+
Resolution
|
|
1529
|
+
</label>
|
|
1530
|
+
<select
|
|
1531
|
+
id="st-export-temporal-size"
|
|
1532
|
+
className="st-select"
|
|
1439
1533
|
value={scale}
|
|
1440
1534
|
onChange={(e) => setScale(Number(e.target.value))}
|
|
1441
1535
|
>
|
|
@@ -1447,16 +1541,86 @@ function ExportDialog({
|
|
|
1447
1541
|
</select>
|
|
1448
1542
|
</div>
|
|
1449
1543
|
)}
|
|
1450
|
-
{!card.handoff && card.format === 'png' && (
|
|
1451
|
-
<div className="st-mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>
|
|
1452
|
-
Resolution multiplier — {scale}× ≈ {1440 * scale}×{900 * scale} for a 1440×900 artboard.
|
|
1453
|
-
</div>
|
|
1454
|
-
)}
|
|
1455
1544
|
{!card.handoff && card.temporal && (
|
|
1456
1545
|
<div className="st-mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>
|
|
1457
1546
|
{scale}× the artboard's native resolution (e.g. 960×540 → {960 * scale}×{540 * scale}).
|
|
1458
1547
|
</div>
|
|
1459
1548
|
)}
|
|
1549
|
+
{!card.handoff && card.format === 'pdf' && (
|
|
1550
|
+
<>
|
|
1551
|
+
<div className="st-dialog-row">
|
|
1552
|
+
<label className="st-dialog-lbl" htmlFor="st-export-pdf-dpi">
|
|
1553
|
+
Image quality
|
|
1554
|
+
</label>
|
|
1555
|
+
<select
|
|
1556
|
+
id="st-export-pdf-dpi"
|
|
1557
|
+
className="st-select"
|
|
1558
|
+
value={pdfDpiId}
|
|
1559
|
+
onChange={(e) => setPdfDpiId(e.target.value)}
|
|
1560
|
+
>
|
|
1561
|
+
{PDF_DPI_OPTIONS.map((d) => (
|
|
1562
|
+
<option key={d.id} value={d.id}>
|
|
1563
|
+
{d.label}
|
|
1564
|
+
</option>
|
|
1565
|
+
))}
|
|
1566
|
+
</select>
|
|
1567
|
+
</div>
|
|
1568
|
+
<div className="st-mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>
|
|
1569
|
+
The page stays vector; this only sets the capture density for raster content on it
|
|
1570
|
+
(dropped photos, large-format art).
|
|
1571
|
+
</div>
|
|
1572
|
+
<div className="st-dialog-row">
|
|
1573
|
+
<label
|
|
1574
|
+
className="st-dialog-lbl"
|
|
1575
|
+
htmlFor="st-export-pdf-bleed"
|
|
1576
|
+
style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer' }}
|
|
1577
|
+
>
|
|
1578
|
+
<input
|
|
1579
|
+
id="st-export-pdf-bleed"
|
|
1580
|
+
type="checkbox"
|
|
1581
|
+
checked={pdfIncludeBleed}
|
|
1582
|
+
onChange={(e) => setPdfIncludeBleed(e.target.checked)}
|
|
1583
|
+
/>
|
|
1584
|
+
Include bleed
|
|
1585
|
+
</label>
|
|
1586
|
+
</div>
|
|
1587
|
+
<div className="st-dialog-row">
|
|
1588
|
+
<button
|
|
1589
|
+
type="button"
|
|
1590
|
+
className="st-dialog-lbl"
|
|
1591
|
+
style={{ background: 'none', border: 0, padding: 0, cursor: 'pointer' }}
|
|
1592
|
+
onClick={() => setPdfMarksOpen((v) => !v)}
|
|
1593
|
+
aria-expanded={pdfMarksOpen}
|
|
1594
|
+
>
|
|
1595
|
+
{pdfMarksOpen ? '▾' : '▸'} Marks
|
|
1596
|
+
</button>
|
|
1597
|
+
</div>
|
|
1598
|
+
{pdfMarksOpen && (
|
|
1599
|
+
<div style={{ padding: '0 12px 4px', display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
1600
|
+
<label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 12 }}>
|
|
1601
|
+
<input
|
|
1602
|
+
type="checkbox"
|
|
1603
|
+
checked={pdfMarksCrop}
|
|
1604
|
+
onChange={(e) => setPdfMarksCrop(e.target.checked)}
|
|
1605
|
+
/>
|
|
1606
|
+
Crop marks
|
|
1607
|
+
</label>
|
|
1608
|
+
<label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 12 }}>
|
|
1609
|
+
<input
|
|
1610
|
+
type="checkbox"
|
|
1611
|
+
checked={pdfMarksRegistration}
|
|
1612
|
+
onChange={(e) => setPdfMarksRegistration(e.target.checked)}
|
|
1613
|
+
/>
|
|
1614
|
+
Registration marks
|
|
1615
|
+
</label>
|
|
1616
|
+
</div>
|
|
1617
|
+
)}
|
|
1618
|
+
<div className="st-mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>
|
|
1619
|
+
Bleed and marks apply to print (<code>kind="print"</code>) artboards only.
|
|
1620
|
+
{scope === 'canvas-as-separate' ? ' One PDF page per artboard.' : ''}
|
|
1621
|
+
</div>
|
|
1622
|
+
</>
|
|
1623
|
+
)}
|
|
1460
1624
|
{!card.handoff && (card.format === 'mp4' || card.format === 'webm') && (
|
|
1461
1625
|
<div className="st-dialog-row">
|
|
1462
1626
|
<label
|
|
@@ -2933,6 +3097,11 @@ function EditDropdown({ onAction, onClose, hasCanvas }) {
|
|
|
2933
3097
|
{ id: 'new-artboard:laptop', label: 'New artboard: Laptop' },
|
|
2934
3098
|
{ id: 'new-artboard:tablet', label: 'New artboard: Tablet' },
|
|
2935
3099
|
{ id: 'new-artboard:mobile', label: 'New artboard: Mobile' },
|
|
3100
|
+
// feature-2-print-artboards T2 — "+ Artboard" quick-insert must
|
|
3101
|
+
// set kind="print" + the print prop together (the plan's own
|
|
3102
|
+
// gotcha for this task), not just a plain digital-sized board.
|
|
3103
|
+
{ id: 'new-artboard:print-a4', label: 'New artboard: A4 (print)' },
|
|
3104
|
+
{ id: 'new-artboard:print-letter', label: 'New artboard: Letter (print)' },
|
|
2936
3105
|
]
|
|
2937
3106
|
: []),
|
|
2938
3107
|
]}
|
|
@@ -2968,6 +3137,8 @@ function Menubar({
|
|
|
2968
3137
|
onToggleZoomCtl,
|
|
2969
3138
|
presentMode,
|
|
2970
3139
|
onTogglePresent,
|
|
3140
|
+
printGuidesVisible,
|
|
3141
|
+
onTogglePrintGuides,
|
|
2971
3142
|
postToActiveCanvas,
|
|
2972
3143
|
onOpenWhatsNew,
|
|
2973
3144
|
onOpenReadiness,
|
|
@@ -3105,6 +3276,18 @@ function Menubar({
|
|
|
3105
3276
|
checked: presentMode,
|
|
3106
3277
|
disabled: !activePath || isSystem,
|
|
3107
3278
|
},
|
|
3279
|
+
// feature-2-print-artboards T3 — per-canvas persisted (overlays.print in
|
|
3280
|
+
// view.json), same lane as the foundation's `guides` key; NOT gated on the
|
|
3281
|
+
// active artboard actually being kind="print" (mirrors minimap/zoomctl,
|
|
3282
|
+
// which aren't content-gated either — the overlay itself renders nothing
|
|
3283
|
+
// for a non-print artboard regardless of this flag).
|
|
3284
|
+
{
|
|
3285
|
+
id: 'print-guides',
|
|
3286
|
+
label: 'Show print guides',
|
|
3287
|
+
shortcut: '',
|
|
3288
|
+
checked: printGuidesVisible,
|
|
3289
|
+
disabled: !activePath || isSystem,
|
|
3290
|
+
},
|
|
3108
3291
|
];
|
|
3109
3292
|
|
|
3110
3293
|
const DROPDOWN_MENUS = ['file', 'edit', 'view', 'selection', 'tools', 'help'];
|
|
@@ -3242,6 +3425,7 @@ function Menubar({
|
|
|
3242
3425
|
else if (id === 'minimap') onToggleMinimap?.();
|
|
3243
3426
|
else if (id === 'zoomctl') onToggleZoomCtl?.();
|
|
3244
3427
|
else if (id === 'present') onTogglePresent?.();
|
|
3428
|
+
else if (id === 'print-guides') onTogglePrintGuides?.();
|
|
3245
3429
|
}}
|
|
3246
3430
|
onZoom={(op) => postToActiveCanvas({ dgn: 'zoom', op })}
|
|
3247
3431
|
hasCanvas={!!activePath && !isSystem}
|
|
@@ -4324,6 +4508,43 @@ function cssSplitUnit(v) {
|
|
|
4324
4508
|
// click re-derives it live — the "tab is there, then it's gone" flicker.
|
|
4325
4509
|
function mergeSelClientFields(incoming, prev) {
|
|
4326
4510
|
if (!incoming || Array.isArray(incoming) || Array.isArray(prev) || !prev) return incoming;
|
|
4511
|
+
// Dogfood follow-up (print artboards) — a whole-ARTBOARD selection has NO
|
|
4512
|
+
// data-cd-id (the frame chrome carries only data-dc-screen), so the id
|
|
4513
|
+
// identity check below never matched it and every server echo returned
|
|
4514
|
+
// `incoming` bare — wiping `attrs` (which carries data-dc-kind/-print for
|
|
4515
|
+
// the ArtboardKnobs panel) seconds after each fresh click. The panel then
|
|
4516
|
+
// silently fell back to kind='digital' + screen presets even though the
|
|
4517
|
+
// artboard was print. Match an id-less pair by the artboard-shaped
|
|
4518
|
+
// SELECTOR + file — NOT by artboardId: the server projection
|
|
4519
|
+
// (inspect.ts `enrich()`) doesn't round-trip `artboardId` at all, so an
|
|
4520
|
+
// artboardId comparison always sees undefined on the echo side and never
|
|
4521
|
+
// matches (the second-round bug on this exact spot). The selector IS the
|
|
4522
|
+
// identity here — `[data-dc-screen="<id>"]` — and enrich() preserves it.
|
|
4523
|
+
if (!incoming.id && !prev.id) {
|
|
4524
|
+
const isArtboardSel = (s) =>
|
|
4525
|
+
typeof s.selector === 'string' && s.selector.startsWith('[data-dc-screen=');
|
|
4526
|
+
if (
|
|
4527
|
+
isArtboardSel(incoming) &&
|
|
4528
|
+
isArtboardSel(prev) &&
|
|
4529
|
+
incoming.selector === prev.selector &&
|
|
4530
|
+
incoming.file === prev.file
|
|
4531
|
+
) {
|
|
4532
|
+
return {
|
|
4533
|
+
...incoming,
|
|
4534
|
+
authored: incoming.authored ?? prev.authored,
|
|
4535
|
+
computed: incoming.computed ?? prev.computed,
|
|
4536
|
+
customStyles: incoming.customStyles ?? prev.customStyles,
|
|
4537
|
+
attrs: incoming.attrs ?? prev.attrs,
|
|
4538
|
+
// enrich() drops artboardId/worldW/worldH from the echo entirely —
|
|
4539
|
+
// restore them from the local snapshot so ArtboardKnobs keeps its
|
|
4540
|
+
// exact W/H and the resize/kind writers keep their target id.
|
|
4541
|
+
artboardId: incoming.artboardId ?? prev.artboardId,
|
|
4542
|
+
worldW: incoming.worldW ?? prev.worldW,
|
|
4543
|
+
worldH: incoming.worldH ?? prev.worldH,
|
|
4544
|
+
};
|
|
4545
|
+
}
|
|
4546
|
+
return incoming;
|
|
4547
|
+
}
|
|
4327
4548
|
if (!incoming.id || incoming.id !== prev.id) return incoming;
|
|
4328
4549
|
return {
|
|
4329
4550
|
...incoming,
|
|
@@ -6760,8 +6981,52 @@ const ARTBOARD_LAYOUT_OPTIONS = [
|
|
|
6760
6981
|
['grid', 'Grid'],
|
|
6761
6982
|
];
|
|
6762
6983
|
|
|
6763
|
-
|
|
6984
|
+
// feature-1-artboard-kinds-foundation, T8 — kind picker options. Order
|
|
6985
|
+
// mirrors the DCArtboard `kind` union; 'digital' is the implicit default
|
|
6986
|
+
// (writes `kind: null`, clearing the explicit prop, per applySetArtboardKind).
|
|
6987
|
+
const ARTBOARD_KIND_OPTIONS = [
|
|
6988
|
+
['digital', 'Digital'],
|
|
6989
|
+
['print', 'Print'],
|
|
6990
|
+
['web', 'Web'],
|
|
6991
|
+
['video', 'Video'],
|
|
6992
|
+
];
|
|
6993
|
+
|
|
6994
|
+
function ArtboardKnobs({
|
|
6995
|
+
el,
|
|
6996
|
+
cfg,
|
|
6997
|
+
onResizeArtboard,
|
|
6998
|
+
onSetArtboardHug,
|
|
6999
|
+
onSetArtboardStyle,
|
|
7000
|
+
onSetArtboardKind,
|
|
7001
|
+
onSetArtboardPrint,
|
|
7002
|
+
}) {
|
|
6764
7003
|
const artboardId = resolveArtboardIdFromSelection(el);
|
|
7004
|
+
// Dogfood (artboard panel ↔ shared inspector controls) — the panel now uses
|
|
7005
|
+
// the SAME control library + design-token plumbing as CssKnobs (NumberField
|
|
7006
|
+
// / Segmented / Select / ColorField+TokenPopover / ValueTokenField), so Bg
|
|
7007
|
+
// binds color variables and Pad/Gap bind space variables exactly like any
|
|
7008
|
+
// CSS-panel row. Token resolution mirrors CssKnobs' own tokenGroups/
|
|
7009
|
+
// flatTokens closures over useAllDsTokens.
|
|
7010
|
+
const _designRel = (cfg?.designRel || cfg?.designRoot || '.design').replace(/^\/+|\/+$/g, '');
|
|
7011
|
+
const _activeDs = activeDsNameFor(el.file, cfg);
|
|
7012
|
+
const allDs = useAllDsTokens(cfg, _designRel, _activeDs);
|
|
7013
|
+
const tokenGroups = (familyKey) =>
|
|
7014
|
+
allDs
|
|
7015
|
+
.map((d) => ({ ds: d.name, names: d[familyKey] || [], vals: d.vals }))
|
|
7016
|
+
.filter((g) => g.names.length);
|
|
7017
|
+
const flatTokens = (familyKey) =>
|
|
7018
|
+
tokenGroups(familyKey).flatMap((g) => (g.names || []).map((n) => ({ name: n, value: g.vals?.[n] || '' })));
|
|
7019
|
+
// Resolve a `var(--x)` binding to its concrete value across every DS (for
|
|
7020
|
+
// the Bg swatch color); a raw value passes through.
|
|
7021
|
+
const resolveTokenValue = (v) => {
|
|
7022
|
+
const m = /^var\(\s*(--[\w-]+)\s*\)$/.exec(v || '');
|
|
7023
|
+
if (!m) return v || '';
|
|
7024
|
+
for (const d of allDs) {
|
|
7025
|
+
const val = d.vals?.[m[1]];
|
|
7026
|
+
if (val) return val;
|
|
7027
|
+
}
|
|
7028
|
+
return '';
|
|
7029
|
+
};
|
|
6765
7030
|
// Dogfood 2026-07-07 (round 2) — `worldW`/`worldH` (zoom-independent) are
|
|
6766
7031
|
// undefined for a selection that reached here via a code path predating
|
|
6767
7032
|
// that field (a canvas iframe that hasn't remounted since); fall back to
|
|
@@ -6798,6 +7063,63 @@ function ArtboardKnobs({ el, onResizeArtboard, onSetArtboardHug, onSetArtboardSt
|
|
|
6798
7063
|
const padding = el.attrs?.['data-dc-padding'] ?? '';
|
|
6799
7064
|
const layoutMode = el.attrs?.['data-dc-layout'] ?? '';
|
|
6800
7065
|
const gap = el.attrs?.['data-dc-gap'] ?? '';
|
|
7066
|
+
// T8 — `data-dc-kind` is DCArtboard's RESOLVED kind (readBackAttrs always
|
|
7067
|
+
// emits it, unlike the optional-override attrs above — see T1's comment on
|
|
7068
|
+
// why), so this reads 'digital' even for an implicit/unmigrated artboard.
|
|
7069
|
+
const kind = el.attrs?.['data-dc-kind'] || 'digital';
|
|
7070
|
+
// feature-2-print-artboards T2 — paper/orientation/bleed. Read-back mirrors
|
|
7071
|
+
// `data-dc-kind` above: a small JSON attr DCArtboard stamps for exactly this
|
|
7072
|
+
// panel (canvas-lib.tsx readBackAttrs), since `print` (unlike bg/padding/…)
|
|
7073
|
+
// is object-valued, not a scalar.
|
|
7074
|
+
let print = null;
|
|
7075
|
+
try {
|
|
7076
|
+
print = el.attrs?.['data-dc-print'] ? JSON.parse(el.attrs['data-dc-print']) : null;
|
|
7077
|
+
} catch {
|
|
7078
|
+
/* malformed attr — treat as absent */
|
|
7079
|
+
}
|
|
7080
|
+
const setKind = (nextKind) => {
|
|
7081
|
+
if (!artboardId) return;
|
|
7082
|
+
// Picking "Digital" clears the explicit prop back to the implicit
|
|
7083
|
+
// default (applySetArtboardKind's `kind: null` path) rather than writing
|
|
7084
|
+
// a redundant `kind="digital"`.
|
|
7085
|
+
onSetArtboardKind?.(artboardId, nextKind === 'digital' ? null : nextKind);
|
|
7086
|
+
// Dogfood fix — switching TO "print" with no print prop yet left the
|
|
7087
|
+
// artboard in a half-configured state: the guides overlay renders
|
|
7088
|
+
// nothing (print-overlay-content.tsx requires the prop) and the PDF
|
|
7089
|
+
// exporter has no bleed to read, even though the Kind chip already
|
|
7090
|
+
// shows "print". Seed a default A4 print prop + its resolved px size in
|
|
7091
|
+
// the SAME gesture, mirroring the "+ Artboard: print preset" flow —
|
|
7092
|
+
// switching a DIGITAL artboard to print should never require a second,
|
|
7093
|
+
// easy-to-miss step in the (still-collapsed) Print section below.
|
|
7094
|
+
if (nextKind === 'print' && !print) {
|
|
7095
|
+
const defaults = { paper: 'a4' };
|
|
7096
|
+
let resolved;
|
|
7097
|
+
try {
|
|
7098
|
+
resolved = resolvePrintArtboard(defaults);
|
|
7099
|
+
} catch {
|
|
7100
|
+
resolved = null;
|
|
7101
|
+
}
|
|
7102
|
+
if (resolved) {
|
|
7103
|
+
onResizeArtboard?.(artboardId, resolved.widthPx, resolved.heightPx);
|
|
7104
|
+
onSetArtboardPrint?.(artboardId, defaults);
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
7107
|
+
};
|
|
7108
|
+
const setPrint = (patch) => {
|
|
7109
|
+
if (!artboardId) return;
|
|
7110
|
+
const next = { paper: 'a4', ...print, ...patch };
|
|
7111
|
+
let resolved;
|
|
7112
|
+
try {
|
|
7113
|
+
resolved = resolvePrintArtboard(next);
|
|
7114
|
+
} catch {
|
|
7115
|
+
return; // unknown paper id — ignore rather than write garbage
|
|
7116
|
+
}
|
|
7117
|
+
// Design Decision 2 — the picker writes BOTH the resolved px size (via
|
|
7118
|
+
// the existing resize lane, DDR-027) and the `print` intent prop, in the
|
|
7119
|
+
// same gesture, so geometry and intent never drift apart.
|
|
7120
|
+
onResizeArtboard?.(artboardId, resolved.widthPx, resolved.heightPx);
|
|
7121
|
+
onSetArtboardPrint?.(artboardId, next);
|
|
7122
|
+
};
|
|
6801
7123
|
const setHug = (nextFixed) => {
|
|
6802
7124
|
if (!artboardId) return;
|
|
6803
7125
|
const freezeHeight = nextFixed && Number.isFinite(h) ? Math.round(h) : undefined;
|
|
@@ -6813,162 +7135,196 @@ function ArtboardKnobs({ el, onResizeArtboard, onSetArtboardHug, onSetArtboardSt
|
|
|
6813
7135
|
<span className="st-cp-sechd">Artboard</span>
|
|
6814
7136
|
</div>
|
|
6815
7137
|
<div style={{ display: 'flex', gap: 8, padding: '4px 12px' }}>
|
|
6816
|
-
<
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
<input
|
|
6838
|
-
className="st-cp-numin"
|
|
6839
|
-
type="number"
|
|
6840
|
-
min="1"
|
|
6841
|
-
aria-label="artboard height"
|
|
6842
|
-
readOnly={!fixed}
|
|
6843
|
-
title={fixed ? undefined : 'Hug — grows to fit content. Switch to Fixed to set an exact height.'}
|
|
6844
|
-
key={`h:${h ?? ''}:${fixed}`}
|
|
6845
|
-
defaultValue={h ?? ''}
|
|
6846
|
-
onKeyDown={(e) => {
|
|
6847
|
-
if (e.key === 'Enter') e.currentTarget.blur();
|
|
6848
|
-
}}
|
|
6849
|
-
onBlur={(e) => {
|
|
6850
|
-
if (!fixed) return;
|
|
6851
|
-
commitSize(null, Number.parseFloat(e.currentTarget.value));
|
|
6852
|
-
}}
|
|
6853
|
-
/>
|
|
6854
|
-
</div>
|
|
7138
|
+
<NumberField
|
|
7139
|
+
value={w ?? 0}
|
|
7140
|
+
min={1}
|
|
7141
|
+
ariaLabel="artboard width"
|
|
7142
|
+
lead="W"
|
|
7143
|
+
steppers={false}
|
|
7144
|
+
onCommit={(n) => commitSize(n, null)}
|
|
7145
|
+
/>
|
|
7146
|
+
<NumberField
|
|
7147
|
+
value={h ?? 0}
|
|
7148
|
+
min={1}
|
|
7149
|
+
ariaLabel="artboard height"
|
|
7150
|
+
lead="H"
|
|
7151
|
+
steppers={false}
|
|
7152
|
+
scrub={fixed}
|
|
7153
|
+
onCommit={(n) => {
|
|
7154
|
+
// Hug — height is content-driven; ignore a typed value rather
|
|
7155
|
+
// than silently switching modes.
|
|
7156
|
+
if (fixed) commitSize(null, n);
|
|
7157
|
+
}}
|
|
7158
|
+
/>
|
|
6855
7159
|
</div>
|
|
6856
7160
|
<div style={{ padding: '0 12px 8px' }}>
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
{
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
{p.label} — {p.width}×{p.height}
|
|
7161
|
+
{kind === 'print' ? (
|
|
7162
|
+
// Dogfood follow-up — a PRINT artboard's "Preset size…" dropdown
|
|
7163
|
+
// showed only the screen presets (Desktop/Laptop/Tablet/Mobile),
|
|
7164
|
+
// which are meaningless for paper. Show the paper ladder here
|
|
7165
|
+
// instead, writing through the SAME setPrint lane as the Print
|
|
7166
|
+
// section below (resolved px + print prop together).
|
|
7167
|
+
<select
|
|
7168
|
+
className="st-cp-nsel"
|
|
7169
|
+
aria-label="artboard paper preset"
|
|
7170
|
+
value={print?.paper ?? ''}
|
|
7171
|
+
onChange={(e) => setPrint({ paper: e.currentTarget.value })}
|
|
7172
|
+
>
|
|
7173
|
+
<option value="" disabled>
|
|
7174
|
+
Paper size…
|
|
6872
7175
|
</option>
|
|
6873
|
-
|
|
6874
|
-
|
|
7176
|
+
{PAPER_PRESETS.map((p) => (
|
|
7177
|
+
<option key={p.id} value={p.id}>
|
|
7178
|
+
{p.label} — {p.width}×{p.height}
|
|
7179
|
+
{p.unit}
|
|
7180
|
+
</option>
|
|
7181
|
+
))}
|
|
7182
|
+
</select>
|
|
7183
|
+
) : (
|
|
7184
|
+
<select
|
|
7185
|
+
className="st-cp-nsel"
|
|
7186
|
+
aria-label="artboard size preset"
|
|
7187
|
+
value={activePreset ?? ''}
|
|
7188
|
+
onChange={(e) => {
|
|
7189
|
+
const p = SCREEN_PRESETS[e.currentTarget.value];
|
|
7190
|
+
if (p) commitSize(p.width, p.height);
|
|
7191
|
+
}}
|
|
7192
|
+
>
|
|
7193
|
+
<option value="" disabled>
|
|
7194
|
+
{activePreset ? SCREEN_PRESETS[activePreset].label : 'Preset size…'}
|
|
7195
|
+
</option>
|
|
7196
|
+
{Object.entries(SCREEN_PRESETS).map(([key, p]) => (
|
|
7197
|
+
<option key={key} value={key}>
|
|
7198
|
+
{p.label} — {p.width}×{p.height}
|
|
7199
|
+
</option>
|
|
7200
|
+
))}
|
|
7201
|
+
</select>
|
|
7202
|
+
)}
|
|
6875
7203
|
</div>
|
|
6876
7204
|
<div style={{ padding: '0 12px 8px' }}>
|
|
6877
|
-
<
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
key={label}
|
|
6887
|
-
type="button"
|
|
6888
|
-
className={`st-cp-modebtn${fixed === val ? ' is-active' : ''}`}
|
|
6889
|
-
aria-pressed={fixed === val}
|
|
6890
|
-
onClick={() => setHug(val)}
|
|
6891
|
-
title={`${label} height`}
|
|
6892
|
-
>
|
|
6893
|
-
{label}
|
|
6894
|
-
</button>
|
|
6895
|
-
))}
|
|
6896
|
-
</div>
|
|
7205
|
+
<Segmented
|
|
7206
|
+
value={fixed ? 'fixed' : 'hug'}
|
|
7207
|
+
ariaLabel="artboard height sizing mode"
|
|
7208
|
+
options={[
|
|
7209
|
+
{ value: 'hug', label: 'Hug' },
|
|
7210
|
+
{ value: 'fixed', label: 'Fixed' },
|
|
7211
|
+
]}
|
|
7212
|
+
onChange={(v) => setHug(v === 'fixed')}
|
|
7213
|
+
/>
|
|
6897
7214
|
</div>
|
|
6898
7215
|
<div className="st-cp-sechd-row">
|
|
6899
|
-
<span className="st-cp-sechd">
|
|
7216
|
+
<span className="st-cp-sechd">Kind</span>
|
|
6900
7217
|
</div>
|
|
6901
|
-
<div style={{ padding: '
|
|
6902
|
-
<
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
<option key={val} value={val}>
|
|
6927
|
-
{label}
|
|
6928
|
-
</option>
|
|
6929
|
-
))}
|
|
6930
|
-
</select>
|
|
6931
|
-
<div style={{ display: 'flex', gap: 8 }}>
|
|
6932
|
-
<div className="st-cp-num">
|
|
6933
|
-
<span className="st-cp-numlead" aria-hidden="true">
|
|
6934
|
-
Pad
|
|
6935
|
-
</span>
|
|
6936
|
-
<input
|
|
6937
|
-
className="st-cp-numin"
|
|
6938
|
-
type="number"
|
|
6939
|
-
min="0"
|
|
6940
|
-
aria-label="artboard padding"
|
|
6941
|
-
key={`pad:${padding}`}
|
|
6942
|
-
defaultValue={padding}
|
|
6943
|
-
onKeyDown={(e) => {
|
|
6944
|
-
if (e.key === 'Enter') e.currentTarget.blur();
|
|
6945
|
-
}}
|
|
6946
|
-
onBlur={(e) => {
|
|
6947
|
-
const v = e.currentTarget.value;
|
|
6948
|
-
commitStyle({ padding: v === '' ? null : Number.parseFloat(v) });
|
|
6949
|
-
}}
|
|
7218
|
+
<div style={{ padding: '0 12px 8px' }}>
|
|
7219
|
+
<Select
|
|
7220
|
+
value={kind}
|
|
7221
|
+
ariaLabel="artboard kind"
|
|
7222
|
+
options={ARTBOARD_KIND_OPTIONS.map(([value, label]) => ({ value, label }))}
|
|
7223
|
+
onChange={setKind}
|
|
7224
|
+
/>
|
|
7225
|
+
</div>
|
|
7226
|
+
{kind === 'print' ? (
|
|
7227
|
+
<>
|
|
7228
|
+
<div className="st-cp-sechd-row">
|
|
7229
|
+
<span className="st-cp-sechd">Print</span>
|
|
7230
|
+
</div>
|
|
7231
|
+
{/* Paper picker lives in the Artboard section's own preset dropdown
|
|
7232
|
+
above (it REPLACES the screen presets for kind="print") — this
|
|
7233
|
+
section carries the print-specific residue: orientation + bleed. */}
|
|
7234
|
+
<div style={{ padding: '0 12px 8px' }}>
|
|
7235
|
+
<Segmented
|
|
7236
|
+
value={print?.orientation ?? 'portrait'}
|
|
7237
|
+
ariaLabel="paper orientation"
|
|
7238
|
+
options={[
|
|
7239
|
+
{ value: 'portrait', label: 'Portrait' },
|
|
7240
|
+
{ value: 'landscape', label: 'Landscape' },
|
|
7241
|
+
]}
|
|
7242
|
+
onChange={(v) => setPrint({ orientation: v })}
|
|
6950
7243
|
/>
|
|
6951
7244
|
</div>
|
|
6952
|
-
<div
|
|
6953
|
-
<
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
}}
|
|
7245
|
+
<div style={{ padding: '0 12px 8px' }}>
|
|
7246
|
+
<NumberField
|
|
7247
|
+
value={Number.isFinite(print?.bleedMm) ? print.bleedMm : 3}
|
|
7248
|
+
min={0}
|
|
7249
|
+
step={0.5}
|
|
7250
|
+
ariaLabel="bleed, millimeters"
|
|
7251
|
+
// Single-glyph lead — the drag-handle slot is sized for 1–2
|
|
7252
|
+
// chars ("W"/"Pad"); a full word clips. mm suffix + aria carry
|
|
7253
|
+
// the meaning.
|
|
7254
|
+
lead="B"
|
|
7255
|
+
steppers={false}
|
|
7256
|
+
unitSlot={
|
|
7257
|
+
<span className="st-cp-numsuffix" aria-hidden="true">
|
|
7258
|
+
mm
|
|
7259
|
+
</span>
|
|
7260
|
+
}
|
|
7261
|
+
onCommit={(n) => setPrint({ bleedMm: n })}
|
|
6970
7262
|
/>
|
|
6971
7263
|
</div>
|
|
7264
|
+
</>
|
|
7265
|
+
) : null}
|
|
7266
|
+
<div className="st-cp-sechd-row">
|
|
7267
|
+
<span className="st-cp-sechd">Style</span>
|
|
7268
|
+
</div>
|
|
7269
|
+
<div style={{ padding: '4px 12px 8px', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
7270
|
+
{(() => {
|
|
7271
|
+
// Bg — the CssKnobs `color()` recipe: TokenPopover swatch as the
|
|
7272
|
+
// flush prefix inside ColorField (HSV picker + per-DS variables).
|
|
7273
|
+
const bgResolved = resolveTokenValue(bg);
|
|
7274
|
+
const bgBound = /var\(\s*--/.test(bg);
|
|
7275
|
+
const bgDisplay = bgBound
|
|
7276
|
+
? bg.replace(/^var\(\s*|\s*\)$/g, '').replace(/^--/, '').replace(/-/g, ' ')
|
|
7277
|
+
: bg;
|
|
7278
|
+
return (
|
|
7279
|
+
<ColorField
|
|
7280
|
+
swatch={
|
|
7281
|
+
<TokenPopover
|
|
7282
|
+
kind="color"
|
|
7283
|
+
swatchClassName="st-cp-cf-sw"
|
|
7284
|
+
groups={tokenGroups('color')}
|
|
7285
|
+
current={bg}
|
|
7286
|
+
activeDs={_activeDs}
|
|
7287
|
+
swatchBg={bgResolved}
|
|
7288
|
+
seedHex={cssColorToHex(bgResolved) || '#000000'}
|
|
7289
|
+
onPick={(v) => commitStyle({ background: v || null })}
|
|
7290
|
+
label="artboard background colour"
|
|
7291
|
+
/>
|
|
7292
|
+
}
|
|
7293
|
+
displayValue={bgDisplay}
|
|
7294
|
+
bound={bgBound}
|
|
7295
|
+
ariaLabel="artboard background"
|
|
7296
|
+
onValue={(v) => commitStyle({ background: (v || '').trim() || null })}
|
|
7297
|
+
/>
|
|
7298
|
+
);
|
|
7299
|
+
})()}
|
|
7300
|
+
<Select
|
|
7301
|
+
value={layoutMode}
|
|
7302
|
+
ariaLabel="artboard body layout"
|
|
7303
|
+
options={ARTBOARD_LAYOUT_OPTIONS.map(([value, label]) => ({
|
|
7304
|
+
value,
|
|
7305
|
+
label,
|
|
7306
|
+
}))}
|
|
7307
|
+
onChange={(v) => commitStyle({ layout: v || null })}
|
|
7308
|
+
/>
|
|
7309
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
7310
|
+
<ValueTokenField
|
|
7311
|
+
value={/^var\(/.test(padding) ? padding : Number.parseFloat(padding) || 0}
|
|
7312
|
+
tokens={flatTokens('space')}
|
|
7313
|
+
ariaLabel="artboard padding"
|
|
7314
|
+
lead="Pad"
|
|
7315
|
+
min={0}
|
|
7316
|
+
onChange={(v) =>
|
|
7317
|
+
commitStyle({ padding: typeof v === 'string' ? v : v > 0 ? v : null })
|
|
7318
|
+
}
|
|
7319
|
+
/>
|
|
7320
|
+
<ValueTokenField
|
|
7321
|
+
value={/^var\(/.test(gap) ? gap : Number.parseFloat(gap) || 0}
|
|
7322
|
+
tokens={flatTokens('space')}
|
|
7323
|
+
ariaLabel="artboard gap"
|
|
7324
|
+
lead="Gap"
|
|
7325
|
+
min={0}
|
|
7326
|
+
onChange={(v) => commitStyle({ gap: typeof v === 'string' ? v : v > 0 ? v : null })}
|
|
7327
|
+
/>
|
|
6972
7328
|
</div>
|
|
6973
7329
|
</div>
|
|
6974
7330
|
</section>
|
|
@@ -7016,6 +7372,8 @@ function InspectorPanel({
|
|
|
7016
7372
|
onResizeArtboard,
|
|
7017
7373
|
onSetArtboardHug,
|
|
7018
7374
|
onSetArtboardStyle,
|
|
7375
|
+
onSetArtboardKind,
|
|
7376
|
+
onSetArtboardPrint,
|
|
7019
7377
|
onUndoRedo,
|
|
7020
7378
|
editScope,
|
|
7021
7379
|
tab: tabProp,
|
|
@@ -7584,9 +7942,12 @@ function InspectorPanel({
|
|
|
7584
7942
|
) : !el.id && resolveArtboardIdFromSelection(el) ? (
|
|
7585
7943
|
<ArtboardKnobs
|
|
7586
7944
|
el={el}
|
|
7945
|
+
cfg={cfg}
|
|
7587
7946
|
onResizeArtboard={onResizeArtboard}
|
|
7588
7947
|
onSetArtboardHug={onSetArtboardHug}
|
|
7589
7948
|
onSetArtboardStyle={onSetArtboardStyle}
|
|
7949
|
+
onSetArtboardKind={onSetArtboardKind}
|
|
7950
|
+
onSetArtboardPrint={onSetArtboardPrint}
|
|
7590
7951
|
/>
|
|
7591
7952
|
) : (
|
|
7592
7953
|
<CssKnobs
|
|
@@ -7680,6 +8041,32 @@ function App() {
|
|
|
7680
8041
|
}, delay)
|
|
7681
8042
|
);
|
|
7682
8043
|
}, []);
|
|
8044
|
+
// Dogfood follow-up — the ArtboardKnobs panel (Kind/Print/Style/Hug/W-H)
|
|
8045
|
+
// writes via structuralWrite, which never refreshed the Inspector's OWN
|
|
8046
|
+
// `attrs` snapshot afterward: the artboard itself re-rendered correctly
|
|
8047
|
+
// (new kind, new size) but the Inspector kept showing the PRE-edit values
|
|
8048
|
+
// (e.g. "Digital" right after picking "Print") until the user manually
|
|
8049
|
+
// re-clicked the artboard. Mirrors scheduleHaloRestore's retry ladder
|
|
8050
|
+
// (the HMR reload lands async, so a single immediate post races it) but
|
|
8051
|
+
// for a bare ARTBOARD selection (no data-cd-id to key off of — compares
|
|
8052
|
+
// by artboardId instead of id).
|
|
8053
|
+
const artboardResyncTimersRef = useRef([]);
|
|
8054
|
+
const scheduleArtboardResync = useCallback((artboardId, file) => {
|
|
8055
|
+
if (!artboardId || !file) return;
|
|
8056
|
+
for (const t of artboardResyncTimersRef.current) clearTimeout(t);
|
|
8057
|
+
artboardResyncTimersRef.current = [50, 450, 1200, 2500, 5000].map((delay) =>
|
|
8058
|
+
setTimeout(() => {
|
|
8059
|
+
const cur = selectedRef.current;
|
|
8060
|
+
const c1 = Array.isArray(cur) ? cur[0] : cur;
|
|
8061
|
+
if (!c1 || c1.artboardId !== artboardId || c1.file !== file) return; // superseded
|
|
8062
|
+
const frame = iframesRef.current.get(file);
|
|
8063
|
+
if (!frame?.contentWindow) return;
|
|
8064
|
+
try {
|
|
8065
|
+
frame.contentWindow.postMessage({ dgn: 'select-by-id', artboardId }, '*');
|
|
8066
|
+
} catch {}
|
|
8067
|
+
}, delay)
|
|
8068
|
+
);
|
|
8069
|
+
}, []);
|
|
7683
8070
|
// Phase 12.1 (DDR-138) — after a reorder writes source, the HMR reload remounts
|
|
7684
8071
|
// the canvas and the positional data-cd-id of the moved element (and everything
|
|
7685
8072
|
// after it) renumbers. Stash the re-settle target { file, movedId, artboardId }
|
|
@@ -8195,6 +8582,12 @@ function App() {
|
|
|
8195
8582
|
const [minimapVisible, setMinimapVisible] = useState(() => readBoolStore(MINIMAP_STORE, false));
|
|
8196
8583
|
const [zoomCtlVisible, setZoomCtlVisible] = useState(() => readBoolStore(ZOOMCTL_STORE, false));
|
|
8197
8584
|
const [presentMode, setPresentMode] = useState(false);
|
|
8585
|
+
// feature-2-print-artboards T3 — "Show print guides". Per-CANVAS persisted
|
|
8586
|
+
// (view.json `overlays.print`, via /_api/canvas-meta), not a global
|
|
8587
|
+
// localStorage pref like minimap/zoomctl — mirrors the foundation's
|
|
8588
|
+
// `overlays.guides` lane. Re-read whenever the active canvas changes (see
|
|
8589
|
+
// the activeArtboards effect below); sent only to the active iframe.
|
|
8590
|
+
const [printGuidesVisible, setPrintGuidesVisible] = useState(false);
|
|
8198
8591
|
// P2/P3 (Plan C) — top-bar live state. (Zoom lives in the canvas toolbar pill,
|
|
8199
8592
|
// so the top bar no longer mirrors it.)
|
|
8200
8593
|
// activeArtboards — real artboard count of the open canvas, read from its
|
|
@@ -8625,6 +9018,24 @@ function App() {
|
|
|
8625
9018
|
setPresentMode(false);
|
|
8626
9019
|
broadcastChrome({ present: false });
|
|
8627
9020
|
}, [broadcastChrome]);
|
|
9021
|
+
// feature-2-print-artboards T3 — per-canvas persisted, so (unlike
|
|
9022
|
+
// broadcastChrome above) this targets ONLY the active iframe and PATCHes
|
|
9023
|
+
// view.json's `overlays.print` through the same GET-merge/PATCH-split lane
|
|
9024
|
+
// the foundation built for `overlays.guides` (api.ts normalizeOverlays is a
|
|
9025
|
+
// flat Record<string, boolean> — no server change needed for a new key).
|
|
9026
|
+
const togglePrintGuides = useCallback(() => {
|
|
9027
|
+
if (!activePath || activePath === SYSTEM_TAB) return;
|
|
9028
|
+
setPrintGuidesVisible((v) => {
|
|
9029
|
+
const next = !v;
|
|
9030
|
+
postToActiveCanvas({ dgn: 'view-chrome', print: next });
|
|
9031
|
+
fetch('/_api/canvas-meta', {
|
|
9032
|
+
method: 'PATCH',
|
|
9033
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9034
|
+
body: JSON.stringify({ file: activePath, patch: { overlays: { print: next } } }),
|
|
9035
|
+
}).catch(() => {});
|
|
9036
|
+
return next;
|
|
9037
|
+
});
|
|
9038
|
+
}, [activePath, postToActiveCanvas]);
|
|
8628
9039
|
|
|
8629
9040
|
// P3 (Plan C) — local git user for the menubar presence avatar. One-shot.
|
|
8630
9041
|
useEffect(() => {
|
|
@@ -8647,6 +9058,7 @@ function App() {
|
|
|
8647
9058
|
useEffect(() => {
|
|
8648
9059
|
if (!activePath || activePath === SYSTEM_TAB) {
|
|
8649
9060
|
setActiveArtboards(0);
|
|
9061
|
+
setPrintGuidesVisible(false);
|
|
8650
9062
|
return;
|
|
8651
9063
|
}
|
|
8652
9064
|
let cancelled = false;
|
|
@@ -8656,9 +9068,15 @@ function App() {
|
|
|
8656
9068
|
if (cancelled) return;
|
|
8657
9069
|
const n = Array.isArray(meta?.artboards) ? meta.artboards.length : 0;
|
|
8658
9070
|
setActiveArtboards(n);
|
|
9071
|
+
// feature-2-print-artboards T3 — reflect THIS canvas's own persisted
|
|
9072
|
+
// print-guides flag in the menu checkbox when switching tabs.
|
|
9073
|
+
setPrintGuidesVisible(meta?.overlays?.print === true);
|
|
8659
9074
|
})
|
|
8660
9075
|
.catch(() => {
|
|
8661
|
-
if (!cancelled)
|
|
9076
|
+
if (!cancelled) {
|
|
9077
|
+
setActiveArtboards(0);
|
|
9078
|
+
setPrintGuidesVisible(false);
|
|
9079
|
+
}
|
|
8662
9080
|
});
|
|
8663
9081
|
return () => {
|
|
8664
9082
|
cancelled = true;
|
|
@@ -9945,6 +10363,15 @@ function App() {
|
|
|
9945
10363
|
if (e.source === activeWin && typeof m.artboardId === 'string') {
|
|
9946
10364
|
deleteArtboardShellRef.current?.(m.artboardId);
|
|
9947
10365
|
}
|
|
10366
|
+
} else if (m.dgn === 'set-artboard-kind-request') {
|
|
10367
|
+
// feature-1-artboard-kinds-foundation, T8 — context-menu "Artboard
|
|
10368
|
+
// kind" submenu (inside the untrusted iframe). `kind: null` clears
|
|
10369
|
+
// back to the implicit default.
|
|
10370
|
+
const activeWin = activePath ? iframesRef.current.get(activePath)?.contentWindow : null;
|
|
10371
|
+
const okKind = m.kind === null || typeof m.kind === 'string';
|
|
10372
|
+
if (e.source === activeWin && typeof m.artboardId === 'string' && okKind) {
|
|
10373
|
+
setArtboardKindShellRef.current?.(m.artboardId, m.kind);
|
|
10374
|
+
}
|
|
9948
10375
|
} else if (m.dgn === 'open-inspector') {
|
|
9949
10376
|
// Phase 12 — context-menu "Inspect" / tool-palette Inspect opens the right panel.
|
|
9950
10377
|
// feature-photo-editor (Task 16) — an optional `tab` lands the panel on a
|
|
@@ -10793,20 +11220,6 @@ function App() {
|
|
|
10793
11220
|
[structuralWrite]
|
|
10794
11221
|
);
|
|
10795
11222
|
|
|
10796
|
-
// Stage I4 — "New artboard: <preset>" from the Edit menu. Generates a unique
|
|
10797
|
-
// id (server 422s a dup, negligible with a random suffix) + a preset label/dims
|
|
10798
|
-
// and inserts an empty <DCArtboard> after the last one (runtime default-grid
|
|
10799
|
-
// places it; DDR-027). The new frame is then selectable + resizable.
|
|
10800
|
-
const onInsertArtboard = useCallback(
|
|
10801
|
-
(presetKey) => {
|
|
10802
|
-
const p = SCREEN_PRESETS[presetKey];
|
|
10803
|
-
if (!p) return;
|
|
10804
|
-
const id = `s${Math.random().toString(36).slice(2, 8)}`;
|
|
10805
|
-
insertArtboardShell({ id, label: p.label, width: p.width, height: p.height });
|
|
10806
|
-
},
|
|
10807
|
-
[insertArtboardShell]
|
|
10808
|
-
);
|
|
10809
|
-
|
|
10810
11223
|
const resizeArtboardShell = useCallback(
|
|
10811
11224
|
(artboardId, width, height) => {
|
|
10812
11225
|
structuralWrite(
|
|
@@ -10818,10 +11231,14 @@ function App() {
|
|
|
10818
11231
|
// during the drag (no HMR yet); on a rejected/failed write, tell it to
|
|
10819
11232
|
// restore the pre-drag box so a phantom resize doesn't linger.
|
|
10820
11233
|
onFail: () => postToActiveCanvas({ dgn: 'resize-artboard-failed' }),
|
|
11234
|
+
// Dogfood follow-up — resync the Inspector's own W/H (and any other
|
|
11235
|
+
// attrs) once the HMR reload lands, so a paper-preset pick doesn't
|
|
11236
|
+
// leave the fields showing the pre-resize size.
|
|
11237
|
+
onOk: () => scheduleArtboardResync(artboardId, activePath),
|
|
10821
11238
|
}
|
|
10822
11239
|
);
|
|
10823
11240
|
},
|
|
10824
|
-
[structuralWrite, postToActiveCanvas]
|
|
11241
|
+
[structuralWrite, postToActiveCanvas, scheduleArtboardResync, activePath]
|
|
10825
11242
|
);
|
|
10826
11243
|
|
|
10827
11244
|
const deleteArtboardShell = useCallback(
|
|
@@ -10839,18 +11256,96 @@ function App() {
|
|
|
10839
11256
|
structuralWrite(
|
|
10840
11257
|
'/_api/set-artboard-hug',
|
|
10841
11258
|
{ artboardId, fixed, freezeHeight },
|
|
10842
|
-
{
|
|
11259
|
+
{
|
|
11260
|
+
label: fixed ? 'pin artboard height' : 'hug artboard height',
|
|
11261
|
+
onOk: () => scheduleArtboardResync(artboardId, activePath),
|
|
11262
|
+
}
|
|
10843
11263
|
);
|
|
10844
11264
|
},
|
|
10845
|
-
[structuralWrite]
|
|
11265
|
+
[structuralWrite, scheduleArtboardResync, activePath]
|
|
10846
11266
|
);
|
|
10847
11267
|
|
|
10848
11268
|
// Artboard "more settings" (background/padding/layout/gap) from ArtboardKnobs.
|
|
10849
11269
|
const setArtboardStyleShell = useCallback(
|
|
10850
11270
|
(artboardId, patch) => {
|
|
10851
|
-
structuralWrite('/_api/set-artboard-style', { artboardId, ...patch }, {
|
|
11271
|
+
structuralWrite('/_api/set-artboard-style', { artboardId, ...patch }, {
|
|
11272
|
+
label: 'artboard style',
|
|
11273
|
+
onOk: () => scheduleArtboardResync(artboardId, activePath),
|
|
11274
|
+
});
|
|
10852
11275
|
},
|
|
10853
|
-
[structuralWrite]
|
|
11276
|
+
[structuralWrite, scheduleArtboardResync, activePath]
|
|
11277
|
+
);
|
|
11278
|
+
// feature-1-artboard-kinds-foundation, T8 — kind-switch surfaces. Direct
|
|
11279
|
+
// callable from ArtboardKnobs (Inspector); the context-menu submenu (inside
|
|
11280
|
+
// the iframe) reaches the SAME endpoint via the postMessage ref below.
|
|
11281
|
+
//
|
|
11282
|
+
// Dogfood follow-up — every one of these ArtboardKnobs writers now resyncs
|
|
11283
|
+
// the Inspector's OWN selection snapshot once the HMR reload lands
|
|
11284
|
+
// (scheduleArtboardResync's retry ladder). Before this fix the ARTBOARD
|
|
11285
|
+
// re-rendered correctly (new kind, new paper size) but the Inspector kept
|
|
11286
|
+
// showing PRE-edit attrs (e.g. "Digital" right after picking "Print",
|
|
11287
|
+
// "Preset size…" still listing screen presets) until the user manually
|
|
11288
|
+
// re-clicked the artboard — structuralWrite's onOk never refreshed
|
|
11289
|
+
// `selected`, only recorded undo history.
|
|
11290
|
+
const setArtboardKindShell = useCallback(
|
|
11291
|
+
(artboardId, kind) => {
|
|
11292
|
+
structuralWrite('/_api/set-artboard-kind', { artboardId, kind }, {
|
|
11293
|
+
label: 'artboard kind',
|
|
11294
|
+
onOk: () => scheduleArtboardResync(artboardId, activePath),
|
|
11295
|
+
});
|
|
11296
|
+
},
|
|
11297
|
+
[structuralWrite, scheduleArtboardResync, activePath]
|
|
11298
|
+
);
|
|
11299
|
+
// feature-2-print-artboards T2 — paper/orientation/bleed/margins. Direct
|
|
11300
|
+
// Inspector-only callable (no canvas-origin postMessage path — same shape
|
|
11301
|
+
// as setArtboardStyleShell, unlike setArtboardKindShell which also has a
|
|
11302
|
+
// context-menu caller inside the iframe).
|
|
11303
|
+
const setArtboardPrintShell = useCallback(
|
|
11304
|
+
(artboardId, print) => {
|
|
11305
|
+
structuralWrite('/_api/set-artboard-print', { artboardId, print }, {
|
|
11306
|
+
label: 'artboard print',
|
|
11307
|
+
onOk: () => scheduleArtboardResync(artboardId, activePath),
|
|
11308
|
+
});
|
|
11309
|
+
},
|
|
11310
|
+
[structuralWrite, scheduleArtboardResync, activePath]
|
|
11311
|
+
);
|
|
11312
|
+
|
|
11313
|
+
// Stage I4 — "New artboard: <preset>" from the Edit menu. Generates a unique
|
|
11314
|
+
// id (server 422s a dup, negligible with a random suffix) + a preset label/dims
|
|
11315
|
+
// and inserts an empty <DCArtboard> after the last one (runtime default-grid
|
|
11316
|
+
// places it; DDR-027). The new frame is then selectable + resizable.
|
|
11317
|
+
const onInsertArtboard = useCallback(
|
|
11318
|
+
(presetKey) => {
|
|
11319
|
+
const id = `s${Math.random().toString(36).slice(2, 8)}`;
|
|
11320
|
+
// feature-2-print-artboards T2 — kind="print" + the print prop must
|
|
11321
|
+
// land together, so the created artboard is never a plain digital
|
|
11322
|
+
// board sized to paper by coincidence. structuralWrite serializes
|
|
11323
|
+
// calls in submission order (editApplyChainRef), so these three
|
|
11324
|
+
// sequential POSTs land as insert → kind → print, in order.
|
|
11325
|
+
if (presetKey.startsWith('print-')) {
|
|
11326
|
+
const paper = presetKey.slice('print-'.length);
|
|
11327
|
+
let resolved;
|
|
11328
|
+
try {
|
|
11329
|
+
resolved = resolvePrintArtboard({ paper });
|
|
11330
|
+
} catch {
|
|
11331
|
+
return;
|
|
11332
|
+
}
|
|
11333
|
+
const preset = PAPER_PRESETS.find((p) => p.id === paper);
|
|
11334
|
+
insertArtboardShell({
|
|
11335
|
+
id,
|
|
11336
|
+
label: preset ? preset.label : paper.toUpperCase(),
|
|
11337
|
+
width: resolved.widthPx,
|
|
11338
|
+
height: resolved.heightPx,
|
|
11339
|
+
});
|
|
11340
|
+
setArtboardKindShell(id, 'print');
|
|
11341
|
+
setArtboardPrintShell(id, { paper });
|
|
11342
|
+
return;
|
|
11343
|
+
}
|
|
11344
|
+
const p = SCREEN_PRESETS[presetKey];
|
|
11345
|
+
if (!p) return;
|
|
11346
|
+
insertArtboardShell({ id, label: p.label, width: p.width, height: p.height });
|
|
11347
|
+
},
|
|
11348
|
+
[insertArtboardShell, setArtboardKindShell, setArtboardPrintShell]
|
|
10854
11349
|
);
|
|
10855
11350
|
// Refs the (stale-closure) onMessage handlers below read.
|
|
10856
11351
|
const deleteElementShellRef = useRef(null);
|
|
@@ -10858,6 +11353,7 @@ function App() {
|
|
|
10858
11353
|
const insertArtboardShellRef = useRef(null);
|
|
10859
11354
|
const resizeArtboardShellRef = useRef(null);
|
|
10860
11355
|
const deleteArtboardShellRef = useRef(null);
|
|
11356
|
+
const setArtboardKindShellRef = useRef(null);
|
|
10861
11357
|
const duplicateElementShellRef = useRef(null);
|
|
10862
11358
|
const copyStyleRef = useRef(null);
|
|
10863
11359
|
const pasteStyleRef = useRef(null);
|
|
@@ -10867,6 +11363,7 @@ function App() {
|
|
|
10867
11363
|
insertArtboardShellRef.current = insertArtboardShell;
|
|
10868
11364
|
resizeArtboardShellRef.current = resizeArtboardShell;
|
|
10869
11365
|
deleteArtboardShellRef.current = deleteArtboardShell;
|
|
11366
|
+
setArtboardKindShellRef.current = setArtboardKindShell;
|
|
10870
11367
|
duplicateElementShellRef.current = duplicateElementShell;
|
|
10871
11368
|
copyStyleRef.current = copyStyle;
|
|
10872
11369
|
pasteStyleRef.current = pasteStyle;
|
|
@@ -10876,6 +11373,7 @@ function App() {
|
|
|
10876
11373
|
insertArtboardShell,
|
|
10877
11374
|
resizeArtboardShell,
|
|
10878
11375
|
deleteArtboardShell,
|
|
11376
|
+
setArtboardKindShell,
|
|
10879
11377
|
duplicateElementShell,
|
|
10880
11378
|
copyStyle,
|
|
10881
11379
|
pasteStyle,
|
|
@@ -11624,6 +12122,8 @@ function App() {
|
|
|
11624
12122
|
onResizeArtboard={resizeArtboardShell}
|
|
11625
12123
|
onSetArtboardHug={setArtboardHugShell}
|
|
11626
12124
|
onSetArtboardStyle={setArtboardStyleShell}
|
|
12125
|
+
onSetArtboardKind={setArtboardKindShell}
|
|
12126
|
+
onSetArtboardPrint={setArtboardPrintShell}
|
|
11627
12127
|
editScope={editScope}
|
|
11628
12128
|
onUndoRedo={(dir) => postToActiveCanvas({ dgn: dir })}
|
|
11629
12129
|
photoSel={photoSel}
|
|
@@ -11733,6 +12233,8 @@ function App() {
|
|
|
11733
12233
|
onToggleZoomCtl={toggleZoomCtl}
|
|
11734
12234
|
presentMode={presentMode}
|
|
11735
12235
|
onTogglePresent={togglePresent}
|
|
12236
|
+
printGuidesVisible={printGuidesVisible}
|
|
12237
|
+
onTogglePrintGuides={togglePrintGuides}
|
|
11736
12238
|
postToActiveCanvas={postToActiveCanvas}
|
|
11737
12239
|
onOpenReadiness={() => setReadinessOpen(true)}
|
|
11738
12240
|
onOpenQuickSetup={() => setQuickSetupOpen(true)}
|