@1agh/maude 0.42.0 → 0.43.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/.ai/cache/_stats.json +7 -0
- package/apps/studio/acp/bootstrap-brief.ts +12 -2
- package/apps/studio/annotations-layer.tsx +1055 -278
- package/apps/studio/annotations-model.ts +58 -0
- package/apps/studio/api.ts +173 -22
- package/apps/studio/bin/_ingest-footage.mjs +386 -0
- package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
- package/apps/studio/bin/_video-playwright.mjs +141 -71
- package/apps/studio/bin/ingest-footage.sh +27 -0
- package/apps/studio/bin/photo-adjust.sh +163 -0
- package/apps/studio/bin/photo-bg-remove.sh +160 -0
- package/apps/studio/bin/probe-footage.sh +28 -0
- package/apps/studio/bin/read-annotations.mjs +125 -2
- package/apps/studio/canvas-edit.ts +459 -10
- package/apps/studio/canvas-lib.tsx +523 -5
- package/apps/studio/canvas-pipeline.ts +65 -7
- package/apps/studio/canvas-shell.tsx +156 -16
- package/apps/studio/client/app.jsx +451 -34
- package/apps/studio/client/export-center.jsx +35 -1
- package/apps/studio/client/github.js +4 -0
- package/apps/studio/client/panels/GitPanel.jsx +8 -0
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
- package/apps/studio/client/panels/StickerPicker.jsx +160 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
- package/apps/studio/client/photo-knobs.jsx +437 -0
- package/apps/studio/client/styles/3-shell-maude.css +42 -0
- package/apps/studio/commands/edit-source-command.ts +31 -4
- package/apps/studio/context-menu.tsx +31 -6
- package/apps/studio/dist/client.bundle.js +5561 -19
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
- package/apps/studio/dist/runtime/pixi-js.js +519 -519
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +25 -0
- package/apps/studio/exporters/jobs.ts +47 -5
- package/apps/studio/exporters/video.ts +24 -4
- package/apps/studio/footage/schema.test.ts +195 -0
- package/apps/studio/footage/schema.ts +484 -0
- package/apps/studio/footage-store.ts +235 -0
- package/apps/studio/fs-watch.ts +10 -0
- package/apps/studio/git/endpoints.ts +68 -7
- package/apps/studio/git/service.ts +196 -20
- package/apps/studio/github/service.ts +67 -0
- package/apps/studio/handoff.ts +3 -2
- package/apps/studio/http.ts +173 -14
- package/apps/studio/input-router.tsx +10 -0
- package/apps/studio/inspect.ts +53 -1
- package/apps/studio/paths.ts +10 -0
- package/apps/studio/photo/filters.ts +285 -0
- package/apps/studio/photo/pipeline.ts +607 -0
- package/apps/studio/photo/schema.ts +367 -0
- package/apps/studio/photo-store.ts +154 -0
- package/apps/studio/runtime-bundle.ts +29 -6
- package/apps/studio/server.ts +5 -1
- package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
- package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
- package/apps/studio/stickers/life-style/best.png +0 -0
- package/apps/studio/stickers/life-style/come-on.png +0 -0
- package/apps/studio/stickers/life-style/cut.png +0 -0
- package/apps/studio/stickers/life-style/detox.png +0 -0
- package/apps/studio/stickers/life-style/just.png +0 -0
- package/apps/studio/stickers/life-style/manifest.json +68 -0
- package/apps/studio/stickers/life-style/mirror.png +0 -0
- package/apps/studio/stickers/life-style/nice.png +0 -0
- package/apps/studio/stickers/life-style/objects.png +0 -0
- package/apps/studio/stickers/life-style/ok.png +0 -0
- package/apps/studio/stickers/life-style/queen.png +0 -0
- package/apps/studio/stickers/life-style/star.png +0 -0
- package/apps/studio/stickers/life-style/sun.png +0 -0
- package/apps/studio/stickers/life-style/water.png +0 -0
- package/apps/studio/stickers/life-style/yeah.png +0 -0
- package/apps/studio/stickers/life-style/you-can.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
- package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
- package/apps/studio/stickers/project-status/group-100.png +0 -0
- package/apps/studio/stickers/project-status/group-101.png +0 -0
- package/apps/studio/stickers/project-status/group-102.png +0 -0
- package/apps/studio/stickers/project-status/group-103.png +0 -0
- package/apps/studio/stickers/project-status/group-104.png +0 -0
- package/apps/studio/stickers/project-status/group-105.png +0 -0
- package/apps/studio/stickers/project-status/group-106.png +0 -0
- package/apps/studio/stickers/project-status/group-107.png +0 -0
- package/apps/studio/stickers/project-status/group-108.png +0 -0
- package/apps/studio/stickers/project-status/group-109.png +0 -0
- package/apps/studio/stickers/project-status/group-110.png +0 -0
- package/apps/studio/stickers/project-status/group-111.png +0 -0
- package/apps/studio/stickers/project-status/group-112.png +0 -0
- package/apps/studio/stickers/project-status/group-113.png +0 -0
- package/apps/studio/stickers/project-status/group-114.png +0 -0
- package/apps/studio/stickers/project-status/group-115.png +0 -0
- package/apps/studio/stickers/project-status/group-117.png +0 -0
- package/apps/studio/stickers/project-status/group-118.png +0 -0
- package/apps/studio/stickers/project-status/group-119.png +0 -0
- package/apps/studio/stickers/project-status/group-120.png +0 -0
- package/apps/studio/stickers/project-status/group-121.png +0 -0
- package/apps/studio/stickers/project-status/group-122.png +0 -0
- package/apps/studio/stickers/project-status/group-41.png +0 -0
- package/apps/studio/stickers/project-status/group-42.png +0 -0
- package/apps/studio/stickers/project-status/group-43.png +0 -0
- package/apps/studio/stickers/project-status/group-44.png +0 -0
- package/apps/studio/stickers/project-status/group-45.png +0 -0
- package/apps/studio/stickers/project-status/group-46.png +0 -0
- package/apps/studio/stickers/project-status/group-47.png +0 -0
- package/apps/studio/stickers/project-status/group-48.png +0 -0
- package/apps/studio/stickers/project-status/group-49.png +0 -0
- package/apps/studio/stickers/project-status/group-50.png +0 -0
- package/apps/studio/stickers/project-status/group-51.png +0 -0
- package/apps/studio/stickers/project-status/group-52.png +0 -0
- package/apps/studio/stickers/project-status/group-53.png +0 -0
- package/apps/studio/stickers/project-status/group-54.png +0 -0
- package/apps/studio/stickers/project-status/group-55.png +0 -0
- package/apps/studio/stickers/project-status/group-56.png +0 -0
- package/apps/studio/stickers/project-status/group-57.png +0 -0
- package/apps/studio/stickers/project-status/group-58.png +0 -0
- package/apps/studio/stickers/project-status/group-59.png +0 -0
- package/apps/studio/stickers/project-status/group-60.png +0 -0
- package/apps/studio/stickers/project-status/group-61.png +0 -0
- package/apps/studio/stickers/project-status/group-62.png +0 -0
- package/apps/studio/stickers/project-status/group-63.png +0 -0
- package/apps/studio/stickers/project-status/group-64.png +0 -0
- package/apps/studio/stickers/project-status/group-65.png +0 -0
- package/apps/studio/stickers/project-status/group-66.png +0 -0
- package/apps/studio/stickers/project-status/group-67.png +0 -0
- package/apps/studio/stickers/project-status/group-68.png +0 -0
- package/apps/studio/stickers/project-status/group-69.png +0 -0
- package/apps/studio/stickers/project-status/group-70.png +0 -0
- package/apps/studio/stickers/project-status/group-71.png +0 -0
- package/apps/studio/stickers/project-status/group-72.png +0 -0
- package/apps/studio/stickers/project-status/group-73.png +0 -0
- package/apps/studio/stickers/project-status/group-74.png +0 -0
- package/apps/studio/stickers/project-status/group-75.png +0 -0
- package/apps/studio/stickers/project-status/group-76.png +0 -0
- package/apps/studio/stickers/project-status/group-77.png +0 -0
- package/apps/studio/stickers/project-status/group-78.png +0 -0
- package/apps/studio/stickers/project-status/group-79.png +0 -0
- package/apps/studio/stickers/project-status/group-80.png +0 -0
- package/apps/studio/stickers/project-status/group-81.png +0 -0
- package/apps/studio/stickers/project-status/group-82.png +0 -0
- package/apps/studio/stickers/project-status/group-83.png +0 -0
- package/apps/studio/stickers/project-status/group-84.png +0 -0
- package/apps/studio/stickers/project-status/group-85.png +0 -0
- package/apps/studio/stickers/project-status/group-86.png +0 -0
- package/apps/studio/stickers/project-status/group-87.png +0 -0
- package/apps/studio/stickers/project-status/group-88.png +0 -0
- package/apps/studio/stickers/project-status/group-89.png +0 -0
- package/apps/studio/stickers/project-status/group-90.png +0 -0
- package/apps/studio/stickers/project-status/group-91.png +0 -0
- package/apps/studio/stickers/project-status/group-92.png +0 -0
- package/apps/studio/stickers/project-status/group-93.png +0 -0
- package/apps/studio/stickers/project-status/group-94.png +0 -0
- package/apps/studio/stickers/project-status/group-96.png +0 -0
- package/apps/studio/stickers/project-status/group-97.png +0 -0
- package/apps/studio/stickers/project-status/group-98.png +0 -0
- package/apps/studio/stickers/project-status/group-99.png +0 -0
- package/apps/studio/stickers/project-status/manifest.json +328 -0
- package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
- package/apps/studio/test/acp-commands.test.ts +2 -1
- package/apps/studio/test/asset-api.test.ts +3 -3
- package/apps/studio/test/canvas-edit.test.ts +17 -6
- package/apps/studio/test/canvas-origin-gate.test.ts +13 -0
- package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
- package/apps/studio/test/edit-source-command.test.ts +23 -0
- package/apps/studio/test/element-structural-edit.test.ts +46 -0
- package/apps/studio/test/footage-store.test.ts +100 -0
- package/apps/studio/test/git-branches.test.ts +97 -0
- package/apps/studio/test/github-api.test.ts +56 -1
- package/apps/studio/test/input-router.test.ts +33 -0
- package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
- package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
- package/apps/studio/test/photo-edit-api.test.ts +201 -0
- package/apps/studio/test/photo-filters.test.ts +149 -0
- package/apps/studio/test/photo-pipeline.test.ts +106 -0
- package/apps/studio/test/photo-taxonomy.test.ts +96 -0
- package/apps/studio/test/read-annotations.test.ts +164 -0
- package/apps/studio/test/shell-importmap.test.ts +49 -0
- package/apps/studio/test/text-editability-stamp.test.ts +131 -0
- package/apps/studio/test/timeline-parse.test.ts +57 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
- package/apps/studio/test/undo-stack.test.ts +33 -0
- package/apps/studio/test/video-asset.test.ts +5 -5
- package/apps/studio/test/video-render-bridge.test.ts +23 -1
- package/apps/studio/text-caret.ts +239 -0
- package/apps/studio/tool-palette.tsx +43 -20
- package/apps/studio/undo-stack.ts +94 -2
- package/apps/studio/use-annotation-resize.tsx +4 -4
- package/apps/studio/use-canvas-media-drop.tsx +38 -1
- package/apps/studio/use-selection-set.tsx +21 -0
- package/apps/studio/video-comp.tsx +48 -7
- package/apps/studio/whats-new.json +53 -0
- package/cli/commands/design.mjs +34 -3
- package/cli/lib/gitignore-block.mjs +1 -0
- package/package.json +10 -10
- package/plugins/design/templates/_shell.html +1 -0
|
@@ -23,6 +23,8 @@ import { ReadinessDialog } from './panels/ReadinessList.jsx';
|
|
|
23
23
|
import TimelinePanel from './panels/TimelinePanel.jsx';
|
|
24
24
|
import { parseCompTimeline } from './panels/timeline-parse.js';
|
|
25
25
|
import RepoBranchSwitcher from './panels/RepoBranchSwitcher.jsx';
|
|
26
|
+
import StickerPicker from './panels/StickerPicker.jsx';
|
|
27
|
+
import { PhotoKnobs } from './photo-knobs.jsx';
|
|
26
28
|
import {
|
|
27
29
|
appIsFirstRun,
|
|
28
30
|
isNativeApp,
|
|
@@ -45,6 +47,8 @@ const THEME_STORE = 'mdcc-theme';
|
|
|
45
47
|
const SHOW_HIDDEN_STORE = 'mdcc-show-hidden';
|
|
46
48
|
const SECTIONS_STORE = 'mdcc-sections-expanded';
|
|
47
49
|
const SIDEBAR_STORE = 'mdcc-sidebar-open';
|
|
50
|
+
const MINIMAP_STORE = 'mdcc-minimap-visible';
|
|
51
|
+
const ZOOMCTL_STORE = 'mdcc-zoomctl-visible';
|
|
48
52
|
const CANVAS_EXT_RE = /\.(tsx|html?)$/i;
|
|
49
53
|
// Bun's `define` substitutes this at build time (see build.ts); falls back when
|
|
50
54
|
// the bundle is consumed in a context that hasn't run the build.
|
|
@@ -1648,6 +1652,8 @@ function Sidebar({
|
|
|
1648
1652
|
dirtyByPath,
|
|
1649
1653
|
project,
|
|
1650
1654
|
gitBranch,
|
|
1655
|
+
remoteSync,
|
|
1656
|
+
onGetLatest,
|
|
1651
1657
|
}) {
|
|
1652
1658
|
const filteredGroups = useMemo(() => {
|
|
1653
1659
|
if (!search) return groups;
|
|
@@ -1889,7 +1895,7 @@ function Sidebar({
|
|
|
1889
1895
|
{/* Phase 29 (E4) — the project + draft switcher: a compact one-line dock that
|
|
1890
1896
|
opens UPWARD, sitting directly above the GitHub identity avatar so the two
|
|
1891
1897
|
form one bottom dock. Renders nothing until the project is a git repo. */}
|
|
1892
|
-
<RepoBranchSwitcher project={project} liveBranch={gitBranch} />
|
|
1898
|
+
<RepoBranchSwitcher project={project} liveBranch={gitBranch} remoteSync={remoteSync} onGetLatest={onGetLatest} />
|
|
1893
1899
|
{/* Phase 28 (E3) — GitHub identity as a compact avatar docked at the BOTTOM:
|
|
1894
1900
|
sign in, connected account + New/Pull/Share, sign out. Self-contained
|
|
1895
1901
|
(owns its device-code + CreateProject dialogs). Renders nothing in browser. */}
|
|
@@ -2427,12 +2433,9 @@ function ViewDropdown({ panels, onToggle, onClose, onZoom, hasCanvas }) {
|
|
|
2427
2433
|
<span className="st-dd-lead">
|
|
2428
2434
|
<span className="st-dd-check">{p.checked ? <StIcon name="check" size={13} /> : null}</span>
|
|
2429
2435
|
<span>{p.label}</span>
|
|
2436
|
+
{p.phase ? <span className="st-dd-phase">{p.phase}</span> : null}
|
|
2430
2437
|
</span>
|
|
2431
|
-
{p.
|
|
2432
|
-
<span className="st-dd-phase">{p.phase}</span>
|
|
2433
|
-
) : p.shortcut ? (
|
|
2434
|
-
<Kbd>{p.shortcut}</Kbd>
|
|
2435
|
-
) : null}
|
|
2438
|
+
{p.shortcut ? <Kbd>{p.shortcut}</Kbd> : null}
|
|
2436
2439
|
</button>
|
|
2437
2440
|
))}
|
|
2438
2441
|
<div className="st-dd-sep" />
|
|
@@ -2740,6 +2743,7 @@ function Menubar({
|
|
|
2740
2743
|
{
|
|
2741
2744
|
id: 'timeline',
|
|
2742
2745
|
label: 'Timeline',
|
|
2746
|
+
shortcut: '⌘ ⇧ T',
|
|
2743
2747
|
phase: hasComps ? 'video' : undefined,
|
|
2744
2748
|
checked: timelineOpen,
|
|
2745
2749
|
disabled: false,
|
|
@@ -3959,6 +3963,12 @@ function cssSplitUnit(v) {
|
|
|
3959
3963
|
// is for the SAME element we already hold locally, preserve those fields instead
|
|
3960
3964
|
// of clobbering them to empty (else the server round-trip wipes the custom-CSS /
|
|
3961
3965
|
// custom-attr rows + computed readout right after selection).
|
|
3966
|
+
// feature-photo-editor — `photoKind`/`photoAsset` (dom-selection.ts) are the SAME
|
|
3967
|
+
// class of client-only DOM-derived field and belong in this list for the same
|
|
3968
|
+
// reason: without it, every server-pushed `selected`/`snapshot` restore (a canvas
|
|
3969
|
+
// switch, a reconnect, the persisted `_active.json` on boot — none of which carry
|
|
3970
|
+
// these fields) silently drops the Inspector's Photo tab until the next fresh
|
|
3971
|
+
// click re-derives it live — the "tab is there, then it's gone" flicker.
|
|
3962
3972
|
function mergeSelClientFields(incoming, prev) {
|
|
3963
3973
|
if (!incoming || Array.isArray(incoming) || Array.isArray(prev) || !prev) return incoming;
|
|
3964
3974
|
if (!incoming.id || incoming.id !== prev.id) return incoming;
|
|
@@ -3968,6 +3978,8 @@ function mergeSelClientFields(incoming, prev) {
|
|
|
3968
3978
|
computed: incoming.computed ?? prev.computed,
|
|
3969
3979
|
customStyles: incoming.customStyles ?? prev.customStyles,
|
|
3970
3980
|
attrs: incoming.attrs ?? prev.attrs,
|
|
3981
|
+
photoKind: incoming.photoKind ?? prev.photoKind,
|
|
3982
|
+
photoAsset: incoming.photoAsset ?? prev.photoAsset,
|
|
3971
3983
|
};
|
|
3972
3984
|
}
|
|
3973
3985
|
|
|
@@ -6072,6 +6084,31 @@ function ArtboardKnobs({ el, onResizeArtboard }) {
|
|
|
6072
6084
|
);
|
|
6073
6085
|
}
|
|
6074
6086
|
|
|
6087
|
+
// feature-photo-editor (Task 13/14) — derive the content-addressed photo asset a
|
|
6088
|
+
// DOM selection points at (an artboard `<img src="assets/<sha8>.<ext>">`). Prefers
|
|
6089
|
+
// the `photoAsset` the resolver stamped (dom-selection.ts), falling back to a scan
|
|
6090
|
+
// of the selection's src/html for a selection that reached the panel via a code
|
|
6091
|
+
// path predating that field. Returns null for a non-photo element.
|
|
6092
|
+
const PHOTO_ASSET_RE = /assets\/[0-9a-f]{8}\.[a-z0-9]+/i;
|
|
6093
|
+
function photoAssetOfSelection(el) {
|
|
6094
|
+
if (!el || Array.isArray(el)) return null;
|
|
6095
|
+
if (el.photoAsset) return el.photoAsset;
|
|
6096
|
+
if ((el.tag || '').toLowerCase() !== 'img') return null;
|
|
6097
|
+
const html = el.html || '';
|
|
6098
|
+
// Once an edit is baked, the element's LIVE src is a `data:` URL (canvas-lib's
|
|
6099
|
+
// PhotoPreviewBridge swaps it in directly) — it never matches PHOTO_ASSET_RE,
|
|
6100
|
+
// so a REPLAYED/serialized selection (a WS resync, or the persisted
|
|
6101
|
+
// `_active.json` restored on boot) that arrives without the dedicated
|
|
6102
|
+
// `photoAsset` field would otherwise permanently lose the Photo tab for any
|
|
6103
|
+
// already-edited photo. The element still carries the `data-photo-asset` tag
|
|
6104
|
+
// the bridge stamped, and that DOES survive into an outerHTML snapshot — check
|
|
6105
|
+
// it before falling back to the plain assets/<sha8> src scan.
|
|
6106
|
+
const tagged = /data-photo-asset="([^"]+)"/.exec(html);
|
|
6107
|
+
if (tagged) return tagged[1];
|
|
6108
|
+
const m = PHOTO_ASSET_RE.exec(`${el.attrs?.src || ''} ${html}`);
|
|
6109
|
+
return m ? m[0] : null;
|
|
6110
|
+
}
|
|
6111
|
+
|
|
6075
6112
|
function InspectorPanel({
|
|
6076
6113
|
selected,
|
|
6077
6114
|
onClose,
|
|
@@ -6092,6 +6129,16 @@ function InspectorPanel({
|
|
|
6092
6129
|
onTabChange,
|
|
6093
6130
|
width,
|
|
6094
6131
|
resizing,
|
|
6132
|
+
// feature-photo-editor (Task 13/15) — the Photo tab + its live-preview /
|
|
6133
|
+
// bg-removal / undo channels. `photoSel` is the annotation-image target
|
|
6134
|
+
// (threaded up separately since the annotation model has no DOM selection);
|
|
6135
|
+
// an artboard `<img>` target is derived from `selected` instead.
|
|
6136
|
+
photoSel,
|
|
6137
|
+
photoRev,
|
|
6138
|
+
onPhotoEdit,
|
|
6139
|
+
onPhotoRemoveBackground,
|
|
6140
|
+
onPhotoRecordEdit,
|
|
6141
|
+
onPhotoUndoRedo,
|
|
6095
6142
|
}) {
|
|
6096
6143
|
// Tab is controllable from the parent (the guided tour drives it to 'css' /
|
|
6097
6144
|
// 'layers' so a spotlight step lands on a real row) but falls back to local
|
|
@@ -6390,10 +6437,41 @@ function InspectorPanel({
|
|
|
6390
6437
|
};
|
|
6391
6438
|
// `selected` may be a single element, an array (multi-select), or null.
|
|
6392
6439
|
const el = Array.isArray(selected) ? selected[0] : selected;
|
|
6440
|
+
// feature-photo-editor (Task 13) — resolve the Photo-tab target. Priority: an
|
|
6441
|
+
// annotation-image threaded up (`photoSel`, no DOM selection) → a Photo-ONLY
|
|
6442
|
+
// panel; else a content-addressed artboard `<img>` selection → Photo alongside
|
|
6443
|
+
// the normal tabs.
|
|
6444
|
+
const photoTarget = photoSel
|
|
6445
|
+
? { asset: photoSel.asset, kind: 'annotation-image' }
|
|
6446
|
+
: (() => {
|
|
6447
|
+
const a = photoAssetOfSelection(el);
|
|
6448
|
+
return a ? { asset: a, kind: 'artboard-img' } : null;
|
|
6449
|
+
})();
|
|
6450
|
+
const photoOnly = photoTarget?.kind === 'annotation-image';
|
|
6451
|
+
// Cmd+Z / Cmd+Shift+Z (or Cmd+Y) while focus is still inside a Photo-tab
|
|
6452
|
+
// slider — mirrors `onKnobKeyDown` above for the same reason: the window-
|
|
6453
|
+
// level shortcut listener bails out whenever `document.activeElement` is an
|
|
6454
|
+
// `<input>` (native text-undo would otherwise win), so a commit-then-Cmd+Z
|
|
6455
|
+
// with the slider still focused needs its own forwarder here.
|
|
6456
|
+
const onPhotoKnobKeyDown = (e) => {
|
|
6457
|
+
if (!(e.metaKey || e.ctrlKey) || e.altKey) return;
|
|
6458
|
+
const k = e.key.toLowerCase();
|
|
6459
|
+
if (k === 'z') {
|
|
6460
|
+
e.preventDefault();
|
|
6461
|
+
onPhotoUndoRedo?.(e.shiftKey ? 'redo' : 'undo');
|
|
6462
|
+
} else if (k === 'y') {
|
|
6463
|
+
e.preventDefault();
|
|
6464
|
+
onPhotoUndoRedo?.('redo');
|
|
6465
|
+
}
|
|
6466
|
+
};
|
|
6467
|
+
// An annotation-image has no element tabs; force Photo. Otherwise honor the
|
|
6468
|
+
// requested tab, but drop off a stale 'photo' tab when the new selection isn't
|
|
6469
|
+
// photo-eligible (so switching from a photo to a normal element lands sanely).
|
|
6470
|
+
const effTab = photoOnly ? 'photo' : tab === 'photo' && !photoTarget ? 'inspect' : tab;
|
|
6393
6471
|
const tabBtn = (id, label, icon) => (
|
|
6394
6472
|
<button
|
|
6395
6473
|
type="button"
|
|
6396
|
-
className={'st-rp-tab' + (
|
|
6474
|
+
className={'st-rp-tab' + (effTab === id ? ' is-active' : '')}
|
|
6397
6475
|
onClick={() => setTab(id)}
|
|
6398
6476
|
>
|
|
6399
6477
|
<StIcon name={icon} size={14} />
|
|
@@ -6409,9 +6487,16 @@ function InspectorPanel({
|
|
|
6409
6487
|
data-tour="inspector"
|
|
6410
6488
|
>
|
|
6411
6489
|
<div className="st-rp-tabs" data-tour="inspector-tabs">
|
|
6412
|
-
{
|
|
6413
|
-
|
|
6414
|
-
|
|
6490
|
+
{photoOnly ? (
|
|
6491
|
+
tabBtn('photo', 'Photo', 'image')
|
|
6492
|
+
) : (
|
|
6493
|
+
<>
|
|
6494
|
+
{tabBtn('inspect', 'Inspect', 'sliders')}
|
|
6495
|
+
{tabBtn('layers', 'Layers', 'layers')}
|
|
6496
|
+
{tabBtn('css', 'CSS', 'code')}
|
|
6497
|
+
{photoTarget ? tabBtn('photo', 'Photo', 'image') : null}
|
|
6498
|
+
</>
|
|
6499
|
+
)}
|
|
6415
6500
|
<button
|
|
6416
6501
|
type="button"
|
|
6417
6502
|
className="st-iconbtn"
|
|
@@ -6447,7 +6532,18 @@ function InspectorPanel({
|
|
|
6447
6532
|
</div>
|
|
6448
6533
|
) : null}
|
|
6449
6534
|
<div className="st-rp-body">
|
|
6450
|
-
{
|
|
6535
|
+
{effTab === 'photo' && photoTarget ? (
|
|
6536
|
+
<div onKeyDown={onPhotoKnobKeyDown}>
|
|
6537
|
+
<PhotoKnobs
|
|
6538
|
+
key={`${photoTarget.asset}:${photoRev ?? 0}`}
|
|
6539
|
+
asset={photoTarget.asset}
|
|
6540
|
+
ColorPicker={ColorPicker}
|
|
6541
|
+
onEdit={(edit) => onPhotoEdit?.(photoTarget.asset, edit)}
|
|
6542
|
+
onRemoveBackground={onPhotoRemoveBackground}
|
|
6543
|
+
onRecordEdit={(before, after) => onPhotoRecordEdit?.(photoTarget.asset, before, after)}
|
|
6544
|
+
/>
|
|
6545
|
+
</div>
|
|
6546
|
+
) : !el ? (
|
|
6451
6547
|
<div className="st-rp-empty">
|
|
6452
6548
|
{/* <p> wrapper — st-rp-empty is a flex column, bare text nodes +
|
|
6453
6549
|
kbd would stack as stretched flex items. */}
|
|
@@ -6455,7 +6551,7 @@ function InspectorPanel({
|
|
|
6455
6551
|
Hold <Kbd>⌘</Kbd> inside the canvas and click an element to inspect it.
|
|
6456
6552
|
</p>
|
|
6457
6553
|
</div>
|
|
6458
|
-
) :
|
|
6554
|
+
) : effTab === 'inspect' ? (
|
|
6459
6555
|
<>
|
|
6460
6556
|
<div className="st-rp-hd">{el.selector || el.tag || 'element'}</div>
|
|
6461
6557
|
<div className="st-insp-row">
|
|
@@ -6504,7 +6600,7 @@ function InspectorPanel({
|
|
|
6504
6600
|
) : null}
|
|
6505
6601
|
<InspectComputed el={el} />
|
|
6506
6602
|
</>
|
|
6507
|
-
) :
|
|
6603
|
+
) : effTab === 'layers' ? (
|
|
6508
6604
|
<>
|
|
6509
6605
|
<div className="st-rp-hd">Layers{layersTree?.nodes?.length ? '' : ' · ancestry'}</div>
|
|
6510
6606
|
{layersTree?.nodes?.length ? (
|
|
@@ -6855,7 +6951,9 @@ function App() {
|
|
|
6855
6951
|
const [activeComps, setActiveComps] = useState([]);
|
|
6856
6952
|
const [timelineFrame, setTimelineFrame] = useState(0);
|
|
6857
6953
|
const [timelinePlaying, setTimelinePlaying] = useState(false);
|
|
6858
|
-
|
|
6954
|
+
// Default off — looping is an explicit opt-in per session, not a surprise
|
|
6955
|
+
// once the user presses Play (rca/issue-video-artboard-loop-defaults-on).
|
|
6956
|
+
const [timelineLoop, setTimelineLoop] = useState(false);
|
|
6859
6957
|
const [timelineMuted, setTimelineMuted] = useState(false);
|
|
6860
6958
|
const [timelineVolume, setTimelineVolume] = useState(1);
|
|
6861
6959
|
const [timelineHeight, setTimelineHeight] = useState(216);
|
|
@@ -6878,6 +6976,15 @@ function App() {
|
|
|
6878
6976
|
useEffect(() => {
|
|
6879
6977
|
timelineCompIdRef.current = timelineCompId;
|
|
6880
6978
|
}, [timelineCompId]);
|
|
6979
|
+
// rca/issue-video-artboard-frame-reset-on-edit — last known playhead, read
|
|
6980
|
+
// from the `timeline-comps` handler below (which fires on every comp
|
|
6981
|
+
// (re)mount, incl. a ⌘R hard iframe reload) to re-seed the fresh Player
|
|
6982
|
+
// instance. A ref, not state, so the handler always sees the latest value
|
|
6983
|
+
// without pulling `timelineFrame` into that big message-listener's deps.
|
|
6984
|
+
const timelineFrameRef = useRef(0);
|
|
6985
|
+
useEffect(() => {
|
|
6986
|
+
timelineFrameRef.current = timelineFrame;
|
|
6987
|
+
}, [timelineFrame]);
|
|
6881
6988
|
// The DCArtboard id the timeline scoped to (from parseCompTimeline). Used for
|
|
6882
6989
|
// /_api/comp-clips + every clip op so the enumerator targets the SAME comp the
|
|
6883
6990
|
// rows came from — `timelineCompId` is the Player's `videocomp-N` id, which
|
|
@@ -6925,6 +7032,17 @@ function App() {
|
|
|
6925
7032
|
// Inspector tab is lifted so View ▸ Layers can open the panel ON the Layers
|
|
6926
7033
|
// tab (the menu item sat disabled as "Phase 12" long after the tab shipped).
|
|
6927
7034
|
const [inspectorTab, setInspectorTab] = useState('inspect');
|
|
7035
|
+
// feature-photo-editor (Task 13) — the annotation-image Photo-tab target,
|
|
7036
|
+
// threaded up from the annotation layer's "Edit Photo…" (the annotation model
|
|
7037
|
+
// has no DOM selection to ride, unlike an artboard `<img>`). `{ asset, strokeId }`
|
|
7038
|
+
// or null. Cleared whenever a normal DOM selection arrives so the two never
|
|
7039
|
+
// fight over the panel. The shell-side photo undo stack lives alongside it.
|
|
7040
|
+
const [photoSel, setPhotoSel] = useState(null);
|
|
7041
|
+
const photoUndoRef = useRef({ undo: [], redo: [] });
|
|
7042
|
+
// Bumped by the Cmd+Z/Cmd+Shift+Z photo-undo handler below so the mounted
|
|
7043
|
+
// PhotoKnobs (keyed on `asset:photoRev`) remounts and re-fetches the sidecar
|
|
7044
|
+
// it just PUT, instead of drifting from its own stale local `edit` state.
|
|
7045
|
+
const [photoRev, setPhotoRev] = useState(0);
|
|
6928
7046
|
// feature-element-editing-robustness Stage C — auto-open the Inspector on the
|
|
6929
7047
|
// CSS tab when a fresh single selection arrives AND no right panel is already
|
|
6930
7048
|
// open. Preference-backed (default ON); disable it in the View menu. Refs keep
|
|
@@ -7113,8 +7231,8 @@ function App() {
|
|
|
7113
7231
|
// persistent prefs broadcast to every open canvas iframe; presentMode is a
|
|
7114
7232
|
// non-destructive "hide ALL chrome + shell, artboards only" overlay with an
|
|
7115
7233
|
// Esc / floating-pill escape hatch back to the chrome.
|
|
7116
|
-
const [minimapVisible, setMinimapVisible] = useState(
|
|
7117
|
-
const [zoomCtlVisible, setZoomCtlVisible] = useState(
|
|
7234
|
+
const [minimapVisible, setMinimapVisible] = useState(() => readBoolStore(MINIMAP_STORE, false));
|
|
7235
|
+
const [zoomCtlVisible, setZoomCtlVisible] = useState(() => readBoolStore(ZOOMCTL_STORE, false));
|
|
7118
7236
|
const [presentMode, setPresentMode] = useState(false);
|
|
7119
7237
|
// P2/P3 (Plan C) — top-bar live state. (Zoom lives in the canvas toolbar pill,
|
|
7120
7238
|
// so the top bar no longer mirrors it.)
|
|
@@ -7145,6 +7263,96 @@ function App() {
|
|
|
7145
7263
|
[activePath]
|
|
7146
7264
|
);
|
|
7147
7265
|
|
|
7266
|
+
// ── feature-photo-editor — the Photo tab's three channels ─────────────────
|
|
7267
|
+
// (1) live preview: broadcast the edit DOWN to the active canvas iframe, whose
|
|
7268
|
+
// canvas-lib `PhotoPreviewBridge` bakes the composite and swaps it directly
|
|
7269
|
+
// into the matching `<img>`/`<image>` element's src/href (iteration 2 — see
|
|
7270
|
+
// the bridge's own header comment for why it's a direct swap, not an
|
|
7271
|
+
// overlay). (2) undo: a shell-side stack (photo edits are sidecar writes,
|
|
7272
|
+
// not the canvas-side source-edit stack). (3) background removal: the
|
|
7273
|
+
// client-side @imgly ML flow (Task 12).
|
|
7274
|
+
const onPhotoEdit = useCallback(
|
|
7275
|
+
(asset, edit) => postToActiveCanvas({ dgn: 'photo-preview', asset, edit }),
|
|
7276
|
+
[postToActiveCanvas]
|
|
7277
|
+
);
|
|
7278
|
+
const onPhotoRecordEdit = useCallback((asset, before, after) => {
|
|
7279
|
+
photoUndoRef.current.undo.push({ asset, before, after });
|
|
7280
|
+
photoUndoRef.current.redo.length = 0;
|
|
7281
|
+
}, []);
|
|
7282
|
+
// Pops the shell-side photo-undo stack, re-persists the reverted edit through
|
|
7283
|
+
// the same PUT route PhotoKnobs itself uses, re-broadcasts it to the live
|
|
7284
|
+
// preview, and bumps `photoRev` so the mounted PhotoKnobs (keyed on
|
|
7285
|
+
// `asset:photoRev`) remounts and re-fetches instead of drifting from its own
|
|
7286
|
+
// stale local state. Returns false (does nothing) when the requested stack is
|
|
7287
|
+
// empty, so callers can fall through to the canvas's own undo stack.
|
|
7288
|
+
const performPhotoUndo = useCallback(
|
|
7289
|
+
(redo) => {
|
|
7290
|
+
const stack = redo ? photoUndoRef.current.redo : photoUndoRef.current.undo;
|
|
7291
|
+
if (!stack.length) return false;
|
|
7292
|
+
const entry = stack.pop();
|
|
7293
|
+
(redo ? photoUndoRef.current.undo : photoUndoRef.current.redo).push(entry);
|
|
7294
|
+
const edit = (redo ? entry.after : entry.before) || {};
|
|
7295
|
+
onPhotoEdit(entry.asset, edit);
|
|
7296
|
+
fetch(`/_api/photo-edit?asset=${encodeURIComponent(entry.asset)}`, {
|
|
7297
|
+
method: 'PUT',
|
|
7298
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7299
|
+
body: JSON.stringify(edit),
|
|
7300
|
+
}).catch(() => {});
|
|
7301
|
+
setPhotoRev((v) => v + 1);
|
|
7302
|
+
return true;
|
|
7303
|
+
},
|
|
7304
|
+
[onPhotoEdit]
|
|
7305
|
+
);
|
|
7306
|
+
// Task 12 — magic background removal, entirely client-side (WASM/WebGPU via
|
|
7307
|
+
// @imgly/background-removal — NEVER the -node native-addon variant, DDR-070).
|
|
7308
|
+
// The lib is DYNAMICALLY imported so a session that never removes a background
|
|
7309
|
+
// pays zero bundle cost (the lazy-bundle guarantee). Fetches the source bytes,
|
|
7310
|
+
// runs the matte, uploads it content-addressed through the SAME /_api/asset lane
|
|
7311
|
+
// drag-drop uses, and returns the new `assets/<sha8>.png` for PhotoEdit.
|
|
7312
|
+
const onPhotoRemoveBackground = useCallback(
|
|
7313
|
+
async (asset) => {
|
|
7314
|
+
// Scan/shimmer reveal on the live photo while the ML pass runs, the same
|
|
7315
|
+
// "something is actively happening here" language as the agent-edit
|
|
7316
|
+
// artboard rim (artboard-activity-overlay.tsx) — but applied directly to
|
|
7317
|
+
// the photo element itself (a `data-photo-busy` attribute + injected CSS
|
|
7318
|
+
// mask sweep, inspect.ts) rather than a floating tracked overlay. A
|
|
7319
|
+
// floating decoy is exactly the architecture DDR-161's addendum tore out
|
|
7320
|
+
// for the live-edit preview (z-index/resize/hit-testing bugs); an
|
|
7321
|
+
// attribute toggle on the real element sidesteps that whole class.
|
|
7322
|
+
postToActiveCanvas({ dgn: 'photo-busy', asset, busy: true });
|
|
7323
|
+
try {
|
|
7324
|
+
const srcRes = await fetch(`/${asset.replace(/^\/+/, '')}`);
|
|
7325
|
+
if (!srcRes.ok) return null;
|
|
7326
|
+
const srcBlob = await srcRes.blob();
|
|
7327
|
+
const { removeBackground } = await import('@imgly/background-removal');
|
|
7328
|
+
// Inference is 100% CLIENT-SIDE (WASM/WebGPU) — the user's pixels NEVER
|
|
7329
|
+
// leave the browser. Only the public model weights (~40 MB, identical for
|
|
7330
|
+
// everyone) are fetched from IMG.LY's default host on first use; they're
|
|
7331
|
+
// too large to bundle in the npm tarball (`resources.json` ships empty).
|
|
7332
|
+
// Self-hosting those weights off the dev server for offline / air-gapped
|
|
7333
|
+
// parity is the flagged Task-11 follow-up (a one-time download step) — until
|
|
7334
|
+
// then the default host provides them, with no pixel-privacy exposure.
|
|
7335
|
+
const matte = await removeBackground(srcBlob);
|
|
7336
|
+
const up = await fetch('/_api/asset', {
|
|
7337
|
+
method: 'POST',
|
|
7338
|
+
headers: { 'content-type': matte.type || 'image/png' },
|
|
7339
|
+
body: matte,
|
|
7340
|
+
});
|
|
7341
|
+
const j = await up.json().catch(() => ({}));
|
|
7342
|
+
if (up.ok && j.path) return { maskAsset: j.path };
|
|
7343
|
+
return null;
|
|
7344
|
+
} catch (err) {
|
|
7345
|
+
console.error('[photo] background removal failed', err);
|
|
7346
|
+
return null;
|
|
7347
|
+
} finally {
|
|
7348
|
+
// Always clears, success or failure — an errored pass must not leave
|
|
7349
|
+
// the shimmer stuck on the photo forever.
|
|
7350
|
+
postToActiveCanvas({ dgn: 'photo-busy', asset, busy: false });
|
|
7351
|
+
}
|
|
7352
|
+
},
|
|
7353
|
+
[postToActiveCanvas]
|
|
7354
|
+
);
|
|
7355
|
+
|
|
7148
7356
|
// DDR-150 dogfood #7 — a file dragged from Finder and dropped on a shell area
|
|
7149
7357
|
// with no drop target used to make the browser NAVIGATE AWAY to the file
|
|
7150
7358
|
// (file:// replaces the app). Document-level guard: always cancel the default
|
|
@@ -7533,6 +7741,16 @@ function App() {
|
|
|
7533
7741
|
localStorage.setItem(SECTIONS_STORE, JSON.stringify(sectionsExpanded));
|
|
7534
7742
|
} catch {}
|
|
7535
7743
|
}, [sectionsExpanded]);
|
|
7744
|
+
useEffect(() => {
|
|
7745
|
+
try {
|
|
7746
|
+
localStorage.setItem(MINIMAP_STORE, minimapVisible ? '1' : '0');
|
|
7747
|
+
} catch {}
|
|
7748
|
+
}, [minimapVisible]);
|
|
7749
|
+
useEffect(() => {
|
|
7750
|
+
try {
|
|
7751
|
+
localStorage.setItem(ZOOMCTL_STORE, zoomCtlVisible ? '1' : '0');
|
|
7752
|
+
} catch {}
|
|
7753
|
+
}, [zoomCtlVisible]);
|
|
7536
7754
|
|
|
7537
7755
|
const toggleSection = useCallback((label, defaultOpen) => {
|
|
7538
7756
|
setSectionsExpanded((prev) => {
|
|
@@ -7863,8 +8081,11 @@ function App() {
|
|
|
7863
8081
|
useEffect(() => {
|
|
7864
8082
|
if (gitStatus?.repo === false) return; // solo / non-git project — no remote
|
|
7865
8083
|
refreshRemoteSync();
|
|
7866
|
-
|
|
7867
|
-
|
|
8084
|
+
// Poll in the background too — not only while Changes is open — so the dock's
|
|
8085
|
+
// "Get latest" nudge surfaces a teammate's publish proactively. Slower cadence
|
|
8086
|
+
// when the panel is closed to stay network-polite (the ahead/behind probe is
|
|
8087
|
+
// TTL-cached server-side, so a missed tick is cheap to re-issue).
|
|
8088
|
+
const id = setInterval(refreshRemoteSync, changesOpen ? 60000 : 120000);
|
|
7868
8089
|
return () => clearInterval(id);
|
|
7869
8090
|
}, [gitStatus?.repo, changesOpen, refreshRemoteSync]);
|
|
7870
8091
|
|
|
@@ -8245,7 +8466,22 @@ function App() {
|
|
|
8245
8466
|
// canvas must not be able to plant a selection with no user gesture. The
|
|
8246
8467
|
// user can only click the visible active canvas, so a non-active source is
|
|
8247
8468
|
// never a legitimate selection.
|
|
8248
|
-
|
|
8469
|
+
//
|
|
8470
|
+
// The SAME gate now covers the privileged SOURCE-WRITE relays — `edit-text`
|
|
8471
|
+
// (inline text commit) and `apply-edit` (undo/redo re-application). Both
|
|
8472
|
+
// ride into a main-origin `.tsx` rewrite; a write path is strictly more
|
|
8473
|
+
// dangerous than a selection, yet was previously ungated (ethical-hacker
|
|
8474
|
+
// F-B, DDR-160 follow-up). A background/synced untrusted canvas (DDR-054)
|
|
8475
|
+
// must not drive a gestureless write into another canvas — and inline edits
|
|
8476
|
+
// + Cmd+Z always originate in the ACTIVE canvas the user is looking at, so
|
|
8477
|
+
// a non-active source is never legitimate here either.
|
|
8478
|
+
if (
|
|
8479
|
+
m.dgn === 'select' ||
|
|
8480
|
+
m.dgn === 'select-set' ||
|
|
8481
|
+
m.dgn === 'clear-select' ||
|
|
8482
|
+
m.dgn === 'edit-text' ||
|
|
8483
|
+
m.dgn === 'apply-edit'
|
|
8484
|
+
) {
|
|
8249
8485
|
const activeWin =
|
|
8250
8486
|
activePath && activePath !== SYSTEM_TAB
|
|
8251
8487
|
? iframesRef.current.get(activePath)?.contentWindow
|
|
@@ -8253,10 +8489,12 @@ function App() {
|
|
|
8253
8489
|
if (e.source !== activeWin) return;
|
|
8254
8490
|
}
|
|
8255
8491
|
if (m.dgn === 'select' && m.selection) {
|
|
8492
|
+
setPhotoSel(null); // a DOM selection supersedes an annotation-image Photo target
|
|
8256
8493
|
wsSend({ type: 'select', selection: m.selection });
|
|
8257
8494
|
setSelected(m.selection);
|
|
8258
8495
|
maybeAutoOpenInspectorOnSelect(m.selection); // Stage C
|
|
8259
8496
|
} else if (m.dgn === 'select-set') {
|
|
8497
|
+
setPhotoSel(null);
|
|
8260
8498
|
// Canvas multi-select. Payload shape:
|
|
8261
8499
|
// null → empty selection
|
|
8262
8500
|
// Selection → length-1 (back-compat with legacy single-element shape)
|
|
@@ -8280,6 +8518,7 @@ function App() {
|
|
|
8280
8518
|
maybeAutoOpenInspectorOnSelect(payload); // Stage C
|
|
8281
8519
|
}
|
|
8282
8520
|
} else if (m.dgn === 'clear-select') {
|
|
8521
|
+
setPhotoSel(null);
|
|
8283
8522
|
wsSend({ type: 'clear-select' });
|
|
8284
8523
|
setSelected(null);
|
|
8285
8524
|
} else if (m.dgn === 'edit-text' && m.id) {
|
|
@@ -8301,7 +8540,15 @@ function App() {
|
|
|
8301
8540
|
fetch('/_api/edit-text', {
|
|
8302
8541
|
method: 'POST',
|
|
8303
8542
|
headers: { 'content-type': 'application/json' },
|
|
8304
|
-
body: JSON.stringify({
|
|
8543
|
+
body: JSON.stringify({
|
|
8544
|
+
canvas: m.file,
|
|
8545
|
+
id: m.id,
|
|
8546
|
+
text: m.text ?? '',
|
|
8547
|
+
// Context for editing `{variable}` text — which rendered instance +
|
|
8548
|
+
// its pre-edit text, so the engine targets the right source string.
|
|
8549
|
+
...(typeof m.occurrence === 'number' ? { occurrence: m.occurrence } : {}),
|
|
8550
|
+
...(typeof m.before === 'string' ? { before: m.before } : {}),
|
|
8551
|
+
}),
|
|
8305
8552
|
})
|
|
8306
8553
|
.then((r) => r.json().catch(() => ({})))
|
|
8307
8554
|
.then((j) => {
|
|
@@ -8334,6 +8581,11 @@ function App() {
|
|
|
8334
8581
|
} else {
|
|
8335
8582
|
url = '/_api/edit-text';
|
|
8336
8583
|
body = { canvas: m.canvas, id: m.id, text: value ?? '' };
|
|
8584
|
+
// Undo/redo of a `{variable}` text edit needs to re-target the source
|
|
8585
|
+
// string: the occurrence + the value currently on disk (`from` — the
|
|
8586
|
+
// side we're replacing FROM). Harmless for literal text.
|
|
8587
|
+
if (typeof m.occurrence === 'number') body.occurrence = m.occurrence;
|
|
8588
|
+
if (typeof m.from === 'string') body.before = m.from;
|
|
8337
8589
|
}
|
|
8338
8590
|
editApplyChainRef.current = editApplyChainRef.current
|
|
8339
8591
|
.catch(() => {})
|
|
@@ -8510,38 +8762,54 @@ function App() {
|
|
|
8510
8762
|
const activeWin = activePath ? iframesRef.current.get(activePath)?.contentWindow : null;
|
|
8511
8763
|
if (e.source === activeWin && typeof m.id === 'string') pasteStyleRef.current?.(m.id);
|
|
8512
8764
|
} else if (m.dgn === 'insert-request') {
|
|
8513
|
-
// Stage I3 — insert a synthesized div/text/image relative to `refId
|
|
8765
|
+
// Stage I3 — insert a synthesized div/text/image relative to `refId`,
|
|
8766
|
+
// OR — empty-artboard fallback (tool-palette "+ Element" on a fresh
|
|
8767
|
+
// artboard with no elements yet) — as a child of `artboardId`. Exactly
|
|
8768
|
+
// one of the two must be present; the artboardId variant only makes
|
|
8769
|
+
// sense inside-start/inside-end (no sibling to be before/after).
|
|
8514
8770
|
const activeWin = activePath ? iframesRef.current.get(activePath)?.contentWindow : null;
|
|
8771
|
+
const hasRefId = typeof m.refId === 'string';
|
|
8772
|
+
const hasArtboardId = typeof m.artboardId === 'string';
|
|
8773
|
+
const okPosition =
|
|
8774
|
+
m.position === 'before' ||
|
|
8775
|
+
m.position === 'after' ||
|
|
8776
|
+
m.position === 'inside-start' ||
|
|
8777
|
+
m.position === 'inside-end';
|
|
8515
8778
|
const okShape =
|
|
8516
|
-
|
|
8779
|
+
hasRefId !== hasArtboardId &&
|
|
8517
8780
|
(m.kind === 'div' || m.kind === 'text' || m.kind === 'image') &&
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
m.position === 'inside-start' ||
|
|
8521
|
-
m.position === 'inside-end');
|
|
8781
|
+
okPosition &&
|
|
8782
|
+
(!hasArtboardId || m.position === 'inside-start' || m.position === 'inside-end');
|
|
8522
8783
|
if (e.source === activeWin && okShape) {
|
|
8523
8784
|
insertElementShellRef.current?.(m.refId, m.position, m.kind, {
|
|
8785
|
+
artboardId: hasArtboardId ? m.artboardId : undefined,
|
|
8524
8786
|
src: typeof m.src === 'string' ? m.src : undefined,
|
|
8525
8787
|
refIndex: Number.isInteger(m.refIndex) ? m.refIndex : undefined,
|
|
8526
8788
|
});
|
|
8527
8789
|
}
|
|
8528
8790
|
} else if (m.dgn === 'insert-image-request') {
|
|
8529
|
-
// Stage F/I3 — "Insert ▸ Image" from the canvas context menu
|
|
8530
|
-
//
|
|
8531
|
-
//
|
|
8532
|
-
//
|
|
8791
|
+
// Stage F/I3 — "Insert ▸ Image" from the canvas context menu, or the
|
|
8792
|
+
// tool-palette's empty-artboard fallback (`artboardId` in place of
|
|
8793
|
+
// `refId`). An image needs a contained asset src, so the shell opens
|
|
8794
|
+
// the AssetPicker; the pick then drives insertElementShell(kind:'image').
|
|
8795
|
+
// Confused-deputy gated + pinned to the active canvas like the other
|
|
8796
|
+
// request verbs.
|
|
8533
8797
|
const activeWin = activePath ? iframesRef.current.get(activePath)?.contentWindow : null;
|
|
8798
|
+
const hasRefId = typeof m.refId === 'string';
|
|
8799
|
+
const hasArtboardId = typeof m.artboardId === 'string';
|
|
8534
8800
|
const okShape =
|
|
8535
|
-
|
|
8801
|
+
hasRefId !== hasArtboardId &&
|
|
8536
8802
|
(m.position === 'before' ||
|
|
8537
8803
|
m.position === 'after' ||
|
|
8538
8804
|
m.position === 'inside-start' ||
|
|
8539
|
-
m.position === 'inside-end')
|
|
8805
|
+
m.position === 'inside-end') &&
|
|
8806
|
+
(!hasArtboardId || m.position === 'inside-start' || m.position === 'inside-end');
|
|
8540
8807
|
if (e.source === activeWin && okShape) {
|
|
8541
8808
|
openAssetPickerRef.current?.({
|
|
8542
8809
|
purpose: 'insert-image',
|
|
8543
8810
|
canvas: activePath,
|
|
8544
8811
|
refId: m.refId,
|
|
8812
|
+
artboardId: hasArtboardId ? m.artboardId : undefined,
|
|
8545
8813
|
position: m.position,
|
|
8546
8814
|
refIndex: Number.isInteger(m.refIndex) ? m.refIndex : undefined,
|
|
8547
8815
|
});
|
|
@@ -8573,6 +8841,27 @@ function App() {
|
|
|
8573
8841
|
before: typeof m.before === 'string' ? m.before : null,
|
|
8574
8842
|
});
|
|
8575
8843
|
}
|
|
8844
|
+
} else if (m.dgn === 'edit-annotation-photo-request') {
|
|
8845
|
+
// feature-photo-editor (Task 17) — "Edit Photo…" on an annotation
|
|
8846
|
+
// ImageStroke. The annotation model has no data-cd-id / DOM selection, so
|
|
8847
|
+
// it can't ride the normal select path; instead the canvas posts its
|
|
8848
|
+
// `assets/<sha8>.<ext>` href up and the shell opens the Photo-only tab on
|
|
8849
|
+
// it. Same confused-deputy gate (DDR-054) + active-canvas pin as F3.
|
|
8850
|
+
const activeWin = activePath ? iframesRef.current.get(activePath)?.contentWindow : null;
|
|
8851
|
+
const asset = PHOTO_ASSET_RE.exec(typeof m.asset === 'string' ? m.asset : '')?.[0] || null;
|
|
8852
|
+
if (e.source === activeWin && typeof m.id === 'string' && asset) {
|
|
8853
|
+
setPhotoSel({ asset, strokeId: m.id });
|
|
8854
|
+
openRightPanel('inspector');
|
|
8855
|
+
setInspectorTab('photo');
|
|
8856
|
+
}
|
|
8857
|
+
} else if (m.dgn === 'open-sticker-picker') {
|
|
8858
|
+
// Phase 4 (whiteboard-improvements) — the toolbar's Stickers button.
|
|
8859
|
+
// Confused-deputy gated + pinned to the active canvas like the other
|
|
8860
|
+
// request verbs above.
|
|
8861
|
+
const activeWin = activePath ? iframesRef.current.get(activePath)?.contentWindow : null;
|
|
8862
|
+
if (e.source === activeWin) {
|
|
8863
|
+
openStickerPickerRef.current?.({ canvas: activePath });
|
|
8864
|
+
}
|
|
8576
8865
|
} else if (m.dgn === 'insert-artboard-request') {
|
|
8577
8866
|
// Stage I4 — insert a new empty artboard from a screen-size preset.
|
|
8578
8867
|
const activeWin = activePath ? iframesRef.current.get(activePath)?.contentWindow : null;
|
|
@@ -8609,7 +8898,12 @@ function App() {
|
|
|
8609
8898
|
}
|
|
8610
8899
|
} else if (m.dgn === 'open-inspector') {
|
|
8611
8900
|
// Phase 12 — context-menu "Inspect" / tool-palette Inspect opens the right panel.
|
|
8901
|
+
// feature-photo-editor (Task 16) — an optional `tab` lands the panel on a
|
|
8902
|
+
// specific tab (the element "Edit Photo…" entry passes `tab: 'photo'`; the
|
|
8903
|
+
// img is already selected via select-set, so the Photo tab derives its
|
|
8904
|
+
// target from that selection's `photoAsset`).
|
|
8612
8905
|
openRightPanel('inspector');
|
|
8906
|
+
if (typeof m.tab === 'string') setInspectorTab(m.tab);
|
|
8613
8907
|
} else if (m.dgn === 'present-enter') {
|
|
8614
8908
|
// Canvas tool-palette "Presentation mode" button — Present Mode is a
|
|
8615
8909
|
// shell-level state (hides the menubar / sidebar / panels), so the
|
|
@@ -8703,6 +8997,23 @@ function App() {
|
|
|
8703
8997
|
height: Math.max(1, Math.round(Number(c.height) || 0)),
|
|
8704
8998
|
}));
|
|
8705
8999
|
setActiveComps(safe);
|
|
9000
|
+
// rca/issue-video-artboard-frame-reset-on-edit — the SAME comp
|
|
9001
|
+
// re-announcing means its Player just remounted (an edit-triggered
|
|
9002
|
+
// HMR remount, or a ⌘R hard iframe reload that wiped video-comp.tsx's
|
|
9003
|
+
// own module-scope frame mirror too). The shell's `timelineFrame`
|
|
9004
|
+
// survives either way (this component doesn't remount) — push it back
|
|
9005
|
+
// down so the fresh Player opens where the Timeline left off, instead
|
|
9006
|
+
// of the poster-frame default. Skip on a genuinely new comp/canvas
|
|
9007
|
+
// (no prior id, or frame still at its post-switch 0).
|
|
9008
|
+
const prevCompId = timelineCompIdRef.current;
|
|
9009
|
+
const stillPresent = prevCompId && safe.some((c) => c.id === prevCompId);
|
|
9010
|
+
if (stillPresent && timelineFrameRef.current > 0) {
|
|
9011
|
+
postToActiveCanvas({
|
|
9012
|
+
dgn: 'timeline-seek',
|
|
9013
|
+
frame: timelineFrameRef.current,
|
|
9014
|
+
id: prevCompId,
|
|
9015
|
+
});
|
|
9016
|
+
}
|
|
8706
9017
|
} else if (m.dgn === 'timeline-frame' && typeof m.frame === 'number') {
|
|
8707
9018
|
// Live playhead mirror from the Player (preview scrub/playback).
|
|
8708
9019
|
const activeWin =
|
|
@@ -8714,6 +9025,17 @@ function App() {
|
|
|
8714
9025
|
// artboard's Player must not fight it for the readout (multi-comp canvas).
|
|
8715
9026
|
if (m.id && timelineCompIdRef.current && m.id !== timelineCompIdRef.current) return;
|
|
8716
9027
|
if (Number.isFinite(m.frame)) setTimelineFrame(Math.max(0, Math.round(m.frame)));
|
|
9028
|
+
} else if (m.dgn === 'timeline-ended') {
|
|
9029
|
+
// The Player stopped itself at the last frame (loop off) — resync the
|
|
9030
|
+
// shell's Play/Pause button, which otherwise has no way to learn
|
|
9031
|
+
// playback ended on its own (rca/issue-video-artboard-loop-defaults-on).
|
|
9032
|
+
const activeWin =
|
|
9033
|
+
activePath && activePath !== SYSTEM_TAB
|
|
9034
|
+
? iframesRef.current.get(activePath)?.contentWindow
|
|
9035
|
+
: null;
|
|
9036
|
+
if (e.source !== activeWin) return;
|
|
9037
|
+
if (m.id && timelineCompIdRef.current && m.id !== timelineCompIdRef.current) return;
|
|
9038
|
+
setTimelinePlaying(false);
|
|
8717
9039
|
} else if (m.dgn === 'toggle-palette') {
|
|
8718
9040
|
// ⌘K pressed while focus was inside the canvas iframe — the injected
|
|
8719
9041
|
// inspector forwards the chord here since the iframe's keydown never
|
|
@@ -8726,6 +9048,8 @@ function App() {
|
|
|
8726
9048
|
else if (m.id === 'inspector') toggleRightPanel('inspector');
|
|
8727
9049
|
else if (m.id === 'assistant' && isNativeApp()) toggleRightPanel('assistant');
|
|
8728
9050
|
else if (m.id === 'comments') toggleRightPanel('comments');
|
|
9051
|
+
else if (m.id === 'changes') toggleRightPanel('changes');
|
|
9052
|
+
else if (m.id === 'timeline') toggleTimeline();
|
|
8729
9053
|
else if (m.id === 'export') setExportDialog({ mode: 'export' });
|
|
8730
9054
|
else if (m.id === 'handoff') setExportDialog({ mode: 'handoff' });
|
|
8731
9055
|
} else if (m.dgn === 'open-export') {
|
|
@@ -8736,6 +9060,11 @@ function App() {
|
|
|
8736
9060
|
mode: 'export',
|
|
8737
9061
|
scope: m.detail && typeof m.detail.scope === 'string' ? m.detail.scope : undefined,
|
|
8738
9062
|
});
|
|
9063
|
+
} else if (m.dgn === 'open-timeline-request') {
|
|
9064
|
+
// Artboard-chrome context menu's "Open Timeline" (video-comp artboards
|
|
9065
|
+
// only). Scope the Timeline to the right-clicked artboard, then open it.
|
|
9066
|
+
if (typeof m.artboardId === 'string') setCanvasActiveArtboard(m.artboardId.slice(0, 120));
|
|
9067
|
+
setTimelineOpen(true);
|
|
8739
9068
|
} else if (m.dgn === 'loaded' && m.file) {
|
|
8740
9069
|
// iframe finished loading — drop the compile skeleton, push current
|
|
8741
9070
|
// comments + carry over focused pin if any
|
|
@@ -8885,6 +9214,7 @@ function App() {
|
|
|
8885
9214
|
zoomCtlVisible,
|
|
8886
9215
|
broadcastChrome,
|
|
8887
9216
|
activePath,
|
|
9217
|
+
postToActiveCanvas,
|
|
8888
9218
|
]);
|
|
8889
9219
|
|
|
8890
9220
|
// Tell the active canvas iframe to drop any persistent selection (canvas
|
|
@@ -9269,6 +9599,7 @@ function App() {
|
|
|
9269
9599
|
'/_api/insert-element',
|
|
9270
9600
|
{
|
|
9271
9601
|
refId,
|
|
9602
|
+
artboardId: typeof opts.artboardId === 'string' ? opts.artboardId : undefined,
|
|
9272
9603
|
position,
|
|
9273
9604
|
kind,
|
|
9274
9605
|
src: typeof opts.src === 'string' ? opts.src : undefined,
|
|
@@ -9474,8 +9805,11 @@ function App() {
|
|
|
9474
9805
|
t.tagName === 'SELECT' ||
|
|
9475
9806
|
t.isContentEditable);
|
|
9476
9807
|
if (editable) return;
|
|
9477
|
-
|
|
9808
|
+
// Suppress the WKWebView back-nav unconditionally once focus isn't editable —
|
|
9809
|
+
// the activePath/SYSTEM_TAB check below is app logic, not default-action gating,
|
|
9810
|
+
// so it must not gate preventDefault (that gap caused the desktop "Starting…" hang).
|
|
9478
9811
|
e.preventDefault();
|
|
9812
|
+
if (!activePath || activePath === SYSTEM_TAB) return;
|
|
9479
9813
|
const one = Array.isArray(selected) ? (selected.length === 1 ? selected[0] : null) : selected;
|
|
9480
9814
|
if (one?.artboardId && !one.id) deleteArtboardShell(one.artboardId);
|
|
9481
9815
|
};
|
|
@@ -9491,6 +9825,16 @@ function App() {
|
|
|
9491
9825
|
useEffect(() => {
|
|
9492
9826
|
openAssetPickerRef.current = (req) => setAssetPickerReq(req);
|
|
9493
9827
|
}, []);
|
|
9828
|
+
|
|
9829
|
+
// Phase 4 (whiteboard-improvements) — { canvas: activePath } when open, null
|
|
9830
|
+
// when closed. Mirrors assetPickerReq's ref-indirection (the message
|
|
9831
|
+
// listener below is set up with a minimal dep array, so it reaches a FRESH
|
|
9832
|
+
// opener via a ref rather than needing setStickerPickerReq in its own deps).
|
|
9833
|
+
const [stickerPickerReq, setStickerPickerReq] = useState(null);
|
|
9834
|
+
const openStickerPickerRef = useRef(null);
|
|
9835
|
+
useEffect(() => {
|
|
9836
|
+
openStickerPickerRef.current = (req) => setStickerPickerReq(req);
|
|
9837
|
+
}, []);
|
|
9494
9838
|
const onAssetPicked = useCallback(
|
|
9495
9839
|
(pickedPath) => {
|
|
9496
9840
|
const req = assetPickerReq;
|
|
@@ -9506,6 +9850,7 @@ function App() {
|
|
|
9506
9850
|
}
|
|
9507
9851
|
if (req.purpose === 'insert-image') {
|
|
9508
9852
|
insertElementShell(req.refId, req.position || 'after', 'image', {
|
|
9853
|
+
artboardId: req.artboardId,
|
|
9509
9854
|
src: pickedPath,
|
|
9510
9855
|
refIndex: req.refIndex,
|
|
9511
9856
|
});
|
|
@@ -9551,6 +9896,47 @@ function App() {
|
|
|
9551
9896
|
},
|
|
9552
9897
|
[assetPickerReq, insertElementShell, activePath, recordSourceEdit, postToActiveCanvas]
|
|
9553
9898
|
);
|
|
9899
|
+
|
|
9900
|
+
// Phase 4 (whiteboard-improvements) — a bundled sticker has no project asset
|
|
9901
|
+
// path yet (it lives in MAUDE's own STICKERS_DIR, main-origin-only per
|
|
9902
|
+
// DDR-054), so re-upload its bytes through the SAME /_api/asset lane every
|
|
9903
|
+
// other image source uses (content-addressed, canvas-origin-allowlisted) —
|
|
9904
|
+
// then relay the resulting project-relative path down to the canvas exactly
|
|
9905
|
+
// like replace-annotation-media above (the annotation model owns its own
|
|
9906
|
+
// strokes; the shell performs no stroke write of its own).
|
|
9907
|
+
const onStickerPicked = useCallback(
|
|
9908
|
+
async (sticker) => {
|
|
9909
|
+
const req = stickerPickerReq;
|
|
9910
|
+
setStickerPickerReq(null);
|
|
9911
|
+
if (!req || !sticker?.url) return;
|
|
9912
|
+
// G3 security (DDR-152) — same re-check as onAssetPicked: the request
|
|
9913
|
+
// captured the active canvas when the picker opened; abort rather than
|
|
9914
|
+
// insert into whatever canvas happens to be active now.
|
|
9915
|
+
if (req.canvas && req.canvas !== activePath) {
|
|
9916
|
+
console.warn('[sticker-picker] active canvas changed since request — aborting');
|
|
9917
|
+
return;
|
|
9918
|
+
}
|
|
9919
|
+
try {
|
|
9920
|
+
const blob = await fetch(sticker.url).then((r) =>
|
|
9921
|
+
r.ok ? r.blob() : Promise.reject(new Error(`HTTP ${r.status}`))
|
|
9922
|
+
);
|
|
9923
|
+
const res = await fetch('/_api/asset', {
|
|
9924
|
+
method: 'POST',
|
|
9925
|
+
headers: { 'content-type': blob.type || 'image/png' },
|
|
9926
|
+
body: blob,
|
|
9927
|
+
});
|
|
9928
|
+
const j = await res.json().catch(() => ({}));
|
|
9929
|
+
if (!res.ok || !j.path) {
|
|
9930
|
+
console.warn('[sticker-picker]', j.error || `upload failed (HTTP ${res.status})`);
|
|
9931
|
+
return;
|
|
9932
|
+
}
|
|
9933
|
+
postToActiveCanvas({ dgn: 'insert-sticker', path: j.path });
|
|
9934
|
+
} catch {
|
|
9935
|
+
console.warn('[sticker-picker] could not load or upload that sticker');
|
|
9936
|
+
}
|
|
9937
|
+
},
|
|
9938
|
+
[stickerPickerReq, activePath, postToActiveCanvas]
|
|
9939
|
+
);
|
|
9554
9940
|
// Media-section "Replace…" (CssKnobs) → open the picker in replace mode with
|
|
9555
9941
|
// the element's current src as the undo before-value (captured from the
|
|
9556
9942
|
// Selection's `attrs.src`, not the resolved URL).
|
|
@@ -9654,8 +10040,19 @@ function App() {
|
|
|
9654
10040
|
) {
|
|
9655
10041
|
return; // the timeline shortcuts effect claims it
|
|
9656
10042
|
}
|
|
9657
|
-
e.preventDefault();
|
|
9658
10043
|
const redo = e.key === 'y' || e.key === 'Y' || e.shiftKey;
|
|
10044
|
+
// feature-photo-editor — photo edits are sidecar writes (`/_api/photo-edit`),
|
|
10045
|
+
// invisible to the canvas's own source-edit undo stack, so they need
|
|
10046
|
+
// their own owner here. Claims the keypress only while the Photo tab
|
|
10047
|
+
// is the one on screen AND it actually has something to undo/redo —
|
|
10048
|
+
// otherwise falls through to the canvas stack as before. (Focus still
|
|
10049
|
+
// inside a Photo-tab slider is handled separately by
|
|
10050
|
+
// `onPhotoKnobKeyDown`, mirroring `onKnobKeyDown` for CSS knobs.)
|
|
10051
|
+
if (inspectorTab === 'photo' && performPhotoUndo(redo)) {
|
|
10052
|
+
e.preventDefault();
|
|
10053
|
+
return;
|
|
10054
|
+
}
|
|
10055
|
+
e.preventDefault();
|
|
9659
10056
|
postToActiveCanvas({ dgn: redo ? 'redo' : 'undo' });
|
|
9660
10057
|
return;
|
|
9661
10058
|
}
|
|
@@ -9713,6 +10110,12 @@ function App() {
|
|
|
9713
10110
|
setExportDialog({ mode: 'handoff' });
|
|
9714
10111
|
return;
|
|
9715
10112
|
}
|
|
10113
|
+
// Cmd+Shift+T — toggle the Timeline (video-comp scrub) dock.
|
|
10114
|
+
if (meta && e.shiftKey && (e.key === 't' || e.key === 'T')) {
|
|
10115
|
+
e.preventDefault();
|
|
10116
|
+
toggleTimeline();
|
|
10117
|
+
return;
|
|
10118
|
+
}
|
|
9716
10119
|
// Cmd+C / Ctrl+C — Phase 4.1 removed the shell-side comment-drop chord.
|
|
9717
10120
|
// Canvas comment-drop is the `C` tool letter (press C in the canvas,
|
|
9718
10121
|
// then click the element) or right-click "Add comment". Cmd+C now
|
|
@@ -9828,6 +10231,9 @@ function App() {
|
|
|
9828
10231
|
clearActiveCanvasSelection,
|
|
9829
10232
|
presentMode,
|
|
9830
10233
|
exitPresent,
|
|
10234
|
+
toggleTimeline,
|
|
10235
|
+
inspectorTab,
|
|
10236
|
+
performPhotoUndo,
|
|
9831
10237
|
]);
|
|
9832
10238
|
|
|
9833
10239
|
const registerIframe = useCallback((path, el) => {
|
|
@@ -10120,6 +10526,8 @@ function App() {
|
|
|
10120
10526
|
dirtyByPath={dirtyByPath}
|
|
10121
10527
|
project={project}
|
|
10122
10528
|
gitBranch={gitStatus?.branch}
|
|
10529
|
+
remoteSync={remoteSync}
|
|
10530
|
+
onGetLatest={gitGetLatest}
|
|
10123
10531
|
/>
|
|
10124
10532
|
{sidebarOpen && (
|
|
10125
10533
|
<PanelGrip
|
|
@@ -10202,6 +10610,12 @@ function App() {
|
|
|
10202
10610
|
onResizeArtboard={resizeArtboardShell}
|
|
10203
10611
|
editScope={editScope}
|
|
10204
10612
|
onUndoRedo={(dir) => postToActiveCanvas({ dgn: dir })}
|
|
10613
|
+
photoSel={photoSel}
|
|
10614
|
+
photoRev={photoRev}
|
|
10615
|
+
onPhotoEdit={onPhotoEdit}
|
|
10616
|
+
onPhotoRemoveBackground={onPhotoRemoveBackground}
|
|
10617
|
+
onPhotoRecordEdit={onPhotoRecordEdit}
|
|
10618
|
+
onPhotoUndoRedo={(dir) => performPhotoUndo(dir === 'redo')}
|
|
10205
10619
|
layersTree={layersTree}
|
|
10206
10620
|
canvasFile={activePath}
|
|
10207
10621
|
onSelectLayer={(n) =>
|
|
@@ -10665,6 +11079,9 @@ function App() {
|
|
|
10665
11079
|
onClose={() => setAssetPickerReq(null)}
|
|
10666
11080
|
/>
|
|
10667
11081
|
)}
|
|
11082
|
+
{stickerPickerReq && (
|
|
11083
|
+
<StickerPicker onPick={onStickerPicked} onClose={() => setStickerPickerReq(null)} />
|
|
11084
|
+
)}
|
|
10668
11085
|
{diffTarget && (
|
|
10669
11086
|
<DiffView
|
|
10670
11087
|
target={diffTarget}
|