@agent-native/core 0.84.1 → 0.84.3
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +15 -0
- package/corpus/core/docs/design/durable-agent-runs.md +34 -35
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builder-engine.ts +81 -9
- package/corpus/core/src/agent/production-agent.ts +9 -2
- package/corpus/core/src/agent/run-loop-with-resume.ts +38 -8
- package/corpus/core/src/agent/run-manager.ts +8 -7
- package/corpus/core/src/agent/thread-data-builder.ts +6 -0
- package/corpus/core/src/cli/design-connect.ts +175 -0
- package/corpus/core/src/cli/skills.ts +5 -7
- package/corpus/core/src/client/AgentPanel.tsx +8 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +4 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +253 -24
- package/corpus/core/src/client/sharing/ShareButton.tsx +70 -21
- package/corpus/core/src/client/sse-event-processor.ts +46 -4
- package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/corpus/templates/analytics/AGENTS.md +1 -1
- package/corpus/templates/design/actions/apply-motion-edit.ts +50 -18
- package/corpus/templates/design/actions/get-motion-timeline.ts +4 -14
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +156 -12
- package/corpus/templates/design/app/components/design/MotionDock.tsx +2 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +217 -75
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +39 -39
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +558 -37
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +75 -3
- package/corpus/templates/design/app/components/design/types.ts +13 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +17 -0
- package/corpus/templates/design/app/i18n-data.ts +245 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +890 -252
- package/corpus/templates/design/changelog/2026-06-30-agent-chat-now-reports-saved-design-generations-clearly-when.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-apply-styles-now-appears-only-for-localhost-visual-edit-scre.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-copying-or-dragging-screen-elements-onto-the-infinite-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-questions-now-use-tighter-editor-typography-and-contr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-element-drags-can-be-cancelled-with-escape-before-they-commi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-pending-visual-style-edits-now-warn-before-you-leave-the-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-now-make-export-and-agent-handoff-easier-to-no.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-style-drags-stay-live-while-pending-edits-can-be-appl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-a-redundant-context-tab-above-the-composer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-motion-track-creation-no-longer-fails-in-local-editors.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-share-general-access-menu-stays-open-when-choosing-organization.md +6 -0
- package/corpus/templates/design/server/plugins/db.ts +10 -0
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +61 -10
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +9 -2
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +2 -2
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +31 -8
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/agent/run-manager.d.ts +8 -7
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +8 -7
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +2 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +5 -0
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/cli/design-connect.d.ts +2 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +140 -0
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +5 -7
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +4 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +3 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +2 -2
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +216 -22
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +6 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +25 -11
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +18 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +34 -5
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +7 -7
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/docs/design/durable-agent-runs.md +34 -35
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
|
@@ -117,6 +117,7 @@ import {
|
|
|
117
117
|
IconExternalLink,
|
|
118
118
|
IconCircleCheck,
|
|
119
119
|
IconTerminal2,
|
|
120
|
+
IconLink,
|
|
120
121
|
} from "@tabler/icons-react";
|
|
121
122
|
import { useQueryClient } from "@tanstack/react-query";
|
|
122
123
|
import {
|
|
@@ -131,7 +132,14 @@ import {
|
|
|
131
132
|
type SetStateAction,
|
|
132
133
|
} from "react";
|
|
133
134
|
import { flushSync } from "react-dom";
|
|
134
|
-
import {
|
|
135
|
+
import {
|
|
136
|
+
useParams,
|
|
137
|
+
useNavigate,
|
|
138
|
+
Link,
|
|
139
|
+
useLocation,
|
|
140
|
+
useBlocker,
|
|
141
|
+
useBeforeUnload,
|
|
142
|
+
} from "react-router";
|
|
135
143
|
import { toast } from "sonner";
|
|
136
144
|
import * as Y from "yjs";
|
|
137
145
|
|
|
@@ -186,6 +194,8 @@ import type {
|
|
|
186
194
|
ElementInfo,
|
|
187
195
|
ElementSelectionIntent,
|
|
188
196
|
DeviceFrameType,
|
|
197
|
+
PortableStyleSnapshot,
|
|
198
|
+
PortableStyleSnapshotNode,
|
|
189
199
|
} from "@/components/design/types";
|
|
190
200
|
import {
|
|
191
201
|
DEVICE_FRAME_VIEWPORTS,
|
|
@@ -193,9 +203,18 @@ import {
|
|
|
193
203
|
} from "@/components/design/types";
|
|
194
204
|
import PromptPopover from "@/components/editor/PromptDialog";
|
|
195
205
|
import type { UploadedFile } from "@/components/editor/PromptDialog";
|
|
206
|
+
import {
|
|
207
|
+
AlertDialog,
|
|
208
|
+
AlertDialogAction,
|
|
209
|
+
AlertDialogCancel,
|
|
210
|
+
AlertDialogContent,
|
|
211
|
+
AlertDialogDescription,
|
|
212
|
+
AlertDialogFooter,
|
|
213
|
+
AlertDialogHeader,
|
|
214
|
+
AlertDialogTitle,
|
|
215
|
+
} from "@/components/ui/alert-dialog";
|
|
196
216
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
197
217
|
import { Button } from "@/components/ui/button";
|
|
198
|
-
import { Checkbox } from "@/components/ui/checkbox";
|
|
199
218
|
import {
|
|
200
219
|
Dialog,
|
|
201
220
|
DialogContent,
|
|
@@ -226,7 +245,6 @@ import {
|
|
|
226
245
|
} from "@/components/ui/popover";
|
|
227
246
|
import { Spinner } from "@/components/ui/spinner";
|
|
228
247
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
229
|
-
import { Textarea } from "@/components/ui/textarea";
|
|
230
248
|
import {
|
|
231
249
|
Tooltip,
|
|
232
250
|
TooltipContent,
|
|
@@ -332,11 +350,29 @@ function dedupeStringIds(ids: string[]): string[] {
|
|
|
332
350
|
return Array.from(new Set(ids.filter(Boolean)));
|
|
333
351
|
}
|
|
334
352
|
|
|
353
|
+
function sameStringIds(a: string[], b: string[]) {
|
|
354
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
355
|
+
}
|
|
356
|
+
|
|
335
357
|
export function isScreenRootElementInfo(info: ElementInfo | null | undefined) {
|
|
336
358
|
const tagName = info?.tagName?.toUpperCase();
|
|
337
359
|
return tagName === "BODY" || tagName === "HTML";
|
|
338
360
|
}
|
|
339
361
|
|
|
362
|
+
export function shouldIgnoreOverviewLayerCreationEcho(args: {
|
|
363
|
+
pendingLayerId: string | null | undefined;
|
|
364
|
+
pendingScreenId: string | null | undefined;
|
|
365
|
+
screenId: string;
|
|
366
|
+
info?: ElementInfo | null;
|
|
367
|
+
event: "select" | "clear";
|
|
368
|
+
}) {
|
|
369
|
+
if (!args.pendingLayerId) return false;
|
|
370
|
+
if (args.pendingScreenId && args.pendingScreenId !== args.screenId) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
return args.event === "clear" || isScreenRootElementInfo(args.info);
|
|
374
|
+
}
|
|
375
|
+
|
|
340
376
|
export function getSelectedScreenIdsForEditorState(args: {
|
|
341
377
|
activeFileId: string | null | undefined;
|
|
342
378
|
overviewSelectedScreenIds: string[];
|
|
@@ -866,6 +902,115 @@ interface LiveScreenSnapshot {
|
|
|
866
902
|
contentType?: string;
|
|
867
903
|
}
|
|
868
904
|
|
|
905
|
+
export interface PendingVisualStyleEdit {
|
|
906
|
+
screenId: string;
|
|
907
|
+
filename: string;
|
|
908
|
+
screenName: string;
|
|
909
|
+
selector: string;
|
|
910
|
+
sourceId?: string | null;
|
|
911
|
+
tagName?: string | null;
|
|
912
|
+
classes: string[];
|
|
913
|
+
styles: Record<string, string>;
|
|
914
|
+
updatedAt: number;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function pendingVisualStyleEditKey(edit: PendingVisualStyleEdit): string {
|
|
918
|
+
return [
|
|
919
|
+
edit.screenId,
|
|
920
|
+
edit.sourceId?.trim() || edit.selector.trim() || "unknown",
|
|
921
|
+
].join("::");
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
export function mergePendingVisualStyleEdit(
|
|
925
|
+
edits: readonly PendingVisualStyleEdit[],
|
|
926
|
+
nextEdit: PendingVisualStyleEdit,
|
|
927
|
+
): PendingVisualStyleEdit[] {
|
|
928
|
+
const nextKey = pendingVisualStyleEditKey(nextEdit);
|
|
929
|
+
let merged = false;
|
|
930
|
+
const next = edits.map((edit) => {
|
|
931
|
+
if (pendingVisualStyleEditKey(edit) !== nextKey) return edit;
|
|
932
|
+
merged = true;
|
|
933
|
+
return {
|
|
934
|
+
...edit,
|
|
935
|
+
...nextEdit,
|
|
936
|
+
classes: nextEdit.classes.length > 0 ? nextEdit.classes : edit.classes,
|
|
937
|
+
styles: { ...edit.styles, ...nextEdit.styles },
|
|
938
|
+
};
|
|
939
|
+
});
|
|
940
|
+
return merged ? next : [...edits, nextEdit];
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
export function getPendingVisualStylePropertyCount(
|
|
944
|
+
edits: readonly PendingVisualStyleEdit[],
|
|
945
|
+
): number {
|
|
946
|
+
return edits.reduce(
|
|
947
|
+
(count, edit) => count + Object.keys(edit.styles).length,
|
|
948
|
+
0,
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
export function shouldBlockPendingVisualStyleNavigation(args: {
|
|
953
|
+
hasPendingVisualStyleEdits: boolean;
|
|
954
|
+
currentPathname: string;
|
|
955
|
+
nextPathname: string;
|
|
956
|
+
}): boolean {
|
|
957
|
+
return (
|
|
958
|
+
args.hasPendingVisualStyleEdits &&
|
|
959
|
+
args.currentPathname !== args.nextPathname
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
export function formatPendingVisualStylePrompt(args: {
|
|
964
|
+
designId?: string | null;
|
|
965
|
+
designTitle?: string | null;
|
|
966
|
+
activeFileId?: string | null;
|
|
967
|
+
activeFilename?: string | null;
|
|
968
|
+
edits: readonly PendingVisualStyleEdit[];
|
|
969
|
+
}): string {
|
|
970
|
+
const title = args.designTitle?.trim();
|
|
971
|
+
const editPayload = args.edits.map((edit) => ({
|
|
972
|
+
screenId: edit.screenId,
|
|
973
|
+
filename: edit.filename,
|
|
974
|
+
screenName: edit.screenName,
|
|
975
|
+
selector: edit.selector,
|
|
976
|
+
sourceId: edit.sourceId ?? null,
|
|
977
|
+
tagName: edit.tagName ?? null,
|
|
978
|
+
classes: edit.classes,
|
|
979
|
+
styles: edit.styles,
|
|
980
|
+
}));
|
|
981
|
+
|
|
982
|
+
return [
|
|
983
|
+
`Apply these pending visual style edits${title ? ` to "${title}"` : ""}.`,
|
|
984
|
+
args.designId ? `Design id: "${args.designId}".` : "",
|
|
985
|
+
args.activeFileId
|
|
986
|
+
? `Active screen: "${args.activeFilename ?? args.activeFileId}" (${args.activeFileId}).`
|
|
987
|
+
: "",
|
|
988
|
+
"",
|
|
989
|
+
"Use the Design source tools to make the source match the current live canvas preview. Read each target screen, resolve source ids/selectors through the code-layer projection, then apply the style changes with focused source edits. Preserve layout, behavior, and unrelated styling.",
|
|
990
|
+
"",
|
|
991
|
+
"Pending style edits:",
|
|
992
|
+
JSON.stringify(editPayload, null, 2),
|
|
993
|
+
]
|
|
994
|
+
.filter((line) => line !== "")
|
|
995
|
+
.join("\n");
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
export function shouldShowPendingVisualStyleApply(args: {
|
|
999
|
+
edits: readonly PendingVisualStyleEdit[];
|
|
1000
|
+
screenSourceTypes: ReadonlyMap<string, unknown>;
|
|
1001
|
+
fallbackSourceType?: unknown;
|
|
1002
|
+
}): boolean {
|
|
1003
|
+
return (
|
|
1004
|
+
args.edits.length > 0 &&
|
|
1005
|
+
args.edits.every(
|
|
1006
|
+
(edit) =>
|
|
1007
|
+
normalizeDesignSourceType(
|
|
1008
|
+
args.screenSourceTypes.get(edit.screenId) ?? args.fallbackSourceType,
|
|
1009
|
+
) === "localhost",
|
|
1010
|
+
)
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
869
1014
|
interface DesignData {
|
|
870
1015
|
id: string;
|
|
871
1016
|
title: string;
|
|
@@ -893,6 +1038,7 @@ interface CanvasLayerClipboardEntry {
|
|
|
893
1038
|
html: string;
|
|
894
1039
|
rootNodeId?: string;
|
|
895
1040
|
sourceFileId: string;
|
|
1041
|
+
portableStyleSnapshot?: PortableStyleSnapshot;
|
|
896
1042
|
}
|
|
897
1043
|
|
|
898
1044
|
interface SelectedCanvasLayerSnapshot extends CanvasLayerClipboardEntry {
|
|
@@ -1451,6 +1597,80 @@ function setAbsolutePositioningForNodeInHtml(
|
|
|
1451
1597
|
}
|
|
1452
1598
|
}
|
|
1453
1599
|
|
|
1600
|
+
function getAbsolutePositioningForNodeInHtml(
|
|
1601
|
+
content: string,
|
|
1602
|
+
nodeAttrId: string,
|
|
1603
|
+
): { x: number; y: number } | null {
|
|
1604
|
+
if (typeof window === "undefined") return null;
|
|
1605
|
+
try {
|
|
1606
|
+
const doc = new DOMParser().parseFromString(content, "text/html");
|
|
1607
|
+
const element = doc.querySelector(
|
|
1608
|
+
`[data-agent-native-node-id="${CSS.escape(nodeAttrId)}"]`,
|
|
1609
|
+
) as HTMLElement | null;
|
|
1610
|
+
if (!element) return null;
|
|
1611
|
+
return {
|
|
1612
|
+
x: Number.parseFloat(element.style.left || "0") || 0,
|
|
1613
|
+
y: Number.parseFloat(element.style.top || "0") || 0,
|
|
1614
|
+
};
|
|
1615
|
+
} catch {
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
function elementAtPortableStylePath(
|
|
1621
|
+
root: Element,
|
|
1622
|
+
node: PortableStyleSnapshotNode,
|
|
1623
|
+
): Element | null {
|
|
1624
|
+
let current: Element | null = root;
|
|
1625
|
+
for (const index of node.path) {
|
|
1626
|
+
if (!current || !Number.isInteger(index) || index < 0) return null;
|
|
1627
|
+
current = current.children.item(index);
|
|
1628
|
+
}
|
|
1629
|
+
return current;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
function applyPortableStyles(
|
|
1633
|
+
element: Element | null,
|
|
1634
|
+
styles: Record<string, string>,
|
|
1635
|
+
) {
|
|
1636
|
+
if (!element) return;
|
|
1637
|
+
const host = styleHost(element);
|
|
1638
|
+
if (!host) return;
|
|
1639
|
+
Object.entries(styles).forEach(([property, value]) => {
|
|
1640
|
+
if (!value) return;
|
|
1641
|
+
if (property.startsWith("--") || property.includes("-")) {
|
|
1642
|
+
host.style.setProperty(property, value);
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
(host.style as any)[property] = value;
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
function applyPortableStyleSnapshotToHtml(
|
|
1650
|
+
content: string,
|
|
1651
|
+
nodeAttrId: string,
|
|
1652
|
+
snapshot?: PortableStyleSnapshot,
|
|
1653
|
+
): string {
|
|
1654
|
+
if (typeof window === "undefined" || !snapshot?.nodes?.length) {
|
|
1655
|
+
return content;
|
|
1656
|
+
}
|
|
1657
|
+
try {
|
|
1658
|
+
const doc = new DOMParser().parseFromString(content, "text/html");
|
|
1659
|
+
const root = doc.querySelector(
|
|
1660
|
+
`[data-agent-native-node-id="${CSS.escape(nodeAttrId)}"]`,
|
|
1661
|
+
);
|
|
1662
|
+
if (!root) return content;
|
|
1663
|
+
root.setAttribute("data-agent-native-preserve-styles", "true");
|
|
1664
|
+
snapshot.nodes.forEach((node) => {
|
|
1665
|
+
const target = elementAtPortableStylePath(root, node);
|
|
1666
|
+
if (target) applyPortableStyles(target, node.styles);
|
|
1667
|
+
});
|
|
1668
|
+
return `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
|
1669
|
+
} catch {
|
|
1670
|
+
return content;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1454
1674
|
function isAbsoluteCodeLayerNode(node: CodeLayerNode | null | undefined) {
|
|
1455
1675
|
const position = String(node?.style.position ?? "").toLowerCase();
|
|
1456
1676
|
return position === "absolute" || position === "fixed";
|
|
@@ -1951,6 +2171,7 @@ function setRootLayerPosition(
|
|
|
1951
2171
|
function prepareClonedHtmlLayer(
|
|
1952
2172
|
doc: Document,
|
|
1953
2173
|
layerHtml: string,
|
|
2174
|
+
styleSnapshot?: PortableStyleSnapshot,
|
|
1954
2175
|
): { element: Element; rootNodeId: string } | null {
|
|
1955
2176
|
const layerDoc = new DOMParser().parseFromString(
|
|
1956
2177
|
`<template>${layerHtml}</template>`,
|
|
@@ -1961,6 +2182,13 @@ function prepareClonedHtmlLayer(
|
|
|
1961
2182
|
layerDoc.body.firstElementChild;
|
|
1962
2183
|
if (!source) return null;
|
|
1963
2184
|
const clone = doc.importNode(source, true) as Element;
|
|
2185
|
+
if (styleSnapshot) {
|
|
2186
|
+
clone.setAttribute("data-agent-native-preserve-styles", "true");
|
|
2187
|
+
styleSnapshot.nodes.forEach((node) => {
|
|
2188
|
+
const target = elementAtPortableStylePath(clone, node);
|
|
2189
|
+
if (target) applyPortableStyles(target, node.styles);
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
1964
2192
|
const rootNodeId = uniqueLayerId("copy");
|
|
1965
2193
|
clone.setAttribute("data-agent-native-node-id", rootNodeId);
|
|
1966
2194
|
Array.from(clone.querySelectorAll("[data-agent-native-node-id]")).forEach(
|
|
@@ -1983,6 +2211,7 @@ function insertClonedHtmlLayers(
|
|
|
1983
2211
|
placement?: "before" | "after" | "inside";
|
|
1984
2212
|
stripRootPosition?: boolean;
|
|
1985
2213
|
positions?: Array<{ x: number; y: number } | null | undefined>;
|
|
2214
|
+
styleSnapshots?: Array<PortableStyleSnapshot | null | undefined>;
|
|
1986
2215
|
} = {},
|
|
1987
2216
|
): { content: string; rootNodeIds: string[] } | null {
|
|
1988
2217
|
if (typeof window === "undefined" || layerHtmls.length === 0) return null;
|
|
@@ -1992,7 +2221,11 @@ function insertClonedHtmlLayers(
|
|
|
1992
2221
|
const fragment = doc.createDocumentFragment();
|
|
1993
2222
|
const rootNodeIds: string[] = [];
|
|
1994
2223
|
layerHtmls.forEach((layerHtml, index) => {
|
|
1995
|
-
const prepared = prepareClonedHtmlLayer(
|
|
2224
|
+
const prepared = prepareClonedHtmlLayer(
|
|
2225
|
+
doc,
|
|
2226
|
+
layerHtml,
|
|
2227
|
+
options.styleSnapshots?.[index] ?? undefined,
|
|
2228
|
+
);
|
|
1996
2229
|
if (!prepared) return;
|
|
1997
2230
|
const position = options.positions?.[index];
|
|
1998
2231
|
if (position) {
|
|
@@ -3078,7 +3311,11 @@ function DesignWorkspaceRail({
|
|
|
3078
3311
|
"group-hover:bg-transparent group-hover:text-muted-foreground",
|
|
3079
3312
|
)}
|
|
3080
3313
|
>
|
|
3081
|
-
|
|
3314
|
+
{motionOpen ? (
|
|
3315
|
+
<IconChevronDown className="size-[15px]" />
|
|
3316
|
+
) : (
|
|
3317
|
+
<IconChevronUp className="size-[15px]" />
|
|
3318
|
+
)}
|
|
3082
3319
|
</span>
|
|
3083
3320
|
<span className="max-w-full truncate leading-none">
|
|
3084
3321
|
{"Motion" /* i18n-ignore */}
|
|
@@ -3970,6 +4207,9 @@ export default function DesignEditor() {
|
|
|
3970
4207
|
const [selectedElement, setSelectedElement] = useState<ElementInfo | null>(
|
|
3971
4208
|
null,
|
|
3972
4209
|
);
|
|
4210
|
+
const [pendingVisualStyleEdits, setPendingVisualStyleEdits] = useState<
|
|
4211
|
+
PendingVisualStyleEdit[]
|
|
4212
|
+
>([]);
|
|
3973
4213
|
const [textEditingState, setTextEditingState] = useState<{
|
|
3974
4214
|
active: boolean;
|
|
3975
4215
|
selector?: string;
|
|
@@ -4930,8 +5170,8 @@ export default function DesignEditor() {
|
|
|
4930
5170
|
null,
|
|
4931
5171
|
);
|
|
4932
5172
|
const [codingHandoffLoading, setCodingHandoffLoading] = useState(false);
|
|
4933
|
-
const [
|
|
4934
|
-
const
|
|
5173
|
+
const [shareLinkCopied, setShareLinkCopied] = useState(false);
|
|
5174
|
+
const shareLinkCopiedResetRef = useRef<number | null>(null);
|
|
4935
5175
|
const [, setPatchProof] = useState<PatchProofState | null>(null);
|
|
4936
5176
|
const pendingFileSavesRef = useRef<Record<string, FileContentSaveRequest>>(
|
|
4937
5177
|
{},
|
|
@@ -5126,6 +5366,13 @@ export default function DesignEditor() {
|
|
|
5126
5366
|
if (!id || typeof window === "undefined") return undefined;
|
|
5127
5367
|
return getDesignEditorShareUrl(id, window.location.origin, appBasePath());
|
|
5128
5368
|
}, [id]);
|
|
5369
|
+
useEffect(() => {
|
|
5370
|
+
return () => {
|
|
5371
|
+
if (shareLinkCopiedResetRef.current !== null) {
|
|
5372
|
+
window.clearTimeout(shareLinkCopiedResetRef.current);
|
|
5373
|
+
}
|
|
5374
|
+
};
|
|
5375
|
+
}, []);
|
|
5129
5376
|
const {
|
|
5130
5377
|
designSystems,
|
|
5131
5378
|
defaultSystem,
|
|
@@ -6738,6 +6985,7 @@ export default function DesignEditor() {
|
|
|
6738
6985
|
// Canvas container ref for cursor overlay coordinate mapping.
|
|
6739
6986
|
const canvasContextMenuRef = useRef<CanvasContextMenuHandle | null>(null);
|
|
6740
6987
|
const canvasContainerRef = useRef<HTMLDivElement>(null);
|
|
6988
|
+
const activeEditorDragRef = useRef(false);
|
|
6741
6989
|
|
|
6742
6990
|
// Live handle to the active DesignCanvas preview iframe. DesignCanvas owns the
|
|
6743
6991
|
// <iframe> internally (tagged data-design-preview-iframe) and does not forward
|
|
@@ -6755,6 +7003,25 @@ export default function DesignEditor() {
|
|
|
6755
7003
|
[],
|
|
6756
7004
|
);
|
|
6757
7005
|
|
|
7006
|
+
const handleEditorDragStateChange = useCallback((active: boolean) => {
|
|
7007
|
+
activeEditorDragRef.current = active;
|
|
7008
|
+
}, []);
|
|
7009
|
+
|
|
7010
|
+
const cancelActiveEditorDrag = useCallback(() => {
|
|
7011
|
+
if (!activeEditorDragRef.current) return false;
|
|
7012
|
+
activeEditorDragRef.current = false;
|
|
7013
|
+
if (typeof document === "undefined") return true;
|
|
7014
|
+
document
|
|
7015
|
+
.querySelectorAll<HTMLIFrameElement>("iframe[data-design-preview-iframe]")
|
|
7016
|
+
.forEach((iframe) => {
|
|
7017
|
+
iframe.contentWindow?.postMessage(
|
|
7018
|
+
{ type: "agent-native:cancel-active-drag" },
|
|
7019
|
+
"*",
|
|
7020
|
+
);
|
|
7021
|
+
});
|
|
7022
|
+
return true;
|
|
7023
|
+
}, []);
|
|
7024
|
+
|
|
6758
7025
|
const handleRunDesignAudit = useCallback(async () => {
|
|
6759
7026
|
if (!id || !activeFile?.id) return;
|
|
6760
7027
|
const auditFileId = activeFile.id;
|
|
@@ -7002,6 +7269,98 @@ export default function DesignEditor() {
|
|
|
7002
7269
|
},
|
|
7003
7270
|
[liveScreenSnapshotsById],
|
|
7004
7271
|
);
|
|
7272
|
+
const recordPendingVisualStyleEdit = useCallback(
|
|
7273
|
+
(
|
|
7274
|
+
screenId: string,
|
|
7275
|
+
selector: string,
|
|
7276
|
+
styles: Record<string, string>,
|
|
7277
|
+
elementInfo?: ElementInfo,
|
|
7278
|
+
) => {
|
|
7279
|
+
if (!canEditDesign) return;
|
|
7280
|
+
const entries = Object.entries(styles).filter(
|
|
7281
|
+
([, value]) => value !== undefined,
|
|
7282
|
+
);
|
|
7283
|
+
if (entries.length === 0) return;
|
|
7284
|
+
const stylePatch = Object.fromEntries(entries);
|
|
7285
|
+
const screen = files.find((file) => file.id === screenId);
|
|
7286
|
+
const fallbackName = screen?.filename ?? screenId;
|
|
7287
|
+
const sourceId =
|
|
7288
|
+
elementInfo?.sourceId ??
|
|
7289
|
+
(screenId === activeFile?.id ? selectedElement?.sourceId : null);
|
|
7290
|
+
const proofId =
|
|
7291
|
+
typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
7292
|
+
? crypto.randomUUID()
|
|
7293
|
+
: `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
7294
|
+
const [firstProperty, firstValue] = entries[0];
|
|
7295
|
+
|
|
7296
|
+
setPendingVisualStyleEdits((current) =>
|
|
7297
|
+
mergePendingVisualStyleEdit(current, {
|
|
7298
|
+
screenId,
|
|
7299
|
+
filename: fallbackName,
|
|
7300
|
+
screenName: prettyScreenName(fallbackName),
|
|
7301
|
+
selector,
|
|
7302
|
+
sourceId,
|
|
7303
|
+
tagName: elementInfo?.tagName ?? null,
|
|
7304
|
+
classes: elementInfo?.classes ?? [],
|
|
7305
|
+
styles: stylePatch,
|
|
7306
|
+
updatedAt: Date.now(),
|
|
7307
|
+
}),
|
|
7308
|
+
);
|
|
7309
|
+
setPatchProof({
|
|
7310
|
+
id: proofId,
|
|
7311
|
+
fileId: screenId,
|
|
7312
|
+
filename: fallbackName,
|
|
7313
|
+
selector,
|
|
7314
|
+
sourceId: sourceId ?? undefined,
|
|
7315
|
+
property:
|
|
7316
|
+
entries.length === 1
|
|
7317
|
+
? firstProperty
|
|
7318
|
+
: entries.map(([property]) => property).join(", "),
|
|
7319
|
+
previousValue:
|
|
7320
|
+
elementInfo?.computedStyles?.[firstProperty] ??
|
|
7321
|
+
(screenId === activeFile?.id
|
|
7322
|
+
? selectedElement?.computedStyles?.[firstProperty]
|
|
7323
|
+
: undefined),
|
|
7324
|
+
nextValue:
|
|
7325
|
+
entries.length === 1
|
|
7326
|
+
? firstValue
|
|
7327
|
+
: entries
|
|
7328
|
+
.map(([property, value]) => `${property}: ${value}`)
|
|
7329
|
+
.join("; "),
|
|
7330
|
+
previousContent: getProjectionContentForScreen(screenId),
|
|
7331
|
+
capability: "deterministic-style-edit",
|
|
7332
|
+
confidence: 0.92,
|
|
7333
|
+
status: "runtime",
|
|
7334
|
+
createdAt: Date.now(),
|
|
7335
|
+
});
|
|
7336
|
+
|
|
7337
|
+
if (screenId !== activeFile?.id) return;
|
|
7338
|
+
setSelectedElement((prev) => {
|
|
7339
|
+
const base = elementInfo ?? prev;
|
|
7340
|
+
if (!base) return prev;
|
|
7341
|
+
return {
|
|
7342
|
+
...base,
|
|
7343
|
+
sourceId: sourceId ?? base.sourceId,
|
|
7344
|
+
selector: selector || base.selector,
|
|
7345
|
+
computedStyles: {
|
|
7346
|
+
...base.computedStyles,
|
|
7347
|
+
...stylePatch,
|
|
7348
|
+
},
|
|
7349
|
+
};
|
|
7350
|
+
});
|
|
7351
|
+
if (sourceId) {
|
|
7352
|
+
setSelectedLayerIdsState([sourceId]);
|
|
7353
|
+
}
|
|
7354
|
+
},
|
|
7355
|
+
[
|
|
7356
|
+
activeFile?.id,
|
|
7357
|
+
canEditDesign,
|
|
7358
|
+
files,
|
|
7359
|
+
getProjectionContentForScreen,
|
|
7360
|
+
selectedElement?.computedStyles,
|
|
7361
|
+
selectedElement?.sourceId,
|
|
7362
|
+
],
|
|
7363
|
+
);
|
|
7005
7364
|
const activeProjectionContent =
|
|
7006
7365
|
activeFile?.id !== undefined
|
|
7007
7366
|
? getProjectionContentForScreen(activeFile.id)
|
|
@@ -7688,6 +8047,7 @@ export default function DesignEditor() {
|
|
|
7688
8047
|
fileId?: unknown;
|
|
7689
8048
|
timelineId?: unknown;
|
|
7690
8049
|
updatedAt?: unknown;
|
|
8050
|
+
contentPatched?: unknown;
|
|
7691
8051
|
};
|
|
7692
8052
|
if (typeof response.timelineId === "string") {
|
|
7693
8053
|
setMotionTimelineId(response.timelineId);
|
|
@@ -7699,7 +8059,8 @@ export default function DesignEditor() {
|
|
|
7699
8059
|
}
|
|
7700
8060
|
if (
|
|
7701
8061
|
typeof response.fileId === "string" &&
|
|
7702
|
-
response.fileId === activeFile.id
|
|
8062
|
+
response.fileId === activeFile.id &&
|
|
8063
|
+
response.contentPatched !== false
|
|
7703
8064
|
) {
|
|
7704
8065
|
applyFileContentUpdate(response.fileId, localPatchedContent, {
|
|
7705
8066
|
refreshPreview: true,
|
|
@@ -7925,7 +8286,8 @@ export default function DesignEditor() {
|
|
|
7925
8286
|
// new primitive selected, matching Figma behaviour. We activate the
|
|
7926
8287
|
// target screen (so the layers panel shows its content) and select the
|
|
7927
8288
|
// new node, but do NOT switch to single/full view.
|
|
7928
|
-
pendingOverviewScreenSelectionRef.current =
|
|
8289
|
+
pendingOverviewScreenSelectionRef.current =
|
|
8290
|
+
options?.selectFrame === false ? null : screenId;
|
|
7929
8291
|
pendingOverviewLayerSelectionRef.current = nodeId;
|
|
7930
8292
|
clearPendingOverviewLayerSelectionTimer();
|
|
7931
8293
|
flushSync(() => {
|
|
@@ -7980,13 +8342,14 @@ export default function DesignEditor() {
|
|
|
7980
8342
|
const handleOverviewScreenSelectionChange = useCallback(
|
|
7981
8343
|
(ids: string[]) => {
|
|
7982
8344
|
const pendingId = pendingOverviewScreenSelectionRef.current;
|
|
7983
|
-
const fileIds = new Set(
|
|
8345
|
+
const fileIds = new Set(
|
|
8346
|
+
files.filter((file) => file.id !== boardFileId).map((file) => file.id),
|
|
8347
|
+
);
|
|
8348
|
+
const nextIds = ids.filter((layerId) => fileIds.has(layerId));
|
|
7984
8349
|
if (pendingId && ids.length === 0) return;
|
|
7985
8350
|
if (pendingId && ids.includes(pendingId)) {
|
|
7986
|
-
setOverviewSelectedScreenIds(
|
|
7987
|
-
|
|
7988
|
-
(layerId) => fileIds.has(layerId) || layerId === pendingId,
|
|
7989
|
-
),
|
|
8351
|
+
setOverviewSelectedScreenIds((current) =>
|
|
8352
|
+
sameStringIds(current, nextIds) ? current : nextIds,
|
|
7990
8353
|
);
|
|
7991
8354
|
if (fileIds.has(pendingId)) {
|
|
7992
8355
|
pendingOverviewScreenSelectionRef.current = null;
|
|
@@ -7999,11 +8362,11 @@ export default function DesignEditor() {
|
|
|
7999
8362
|
clearPendingOverviewLayerSelectionTimer();
|
|
8000
8363
|
setCreatedOverviewLayerSelection(null);
|
|
8001
8364
|
}
|
|
8002
|
-
setOverviewSelectedScreenIds(
|
|
8003
|
-
|
|
8365
|
+
setOverviewSelectedScreenIds((current) =>
|
|
8366
|
+
sameStringIds(current, nextIds) ? current : nextIds,
|
|
8004
8367
|
);
|
|
8005
8368
|
},
|
|
8006
|
-
[clearPendingOverviewLayerSelectionTimer, files],
|
|
8369
|
+
[boardFileId, clearPendingOverviewLayerSelectionTimer, files],
|
|
8007
8370
|
);
|
|
8008
8371
|
|
|
8009
8372
|
const shouldPreserveBlockedOverviewLayerSelectionRef = useRef<
|
|
@@ -8469,14 +8832,14 @@ export default function DesignEditor() {
|
|
|
8469
8832
|
(screenId: string, info: ElementInfo, intent?: ElementSelectionIntent) => {
|
|
8470
8833
|
const pendingLayerId = pendingOverviewLayerSelectionRef.current;
|
|
8471
8834
|
const pendingScreenId = pendingOverviewScreenSelectionRef.current;
|
|
8472
|
-
const createdSelection = createdOverviewLayerSelection;
|
|
8473
|
-
if (createdSelection?.screenId === screenId) {
|
|
8474
|
-
return;
|
|
8475
|
-
}
|
|
8476
8835
|
if (
|
|
8477
|
-
(
|
|
8478
|
-
|
|
8479
|
-
|
|
8836
|
+
shouldIgnoreOverviewLayerCreationEcho({
|
|
8837
|
+
pendingLayerId,
|
|
8838
|
+
pendingScreenId,
|
|
8839
|
+
screenId,
|
|
8840
|
+
info,
|
|
8841
|
+
event: "select",
|
|
8842
|
+
})
|
|
8480
8843
|
) {
|
|
8481
8844
|
return;
|
|
8482
8845
|
}
|
|
@@ -8535,7 +8898,6 @@ export default function DesignEditor() {
|
|
|
8535
8898
|
},
|
|
8536
8899
|
[
|
|
8537
8900
|
clearPendingOverviewLayerSelectionTimer,
|
|
8538
|
-
createdOverviewLayerSelection,
|
|
8539
8901
|
focusDesignInspectorForSelection,
|
|
8540
8902
|
getCodeLayerProjectionForScreen,
|
|
8541
8903
|
selectedLayerIdsState,
|
|
@@ -8546,13 +8908,13 @@ export default function DesignEditor() {
|
|
|
8546
8908
|
(screenId: string) => {
|
|
8547
8909
|
const pendingLayerId = pendingOverviewLayerSelectionRef.current;
|
|
8548
8910
|
const pendingScreenId = pendingOverviewScreenSelectionRef.current;
|
|
8549
|
-
const createdSelection = createdOverviewLayerSelection;
|
|
8550
|
-
if (createdSelection?.screenId === screenId) {
|
|
8551
|
-
return;
|
|
8552
|
-
}
|
|
8553
8911
|
if (
|
|
8554
|
-
(
|
|
8555
|
-
|
|
8912
|
+
shouldIgnoreOverviewLayerCreationEcho({
|
|
8913
|
+
pendingLayerId,
|
|
8914
|
+
pendingScreenId,
|
|
8915
|
+
screenId,
|
|
8916
|
+
event: "clear",
|
|
8917
|
+
})
|
|
8556
8918
|
) {
|
|
8557
8919
|
return;
|
|
8558
8920
|
}
|
|
@@ -8574,7 +8936,7 @@ export default function DesignEditor() {
|
|
|
8574
8936
|
setActiveTool("move");
|
|
8575
8937
|
setMode("edit");
|
|
8576
8938
|
},
|
|
8577
|
-
[clearPendingOverviewLayerSelectionTimer
|
|
8939
|
+
[clearPendingOverviewLayerSelectionTimer],
|
|
8578
8940
|
);
|
|
8579
8941
|
|
|
8580
8942
|
const handleElementSelect = useCallback(
|
|
@@ -9252,12 +9614,15 @@ export default function DesignEditor() {
|
|
|
9252
9614
|
styles: Record<string, string>,
|
|
9253
9615
|
elementInfo?: ElementInfo,
|
|
9254
9616
|
) => {
|
|
9255
|
-
|
|
9256
|
-
|
|
9617
|
+
if (!activeFile?.id) return;
|
|
9618
|
+
recordPendingVisualStyleEdit(
|
|
9619
|
+
activeFile.id,
|
|
9620
|
+
selector,
|
|
9621
|
+
styles,
|
|
9257
9622
|
elementInfo,
|
|
9258
|
-
|
|
9623
|
+
);
|
|
9259
9624
|
},
|
|
9260
|
-
[
|
|
9625
|
+
[activeFile?.id, recordPendingVisualStyleEdit],
|
|
9261
9626
|
);
|
|
9262
9627
|
|
|
9263
9628
|
const handleVisualStructureChange = useCallback(
|
|
@@ -9270,6 +9635,9 @@ export default function DesignEditor() {
|
|
|
9270
9635
|
sourceId?: string;
|
|
9271
9636
|
anchorSourceId?: string;
|
|
9272
9637
|
requestId?: string;
|
|
9638
|
+
dropMode?: "flow-insert" | "absolute-container";
|
|
9639
|
+
sourceRect?: { x: number; y: number; width: number; height: number };
|
|
9640
|
+
anchorRect?: { x: number; y: number; width: number; height: number };
|
|
9273
9641
|
},
|
|
9274
9642
|
) => {
|
|
9275
9643
|
if (!canEditDesign) return false;
|
|
@@ -9319,12 +9687,29 @@ export default function DesignEditor() {
|
|
|
9319
9687
|
(node) => node.id === patch.result.after?.nodeId,
|
|
9320
9688
|
)?.dataAttributes["data-agent-native-node-id"]
|
|
9321
9689
|
: undefined);
|
|
9690
|
+
const absoluteContainerOffset =
|
|
9691
|
+
details?.dropMode === "absolute-container" &&
|
|
9692
|
+
details.sourceRect &&
|
|
9693
|
+
details.anchorRect
|
|
9694
|
+
? {
|
|
9695
|
+
x: details.sourceRect.x - details.anchorRect.x,
|
|
9696
|
+
y: details.sourceRect.y - details.anchorRect.y,
|
|
9697
|
+
}
|
|
9698
|
+
: null;
|
|
9322
9699
|
const nextContent =
|
|
9323
9700
|
isAbsoluteCodeLayerNode(targetNode) && movedNodeAttrId
|
|
9324
|
-
?
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9701
|
+
? details?.dropMode === "absolute-container"
|
|
9702
|
+
? absoluteContainerOffset
|
|
9703
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
9704
|
+
patch.content,
|
|
9705
|
+
movedNodeAttrId,
|
|
9706
|
+
absoluteContainerOffset,
|
|
9707
|
+
)
|
|
9708
|
+
: patch.content
|
|
9709
|
+
: removeAbsolutePositioningFromNodeInHtml(
|
|
9710
|
+
patch.content,
|
|
9711
|
+
movedNodeAttrId,
|
|
9712
|
+
)
|
|
9328
9713
|
: patch.content;
|
|
9329
9714
|
const nextProjection = buildCodeLayerProjection(nextContent);
|
|
9330
9715
|
const movedNode =
|
|
@@ -9554,76 +9939,9 @@ export default function DesignEditor() {
|
|
|
9554
9939
|
styles: Record<string, string>,
|
|
9555
9940
|
elementInfo?: ElementInfo,
|
|
9556
9941
|
) => {
|
|
9557
|
-
|
|
9558
|
-
handleVisualStyleChange(selector, styles, elementInfo);
|
|
9559
|
-
return;
|
|
9560
|
-
}
|
|
9561
|
-
if (!canEditDesign) return;
|
|
9562
|
-
const entries = Object.entries(styles).filter(
|
|
9563
|
-
([, value]) => value !== undefined,
|
|
9564
|
-
);
|
|
9565
|
-
if (entries.length === 0) return;
|
|
9566
|
-
const liveSnapshot = liveScreenSnapshotsById[screenId];
|
|
9567
|
-
const baseContent = liveSnapshot?.html ?? getScreenContent(screenId);
|
|
9568
|
-
const projection = buildCodeLayerProjection(baseContent);
|
|
9569
|
-
const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
|
|
9570
|
-
const targetNode = targetInfo
|
|
9571
|
-
? resolveCodeLayerNodeFromElementInfo(projection, targetInfo)
|
|
9572
|
-
: resolveCodeLayerNodeFromBridge(projection, selector);
|
|
9573
|
-
const fallbackContent = applyInlineStylesToHtml(baseContent, selector, {
|
|
9574
|
-
...Object.fromEntries(entries),
|
|
9575
|
-
});
|
|
9576
|
-
const stylePatch = entries.reduce<{
|
|
9577
|
-
content: string;
|
|
9578
|
-
failed: string | null;
|
|
9579
|
-
}>(
|
|
9580
|
-
(current, [property, value]) => {
|
|
9581
|
-
if (current.failed) return current;
|
|
9582
|
-
const patch = applyVisualEdit(current.content, {
|
|
9583
|
-
kind: "style",
|
|
9584
|
-
target: targetNode ? { nodeId: targetNode.id } : { selector },
|
|
9585
|
-
property,
|
|
9586
|
-
value,
|
|
9587
|
-
});
|
|
9588
|
-
if (patch.result.status !== "applied") {
|
|
9589
|
-
return {
|
|
9590
|
-
content: current.content,
|
|
9591
|
-
failed: codeLayerPatchMessage(
|
|
9592
|
-
patch.result.message,
|
|
9593
|
-
t("designEditor.patchProof.selectorMissing"),
|
|
9594
|
-
),
|
|
9595
|
-
};
|
|
9596
|
-
}
|
|
9597
|
-
return { content: patch.content, failed: null };
|
|
9598
|
-
},
|
|
9599
|
-
{ content: baseContent, failed: null },
|
|
9600
|
-
);
|
|
9601
|
-
const nextContent = stylePatch.failed
|
|
9602
|
-
? fallbackContent
|
|
9603
|
-
: stylePatch.content;
|
|
9604
|
-
if (!nextContent) {
|
|
9605
|
-
toast.error(
|
|
9606
|
-
stylePatch.failed ?? t("designEditor.patchProof.selectorMissing"),
|
|
9607
|
-
{ duration: 4000 },
|
|
9608
|
-
);
|
|
9609
|
-
return;
|
|
9610
|
-
}
|
|
9611
|
-
if (liveSnapshot) {
|
|
9612
|
-
updateLiveScreenSnapshotContent(screenId, nextContent);
|
|
9613
|
-
return;
|
|
9614
|
-
}
|
|
9615
|
-
applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
|
|
9942
|
+
recordPendingVisualStyleEdit(screenId, selector, styles, elementInfo);
|
|
9616
9943
|
},
|
|
9617
|
-
[
|
|
9618
|
-
activeFile?.id,
|
|
9619
|
-
applyFileContentUpdate,
|
|
9620
|
-
canEditDesign,
|
|
9621
|
-
getScreenContent,
|
|
9622
|
-
handleVisualStyleChange,
|
|
9623
|
-
liveScreenSnapshotsById,
|
|
9624
|
-
t,
|
|
9625
|
-
updateLiveScreenSnapshotContent,
|
|
9626
|
-
],
|
|
9944
|
+
[recordPendingVisualStyleEdit],
|
|
9627
9945
|
);
|
|
9628
9946
|
|
|
9629
9947
|
const handleScreenVisualStructureChange = useCallback(
|
|
@@ -9637,6 +9955,9 @@ export default function DesignEditor() {
|
|
|
9637
9955
|
sourceId?: string;
|
|
9638
9956
|
anchorSourceId?: string;
|
|
9639
9957
|
requestId?: string;
|
|
9958
|
+
dropMode?: "flow-insert" | "absolute-container";
|
|
9959
|
+
sourceRect?: { x: number; y: number; width: number; height: number };
|
|
9960
|
+
anchorRect?: { x: number; y: number; width: number; height: number };
|
|
9640
9961
|
},
|
|
9641
9962
|
) => {
|
|
9642
9963
|
if (screenId === activeFile?.id) {
|
|
@@ -9687,15 +10008,51 @@ export default function DesignEditor() {
|
|
|
9687
10008
|
);
|
|
9688
10009
|
return false;
|
|
9689
10010
|
}
|
|
9690
|
-
|
|
9691
|
-
|
|
10011
|
+
const movedNodeAttrId =
|
|
10012
|
+
targetNode?.dataAttributes["data-agent-native-node-id"] ??
|
|
10013
|
+
details?.sourceId ??
|
|
10014
|
+
elementInfo?.sourceId ??
|
|
9692
10015
|
(patch.result.after?.nodeId
|
|
9693
10016
|
? patch.projection.nodes.find(
|
|
9694
10017
|
(node) => node.id === patch.result.after?.nodeId,
|
|
10018
|
+
)?.dataAttributes["data-agent-native-node-id"]
|
|
10019
|
+
: undefined);
|
|
10020
|
+
const absoluteContainerOffset =
|
|
10021
|
+
details?.dropMode === "absolute-container" &&
|
|
10022
|
+
details.sourceRect &&
|
|
10023
|
+
details.anchorRect
|
|
10024
|
+
? {
|
|
10025
|
+
x: details.sourceRect.x - details.anchorRect.x,
|
|
10026
|
+
y: details.sourceRect.y - details.anchorRect.y,
|
|
10027
|
+
}
|
|
10028
|
+
: null;
|
|
10029
|
+
const nextContent =
|
|
10030
|
+
isAbsoluteCodeLayerNode(targetNode) && movedNodeAttrId
|
|
10031
|
+
? details?.dropMode === "absolute-container"
|
|
10032
|
+
? absoluteContainerOffset
|
|
10033
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
10034
|
+
patch.content,
|
|
10035
|
+
movedNodeAttrId,
|
|
10036
|
+
absoluteContainerOffset,
|
|
10037
|
+
)
|
|
10038
|
+
: patch.content
|
|
10039
|
+
: removeAbsolutePositioningFromNodeInHtml(
|
|
10040
|
+
patch.content,
|
|
10041
|
+
movedNodeAttrId,
|
|
10042
|
+
)
|
|
10043
|
+
: patch.content;
|
|
10044
|
+
const nextProjection = buildCodeLayerProjection(nextContent);
|
|
10045
|
+
applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
|
|
10046
|
+
const movedNode =
|
|
10047
|
+
(movedNodeAttrId
|
|
10048
|
+
? nextProjection.nodes.find(
|
|
10049
|
+
(node) =>
|
|
10050
|
+
node.dataAttributes["data-agent-native-node-id"] ===
|
|
10051
|
+
movedNodeAttrId,
|
|
9695
10052
|
)
|
|
9696
10053
|
: null) ??
|
|
9697
10054
|
resolveCodeLayerNodeFromBridge(
|
|
9698
|
-
|
|
10055
|
+
nextProjection,
|
|
9699
10056
|
selector,
|
|
9700
10057
|
details?.sourceId ??
|
|
9701
10058
|
elementInfo?.sourceId ??
|
|
@@ -9925,11 +10282,18 @@ export default function DesignEditor() {
|
|
|
9925
10282
|
candidate.dataAttributes["data-agent-native-node-id"] === layerId,
|
|
9926
10283
|
);
|
|
9927
10284
|
if (!node?.source) continue;
|
|
10285
|
+
const portableStyleSnapshot =
|
|
10286
|
+
selectedElementLayerId &&
|
|
10287
|
+
node.id === selectedElementLayerId &&
|
|
10288
|
+
selectedElement?.portableStyleSnapshot
|
|
10289
|
+
? selectedElement.portableStyleSnapshot
|
|
10290
|
+
: undefined;
|
|
9928
10291
|
snapshots.push({
|
|
9929
10292
|
html: content.slice(node.source.start, node.source.end),
|
|
9930
10293
|
rootNodeId:
|
|
9931
10294
|
node.dataAttributes["data-agent-native-node-id"] ?? node.id,
|
|
9932
10295
|
sourceFileId: file.id,
|
|
10296
|
+
portableStyleSnapshot,
|
|
9933
10297
|
node,
|
|
9934
10298
|
sourceIndex: node.source.start,
|
|
9935
10299
|
tree,
|
|
@@ -9956,6 +10320,7 @@ export default function DesignEditor() {
|
|
|
9956
10320
|
selectedElement.sourceId ??
|
|
9957
10321
|
selectedElement.id,
|
|
9958
10322
|
sourceFileId: activeFile.id,
|
|
10323
|
+
portableStyleSnapshot: selectedElement.portableStyleSnapshot,
|
|
9959
10324
|
node,
|
|
9960
10325
|
sourceIndex: node.source?.start ?? Number.MAX_SAFE_INTEGER,
|
|
9961
10326
|
tree,
|
|
@@ -10020,19 +10385,31 @@ export default function DesignEditor() {
|
|
|
10020
10385
|
)
|
|
10021
10386
|
.filter((node): node is CodeLayerNode => Boolean(node));
|
|
10022
10387
|
if (insertedNodes.length === 0) return;
|
|
10388
|
+
const lastNode = insertedNodes[insertedNodes.length - 1];
|
|
10389
|
+
if (lastNode) {
|
|
10390
|
+
pendingOverviewScreenSelectionRef.current =
|
|
10391
|
+
screenId === boardFileId ? null : screenId;
|
|
10392
|
+
pendingOverviewLayerSelectionRef.current = lastNode.id;
|
|
10393
|
+
clearPendingOverviewLayerSelectionTimer();
|
|
10394
|
+
setCreatedOverviewLayerSelection({
|
|
10395
|
+
screenId,
|
|
10396
|
+
layerId: lastNode.id,
|
|
10397
|
+
});
|
|
10398
|
+
}
|
|
10023
10399
|
setActiveFileId(screenId);
|
|
10024
10400
|
setSelectedLayerIdsState(insertedNodes.map((node) => node.id));
|
|
10025
|
-
const lastNode = insertedNodes[insertedNodes.length - 1];
|
|
10026
10401
|
setSelectedElement(
|
|
10027
10402
|
lastNode ? elementInfoFromCodeLayerNode(lastNode) : null,
|
|
10028
10403
|
);
|
|
10029
10404
|
setActiveTool("move");
|
|
10030
10405
|
setMode("edit");
|
|
10031
10406
|
if (viewModeRef.current === "overview") {
|
|
10032
|
-
setOverviewSelectedScreenIds(
|
|
10407
|
+
setOverviewSelectedScreenIds(
|
|
10408
|
+
screenId === boardFileId ? [] : [screenId],
|
|
10409
|
+
);
|
|
10033
10410
|
}
|
|
10034
10411
|
},
|
|
10035
|
-
[],
|
|
10412
|
+
[boardFileId, clearPendingOverviewLayerSelectionTimer],
|
|
10036
10413
|
);
|
|
10037
10414
|
|
|
10038
10415
|
const handleCopySelection = useCallback(async () => {
|
|
@@ -10040,6 +10417,7 @@ export default function DesignEditor() {
|
|
|
10040
10417
|
html: snapshot.html,
|
|
10041
10418
|
rootNodeId: snapshot.rootNodeId,
|
|
10042
10419
|
sourceFileId: snapshot.sourceFileId,
|
|
10420
|
+
portableStyleSnapshot: snapshot.portableStyleSnapshot,
|
|
10043
10421
|
}));
|
|
10044
10422
|
if (entries.length === 0) return;
|
|
10045
10423
|
const clipboardText = entries.map((entry) => entry.html).join("\n");
|
|
@@ -10057,9 +10435,32 @@ export default function DesignEditor() {
|
|
|
10057
10435
|
const handlePasteSelection = useCallback(
|
|
10058
10436
|
(position?: { x: number; y: number }) => {
|
|
10059
10437
|
const entries = getCanvasClipboardEntries();
|
|
10060
|
-
|
|
10061
|
-
|
|
10438
|
+
const targetFileId =
|
|
10439
|
+
viewModeRef.current === "overview" && position && boardFileId
|
|
10440
|
+
? boardFileId
|
|
10441
|
+
: activeFile?.id;
|
|
10442
|
+
if (!targetFileId || !canEditDesign || entries.length === 0) return;
|
|
10443
|
+
const targetIsBoard = targetFileId === boardFileId;
|
|
10444
|
+
const baseContent =
|
|
10445
|
+
targetFileId === activeFile?.id
|
|
10446
|
+
? getFreshActiveContent()
|
|
10447
|
+
: (getScreenContent(targetFileId) ?? "");
|
|
10448
|
+
if (!baseContent && targetFileId !== boardFileId) return;
|
|
10062
10449
|
const layerHtmls = entries.map((entry) => entry.html);
|
|
10450
|
+
const styleSnapshots = targetIsBoard
|
|
10451
|
+
? entries.map((entry) => entry.portableStyleSnapshot)
|
|
10452
|
+
: undefined;
|
|
10453
|
+
const applyPasteContentUpdate = (nextContent: string) => {
|
|
10454
|
+
if (targetFileId === activeFile?.id) {
|
|
10455
|
+
applyLocalContentUpdate(nextContent, {
|
|
10456
|
+
forcePreviewFullDocument: true,
|
|
10457
|
+
});
|
|
10458
|
+
return;
|
|
10459
|
+
}
|
|
10460
|
+
applyFileContentUpdate(targetFileId, nextContent, {
|
|
10461
|
+
forcePreviewFullDocument: true,
|
|
10462
|
+
});
|
|
10463
|
+
};
|
|
10063
10464
|
|
|
10064
10465
|
// B7 fix: when an element is selected and no explicit canvas position was
|
|
10065
10466
|
// given, insert the clone as an in-flow sibling right AFTER the selected
|
|
@@ -10067,20 +10468,19 @@ export default function DesignEditor() {
|
|
|
10067
10468
|
// in normal document flow instead of being an absolutely-positioned body
|
|
10068
10469
|
// child. Fall back to the old position-based clone when nothing is
|
|
10069
10470
|
// selected or a "Paste here" position is provided.
|
|
10070
|
-
if (!position && selectedElement?.selector) {
|
|
10471
|
+
if (!position && !targetIsBoard && selectedElement?.selector) {
|
|
10071
10472
|
const selector = selectedCanvasSelector ?? selectedElement.selector;
|
|
10072
10473
|
const result = insertClonedHtmlLayers(baseContent, layerHtmls, {
|
|
10073
10474
|
targetSelectors: [selector],
|
|
10074
10475
|
placement: "after",
|
|
10075
10476
|
stripRootPosition: true,
|
|
10477
|
+
styleSnapshots,
|
|
10076
10478
|
});
|
|
10077
10479
|
if (result) {
|
|
10078
10480
|
pasteCascadeRef.current += 1;
|
|
10079
|
-
|
|
10080
|
-
forcePreviewFullDocument: true,
|
|
10081
|
-
});
|
|
10481
|
+
applyPasteContentUpdate(result.content);
|
|
10082
10482
|
selectInsertedLayers(
|
|
10083
|
-
|
|
10483
|
+
targetFileId,
|
|
10084
10484
|
result.content,
|
|
10085
10485
|
result.rootNodeIds,
|
|
10086
10486
|
);
|
|
@@ -10127,20 +10527,22 @@ export default function DesignEditor() {
|
|
|
10127
10527
|
});
|
|
10128
10528
|
const result = insertClonedHtmlLayers(baseContent, layerHtmls, {
|
|
10129
10529
|
positions,
|
|
10530
|
+
styleSnapshots,
|
|
10130
10531
|
});
|
|
10131
10532
|
if (!result) return;
|
|
10132
10533
|
if (!position) pasteCascadeRef.current += 1;
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
});
|
|
10136
|
-
selectInsertedLayers(activeFile.id, result.content, result.rootNodeIds);
|
|
10534
|
+
applyPasteContentUpdate(result.content);
|
|
10535
|
+
selectInsertedLayers(targetFileId, result.content, result.rootNodeIds);
|
|
10137
10536
|
},
|
|
10138
10537
|
[
|
|
10139
10538
|
activeFile,
|
|
10539
|
+
applyFileContentUpdate,
|
|
10140
10540
|
applyLocalContentUpdate,
|
|
10541
|
+
boardFileId,
|
|
10141
10542
|
canEditDesign,
|
|
10142
10543
|
getCanvasClipboardEntries,
|
|
10143
10544
|
getFreshActiveContent,
|
|
10545
|
+
getScreenContent,
|
|
10144
10546
|
selectInsertedLayers,
|
|
10145
10547
|
selectedCanvasSelector,
|
|
10146
10548
|
selectedElement,
|
|
@@ -10153,6 +10555,7 @@ export default function DesignEditor() {
|
|
|
10153
10555
|
const baseContent = getFreshActiveContent();
|
|
10154
10556
|
if (selectedElement?.boundingRect) {
|
|
10155
10557
|
const { x, y } = selectedElement.boundingRect;
|
|
10558
|
+
const targetIsBoard = activeFile.id === boardFileId;
|
|
10156
10559
|
const result = insertClonedHtmlLayers(
|
|
10157
10560
|
baseContent,
|
|
10158
10561
|
entries.map((entry) => entry.html),
|
|
@@ -10161,6 +10564,9 @@ export default function DesignEditor() {
|
|
|
10161
10564
|
x: x + index * 16,
|
|
10162
10565
|
y: y + index * 16,
|
|
10163
10566
|
})),
|
|
10567
|
+
styleSnapshots: targetIsBoard
|
|
10568
|
+
? entries.map((entry) => entry.portableStyleSnapshot)
|
|
10569
|
+
: undefined,
|
|
10164
10570
|
},
|
|
10165
10571
|
);
|
|
10166
10572
|
if (!result) return;
|
|
@@ -10174,6 +10580,7 @@ export default function DesignEditor() {
|
|
|
10174
10580
|
}, [
|
|
10175
10581
|
activeFile,
|
|
10176
10582
|
applyLocalContentUpdate,
|
|
10583
|
+
boardFileId,
|
|
10177
10584
|
getCanvasClipboardEntries,
|
|
10178
10585
|
getFreshActiveContent,
|
|
10179
10586
|
handlePasteSelection,
|
|
@@ -10502,9 +10909,9 @@ export default function DesignEditor() {
|
|
|
10502
10909
|
* Handle a primitive being drag-dropped onto another primitive in the
|
|
10503
10910
|
* MultiScreenCanvas overview (CONTRACT: onPrimitiveReparent prop).
|
|
10504
10911
|
*
|
|
10505
|
-
* Same-screen: applies a moveNode intent then
|
|
10506
|
-
* absolute
|
|
10507
|
-
*
|
|
10912
|
+
* Same-screen: applies a moveNode intent then rebases the moved node's
|
|
10913
|
+
* absolute coordinates relative to the target rectangle. Cross-screen uses
|
|
10914
|
+
* moveNodeBetweenDocuments and persists both files.
|
|
10508
10915
|
*/
|
|
10509
10916
|
const handleOverviewPrimitiveReparent = useCallback(
|
|
10510
10917
|
({
|
|
@@ -10544,27 +10951,38 @@ export default function DesignEditor() {
|
|
|
10544
10951
|
return;
|
|
10545
10952
|
}
|
|
10546
10953
|
|
|
10547
|
-
// 2. Strip absolute positioning from the moved node so it flows naturally.
|
|
10548
|
-
// Use removeAbsolutePositioningFromNodeInHtml (DOM-based) because the
|
|
10549
|
-
// applyVisualEdit substrate rejects empty-string values in isSafeStyleValue,
|
|
10550
|
-
// making applyVisualEdit({kind:"style",value:""}) a silent no-op.
|
|
10551
10954
|
const movedNodeAttrId =
|
|
10552
10955
|
movePatch.projection.nodes.find(
|
|
10553
10956
|
(n) =>
|
|
10554
10957
|
n.dataAttributes["data-agent-native-node-id"] === sourceNodeId ||
|
|
10555
10958
|
n.id === sourceNodeId,
|
|
10556
10959
|
)?.dataAttributes["data-agent-native-node-id"] ?? sourceNodeId;
|
|
10557
|
-
const
|
|
10558
|
-
|
|
10559
|
-
|
|
10960
|
+
const sourcePosition = getAbsolutePositioningForNodeInHtml(
|
|
10961
|
+
baseContent,
|
|
10962
|
+
sourceNodeId,
|
|
10560
10963
|
);
|
|
10964
|
+
const targetPosition = getAbsolutePositioningForNodeInHtml(
|
|
10965
|
+
baseContent,
|
|
10966
|
+
targetNodeId,
|
|
10967
|
+
);
|
|
10968
|
+
const nextContent =
|
|
10969
|
+
sourcePosition && targetPosition
|
|
10970
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
10971
|
+
movePatch.content,
|
|
10972
|
+
movedNodeAttrId,
|
|
10973
|
+
{
|
|
10974
|
+
x: sourcePosition.x - targetPosition.x,
|
|
10975
|
+
y: sourcePosition.y - targetPosition.y,
|
|
10976
|
+
},
|
|
10977
|
+
)
|
|
10978
|
+
: movePatch.content;
|
|
10561
10979
|
|
|
10562
|
-
applyFileContentUpdate(sourceScreenId,
|
|
10980
|
+
applyFileContentUpdate(sourceScreenId, nextContent, {
|
|
10563
10981
|
skipPreview: true,
|
|
10564
10982
|
});
|
|
10565
10983
|
|
|
10566
10984
|
// Re-select the moved node.
|
|
10567
|
-
const nextProjection = buildCodeLayerProjection(
|
|
10985
|
+
const nextProjection = buildCodeLayerProjection(nextContent);
|
|
10568
10986
|
const movedNodeAfter = nextProjection.nodes.find(
|
|
10569
10987
|
(n) =>
|
|
10570
10988
|
n.dataAttributes["data-agent-native-node-id"] === sourceNodeId ||
|
|
@@ -10616,18 +11034,26 @@ export default function DesignEditor() {
|
|
|
10616
11034
|
return;
|
|
10617
11035
|
}
|
|
10618
11036
|
|
|
10619
|
-
// Strip absolute positioning from the moved node in the destination.
|
|
10620
|
-
// Use removeAbsolutePositioningFromNodeInHtml (DOM-based) because
|
|
10621
|
-
// applyVisualEdit({kind:"style",value:""}) is a silent no-op (the
|
|
10622
|
-
// substrate rejects empty-string values in isSafeStyleValue). Use
|
|
10623
|
-
// result.movedNodeId (the final id in destHtml, which may differ from
|
|
10624
|
-
// nodeAttrId when a collision triggered an id re-stamp) so the strip and
|
|
10625
|
-
// re-selection always find the correct element.
|
|
10626
11037
|
const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
|
|
10627
|
-
const
|
|
10628
|
-
|
|
10629
|
-
|
|
11038
|
+
const sourcePosition = getAbsolutePositioningForNodeInHtml(
|
|
11039
|
+
sourceContent,
|
|
11040
|
+
nodeAttrId,
|
|
10630
11041
|
);
|
|
11042
|
+
const targetPosition = getAbsolutePositioningForNodeInHtml(
|
|
11043
|
+
destContent,
|
|
11044
|
+
anchorAttrId,
|
|
11045
|
+
);
|
|
11046
|
+
const nextDestContent =
|
|
11047
|
+
sourcePosition && targetPosition
|
|
11048
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
11049
|
+
result.destHtml,
|
|
11050
|
+
destNodeAttrId,
|
|
11051
|
+
{
|
|
11052
|
+
x: sourcePosition.x - targetPosition.x,
|
|
11053
|
+
y: sourcePosition.y - targetPosition.y,
|
|
11054
|
+
},
|
|
11055
|
+
)
|
|
11056
|
+
: result.destHtml;
|
|
10631
11057
|
|
|
10632
11058
|
recordContentHistoryEntry({
|
|
10633
11059
|
changes: [
|
|
@@ -10639,7 +11065,7 @@ export default function DesignEditor() {
|
|
|
10639
11065
|
{
|
|
10640
11066
|
fileId: targetScreenId,
|
|
10641
11067
|
before: destContent,
|
|
10642
|
-
after:
|
|
11068
|
+
after: nextDestContent,
|
|
10643
11069
|
},
|
|
10644
11070
|
],
|
|
10645
11071
|
});
|
|
@@ -10648,13 +11074,13 @@ export default function DesignEditor() {
|
|
|
10648
11074
|
recordHistory: false,
|
|
10649
11075
|
refreshPreview: true,
|
|
10650
11076
|
});
|
|
10651
|
-
applyFileContentUpdate(targetScreenId,
|
|
11077
|
+
applyFileContentUpdate(targetScreenId, nextDestContent, {
|
|
10652
11078
|
recordHistory: false,
|
|
10653
11079
|
refreshPreview: true,
|
|
10654
11080
|
});
|
|
10655
11081
|
|
|
10656
11082
|
// Re-select the moved node in the destination.
|
|
10657
|
-
const finalProjection = buildCodeLayerProjection(
|
|
11083
|
+
const finalProjection = buildCodeLayerProjection(nextDestContent);
|
|
10658
11084
|
const movedNodeFinal = finalProjection.nodes.find(
|
|
10659
11085
|
(n) => n.dataAttributes["data-agent-native-node-id"] === destNodeAttrId,
|
|
10660
11086
|
);
|
|
@@ -10695,8 +11121,11 @@ export default function DesignEditor() {
|
|
|
10695
11121
|
targetScreenId,
|
|
10696
11122
|
targetAnchorNodeId,
|
|
10697
11123
|
targetAnchorPlacement,
|
|
11124
|
+
targetDropMode,
|
|
11125
|
+
targetAnchorRect,
|
|
10698
11126
|
targetLocalPoint,
|
|
10699
11127
|
sourcePointerOffset,
|
|
11128
|
+
styleSnapshot,
|
|
10700
11129
|
}: {
|
|
10701
11130
|
sourceSelector: string;
|
|
10702
11131
|
sourceNodeId?: string;
|
|
@@ -10704,9 +11133,17 @@ export default function DesignEditor() {
|
|
|
10704
11133
|
targetScreenId: string;
|
|
10705
11134
|
targetAnchorNodeId?: string;
|
|
10706
11135
|
targetAnchorPlacement?: "before" | "after" | "inside";
|
|
11136
|
+
targetDropMode?: "flow-insert" | "absolute-container";
|
|
11137
|
+
targetAnchorRect?: {
|
|
11138
|
+
left: number;
|
|
11139
|
+
top: number;
|
|
11140
|
+
width: number;
|
|
11141
|
+
height: number;
|
|
11142
|
+
};
|
|
10707
11143
|
targetCanvasPoint?: { x: number; y: number };
|
|
10708
11144
|
targetLocalPoint?: { x: number; y: number };
|
|
10709
11145
|
sourcePointerOffset?: { x: number; y: number };
|
|
11146
|
+
styleSnapshot?: PortableStyleSnapshot;
|
|
10710
11147
|
}) => {
|
|
10711
11148
|
if (!canEditDesign) return;
|
|
10712
11149
|
if (sourceScreenId === targetScreenId) return;
|
|
@@ -10772,19 +11209,39 @@ export default function DesignEditor() {
|
|
|
10772
11209
|
// there is no anchor, preserve absolute mode and rebase left/top to the
|
|
10773
11210
|
// release point so screen↔board moves behave like Figma absolute layers.
|
|
10774
11211
|
const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
|
|
11212
|
+
const stylePreservedDest =
|
|
11213
|
+
targetScreenId === boardFileId
|
|
11214
|
+
? applyPortableStyleSnapshotToHtml(
|
|
11215
|
+
result.destHtml,
|
|
11216
|
+
destNodeAttrId,
|
|
11217
|
+
styleSnapshot,
|
|
11218
|
+
)
|
|
11219
|
+
: result.destHtml;
|
|
10775
11220
|
const nextDestContent = targetAnchorAttrId
|
|
10776
|
-
?
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
11221
|
+
? targetDropMode === "absolute-container"
|
|
11222
|
+
? targetLocalPoint && targetAnchorRect
|
|
11223
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
11224
|
+
stylePreservedDest,
|
|
11225
|
+
destNodeAttrId,
|
|
11226
|
+
{
|
|
11227
|
+
x: targetLocalPoint.x - targetAnchorRect.left,
|
|
11228
|
+
y: targetLocalPoint.y - targetAnchorRect.top,
|
|
11229
|
+
},
|
|
11230
|
+
sourcePointerOffset,
|
|
11231
|
+
)
|
|
11232
|
+
: stylePreservedDest
|
|
11233
|
+
: removeAbsolutePositioningFromNodeInHtml(
|
|
11234
|
+
stylePreservedDest,
|
|
11235
|
+
destNodeAttrId,
|
|
11236
|
+
)
|
|
10780
11237
|
: targetLocalPoint
|
|
10781
11238
|
? setAbsolutePositioningForNodeInHtml(
|
|
10782
|
-
|
|
11239
|
+
stylePreservedDest,
|
|
10783
11240
|
destNodeAttrId,
|
|
10784
11241
|
targetLocalPoint,
|
|
10785
11242
|
sourcePointerOffset,
|
|
10786
11243
|
)
|
|
10787
|
-
:
|
|
11244
|
+
: stylePreservedDest;
|
|
10788
11245
|
|
|
10789
11246
|
recordContentHistoryEntry({
|
|
10790
11247
|
changes: [
|
|
@@ -10812,19 +11269,34 @@ export default function DesignEditor() {
|
|
|
10812
11269
|
|
|
10813
11270
|
// Switch active screen to the target and select the moved node; viewMode
|
|
10814
11271
|
// stays "overview" (no setViewMode call).
|
|
11272
|
+
pendingOverviewScreenSelectionRef.current =
|
|
11273
|
+
targetScreenId === boardFileId ? null : targetScreenId;
|
|
11274
|
+
pendingOverviewLayerSelectionRef.current = destNodeAttrId;
|
|
11275
|
+
clearPendingOverviewLayerSelectionTimer();
|
|
10815
11276
|
setActiveFileId(targetScreenId);
|
|
10816
11277
|
const finalProjection = buildCodeLayerProjection(nextDestContent);
|
|
10817
11278
|
const movedNodeFinal = finalProjection.nodes.find(
|
|
10818
11279
|
(n) => n.dataAttributes["data-agent-native-node-id"] === destNodeAttrId,
|
|
10819
11280
|
);
|
|
10820
11281
|
if (movedNodeFinal) {
|
|
11282
|
+
setCreatedOverviewLayerSelection({
|
|
11283
|
+
screenId: targetScreenId,
|
|
11284
|
+
layerId: movedNodeFinal.id,
|
|
11285
|
+
});
|
|
10821
11286
|
setSelectedLayerIdsState([movedNodeFinal.id]);
|
|
10822
11287
|
setSelectedElement(elementInfoFromCodeLayerNode(movedNodeFinal));
|
|
11288
|
+
if (viewModeRef.current === "overview") {
|
|
11289
|
+
setOverviewSelectedScreenIds(
|
|
11290
|
+
targetScreenId === boardFileId ? [] : [targetScreenId],
|
|
11291
|
+
);
|
|
11292
|
+
}
|
|
10823
11293
|
}
|
|
10824
11294
|
},
|
|
10825
11295
|
[
|
|
10826
11296
|
applyFileContentUpdate,
|
|
11297
|
+
boardFileId,
|
|
10827
11298
|
canEditDesign,
|
|
11299
|
+
clearPendingOverviewLayerSelectionTimer,
|
|
10828
11300
|
getScreenContent,
|
|
10829
11301
|
recordContentHistoryEntry,
|
|
10830
11302
|
t,
|
|
@@ -11707,6 +12179,7 @@ export default function DesignEditor() {
|
|
|
11707
12179
|
]);
|
|
11708
12180
|
|
|
11709
12181
|
const handleEscapeHotkey = useCallback(() => {
|
|
12182
|
+
if (cancelActiveEditorDrag()) return;
|
|
11710
12183
|
if (
|
|
11711
12184
|
shouldEscapeToOverview({
|
|
11712
12185
|
activeTool,
|
|
@@ -11730,6 +12203,7 @@ export default function DesignEditor() {
|
|
|
11730
12203
|
setMode("edit");
|
|
11731
12204
|
}, [
|
|
11732
12205
|
activeTool,
|
|
12206
|
+
cancelActiveEditorDrag,
|
|
11733
12207
|
drawMode,
|
|
11734
12208
|
enterOverviewFromZoom,
|
|
11735
12209
|
mode,
|
|
@@ -12078,17 +12552,13 @@ export default function DesignEditor() {
|
|
|
12078
12552
|
|
|
12079
12553
|
const getCodingHandoffClipboardText = useCallback(
|
|
12080
12554
|
(result: CodingHandoffResult | null) => {
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
: "";
|
|
12087
|
-
const detail = codingHandoffDetail.trim();
|
|
12088
|
-
if (!base || !detail) return base;
|
|
12089
|
-
return `${base}\n\nAdditional implementation detail:\n${detail}`;
|
|
12555
|
+
return typeof result?.clipboardText === "string"
|
|
12556
|
+
? result.clipboardText
|
|
12557
|
+
: typeof result?.prompt === "string"
|
|
12558
|
+
? result.prompt
|
|
12559
|
+
: "";
|
|
12090
12560
|
},
|
|
12091
|
-
[
|
|
12561
|
+
[],
|
|
12092
12562
|
);
|
|
12093
12563
|
|
|
12094
12564
|
const handleCopyCodingHandoff = useCallback(async () => {
|
|
@@ -12106,6 +12576,125 @@ export default function DesignEditor() {
|
|
|
12106
12576
|
}
|
|
12107
12577
|
}, [ensureCodingHandoff, getCodingHandoffClipboardText, t]);
|
|
12108
12578
|
|
|
12579
|
+
const handleCopyShareLink = useCallback(async () => {
|
|
12580
|
+
if (!editorShareUrl) return;
|
|
12581
|
+
try {
|
|
12582
|
+
await navigator.clipboard.writeText(editorShareUrl);
|
|
12583
|
+
setShareLinkCopied(true);
|
|
12584
|
+
if (shareLinkCopiedResetRef.current !== null) {
|
|
12585
|
+
window.clearTimeout(shareLinkCopiedResetRef.current);
|
|
12586
|
+
}
|
|
12587
|
+
shareLinkCopiedResetRef.current = window.setTimeout(() => {
|
|
12588
|
+
setShareLinkCopied(false);
|
|
12589
|
+
shareLinkCopiedResetRef.current = null;
|
|
12590
|
+
}, 1400);
|
|
12591
|
+
toast.success("Share link copied" /* i18n-ignore share copy toast */);
|
|
12592
|
+
} catch {
|
|
12593
|
+
toast.error(t("designEditor.toasts.clipboardBlocked"));
|
|
12594
|
+
}
|
|
12595
|
+
}, [editorShareUrl, t]);
|
|
12596
|
+
|
|
12597
|
+
const hasPendingVisualStyleEdits = pendingVisualStyleEdits.length > 0;
|
|
12598
|
+
useBeforeUnload(
|
|
12599
|
+
useCallback(
|
|
12600
|
+
(event: BeforeUnloadEvent) => {
|
|
12601
|
+
if (!hasPendingVisualStyleEdits) return;
|
|
12602
|
+
event.preventDefault();
|
|
12603
|
+
event.returnValue = "";
|
|
12604
|
+
},
|
|
12605
|
+
[hasPendingVisualStyleEdits],
|
|
12606
|
+
),
|
|
12607
|
+
{ capture: true },
|
|
12608
|
+
);
|
|
12609
|
+
const pendingVisualStyleNavigationBlocker = useBlocker(
|
|
12610
|
+
useCallback(
|
|
12611
|
+
({ currentLocation, nextLocation }) =>
|
|
12612
|
+
shouldBlockPendingVisualStyleNavigation({
|
|
12613
|
+
hasPendingVisualStyleEdits,
|
|
12614
|
+
currentPathname: currentLocation.pathname,
|
|
12615
|
+
nextPathname: nextLocation.pathname,
|
|
12616
|
+
}),
|
|
12617
|
+
[hasPendingVisualStyleEdits],
|
|
12618
|
+
),
|
|
12619
|
+
);
|
|
12620
|
+
const pendingVisualStyleWarningOpen =
|
|
12621
|
+
pendingVisualStyleNavigationBlocker.state === "blocked";
|
|
12622
|
+
const handleStayOnPendingVisualStyleNavigation = useCallback(() => {
|
|
12623
|
+
if (pendingVisualStyleNavigationBlocker.state !== "blocked") return;
|
|
12624
|
+
pendingVisualStyleNavigationBlocker.reset();
|
|
12625
|
+
}, [pendingVisualStyleNavigationBlocker]);
|
|
12626
|
+
const handleDiscardPendingVisualStylesAndNavigate = useCallback(() => {
|
|
12627
|
+
if (pendingVisualStyleNavigationBlocker.state !== "blocked") return;
|
|
12628
|
+
setPendingVisualStyleEdits([]);
|
|
12629
|
+
pendingVisualStyleNavigationBlocker.proceed();
|
|
12630
|
+
}, [pendingVisualStyleNavigationBlocker]);
|
|
12631
|
+
|
|
12632
|
+
const pendingVisualStylePropertyCount = useMemo(
|
|
12633
|
+
() => getPendingVisualStylePropertyCount(pendingVisualStyleEdits),
|
|
12634
|
+
[pendingVisualStyleEdits],
|
|
12635
|
+
);
|
|
12636
|
+
const pendingVisualStyleScreenSourceTypes = useMemo(
|
|
12637
|
+
() =>
|
|
12638
|
+
new Map<string, unknown>(
|
|
12639
|
+
overviewScreens.map((screen) => [
|
|
12640
|
+
screen.id,
|
|
12641
|
+
screen.sourceType ?? designSourceType,
|
|
12642
|
+
]),
|
|
12643
|
+
),
|
|
12644
|
+
[designSourceType, overviewScreens],
|
|
12645
|
+
);
|
|
12646
|
+
const showPendingVisualStyleApply = useMemo(
|
|
12647
|
+
() =>
|
|
12648
|
+
shouldShowPendingVisualStyleApply({
|
|
12649
|
+
edits: pendingVisualStyleEdits,
|
|
12650
|
+
screenSourceTypes: pendingVisualStyleScreenSourceTypes,
|
|
12651
|
+
fallbackSourceType: designSourceType,
|
|
12652
|
+
}),
|
|
12653
|
+
[
|
|
12654
|
+
designSourceType,
|
|
12655
|
+
pendingVisualStyleEdits,
|
|
12656
|
+
pendingVisualStyleScreenSourceTypes,
|
|
12657
|
+
],
|
|
12658
|
+
);
|
|
12659
|
+
const pendingVisualStylePrompt = useMemo(
|
|
12660
|
+
() =>
|
|
12661
|
+
formatPendingVisualStylePrompt({
|
|
12662
|
+
designId: id,
|
|
12663
|
+
designTitle: design?.title,
|
|
12664
|
+
activeFileId: activeFile?.id,
|
|
12665
|
+
activeFilename: activeFile?.filename,
|
|
12666
|
+
edits: pendingVisualStyleEdits,
|
|
12667
|
+
}),
|
|
12668
|
+
[
|
|
12669
|
+
activeFile?.filename,
|
|
12670
|
+
activeFile?.id,
|
|
12671
|
+
design?.title,
|
|
12672
|
+
id,
|
|
12673
|
+
pendingVisualStyleEdits,
|
|
12674
|
+
],
|
|
12675
|
+
);
|
|
12676
|
+
const handleApplyPendingVisualStylesWithAgent = useCallback(() => {
|
|
12677
|
+
if (pendingVisualStyleEdits.length === 0) return;
|
|
12678
|
+
sendToDesignAgentChat({
|
|
12679
|
+
message: t("designEditor.pendingVisualStyles.agentMessage"),
|
|
12680
|
+
context: pendingVisualStylePrompt,
|
|
12681
|
+
submit: true,
|
|
12682
|
+
openSidebar: true,
|
|
12683
|
+
});
|
|
12684
|
+
setPendingVisualStyleEdits([]);
|
|
12685
|
+
setActiveLeftPanel("agent");
|
|
12686
|
+
toast.success(t("designEditor.pendingVisualStyles.sentToast"));
|
|
12687
|
+
}, [pendingVisualStyleEdits.length, pendingVisualStylePrompt, t]);
|
|
12688
|
+
const handleCopyPendingVisualStylePrompt = useCallback(async () => {
|
|
12689
|
+
if (pendingVisualStyleEdits.length === 0) return;
|
|
12690
|
+
try {
|
|
12691
|
+
await navigator.clipboard.writeText(pendingVisualStylePrompt);
|
|
12692
|
+
toast.success(t("designEditor.pendingVisualStyles.copiedToast"));
|
|
12693
|
+
} catch {
|
|
12694
|
+
toast.error(t("designEditor.toasts.clipboardBlocked"));
|
|
12695
|
+
}
|
|
12696
|
+
}, [pendingVisualStyleEdits.length, pendingVisualStylePrompt, t]);
|
|
12697
|
+
|
|
12109
12698
|
const triggerBlobDownload = useCallback((blob: Blob, filename: string) => {
|
|
12110
12699
|
const url = URL.createObjectURL(blob);
|
|
12111
12700
|
const a = document.createElement("a");
|
|
@@ -12173,22 +12762,6 @@ export default function DesignEditor() {
|
|
|
12173
12762
|
});
|
|
12174
12763
|
}, [exportZipMutation, fallbackExportName, id, t, triggerBlobDownload]);
|
|
12175
12764
|
|
|
12176
|
-
const handleDownloadHandoffZip = useCallback(async () => {
|
|
12177
|
-
const result = await ensureCodingHandoff();
|
|
12178
|
-
if (!result?.zipUrl) {
|
|
12179
|
-
toast.error(t("designEditor.toasts.zipCreateError"));
|
|
12180
|
-
return;
|
|
12181
|
-
}
|
|
12182
|
-
const a = document.createElement("a");
|
|
12183
|
-
a.href = result.zipUrl;
|
|
12184
|
-
a.download = fallbackExportName("zip", "agent-handoff");
|
|
12185
|
-
a.rel = "noopener";
|
|
12186
|
-
document.body.appendChild(a);
|
|
12187
|
-
a.click();
|
|
12188
|
-
a.remove();
|
|
12189
|
-
toast.success(t("designEditor.toasts.zipDownloaded"));
|
|
12190
|
-
}, [ensureCodingHandoff, fallbackExportName, t]);
|
|
12191
|
-
|
|
12192
12765
|
const handleDownloadPng = useCallback(
|
|
12193
12766
|
async (settings?: Partial<ExportSettingsValue>) => {
|
|
12194
12767
|
if (pngExportingRef.current) return;
|
|
@@ -12395,14 +12968,6 @@ ${serializedHtml}
|
|
|
12395
12968
|
[handleDownloadPng, handleDownloadSvg],
|
|
12396
12969
|
);
|
|
12397
12970
|
|
|
12398
|
-
const handleSendToPrimaryAction = useCallback(() => {
|
|
12399
|
-
if (downloadZipInstead) {
|
|
12400
|
-
void handleDownloadHandoffZip();
|
|
12401
|
-
return;
|
|
12402
|
-
}
|
|
12403
|
-
void handleCopyCodingHandoff();
|
|
12404
|
-
}, [downloadZipInstead, handleCopyCodingHandoff, handleDownloadHandoffZip]);
|
|
12405
|
-
|
|
12406
12971
|
const shareExportOptions: Array<{
|
|
12407
12972
|
value: ShareExportFormat;
|
|
12408
12973
|
title: string;
|
|
@@ -12566,84 +13131,72 @@ ${serializedHtml}
|
|
|
12566
13131
|
<div className="flex flex-wrap items-center gap-2">
|
|
12567
13132
|
<Button
|
|
12568
13133
|
type="button"
|
|
12569
|
-
onClick={
|
|
12570
|
-
disabled={
|
|
12571
|
-
downloadZipInstead
|
|
12572
|
-
? !activeFile || codingHandoffLoading
|
|
12573
|
-
: codingHandoffLoading
|
|
12574
|
-
}
|
|
13134
|
+
onClick={() => void handleCopyCodingHandoff()}
|
|
13135
|
+
disabled={codingHandoffLoading}
|
|
12575
13136
|
className="h-8 gap-1.5 rounded-md px-3 text-[12px]"
|
|
12576
13137
|
>
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
) : (
|
|
12580
|
-
<IconClipboard className="size-3.5" />
|
|
12581
|
-
)}
|
|
12582
|
-
{
|
|
12583
|
-
downloadZipInstead
|
|
12584
|
-
? t("designEditor.downloadZip")
|
|
12585
|
-
: "Copy agent prompt" /* i18n-ignore share send action */
|
|
12586
|
-
}
|
|
13138
|
+
<IconClipboard className="size-3.5" />
|
|
13139
|
+
{"Copy agent prompt" /* i18n-ignore share send action */}
|
|
12587
13140
|
</Button>
|
|
12588
13141
|
</div>
|
|
12589
|
-
|
|
12590
|
-
<div className="space-y-3">
|
|
12591
|
-
<div className="flex items-start gap-2.5">
|
|
12592
|
-
<Checkbox
|
|
12593
|
-
checked={downloadZipInstead}
|
|
12594
|
-
onCheckedChange={(checked) =>
|
|
12595
|
-
setDownloadZipInstead(checked === true)
|
|
12596
|
-
}
|
|
12597
|
-
className="mt-0.5"
|
|
12598
|
-
/>
|
|
12599
|
-
<div className="min-w-0">
|
|
12600
|
-
<div className="text-[12px] font-medium text-foreground">
|
|
12601
|
-
{"Download zip instead" /* i18n-ignore share send option */}
|
|
12602
|
-
</div>
|
|
12603
|
-
<div className="mt-0.5 !text-[11px] leading-4 text-muted-foreground">
|
|
12604
|
-
{
|
|
12605
|
-
"For agents without the Design connector, drop the bundle into your agent's chat manually." /* i18n-ignore share send option description */
|
|
12606
|
-
}
|
|
12607
|
-
</div>
|
|
12608
|
-
</div>
|
|
12609
|
-
</div>
|
|
12610
|
-
|
|
12611
|
-
<div className="space-y-1.5">
|
|
12612
|
-
<label className="text-[12px] font-medium text-foreground">
|
|
12613
|
-
{
|
|
12614
|
-
"Give the agent more detail on what to implement" /* i18n-ignore share send detail label */
|
|
12615
|
-
}{" "}
|
|
12616
|
-
<span className="font-normal text-muted-foreground">
|
|
12617
|
-
{"(optional)" /* i18n-ignore optional label */}
|
|
12618
|
-
</span>
|
|
12619
|
-
</label>
|
|
12620
|
-
<Textarea
|
|
12621
|
-
value={codingHandoffDetail}
|
|
12622
|
-
onChange={(event) => setCodingHandoffDetail(event.target.value)}
|
|
12623
|
-
placeholder={activeFile?.filename ?? "Add implementation notes..."}
|
|
12624
|
-
className="min-h-20 resize-none rounded-md bg-[var(--design-editor-control-bg)] text-[12px]"
|
|
12625
|
-
/>
|
|
12626
|
-
</div>
|
|
12627
|
-
</div>
|
|
12628
13142
|
</div>
|
|
12629
13143
|
);
|
|
13144
|
+
const shareLinkFooter = (
|
|
13145
|
+
<div className="mt-3 flex flex-wrap items-center gap-2 border-t border-[var(--design-editor-panel-divider-color)] pt-3">
|
|
13146
|
+
<Button
|
|
13147
|
+
type="button"
|
|
13148
|
+
onClick={() => void handleCopyShareLink()}
|
|
13149
|
+
disabled={!editorShareUrl}
|
|
13150
|
+
className="h-8 min-w-[8.75rem] gap-1.5 rounded-md px-3 text-[12px]"
|
|
13151
|
+
>
|
|
13152
|
+
{shareLinkCopied ? (
|
|
13153
|
+
<IconCheck className="size-3.5" />
|
|
13154
|
+
) : (
|
|
13155
|
+
<IconClipboard className="size-3.5" />
|
|
13156
|
+
)}
|
|
13157
|
+
{
|
|
13158
|
+
shareLinkCopied
|
|
13159
|
+
? "Copied" /* i18n-ignore share copy action copied */
|
|
13160
|
+
: "Copy share link" /* i18n-ignore share copy action */
|
|
13161
|
+
}
|
|
13162
|
+
</Button>
|
|
13163
|
+
</div>
|
|
13164
|
+
);
|
|
13165
|
+
const designShareTabLabelClassName =
|
|
13166
|
+
"inline-flex items-center justify-center gap-1.5";
|
|
12630
13167
|
const designSharePopoverClassName =
|
|
12631
13168
|
"z-[100010] !w-[min(620px,calc(100vw-32px))] !p-3 " +
|
|
12632
|
-
"[&_[role=tablist]]:!inline-flex [&_[role=tablist]]:!w-fit [&_[role=tablist]]:!self-start [&_[role=tablist]]:justify-start [&_[role=tablist]]:gap-
|
|
12633
|
-
"[&_[role=tab]]:!h-
|
|
12634
|
-
"[&_[role=tab]:hover]:bg-
|
|
13169
|
+
"[&_[role=tablist]]:!inline-flex [&_[role=tablist]]:!w-fit [&_[role=tablist]]:!self-start [&_[role=tablist]]:justify-start [&_[role=tablist]]:gap-1 [&_[role=tablist]]:rounded-lg [&_[role=tablist]]:border [&_[role=tablist]]:border-[var(--design-editor-panel-divider-color)] [&_[role=tablist]]:bg-[var(--design-editor-panel-raised-bg)] [&_[role=tablist]]:p-1 " +
|
|
13170
|
+
"[&_[role=tab]]:!h-8 [&_[role=tab]]:!flex-none [&_[role=tab]]:rounded-md [&_[role=tab]]:px-3 [&_[role=tab]]:!text-[12px] [&_[role=tab]]:font-semibold [&_[role=tab]]:shadow-none [&_[role=tab]]:ring-0 " +
|
|
13171
|
+
"[&_[role=tab]:hover]:bg-white/70 dark:[&_[role=tab]:hover]:bg-[var(--design-editor-control-bg)] [&_[role=tab]:hover]:text-foreground " +
|
|
13172
|
+
"[&_[role=tab][aria-selected=true]]:bg-white dark:[&_[role=tab][aria-selected=true]]:bg-[var(--design-editor-control-bg)] [&_[role=tab][aria-selected=true]]:text-foreground [&_[role=tab][aria-selected=true]]:shadow-sm [&_[role=tab][aria-selected=true]]:ring-1 [&_[role=tab][aria-selected=true]]:ring-[var(--design-editor-control-border)]";
|
|
12635
13173
|
const designShareTabs = {
|
|
12636
|
-
shareLabel:
|
|
13174
|
+
shareLabel: (
|
|
13175
|
+
<span className={designShareTabLabelClassName}>
|
|
13176
|
+
<IconLink className="size-3.5" />
|
|
13177
|
+
{"Share link" /* i18n-ignore share tab label */}
|
|
13178
|
+
</span>
|
|
13179
|
+
),
|
|
12637
13180
|
defaultValue: "share",
|
|
12638
13181
|
tabs: [
|
|
12639
13182
|
{
|
|
12640
13183
|
value: "export",
|
|
12641
|
-
label:
|
|
13184
|
+
label: (
|
|
13185
|
+
<span className={designShareTabLabelClassName}>
|
|
13186
|
+
<IconFileExport className="size-3.5" />
|
|
13187
|
+
{t("designEditor.export")}
|
|
13188
|
+
</span>
|
|
13189
|
+
),
|
|
12642
13190
|
content: shareExportTab,
|
|
12643
13191
|
},
|
|
12644
13192
|
{
|
|
12645
13193
|
value: "send",
|
|
12646
|
-
label:
|
|
13194
|
+
label: (
|
|
13195
|
+
<span className={designShareTabLabelClassName}>
|
|
13196
|
+
<IconTerminal2 className="size-3.5" />
|
|
13197
|
+
{"Send to agent" /* i18n-ignore share tab label */}
|
|
13198
|
+
</span>
|
|
13199
|
+
),
|
|
12647
13200
|
content: shareSendToTab,
|
|
12648
13201
|
},
|
|
12649
13202
|
],
|
|
@@ -14950,6 +15503,9 @@ ${serializedHtml}
|
|
|
14950
15503
|
shareUrl={editorShareUrl}
|
|
14951
15504
|
shareUrlLabel={t("designEditor.shareEditorLink")}
|
|
14952
15505
|
shareUrlDescription={t("designEditor.shareEditorLinkDescription")}
|
|
15506
|
+
showShareLinks={false}
|
|
15507
|
+
showDoneButton={false}
|
|
15508
|
+
shareFooterContent={shareLinkFooter}
|
|
14953
15509
|
shareTabs={designShareTabs}
|
|
14954
15510
|
popoverClassName={designSharePopoverClassName}
|
|
14955
15511
|
triggerClassName="h-8 rounded-md !border-[var(--design-editor-accent-color)] !bg-[var(--design-editor-accent-color)] px-3 text-sm !text-[var(--design-editor-accent-contrast-color)] shadow-none hover:!border-[var(--design-editor-accent-hover-color)] hover:!bg-[var(--design-editor-accent-hover-color)] hover:!text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)] [&_svg]:!text-[var(--design-editor-accent-contrast-color)]"
|
|
@@ -15349,6 +15905,9 @@ ${serializedHtml}
|
|
|
15349
15905
|
shareUrlDescription={t(
|
|
15350
15906
|
"designEditor.shareEditorLinkDescription",
|
|
15351
15907
|
)}
|
|
15908
|
+
showShareLinks={false}
|
|
15909
|
+
showDoneButton={false}
|
|
15910
|
+
shareFooterContent={shareLinkFooter}
|
|
15352
15911
|
shareTabs={designShareTabs}
|
|
15353
15912
|
popoverClassName={designSharePopoverClassName}
|
|
15354
15913
|
triggerClassName="h-8 rounded-md !border-[var(--design-editor-accent-color)] !bg-[var(--design-editor-accent-color)] px-3 !text-[var(--design-editor-accent-contrast-color)] shadow-none hover:!border-[var(--design-editor-accent-hover-color)] hover:!bg-[var(--design-editor-accent-hover-color)] hover:!text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)] [&_svg]:!text-[var(--design-editor-accent-contrast-color)]"
|
|
@@ -15445,6 +16004,7 @@ ${serializedHtml}
|
|
|
15445
16004
|
t("chat.suggestionMobile"),
|
|
15446
16005
|
]}
|
|
15447
16006
|
scope={designChatScope}
|
|
16007
|
+
showScopeBadge={false}
|
|
15448
16008
|
browserTabId={browserTabId}
|
|
15449
16009
|
/>
|
|
15450
16010
|
</div>
|
|
@@ -15665,6 +16225,49 @@ ${serializedHtml}
|
|
|
15665
16225
|
}}
|
|
15666
16226
|
/>
|
|
15667
16227
|
)}
|
|
16228
|
+
{showPendingVisualStyleApply ? (
|
|
16229
|
+
<div className="pointer-events-none absolute bottom-5 right-5 z-[70] flex items-end">
|
|
16230
|
+
<DropdownMenu>
|
|
16231
|
+
<DropdownMenuTrigger asChild>
|
|
16232
|
+
<Button
|
|
16233
|
+
className="pointer-events-auto h-11 cursor-pointer rounded-md bg-blue-500 px-4 text-sm font-semibold text-white shadow-[0_18px_40px_-20px_rgba(37,99,235,0.9)] hover:bg-blue-400 focus-visible:ring-blue-400"
|
|
16234
|
+
aria-label={t(
|
|
16235
|
+
"designEditor.pendingVisualStyles.applyAria",
|
|
16236
|
+
)}
|
|
16237
|
+
>
|
|
16238
|
+
<IconBrush className="h-4 w-4" />
|
|
16239
|
+
{t("designEditor.pendingVisualStyles.applyButton")}
|
|
16240
|
+
<span className="rounded bg-white/20 px-1.5 py-0.5 text-xs font-semibold text-white">
|
|
16241
|
+
{pendingVisualStylePropertyCount}
|
|
16242
|
+
</span>
|
|
16243
|
+
<IconChevronDown className="h-4 w-4" />
|
|
16244
|
+
</Button>
|
|
16245
|
+
</DropdownMenuTrigger>
|
|
16246
|
+
<DropdownMenuContent
|
|
16247
|
+
align="end"
|
|
16248
|
+
className="design-editor-app-menu-content w-64"
|
|
16249
|
+
>
|
|
16250
|
+
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
|
16251
|
+
{t("designEditor.pendingVisualStyles.previewLabel")}
|
|
16252
|
+
</DropdownMenuLabel>
|
|
16253
|
+
<DropdownMenuItem
|
|
16254
|
+
onClick={handleApplyPendingVisualStylesWithAgent}
|
|
16255
|
+
>
|
|
16256
|
+
<IconMessage className="mr-2 h-4 w-4" />
|
|
16257
|
+
{t(
|
|
16258
|
+
"designEditor.pendingVisualStyles.applyWithAgent",
|
|
16259
|
+
)}
|
|
16260
|
+
</DropdownMenuItem>
|
|
16261
|
+
<DropdownMenuItem
|
|
16262
|
+
onClick={handleCopyPendingVisualStylePrompt}
|
|
16263
|
+
>
|
|
16264
|
+
<IconClipboard className="mr-2 h-4 w-4" />
|
|
16265
|
+
{t("designEditor.pendingVisualStyles.copyPrompt")}
|
|
16266
|
+
</DropdownMenuItem>
|
|
16267
|
+
</DropdownMenuContent>
|
|
16268
|
+
</DropdownMenu>
|
|
16269
|
+
</div>
|
|
16270
|
+
) : null}
|
|
15668
16271
|
{viewMode === "overview" ? (
|
|
15669
16272
|
<MultiScreenCanvas
|
|
15670
16273
|
screens={overviewScreens}
|
|
@@ -16019,6 +16622,9 @@ ${serializedHtml}
|
|
|
16019
16622
|
onElementHover={(info) =>
|
|
16020
16623
|
handleScreenElementHover(screen.id, info)
|
|
16021
16624
|
}
|
|
16625
|
+
onEditorDragStateChange={
|
|
16626
|
+
handleEditorDragStateChange
|
|
16627
|
+
}
|
|
16022
16628
|
onClearSelection={() =>
|
|
16023
16629
|
handleScreenElementClear(screen.id)
|
|
16024
16630
|
}
|
|
@@ -16139,6 +16745,7 @@ ${serializedHtml}
|
|
|
16139
16745
|
onElementSelect={handleElementSelect}
|
|
16140
16746
|
onElementMarqueeSelect={handleElementMarqueeSelect}
|
|
16141
16747
|
onElementHover={handleElementHover}
|
|
16748
|
+
onEditorDragStateChange={handleEditorDragStateChange}
|
|
16142
16749
|
onClearSelection={() => {
|
|
16143
16750
|
setSelectedElement(null);
|
|
16144
16751
|
setHoveredElement(null);
|
|
@@ -16399,6 +17006,37 @@ ${serializedHtml}
|
|
|
16399
17006
|
) : null}
|
|
16400
17007
|
</div>
|
|
16401
17008
|
|
|
17009
|
+
<AlertDialog open={pendingVisualStyleWarningOpen}>
|
|
17010
|
+
<AlertDialogContent>
|
|
17011
|
+
<AlertDialogHeader>
|
|
17012
|
+
<AlertDialogTitle>
|
|
17013
|
+
{t("designEditor.pendingVisualStyles.leaveTitle")}
|
|
17014
|
+
</AlertDialogTitle>
|
|
17015
|
+
<AlertDialogDescription>
|
|
17016
|
+
{t(
|
|
17017
|
+
pendingVisualStylePropertyCount === 1
|
|
17018
|
+
? "designEditor.pendingVisualStyles.leaveDescriptionOne"
|
|
17019
|
+
: "designEditor.pendingVisualStyles.leaveDescriptionOther",
|
|
17020
|
+
{ count: pendingVisualStylePropertyCount },
|
|
17021
|
+
)}
|
|
17022
|
+
</AlertDialogDescription>
|
|
17023
|
+
</AlertDialogHeader>
|
|
17024
|
+
<AlertDialogFooter>
|
|
17025
|
+
<AlertDialogCancel
|
|
17026
|
+
onClick={handleStayOnPendingVisualStyleNavigation}
|
|
17027
|
+
>
|
|
17028
|
+
{t("designEditor.pendingVisualStyles.stay")}
|
|
17029
|
+
</AlertDialogCancel>
|
|
17030
|
+
<AlertDialogAction
|
|
17031
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
17032
|
+
onClick={handleDiscardPendingVisualStylesAndNavigate}
|
|
17033
|
+
>
|
|
17034
|
+
{t("designEditor.pendingVisualStyles.leave")}
|
|
17035
|
+
</AlertDialogAction>
|
|
17036
|
+
</AlertDialogFooter>
|
|
17037
|
+
</AlertDialogContent>
|
|
17038
|
+
</AlertDialog>
|
|
17039
|
+
|
|
16402
17040
|
{/* Motion dock (§6.3) — bottom timeline mounted while opening, open, or
|
|
16403
17041
|
closing. Canvas remains visible above.
|
|
16404
17042
|
Preview-only scrubbing fires a motion-preview postMessage to the
|