@agent-native/core 0.163.4 → 0.164.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/templates/analytics/app/components/agents/FeatureFlagsFleetPanel.tsx +78 -19
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
- package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +4 -4
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +6 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
- package/corpus/templates/analytics/app/routes/_index.tsx +3 -2
- package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +7 -2
- package/corpus/templates/analytics/app/routes/dashboard.tsx +3 -2
- package/corpus/templates/analytics/app/routes/overview.tsx +3 -2
- package/corpus/templates/analytics/app/routes/traffic.tsx +3 -2
- package/corpus/templates/analytics/changelog/2026-08-18-verify-cross-app-feature-flag-changes.md +6 -0
- package/corpus/templates/analytics/netlify.toml +2 -0
- package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +196 -48
- package/corpus/templates/analytics/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/analytics/shared/feature-flags.ts +10 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +5 -1
- package/corpus/templates/assets/app/routes/brand-kits._index.tsx +4 -1
- package/corpus/templates/assets/app/routes/libraries.tsx +4 -1
- package/corpus/templates/assets/app/routes/picker.tsx +4 -1
- package/corpus/templates/assets/netlify.toml +2 -0
- package/corpus/templates/brain/netlify.toml +2 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
- package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
- package/corpus/templates/calendar/netlify.toml +2 -0
- package/corpus/templates/chat/netlify.toml +2 -0
- package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
- package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
- package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
- package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
- package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
- package/corpus/templates/clips/app/routes/_app.wispr.tsx +2 -1
- package/corpus/templates/clips/app/routes/_index.tsx +7 -3
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
- package/corpus/templates/clips/netlify.toml +2 -0
- package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
- package/corpus/templates/content/netlify.toml +2 -0
- package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
- package/corpus/templates/design/AGENTS.md +3 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
- package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
- package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
- package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
- package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
- package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
- package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
- package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
- package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
- package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
- package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
- package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
- package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
- package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
- package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
- package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
- package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
- package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
- package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
- package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
- package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
- package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
- package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
- package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
- package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
- package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
- package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
- package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
- package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
- package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
- package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
- package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
- package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
- package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
- package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
- package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
- package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
- package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
- package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
- package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
- package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
- package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
- package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
- package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
- package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
- package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
- package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
- package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
- package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
- package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
- package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
- package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
- package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
- package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
- package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
- package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
- package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
- package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
- package/corpus/templates/design/app/routes/examples.tsx +2 -1
- package/corpus/templates/design/netlify.toml +2 -0
- package/corpus/templates/design/shared/board-file.ts +2 -1
- package/corpus/templates/dispatch/netlify.toml +2 -0
- package/corpus/templates/forms/app/routes/_index.tsx +3 -2
- package/corpus/templates/forms/netlify.toml +2 -0
- package/corpus/templates/mail/app/routes/_index.tsx +3 -2
- package/corpus/templates/mail/netlify.toml +2 -0
- package/corpus/templates/plan/netlify.toml +2 -0
- package/corpus/templates/slides/netlify.toml +2 -0
- package/corpus/templates/tasks/app/routes/_index.tsx +2 -1
- package/dist/agent/engine/builder-engine.d.ts +6 -4
- package/dist/agent/engine/builder-engine.js +91 -26
- package/dist/agent/engine/registry.js +16 -0
- package/dist/cli/code-agent-agent-tools.d.ts +2 -0
- package/dist/cli/code-agent-agent-tools.js +188 -0
- package/dist/cli/code-agent-collaboration.d.ts +34 -0
- package/dist/cli/code-agent-collaboration.js +104 -0
- package/dist/cli/code-agent-executor.js +6 -0
- package/dist/cli/code-agent-schedules.d.ts +54 -0
- package/dist/cli/code-agent-schedules.js +198 -0
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/ConnectBuilderCard.js +5 -2
- package/dist/client/EnvironmentBadge.js +6 -5
- package/dist/client/chat/run-recovery.js +3 -2
- package/dist/client/chat/tool-call-display.js +2 -3
- package/dist/client/components/CodeRequiredDialog.js +4 -2
- package/dist/client/i18n.js +5 -4
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +6 -1
- package/dist/client/settings/useBuilderStatus.js +22 -33
- package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
- package/dist/code-agents/index.d.ts +2 -0
- package/dist/code-agents/index.js +2 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.js +10 -4
- package/dist/localization/shared.d.ts +2 -0
- package/dist/localization/shared.js +5 -0
- package/dist/mcp-client/oauth-client.d.ts +1 -0
- package/dist/mcp-client/oauth-client.js +50 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/server/auth-marketing-locales.d.ts +7 -0
- package/dist/server/auth-marketing-locales.js +1322 -0
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +35 -6
- package/dist/server/auth.js +4 -17
- package/dist/server/builder-browser.d.ts +22 -0
- package/dist/server/builder-browser.js +77 -1
- package/dist/server/builder-oauth.d.ts +50 -0
- package/dist/server/builder-oauth.js +263 -0
- package/dist/server/core-routes-plugin.d.ts +11 -0
- package/dist/server/core-routes-plugin.js +291 -415
- package/dist/server/credential-provider.d.ts +1 -1
- package/dist/server/credential-provider.js +1 -1
- package/dist/server/csrf.js +2 -2
- package/dist/server/onboarding-html.js +52 -120
- package/dist/server/ssr-handler.js +13 -4
- package/dist/settings/store.d.ts +9 -0
- package/dist/settings/store.js +19 -0
- package/dist/shared/cache-control.d.ts +22 -0
- package/dist/shared/cache-control.js +25 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/templates/chat/netlify.toml +2 -0
- package/docs/content/deployment.mdx +2 -0
- package/docs/content/multi-app-workspace.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/netlify.toml +2 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useActionMutation } from "@agent-native/core/client/hooks";
|
|
2
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
3
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
|
|
6
|
+
import type { DesignMigrationResult } from "@/components/design/editor/MakeRealDialog";
|
|
7
|
+
import type { DesignDataOperation } from "@/pages/design-editor/data-operations";
|
|
8
|
+
import { applyDesignDataOperations } from "@/pages/design-editor/data-operations";
|
|
9
|
+
|
|
10
|
+
export interface ConfirmMakeRealArgs {
|
|
11
|
+
designDataJsonRef: RefObject<Record<string, unknown>>;
|
|
12
|
+
id: string | undefined;
|
|
13
|
+
migrateMutation: ReturnType<
|
|
14
|
+
typeof useActionMutation<
|
|
15
|
+
undefined,
|
|
16
|
+
undefined,
|
|
17
|
+
"migrate-inline-design-to-app"
|
|
18
|
+
>
|
|
19
|
+
>;
|
|
20
|
+
queryClient: QueryClient;
|
|
21
|
+
setMigrationResult: Dispatch<SetStateAction<DesignMigrationResult | null>>;
|
|
22
|
+
updateDesignMutation: ReturnType<
|
|
23
|
+
typeof useActionMutation<undefined, undefined, "update-design">
|
|
24
|
+
>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function runConfirmMakeReal({
|
|
28
|
+
designDataJsonRef,
|
|
29
|
+
id,
|
|
30
|
+
migrateMutation,
|
|
31
|
+
queryClient,
|
|
32
|
+
setMigrationResult,
|
|
33
|
+
updateDesignMutation,
|
|
34
|
+
}: ConfirmMakeRealArgs) {
|
|
35
|
+
if (!id) return;
|
|
36
|
+
try {
|
|
37
|
+
const result = await migrateMutation.mutateAsync({ designId: id } as any);
|
|
38
|
+
const r = result as any;
|
|
39
|
+
setMigrationResult({
|
|
40
|
+
branchName: r?.branchName,
|
|
41
|
+
url: r?.url,
|
|
42
|
+
versionId: r?.versionId,
|
|
43
|
+
seedFileCount: r?.seedFileCount,
|
|
44
|
+
status: r?.status,
|
|
45
|
+
projectId: r?.projectId,
|
|
46
|
+
cta: r?.cta,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// When the Builder agent accepted the job (status = "processing"),
|
|
50
|
+
// flip the design data to sourceType "fusion" so capability-gated
|
|
51
|
+
// panels (branches, deploy) light up on refresh.
|
|
52
|
+
if (r?.status === "processing" && r?.url) {
|
|
53
|
+
const dataOperations: DesignDataOperation[] = [
|
|
54
|
+
{ op: "set", path: ["sourceType"], value: "fusion" },
|
|
55
|
+
{ op: "set", path: ["fusionUrl"], value: r.url },
|
|
56
|
+
r.branchName === undefined
|
|
57
|
+
? { op: "delete", path: ["fusionBranchName"] }
|
|
58
|
+
: {
|
|
59
|
+
op: "set",
|
|
60
|
+
path: ["fusionBranchName"],
|
|
61
|
+
value: r.branchName,
|
|
62
|
+
},
|
|
63
|
+
r.projectId === undefined
|
|
64
|
+
? { op: "delete", path: ["fusionProjectId"] }
|
|
65
|
+
: {
|
|
66
|
+
op: "set",
|
|
67
|
+
path: ["fusionProjectId"],
|
|
68
|
+
value: r.projectId,
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
const nextData = applyDesignDataOperations(
|
|
72
|
+
designDataJsonRef.current,
|
|
73
|
+
dataOperations,
|
|
74
|
+
);
|
|
75
|
+
designDataJsonRef.current = nextData;
|
|
76
|
+
queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
|
|
77
|
+
if (!old || typeof old !== "object") return old;
|
|
78
|
+
return { ...old, data: JSON.stringify(nextData) };
|
|
79
|
+
});
|
|
80
|
+
updateDesignMutation.mutate({ id, dataOperations } as any, {
|
|
81
|
+
onSuccess: () => {
|
|
82
|
+
queryClient.invalidateQueries({
|
|
83
|
+
queryKey: ["action", "get-design"],
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
onError: () => {
|
|
87
|
+
queryClient.invalidateQueries({
|
|
88
|
+
queryKey: ["action", "get-design"],
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
} else if (r?.status === "not-configured") {
|
|
93
|
+
// Builder not connected — leave dialog open to show the CTA.
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
const message = err instanceof Error ? err.message : "Migration failed";
|
|
97
|
+
toast.error(message);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
2
|
+
import { toast } from "sonner";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
LiveFigmaSvgSnapshot,
|
|
6
|
+
LiveFigmaSvgSource,
|
|
7
|
+
} from "@/lib/figma-svg-copy";
|
|
8
|
+
import {
|
|
9
|
+
FigmaSvgCopyError,
|
|
10
|
+
canCopyFigmaSvgToClipboard,
|
|
11
|
+
copyDesignAsFigmaSvg,
|
|
12
|
+
} from "@/lib/figma-svg-copy";
|
|
13
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
14
|
+
|
|
15
|
+
export interface CopyAsFigmaSvgArgs {
|
|
16
|
+
activeFile: DesignFile;
|
|
17
|
+
figmaSvgExportingRef: RefObject<boolean>;
|
|
18
|
+
id: string | undefined;
|
|
19
|
+
resolveLiveFigmaSvgSnapshot: (
|
|
20
|
+
targetFileId: string | undefined,
|
|
21
|
+
) => LiveFigmaSvgSnapshot | null;
|
|
22
|
+
resolveLiveFigmaSvgSource: (
|
|
23
|
+
targetFileId: string | undefined,
|
|
24
|
+
) => LiveFigmaSvgSource | null;
|
|
25
|
+
selectedElementLayerId: string | null;
|
|
26
|
+
selectedScreenIds: string[];
|
|
27
|
+
setFigmaSvgExporting: Dispatch<SetStateAction<boolean>>;
|
|
28
|
+
t: (key: string, options?: Record<string, unknown>) => string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function runCopyAsFigmaSvg({
|
|
32
|
+
activeFile,
|
|
33
|
+
figmaSvgExportingRef,
|
|
34
|
+
id,
|
|
35
|
+
resolveLiveFigmaSvgSnapshot,
|
|
36
|
+
resolveLiveFigmaSvgSource,
|
|
37
|
+
selectedElementLayerId,
|
|
38
|
+
selectedScreenIds,
|
|
39
|
+
setFigmaSvgExporting,
|
|
40
|
+
t,
|
|
41
|
+
}: CopyAsFigmaSvgArgs) {
|
|
42
|
+
if (figmaSvgExportingRef.current) return;
|
|
43
|
+
if (!canCopyFigmaSvgToClipboard()) {
|
|
44
|
+
toast.error(t("designEditor.toasts.figmaSvgUnsupported"));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
figmaSvgExportingRef.current = true;
|
|
49
|
+
setFigmaSvgExporting(true);
|
|
50
|
+
try {
|
|
51
|
+
// The export action defaults to `filename: "index.html"` when no
|
|
52
|
+
// fileId is given -- that only happens to work for a design whose
|
|
53
|
+
// screen is literally named index.html. This context menu is shared
|
|
54
|
+
// between single-screen edit view and overview mode, so resolve the
|
|
55
|
+
// actual target screen the same way PNG export does (selectedScreenIds
|
|
56
|
+
// covers both: activeFile.id in single-screen view, the
|
|
57
|
+
// overview-selected screen id(s) in overview mode) instead of letting
|
|
58
|
+
// the action guess. Regression: "Copy as SVG" on any non-index.html
|
|
59
|
+
// screen failed with "Design file not found".
|
|
60
|
+
const targetFileId = activeFile?.id ?? selectedScreenIds[0] ?? undefined;
|
|
61
|
+
await copyDesignAsFigmaSvg(
|
|
62
|
+
{
|
|
63
|
+
designId: id,
|
|
64
|
+
fileId: targetFileId,
|
|
65
|
+
nodeId: selectedElementLayerId ?? undefined,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
liveSource: resolveLiveFigmaSvgSource(targetFileId),
|
|
69
|
+
liveSnapshot: resolveLiveFigmaSvgSnapshot(targetFileId),
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
toast.success(t("designEditor.toasts.figmaSvgCopied"));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error instanceof FigmaSvgCopyError) {
|
|
75
|
+
const key =
|
|
76
|
+
error.code === "blocked"
|
|
77
|
+
? "designEditor.toasts.figmaSvgBlocked"
|
|
78
|
+
: error.code === "unsupported"
|
|
79
|
+
? "designEditor.toasts.figmaSvgUnsupported"
|
|
80
|
+
: error.code === "render-failed"
|
|
81
|
+
? "designEditor.toasts.figmaSvgRenderError"
|
|
82
|
+
: "designEditor.toasts.figmaSvgWriteError";
|
|
83
|
+
toast.error(t(key));
|
|
84
|
+
} else {
|
|
85
|
+
toast.error(
|
|
86
|
+
error instanceof Error
|
|
87
|
+
? error.message
|
|
88
|
+
: t("designEditor.toasts.figmaSvgExportError"),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
} finally {
|
|
92
|
+
figmaSvgExportingRef.current = false;
|
|
93
|
+
setFigmaSvgExporting(false);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
|
|
2
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
3
|
+
import { toast } from "sonner";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getDesignClipboardTrustToken,
|
|
7
|
+
plainTextFromDesignHtml,
|
|
8
|
+
writeDesignClipboard,
|
|
9
|
+
} from "@/lib/design-clipboard";
|
|
10
|
+
import type {
|
|
11
|
+
DesignClipboardPayload,
|
|
12
|
+
DesignClipboardScreenEntry,
|
|
13
|
+
} from "@/lib/design-import";
|
|
14
|
+
import { serializeDesignClipboardPayload } from "@/lib/design-import";
|
|
15
|
+
import { resolveClipboardLayerSourceHtml } from "@/pages/design-editor/clipboard-layer-source";
|
|
16
|
+
import { preserveClipboardLayerName } from "@/pages/design-editor/clone-and-pen-edit";
|
|
17
|
+
import type {
|
|
18
|
+
CanvasLayerClipboardEntry,
|
|
19
|
+
LiveScreenSnapshot,
|
|
20
|
+
RuntimeLayerSnapshot,
|
|
21
|
+
SelectedCanvasLayerSnapshot,
|
|
22
|
+
} from "@/pages/design-editor/command-types";
|
|
23
|
+
import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
|
|
24
|
+
import { shouldUseRuntimeLayerProjection } from "@/pages/design-editor/pending-edits";
|
|
25
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
26
|
+
|
|
27
|
+
export interface CopySelectionArgs {
|
|
28
|
+
canvasFrameGeometryById: CanvasFrameGeometryById;
|
|
29
|
+
copiedLayerEntriesRef: RefObject<CanvasLayerClipboardEntry[]>;
|
|
30
|
+
copiedLayerHtmlRef: RefObject<string | null>;
|
|
31
|
+
copiedScreenEntriesRef: RefObject<DesignClipboardScreenEntry[] | undefined>;
|
|
32
|
+
designSourceType: "inline" | "localhost" | "fusion";
|
|
33
|
+
files: DesignFile[];
|
|
34
|
+
getScreenContent: (screenId: string) => string;
|
|
35
|
+
getSelectedLayerSnapshots: () => SelectedCanvasLayerSnapshot[];
|
|
36
|
+
lastWrittenClipboardMarkerRef: RefObject<string | null>;
|
|
37
|
+
lastWrittenClipboardPlainTextRef: RefObject<string | null>;
|
|
38
|
+
liveScreenSnapshotsById: Record<string, LiveScreenSnapshot>;
|
|
39
|
+
overviewScreens: OverviewScreen[];
|
|
40
|
+
overviewSelectedScreenIds: string[];
|
|
41
|
+
pasteCascadeRef: RefObject<number>;
|
|
42
|
+
runtimeLayerSnapshotsById: Record<string, RuntimeLayerSnapshot>;
|
|
43
|
+
setHasCanvasClipboard: Dispatch<SetStateAction<boolean>>;
|
|
44
|
+
t: (key: string, options?: Record<string, unknown>) => string;
|
|
45
|
+
viewModeRef: RefObject<"single" | "overview">;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function runCopySelection({
|
|
49
|
+
canvasFrameGeometryById,
|
|
50
|
+
copiedLayerEntriesRef,
|
|
51
|
+
copiedLayerHtmlRef,
|
|
52
|
+
copiedScreenEntriesRef,
|
|
53
|
+
designSourceType,
|
|
54
|
+
files,
|
|
55
|
+
getScreenContent,
|
|
56
|
+
getSelectedLayerSnapshots,
|
|
57
|
+
lastWrittenClipboardMarkerRef,
|
|
58
|
+
lastWrittenClipboardPlainTextRef,
|
|
59
|
+
liveScreenSnapshotsById,
|
|
60
|
+
overviewScreens,
|
|
61
|
+
overviewSelectedScreenIds,
|
|
62
|
+
pasteCascadeRef,
|
|
63
|
+
runtimeLayerSnapshotsById,
|
|
64
|
+
setHasCanvasClipboard,
|
|
65
|
+
t,
|
|
66
|
+
viewModeRef,
|
|
67
|
+
}: CopySelectionArgs) {
|
|
68
|
+
const entries = getSelectedLayerSnapshots().map((snapshot) => ({
|
|
69
|
+
html: preserveClipboardLayerName(snapshot.html, snapshot.node.layerName),
|
|
70
|
+
rootNodeId: snapshot.rootNodeId,
|
|
71
|
+
sourceFileId: snapshot.sourceFileId,
|
|
72
|
+
portableStyleSnapshot: snapshot.portableStyleSnapshot,
|
|
73
|
+
managedStyleSnapshot: snapshot.managedStyleSnapshot,
|
|
74
|
+
}));
|
|
75
|
+
// Whole-screen copy (U6): getSelectedLayerSnapshots explicitly excludes
|
|
76
|
+
// file/screen ids from layer candidates, so selecting one or more whole
|
|
77
|
+
// screens/frames in the overview and pressing Cmd+C previously produced
|
|
78
|
+
// zero entries and silently no-opped, leaving the clipboard unchanged.
|
|
79
|
+
// Fall back to screen-level snapshots (full file content + geometry) when
|
|
80
|
+
// there is no deeper layer selection to copy.
|
|
81
|
+
const screens: DesignClipboardPayload["screens"] =
|
|
82
|
+
entries.length === 0 && viewModeRef.current === "overview"
|
|
83
|
+
? overviewSelectedScreenIds
|
|
84
|
+
.map((screenId): DesignClipboardScreenEntry | null => {
|
|
85
|
+
const file = files.find((candidate) => candidate.id === screenId);
|
|
86
|
+
const runtimeProjectionEligible =
|
|
87
|
+
file &&
|
|
88
|
+
shouldUseRuntimeLayerProjection({
|
|
89
|
+
screen: overviewScreens.find(
|
|
90
|
+
(screen) => screen.id === screenId,
|
|
91
|
+
),
|
|
92
|
+
fallbackSourceType: designSourceType,
|
|
93
|
+
content: file.content ?? "",
|
|
94
|
+
});
|
|
95
|
+
const runtimeSnapshot = runtimeProjectionEligible
|
|
96
|
+
? runtimeLayerSnapshotsById[screenId]
|
|
97
|
+
: undefined;
|
|
98
|
+
const content = resolveClipboardLayerSourceHtml({
|
|
99
|
+
runtimeProjectionEligible: Boolean(runtimeProjectionEligible),
|
|
100
|
+
runtimeSnapshot,
|
|
101
|
+
liveSnapshotHtml: liveScreenSnapshotsById[screenId]?.html,
|
|
102
|
+
storedContent: getScreenContent(screenId) ?? file?.content,
|
|
103
|
+
});
|
|
104
|
+
if (!file || typeof content !== "string") return null;
|
|
105
|
+
return {
|
|
106
|
+
filename: file.filename,
|
|
107
|
+
fileType: file.fileType,
|
|
108
|
+
content,
|
|
109
|
+
canvasFrame: canvasFrameGeometryById[screenId],
|
|
110
|
+
};
|
|
111
|
+
})
|
|
112
|
+
.filter((entry): entry is DesignClipboardScreenEntry =>
|
|
113
|
+
Boolean(entry),
|
|
114
|
+
)
|
|
115
|
+
: [];
|
|
116
|
+
if (entries.length === 0 && screens.length === 0) return false;
|
|
117
|
+
const copiedHtml =
|
|
118
|
+
entries.length > 0
|
|
119
|
+
? entries.map((entry) => entry.html).join("\n")
|
|
120
|
+
: screens.map((screen) => screen.content).join("\n");
|
|
121
|
+
const plainText = plainTextFromDesignHtml(
|
|
122
|
+
entries.length > 0
|
|
123
|
+
? entries.map((entry) => entry.html)
|
|
124
|
+
: screens.map((screen) => screen.content),
|
|
125
|
+
);
|
|
126
|
+
// The lossless layer payload stays in text/html while text/plain contains
|
|
127
|
+
// only readable content. This mirrors Figma's clipboard behavior: Design
|
|
128
|
+
// can round-trip structure across tabs without dumping source and marker
|
|
129
|
+
// data into ordinary text destinations.
|
|
130
|
+
const clipboardHtml = serializeDesignClipboardPayload(
|
|
131
|
+
copiedHtml,
|
|
132
|
+
{
|
|
133
|
+
version: 1,
|
|
134
|
+
entries,
|
|
135
|
+
screens,
|
|
136
|
+
},
|
|
137
|
+
getDesignClipboardTrustToken() ?? undefined,
|
|
138
|
+
);
|
|
139
|
+
copiedLayerEntriesRef.current = entries;
|
|
140
|
+
copiedScreenEntriesRef.current = screens;
|
|
141
|
+
copiedLayerHtmlRef.current = clipboardHtml;
|
|
142
|
+
lastWrittenClipboardMarkerRef.current = clipboardHtml;
|
|
143
|
+
lastWrittenClipboardPlainTextRef.current = plainText;
|
|
144
|
+
pasteCascadeRef.current = 0;
|
|
145
|
+
setHasCanvasClipboard(true);
|
|
146
|
+
try {
|
|
147
|
+
await writeDesignClipboard({ plainText, html: clipboardHtml });
|
|
148
|
+
} catch {
|
|
149
|
+
// The OS clipboard write failing is tolerated: the in-memory refs
|
|
150
|
+
// above are already populated, so in-app cut-then-paste still works.
|
|
151
|
+
// Only the true "nothing was captured at all" case (the early return
|
|
152
|
+
// above) should abort a cut (see U15).
|
|
153
|
+
toast.error(t("designEditor.toasts.clipboardBlocked"));
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { buildCodeLayerProjection } from "@shared/code-layer";
|
|
2
|
+
import { shouldUseLiveFileContent } from "@shared/html-content";
|
|
3
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
4
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
import * as Y from "yjs";
|
|
7
|
+
|
|
8
|
+
import type { CanvasPrimitiveInsert } from "@/components/design/multi-screen/types";
|
|
9
|
+
import type { RuntimeStructureInsertRequest } from "@/components/design/types";
|
|
10
|
+
import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
|
|
11
|
+
import {
|
|
12
|
+
appendCanvasPrimitiveToHtml,
|
|
13
|
+
blankScreenHtml,
|
|
14
|
+
extractCanvasPrimitiveHtml,
|
|
15
|
+
uniqueLayerId,
|
|
16
|
+
} from "@/pages/design-editor/canvas-primitive-insert";
|
|
17
|
+
import { parsePenPathFromSerializedD } from "@/pages/design-editor/canvas-primitives";
|
|
18
|
+
import { setPenNodesAttributeOnElement } from "@/pages/design-editor/clone-and-pen-edit";
|
|
19
|
+
import type { FileContentSaveRequest } from "@/pages/design-editor/editor-state";
|
|
20
|
+
import { isStandaloneHttpUrl } from "@/pages/design-editor/editor-state";
|
|
21
|
+
import type {
|
|
22
|
+
ContentHistoryEntry,
|
|
23
|
+
PendingTextCreationHistory,
|
|
24
|
+
} from "@/pages/design-editor/history";
|
|
25
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
26
|
+
|
|
27
|
+
export interface CreatePrimitiveArgs {
|
|
28
|
+
activeContent: string;
|
|
29
|
+
activeFile: DesignFile;
|
|
30
|
+
applyLocalContentUpdate: (
|
|
31
|
+
nextContent: string,
|
|
32
|
+
options?: {
|
|
33
|
+
refreshPreview?: boolean;
|
|
34
|
+
skipPreview?: boolean;
|
|
35
|
+
forcePreviewFullDocument?: boolean;
|
|
36
|
+
immediateSave?: boolean;
|
|
37
|
+
persist?: boolean;
|
|
38
|
+
recordHistory?: boolean;
|
|
39
|
+
historyBeforeContent?: string;
|
|
40
|
+
updatedAt?: string;
|
|
41
|
+
clipboardMutation?: ClipboardContentMutationPublication;
|
|
42
|
+
},
|
|
43
|
+
) => void;
|
|
44
|
+
boardFileId: string | undefined;
|
|
45
|
+
canEditDesign: boolean;
|
|
46
|
+
collabContentFileIdRef: RefObject<string | null>;
|
|
47
|
+
collabContentRef: RefObject<string | null>;
|
|
48
|
+
createFileContentSaveRequest: (
|
|
49
|
+
fileId: string,
|
|
50
|
+
content: string,
|
|
51
|
+
syncCollab: boolean,
|
|
52
|
+
) => FileContentSaveRequest;
|
|
53
|
+
files: DesignFile[];
|
|
54
|
+
id: string | undefined;
|
|
55
|
+
isSynced: boolean;
|
|
56
|
+
markPendingLocalFileContent: (
|
|
57
|
+
fileId: string,
|
|
58
|
+
content: string,
|
|
59
|
+
baseUpdatedAt?: string | null,
|
|
60
|
+
) => void;
|
|
61
|
+
pendingLocalFileContentsRef: RefObject<
|
|
62
|
+
Map<
|
|
63
|
+
string,
|
|
64
|
+
{ content: string; startedAt: number; baseUpdatedAt?: string | null }
|
|
65
|
+
>
|
|
66
|
+
>;
|
|
67
|
+
pendingTextCreationHistoryRef: RefObject<PendingTextCreationHistory | null>;
|
|
68
|
+
pendingTextEditNodeIdRef: RefObject<string | null>;
|
|
69
|
+
queryClient: QueryClient;
|
|
70
|
+
recordContentHistoryEntry: (entry: ContentHistoryEntry) => void;
|
|
71
|
+
runtimeStructureInsertRevisionRef: RefObject<number>;
|
|
72
|
+
saveFileContent: (pending: FileContentSaveRequest) => void;
|
|
73
|
+
setRuntimeStructureInsertRequest: Dispatch<
|
|
74
|
+
SetStateAction<
|
|
75
|
+
(RuntimeStructureInsertRequest & { screenId: string }) | null
|
|
76
|
+
>
|
|
77
|
+
>;
|
|
78
|
+
t: (key: string, options?: Record<string, unknown>) => string;
|
|
79
|
+
viewModeRef: RefObject<"single" | "overview">;
|
|
80
|
+
ydoc: Y.Doc | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function runCreatePrimitive(
|
|
84
|
+
{
|
|
85
|
+
activeContent,
|
|
86
|
+
activeFile,
|
|
87
|
+
applyLocalContentUpdate,
|
|
88
|
+
boardFileId,
|
|
89
|
+
canEditDesign,
|
|
90
|
+
collabContentFileIdRef,
|
|
91
|
+
collabContentRef,
|
|
92
|
+
createFileContentSaveRequest,
|
|
93
|
+
files,
|
|
94
|
+
id,
|
|
95
|
+
isSynced,
|
|
96
|
+
markPendingLocalFileContent,
|
|
97
|
+
pendingLocalFileContentsRef,
|
|
98
|
+
pendingTextCreationHistoryRef,
|
|
99
|
+
pendingTextEditNodeIdRef,
|
|
100
|
+
queryClient,
|
|
101
|
+
recordContentHistoryEntry,
|
|
102
|
+
runtimeStructureInsertRevisionRef,
|
|
103
|
+
saveFileContent,
|
|
104
|
+
setRuntimeStructureInsertRequest,
|
|
105
|
+
t,
|
|
106
|
+
viewModeRef,
|
|
107
|
+
ydoc,
|
|
108
|
+
}: CreatePrimitiveArgs,
|
|
109
|
+
screenId: string,
|
|
110
|
+
primitive: CanvasPrimitiveInsert,
|
|
111
|
+
) {
|
|
112
|
+
if (!canEditDesign) return false;
|
|
113
|
+
const targetFile = files.find((file) => file.id === screenId);
|
|
114
|
+
if (!targetFile) return false;
|
|
115
|
+
const pendingContent = pendingLocalFileContentsRef.current.get(
|
|
116
|
+
targetFile.id,
|
|
117
|
+
)?.content;
|
|
118
|
+
const storedContent = targetFile.content ?? "";
|
|
119
|
+
const baseContent =
|
|
120
|
+
pendingContent ??
|
|
121
|
+
(targetFile.id === activeFile?.id
|
|
122
|
+
? (() => {
|
|
123
|
+
const liveContent =
|
|
124
|
+
ydoc && isSynced
|
|
125
|
+
? ydoc.getText("content").toString()
|
|
126
|
+
: ((collabContentFileIdRef.current === activeFile.id
|
|
127
|
+
? collabContentRef.current
|
|
128
|
+
: null) ?? activeContent);
|
|
129
|
+
return shouldUseLiveFileContent({
|
|
130
|
+
liveContent,
|
|
131
|
+
storedContent,
|
|
132
|
+
fileType: targetFile.fileType,
|
|
133
|
+
})
|
|
134
|
+
? liveContent
|
|
135
|
+
: storedContent;
|
|
136
|
+
})()
|
|
137
|
+
: storedContent);
|
|
138
|
+
// A localhost screen's stored content is its route URL, not an editable
|
|
139
|
+
// document. Keep that URL intact and send one serialized primitive
|
|
140
|
+
// through the same live insert bridge used by board-to-screen drops.
|
|
141
|
+
// The bridge echo records the pending source handoff and owns the
|
|
142
|
+
// optimistic DOM/history lifecycle (selection, Layers, undo, and redo).
|
|
143
|
+
if (isStandaloneHttpUrl(baseContent)) {
|
|
144
|
+
const nodeId =
|
|
145
|
+
primitive.nodeId ?? uniqueLayerId(primitive.kind || "primitive");
|
|
146
|
+
const livePrimitive = { ...primitive, nodeId };
|
|
147
|
+
const temporaryDocument = appendCanvasPrimitiveToHtml(
|
|
148
|
+
blankScreenHtml("Live insert"),
|
|
149
|
+
livePrimitive,
|
|
150
|
+
);
|
|
151
|
+
if (!temporaryDocument) {
|
|
152
|
+
toast.error(t("designEditor.toasts.primitiveInsertFailed"));
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const enrichedDocument =
|
|
156
|
+
primitive.kind === "path" && primitive.pathData
|
|
157
|
+
? (() => {
|
|
158
|
+
const reconstructed = parsePenPathFromSerializedD(
|
|
159
|
+
primitive.pathData!,
|
|
160
|
+
);
|
|
161
|
+
return reconstructed
|
|
162
|
+
? setPenNodesAttributeOnElement(
|
|
163
|
+
temporaryDocument,
|
|
164
|
+
nodeId,
|
|
165
|
+
reconstructed,
|
|
166
|
+
)
|
|
167
|
+
: temporaryDocument;
|
|
168
|
+
})()
|
|
169
|
+
: temporaryDocument;
|
|
170
|
+
const insertedHtml = extractCanvasPrimitiveHtml(enrichedDocument, nodeId);
|
|
171
|
+
if (!insertedHtml) {
|
|
172
|
+
toast.error(t("designEditor.toasts.primitiveInsertFailed"));
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
pendingTextEditNodeIdRef.current =
|
|
176
|
+
primitive.kind === "text" ? nodeId : null;
|
|
177
|
+
runtimeStructureInsertRevisionRef.current += 1;
|
|
178
|
+
setRuntimeStructureInsertRequest({
|
|
179
|
+
requestId: runtimeStructureInsertRevisionRef.current,
|
|
180
|
+
screenId: targetFile.id,
|
|
181
|
+
html: insertedHtml,
|
|
182
|
+
anchor: { selector: "body" },
|
|
183
|
+
placement: "inside",
|
|
184
|
+
});
|
|
185
|
+
return nodeId;
|
|
186
|
+
}
|
|
187
|
+
const insertedContent = appendCanvasPrimitiveToHtml(baseContent, primitive, {
|
|
188
|
+
preserveNegativePosition: targetFile.id === boardFileId,
|
|
189
|
+
isBoardTarget: targetFile.id === boardFileId,
|
|
190
|
+
});
|
|
191
|
+
if (!insertedContent) {
|
|
192
|
+
toast.error(t("designEditor.toasts.primitiveInsertFailed"));
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
// Vector-edit foundations: stash the structured pen path (nodes +
|
|
196
|
+
// handles) alongside the flattened `d` so a later double-click/Enter
|
|
197
|
+
// can re-hydrate it into an editable path instead of only having the
|
|
198
|
+
// already-flattened curve. `primitive.pathData` is the only carrier of
|
|
199
|
+
// pen geometry that crosses the MultiScreenCanvas -> DesignEditor
|
|
200
|
+
// boundary for an OVERVIEW-drawn pen path (see
|
|
201
|
+
// parsePenPathFromSerializedD's doc comment for why this reconstructs
|
|
202
|
+
// rather than receives the structured path directly).
|
|
203
|
+
const nextContent =
|
|
204
|
+
primitive.kind === "path" && primitive.pathData && primitive.nodeId
|
|
205
|
+
? (() => {
|
|
206
|
+
const reconstructed = parsePenPathFromSerializedD(
|
|
207
|
+
primitive.pathData!,
|
|
208
|
+
);
|
|
209
|
+
return reconstructed
|
|
210
|
+
? setPenNodesAttributeOnElement(
|
|
211
|
+
insertedContent,
|
|
212
|
+
primitive.nodeId!,
|
|
213
|
+
reconstructed,
|
|
214
|
+
)
|
|
215
|
+
: insertedContent;
|
|
216
|
+
})()
|
|
217
|
+
: insertedContent;
|
|
218
|
+
const projectedNodeId = primitive.nodeId
|
|
219
|
+
? buildCodeLayerProjection(nextContent).nodes.find(
|
|
220
|
+
(node) =>
|
|
221
|
+
node.dataAttributes["data-agent-native-node-id"] === primitive.nodeId,
|
|
222
|
+
)?.id
|
|
223
|
+
: null;
|
|
224
|
+
|
|
225
|
+
pendingTextCreationHistoryRef.current =
|
|
226
|
+
primitive.kind === "text" &&
|
|
227
|
+
primitive.nodeId &&
|
|
228
|
+
viewModeRef.current === "overview"
|
|
229
|
+
? {
|
|
230
|
+
fileId: targetFile.id,
|
|
231
|
+
nodeId: primitive.nodeId,
|
|
232
|
+
before: baseContent,
|
|
233
|
+
created: nextContent,
|
|
234
|
+
}
|
|
235
|
+
: null;
|
|
236
|
+
|
|
237
|
+
if (targetFile.id === activeFile?.id) {
|
|
238
|
+
applyLocalContentUpdate(nextContent, {
|
|
239
|
+
forcePreviewFullDocument: true,
|
|
240
|
+
historyBeforeContent: baseContent,
|
|
241
|
+
immediateSave: true,
|
|
242
|
+
});
|
|
243
|
+
} else {
|
|
244
|
+
recordContentHistoryEntry({
|
|
245
|
+
fileId: targetFile.id,
|
|
246
|
+
before: baseContent,
|
|
247
|
+
after: nextContent,
|
|
248
|
+
});
|
|
249
|
+
// Stamp the server-clock base the same way applyFileContentUpdate
|
|
250
|
+
// does. Without it the reconcile effect reads the optimistic cache
|
|
251
|
+
// write below as a server acknowledgement and retires the pending
|
|
252
|
+
// entry immediately, leaving that cache the only carrier of the
|
|
253
|
+
// insert — so any get-design response already in flight (the board
|
|
254
|
+
// file's own lazy migration invalidates on success, so one usually
|
|
255
|
+
// is) overwrites it with pre-insert content and the primitive
|
|
256
|
+
// disappears from the canvas until a reload.
|
|
257
|
+
markPendingLocalFileContent(
|
|
258
|
+
targetFile.id,
|
|
259
|
+
nextContent,
|
|
260
|
+
targetFile.updatedAt,
|
|
261
|
+
);
|
|
262
|
+
queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
|
|
263
|
+
if (!old || typeof old !== "object" || !Array.isArray(old.files)) {
|
|
264
|
+
return old;
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
...old,
|
|
268
|
+
files: old.files.map((file: DesignFile) =>
|
|
269
|
+
file.id === targetFile.id ? { ...file, content: nextContent } : file,
|
|
270
|
+
),
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
saveFileContent(
|
|
274
|
+
createFileContentSaveRequest(targetFile.id, nextContent, true),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const result = projectedNodeId ?? primitive.nodeId ?? true;
|
|
279
|
+
|
|
280
|
+
// Record the nodeId when a TEXT primitive is created so the next
|
|
281
|
+
// handlePrimitiveCreated (or handleBoardDrawPrimitive) can immediately
|
|
282
|
+
// enter text-edit mode — fixing the "click to add text should let me
|
|
283
|
+
// type immediately" bug. The ref is read once and cleared.
|
|
284
|
+
if (primitive.kind === "text") {
|
|
285
|
+
pendingTextEditNodeIdRef.current = primitive.nodeId ?? null;
|
|
286
|
+
} else {
|
|
287
|
+
pendingTextEditNodeIdRef.current = null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return result;
|
|
291
|
+
}
|