@agent-native/core 0.163.3 → 0.164.1
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/hooks/use-dashboard-chat-context.ts +29 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
- package/corpus/templates/analytics/netlify.toml +2 -0
- 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/_index.tsx +10 -2
- 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/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/netlify.toml +2 -0
- 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/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/routes.d.ts +1 -1
- 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/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- 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/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/store.d.ts +9 -0
- package/dist/settings/store.js +19 -0
- package/dist/templates/chat/netlify.toml +2 -0
- package/dist/vite/client.js +35 -37
- 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,209 @@
|
|
|
1
|
+
import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
|
|
2
|
+
import type { RefObject } from "react";
|
|
3
|
+
import { toast } from "sonner";
|
|
4
|
+
|
|
5
|
+
import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
|
|
6
|
+
import { uniqueLayerId } from "@/pages/design-editor/canvas-primitive-insert";
|
|
7
|
+
import { cloneHtmlLayerAtPosition } from "@/pages/design-editor/clone-and-pen-edit";
|
|
8
|
+
import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
|
|
9
|
+
import { escapeHtmlAttributeValue } from "@/pages/design-editor/dom-utils";
|
|
10
|
+
import {
|
|
11
|
+
findScreenFrameAtCanvasPoint,
|
|
12
|
+
getAllScreenFrameEntries,
|
|
13
|
+
} from "@/pages/design-editor/overview-camera";
|
|
14
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
15
|
+
|
|
16
|
+
export interface PastedImageFilesTarget {
|
|
17
|
+
fileId: string;
|
|
18
|
+
point: { x: number; y: number };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PastedImageFilesArgs {
|
|
22
|
+
activeFile: DesignFile;
|
|
23
|
+
applyFileContentUpdate: (
|
|
24
|
+
fileId: string,
|
|
25
|
+
nextContent: string,
|
|
26
|
+
options?: {
|
|
27
|
+
refreshPreview?: boolean;
|
|
28
|
+
skipPreview?: boolean;
|
|
29
|
+
forcePreviewFullDocument?: boolean;
|
|
30
|
+
persist?: boolean;
|
|
31
|
+
recordHistory?: boolean;
|
|
32
|
+
updatedAt?: string;
|
|
33
|
+
clipboardMutation?: ClipboardContentMutationPublication;
|
|
34
|
+
},
|
|
35
|
+
) => void;
|
|
36
|
+
applyLocalContentUpdate: (
|
|
37
|
+
nextContent: string,
|
|
38
|
+
options?: {
|
|
39
|
+
refreshPreview?: boolean;
|
|
40
|
+
skipPreview?: boolean;
|
|
41
|
+
forcePreviewFullDocument?: boolean;
|
|
42
|
+
immediateSave?: boolean;
|
|
43
|
+
persist?: boolean;
|
|
44
|
+
recordHistory?: boolean;
|
|
45
|
+
historyBeforeContent?: string;
|
|
46
|
+
updatedAt?: string;
|
|
47
|
+
clipboardMutation?: ClipboardContentMutationPublication;
|
|
48
|
+
},
|
|
49
|
+
) => void;
|
|
50
|
+
boardFileId: string | undefined;
|
|
51
|
+
canEditDesign: boolean;
|
|
52
|
+
canvasContainerRef: RefObject<HTMLDivElement | null>;
|
|
53
|
+
canvasFrameGeometryById: CanvasFrameGeometryById;
|
|
54
|
+
getFreshActiveContent: () => string;
|
|
55
|
+
getScreenContent: (screenId: string) => string;
|
|
56
|
+
overviewScreens: OverviewScreen[];
|
|
57
|
+
overviewSelectedScreenIds: string[];
|
|
58
|
+
pasteCascadeRef: RefObject<number>;
|
|
59
|
+
selectInsertedLayers: (
|
|
60
|
+
screenId: string,
|
|
61
|
+
content: string,
|
|
62
|
+
rootNodeIds: string[],
|
|
63
|
+
) => void;
|
|
64
|
+
t: (key: string, options?: Record<string, unknown>) => string;
|
|
65
|
+
uploadImageFileForHtml: (file: File) => Promise<string>;
|
|
66
|
+
viewModeRef: RefObject<"single" | "overview">;
|
|
67
|
+
zoom: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function runPastedImageFiles(
|
|
71
|
+
{
|
|
72
|
+
activeFile,
|
|
73
|
+
applyFileContentUpdate,
|
|
74
|
+
applyLocalContentUpdate,
|
|
75
|
+
boardFileId,
|
|
76
|
+
canEditDesign,
|
|
77
|
+
canvasContainerRef,
|
|
78
|
+
canvasFrameGeometryById,
|
|
79
|
+
getFreshActiveContent,
|
|
80
|
+
getScreenContent,
|
|
81
|
+
overviewScreens,
|
|
82
|
+
overviewSelectedScreenIds,
|
|
83
|
+
pasteCascadeRef,
|
|
84
|
+
selectInsertedLayers,
|
|
85
|
+
t,
|
|
86
|
+
uploadImageFileForHtml,
|
|
87
|
+
viewModeRef,
|
|
88
|
+
zoom,
|
|
89
|
+
}: PastedImageFilesArgs,
|
|
90
|
+
files: File[],
|
|
91
|
+
target?: PastedImageFilesTarget,
|
|
92
|
+
) {
|
|
93
|
+
if (files.length === 0 || !canEditDesign) return false;
|
|
94
|
+
|
|
95
|
+
const insertFilesAtPoint = (
|
|
96
|
+
targetFileId: string,
|
|
97
|
+
localPoint: { x: number; y: number } | (() => { x: number; y: number }),
|
|
98
|
+
) => {
|
|
99
|
+
void (async () => {
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
const imageUrl = await uploadImageFileForHtml(file);
|
|
102
|
+
if (!imageUrl) continue;
|
|
103
|
+
const baseContent =
|
|
104
|
+
targetFileId === activeFile?.id
|
|
105
|
+
? getFreshActiveContent()
|
|
106
|
+
: (getScreenContent(targetFileId) ?? "");
|
|
107
|
+
const resolvedPoint =
|
|
108
|
+
typeof localPoint === "function" ? localPoint() : localPoint;
|
|
109
|
+
const cascadeOffset = pasteCascadeRef.current * 16;
|
|
110
|
+
pasteCascadeRef.current += 1;
|
|
111
|
+
const nodeId = uniqueLayerId("pasted-image");
|
|
112
|
+
const html = `<img src="${imageUrl}" alt="${escapeHtmlAttributeValue(file.name || "Pasted image")}" data-agent-native-node-id="${nodeId}" data-agent-native-layer-name="Pasted image" style="position:absolute;width:320px;height:auto;" />`;
|
|
113
|
+
const nextContent = cloneHtmlLayerAtPosition(baseContent, html, {
|
|
114
|
+
x: resolvedPoint.x + cascadeOffset,
|
|
115
|
+
y: resolvedPoint.y + cascadeOffset,
|
|
116
|
+
});
|
|
117
|
+
if (!nextContent) {
|
|
118
|
+
toast.error(t("designEditor.toasts.duplicateElementFailed"));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (targetFileId === activeFile?.id) {
|
|
122
|
+
applyLocalContentUpdate(nextContent, {
|
|
123
|
+
forcePreviewFullDocument: true,
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
applyFileContentUpdate(targetFileId, nextContent, {
|
|
127
|
+
forcePreviewFullDocument: true,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
selectInsertedLayers(targetFileId, nextContent, [nodeId]);
|
|
131
|
+
}
|
|
132
|
+
})();
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (target) {
|
|
136
|
+
insertFilesAtPoint(target.fileId, target.point);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (viewModeRef.current !== "overview") {
|
|
141
|
+
const targetFileId = activeFile?.id;
|
|
142
|
+
if (!targetFileId) return false;
|
|
143
|
+
const getCenter = () => {
|
|
144
|
+
const iframe = canvasContainerRef.current?.querySelector<HTMLElement>(
|
|
145
|
+
"[data-design-preview-iframe]",
|
|
146
|
+
);
|
|
147
|
+
if (iframe) {
|
|
148
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
149
|
+
const factor = zoom / 100;
|
|
150
|
+
return {
|
|
151
|
+
x: Math.max(0, iframeRect.width / 2 / factor),
|
|
152
|
+
y: Math.max(0, iframeRect.height / 2 / factor),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const rect = canvasContainerRef.current?.getBoundingClientRect();
|
|
156
|
+
return rect
|
|
157
|
+
? {
|
|
158
|
+
x: Math.max(0, rect.width / 2),
|
|
159
|
+
y: Math.max(0, rect.height / 2),
|
|
160
|
+
}
|
|
161
|
+
: { x: 120, y: 120 };
|
|
162
|
+
};
|
|
163
|
+
insertFilesAtPoint(targetFileId, getCenter);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Overview mode: resolve a canvas-space anchor point, then hit-test it
|
|
168
|
+
// against real screen frames.
|
|
169
|
+
if (!boardFileId) return false;
|
|
170
|
+
const frames = getAllScreenFrameEntries({
|
|
171
|
+
overviewScreens,
|
|
172
|
+
canvasFrameGeometryById,
|
|
173
|
+
});
|
|
174
|
+
const anchorCanvasPoint = (() => {
|
|
175
|
+
if (overviewSelectedScreenIds.length === 1) {
|
|
176
|
+
const screenId = overviewSelectedScreenIds[0]!;
|
|
177
|
+
const frame = frames.find((entry) => entry.id === screenId);
|
|
178
|
+
if (frame) {
|
|
179
|
+
return {
|
|
180
|
+
x: frame.geometry.x + frame.geometry.width / 2,
|
|
181
|
+
y: frame.geometry.y + frame.geometry.height / 2,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Best-effort fallback (matches the prior single-image behavior):
|
|
186
|
+
// container-relative pixels as a stand-in canvas point. Overview pan/
|
|
187
|
+
// zoom camera state lives inside MultiScreenCanvas, not here, so this
|
|
188
|
+
// can't account for the live camera transform — see FINAL REPORT.
|
|
189
|
+
const rect = canvasContainerRef.current?.getBoundingClientRect();
|
|
190
|
+
return rect
|
|
191
|
+
? { x: Math.max(0, rect.width / 2), y: Math.max(0, rect.height / 2) }
|
|
192
|
+
: { x: 120, y: 120 };
|
|
193
|
+
})();
|
|
194
|
+
const hitFrame = findScreenFrameAtCanvasPoint(
|
|
195
|
+
anchorCanvasPoint,
|
|
196
|
+
frames,
|
|
197
|
+
boardFileId,
|
|
198
|
+
);
|
|
199
|
+
const targetFileId = hitFrame?.id ?? boardFileId;
|
|
200
|
+
const localAnchor = hitFrame
|
|
201
|
+
? {
|
|
202
|
+
x: anchorCanvasPoint.x - hitFrame.geometry.x,
|
|
203
|
+
y: anchorCanvasPoint.y - hitFrame.geometry.y,
|
|
204
|
+
}
|
|
205
|
+
: anchorCanvasPoint;
|
|
206
|
+
|
|
207
|
+
insertFilesAtPoint(targetFileId, localAnchor);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { tryCallActionKeepalive } from "@agent-native/core/client/hooks";
|
|
2
|
+
import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
|
|
3
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
4
|
+
import type { RefObject } from "react";
|
|
5
|
+
|
|
6
|
+
import type { DesignSaveOutboxEntry } from "@/lib/design-save-outbox";
|
|
7
|
+
import type {
|
|
8
|
+
DesignDataOperation,
|
|
9
|
+
PendingDesignDataOperations,
|
|
10
|
+
} from "@/pages/design-editor/data-operations";
|
|
11
|
+
import {
|
|
12
|
+
applyDesignDataOperations,
|
|
13
|
+
buildFrameGeometryDataOperations,
|
|
14
|
+
compactDesignDataOperations,
|
|
15
|
+
pendingDesignDataOperations,
|
|
16
|
+
} from "@/pages/design-editor/data-operations";
|
|
17
|
+
import { sanitizeCanvasFrameGeometryForPersist } from "@/pages/design-editor/geometry-persistence";
|
|
18
|
+
|
|
19
|
+
export interface PersistFrameGeometrySaveArgs {
|
|
20
|
+
acknowledgeOutboxEntry: (entry: DesignSaveOutboxEntry) => Promise<void>;
|
|
21
|
+
boardFileId: string | undefined;
|
|
22
|
+
canEditDesignRef: RefObject<boolean>;
|
|
23
|
+
createFrameGeometryOutboxEntry: (
|
|
24
|
+
dataOperations: readonly DesignDataOperation[],
|
|
25
|
+
revision: number,
|
|
26
|
+
) => DesignSaveOutboxEntry | null;
|
|
27
|
+
designDataJsonRef: RefObject<Record<string, unknown>>;
|
|
28
|
+
enqueueFrameGeometryDataSave: (
|
|
29
|
+
dataOperations: DesignDataOperation[],
|
|
30
|
+
) => boolean;
|
|
31
|
+
frameGeometryOperationRevisionRef: RefObject<number>;
|
|
32
|
+
id: string | undefined;
|
|
33
|
+
journalOutboxEntry: (entry: DesignSaveOutboxEntry) => Promise<boolean>;
|
|
34
|
+
pendingFrameGeometryOperationsForUnloadRef: RefObject<PendingDesignDataOperations>;
|
|
35
|
+
queryClient: QueryClient;
|
|
36
|
+
warnChangesWillRetry: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function runPersistFrameGeometrySave(
|
|
40
|
+
{
|
|
41
|
+
acknowledgeOutboxEntry,
|
|
42
|
+
boardFileId,
|
|
43
|
+
canEditDesignRef,
|
|
44
|
+
createFrameGeometryOutboxEntry,
|
|
45
|
+
designDataJsonRef,
|
|
46
|
+
enqueueFrameGeometryDataSave,
|
|
47
|
+
frameGeometryOperationRevisionRef,
|
|
48
|
+
id,
|
|
49
|
+
journalOutboxEntry,
|
|
50
|
+
pendingFrameGeometryOperationsForUnloadRef,
|
|
51
|
+
queryClient,
|
|
52
|
+
warnChangesWillRetry,
|
|
53
|
+
}: PersistFrameGeometrySaveArgs,
|
|
54
|
+
pending: {
|
|
55
|
+
geometryById: CanvasFrameGeometryById;
|
|
56
|
+
previousGeometry: CanvasFrameGeometryById;
|
|
57
|
+
},
|
|
58
|
+
keepalive = false,
|
|
59
|
+
): boolean {
|
|
60
|
+
if (!id || !canEditDesignRef.current) return false;
|
|
61
|
+
// Geometry-persist guard: refuse absurd per-frame geometry (see
|
|
62
|
+
// sanitizeCanvasFrameGeometryForPersist) so a corrupted transient
|
|
63
|
+
// zoom basis can never shred the persisted layout. The board frame
|
|
64
|
+
// is exempt — its surface is a legitimate 131k square.
|
|
65
|
+
const { geometryById: safeGeometryById } =
|
|
66
|
+
sanitizeCanvasFrameGeometryForPersist(
|
|
67
|
+
pending.geometryById,
|
|
68
|
+
pending.previousGeometry,
|
|
69
|
+
boardFileId ? [boardFileId] : [],
|
|
70
|
+
);
|
|
71
|
+
const dataOperations = buildFrameGeometryDataOperations({
|
|
72
|
+
previousGeometry: pending.previousGeometry,
|
|
73
|
+
nextGeometry: safeGeometryById,
|
|
74
|
+
designData: designDataJsonRef.current,
|
|
75
|
+
});
|
|
76
|
+
if (keepalive) {
|
|
77
|
+
const combinedOperations = compactDesignDataOperations([
|
|
78
|
+
...pendingDesignDataOperations(
|
|
79
|
+
pendingFrameGeometryOperationsForUnloadRef.current,
|
|
80
|
+
),
|
|
81
|
+
...dataOperations,
|
|
82
|
+
]);
|
|
83
|
+
if (combinedOperations.length === 0) return true;
|
|
84
|
+
const operationRevision = frameGeometryOperationRevisionRef.current + 1;
|
|
85
|
+
frameGeometryOperationRevisionRef.current = operationRevision;
|
|
86
|
+
const entry = createFrameGeometryOutboxEntry(
|
|
87
|
+
combinedOperations,
|
|
88
|
+
operationRevision,
|
|
89
|
+
);
|
|
90
|
+
if (!entry) return false;
|
|
91
|
+
void journalOutboxEntry(entry);
|
|
92
|
+
const attempt = tryCallActionKeepalive(
|
|
93
|
+
"update-design",
|
|
94
|
+
entry.payload as any,
|
|
95
|
+
);
|
|
96
|
+
if (!attempt.accepted) return false;
|
|
97
|
+
void attempt.completion
|
|
98
|
+
.then(() => acknowledgeOutboxEntry(entry))
|
|
99
|
+
.catch(warnChangesWillRetry);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (dataOperations.length === 0) return true;
|
|
103
|
+
const nextData = applyDesignDataOperations(
|
|
104
|
+
designDataJsonRef.current,
|
|
105
|
+
dataOperations,
|
|
106
|
+
);
|
|
107
|
+
designDataJsonRef.current = nextData;
|
|
108
|
+
queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
|
|
109
|
+
if (!old || typeof old !== "object") return old;
|
|
110
|
+
return { ...old, data: JSON.stringify(nextData) };
|
|
111
|
+
});
|
|
112
|
+
return enqueueFrameGeometryDataSave(dataOperations);
|
|
113
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { useActionMutation } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { tweakSelectionsHash } from "@shared/resolve-tweaks";
|
|
3
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
4
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
|
|
7
|
+
import type { DesignSaveOutboxEntry } from "@/lib/design-save-outbox";
|
|
8
|
+
import type { PendingTweakSave } from "@/pages/design-editor/tweak-save";
|
|
9
|
+
import {
|
|
10
|
+
classifyTweakSaveFailure,
|
|
11
|
+
clearCompletedTweakSave,
|
|
12
|
+
rebaseTweakSaveForSend,
|
|
13
|
+
retainLatestFailedTweakSave,
|
|
14
|
+
} from "@/pages/design-editor/tweak-save";
|
|
15
|
+
|
|
16
|
+
export interface PersistTweakSaveArgs {
|
|
17
|
+
acknowledgeOutboxEntry: (entry: DesignSaveOutboxEntry) => Promise<void>;
|
|
18
|
+
applyTweaksAsync: ReturnType<
|
|
19
|
+
typeof useActionMutation<undefined, undefined, "apply-tweaks">
|
|
20
|
+
>["mutateAsync"];
|
|
21
|
+
canEditDesignRef: RefObject<boolean>;
|
|
22
|
+
confirmedTweakSelectionsHashRef: RefObject<string>;
|
|
23
|
+
createTweakSaveOutboxEntry: (
|
|
24
|
+
pending: PendingTweakSave,
|
|
25
|
+
) => DesignSaveOutboxEntry | null;
|
|
26
|
+
id: string | undefined;
|
|
27
|
+
journalTweakOutboxEntry: (entry: DesignSaveOutboxEntry) => Promise<boolean>;
|
|
28
|
+
pendingTweakSaveRef: RefObject<PendingTweakSave | null>;
|
|
29
|
+
queryClient: QueryClient;
|
|
30
|
+
setTweakSaveActive: Dispatch<SetStateAction<boolean>>;
|
|
31
|
+
t: (key: string, options?: Record<string, unknown>) => string;
|
|
32
|
+
tweakSaveChainRef: RefObject<Promise<void>>;
|
|
33
|
+
tweakSaveInFlightRef: RefObject<boolean>;
|
|
34
|
+
tweakSaveRevisionRef: RefObject<number>;
|
|
35
|
+
warnChangesWillRetry: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function runPersistTweakSave(
|
|
39
|
+
{
|
|
40
|
+
acknowledgeOutboxEntry,
|
|
41
|
+
applyTweaksAsync,
|
|
42
|
+
canEditDesignRef,
|
|
43
|
+
confirmedTweakSelectionsHashRef,
|
|
44
|
+
createTweakSaveOutboxEntry,
|
|
45
|
+
id,
|
|
46
|
+
journalTweakOutboxEntry,
|
|
47
|
+
pendingTweakSaveRef,
|
|
48
|
+
queryClient,
|
|
49
|
+
setTweakSaveActive,
|
|
50
|
+
t,
|
|
51
|
+
tweakSaveChainRef,
|
|
52
|
+
tweakSaveInFlightRef,
|
|
53
|
+
tweakSaveRevisionRef,
|
|
54
|
+
warnChangesWillRetry,
|
|
55
|
+
}: PersistTweakSaveArgs,
|
|
56
|
+
pending: PendingTweakSave,
|
|
57
|
+
) {
|
|
58
|
+
if (!id || !canEditDesignRef.current) return;
|
|
59
|
+
const previous = tweakSaveChainRef.current;
|
|
60
|
+
const current = previous
|
|
61
|
+
.catch(() => {})
|
|
62
|
+
.then(async () => {
|
|
63
|
+
tweakSaveInFlightRef.current = true;
|
|
64
|
+
const sendPending = rebaseTweakSaveForSend(
|
|
65
|
+
pending,
|
|
66
|
+
confirmedTweakSelectionsHashRef.current,
|
|
67
|
+
);
|
|
68
|
+
const entry = createTweakSaveOutboxEntry(sendPending);
|
|
69
|
+
if (!entry) {
|
|
70
|
+
tweakSaveInFlightRef.current = false;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const journaled = await journalTweakOutboxEntry(entry);
|
|
74
|
+
try {
|
|
75
|
+
const result = (await applyTweaksAsync(entry.payload as any)) as {
|
|
76
|
+
appliedTweaks?: Record<string, unknown>;
|
|
77
|
+
selectionsHash?: string;
|
|
78
|
+
};
|
|
79
|
+
const confirmedHash =
|
|
80
|
+
typeof result?.selectionsHash === "string"
|
|
81
|
+
? result.selectionsHash
|
|
82
|
+
: tweakSelectionsHash(
|
|
83
|
+
result?.appliedTweaks ?? sendPending.selections,
|
|
84
|
+
);
|
|
85
|
+
confirmedTweakSelectionsHashRef.current = confirmedHash;
|
|
86
|
+
if (result?.appliedTweaks) {
|
|
87
|
+
queryClient.setQueryData(
|
|
88
|
+
["action", "get-design", { id }],
|
|
89
|
+
(old: any) => {
|
|
90
|
+
if (!old || typeof old !== "object") return old;
|
|
91
|
+
let data: Record<string, unknown>;
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(old.data ?? "{}");
|
|
94
|
+
data =
|
|
95
|
+
parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
96
|
+
? parsed
|
|
97
|
+
: {};
|
|
98
|
+
} catch {
|
|
99
|
+
return old;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
...old,
|
|
103
|
+
data: JSON.stringify({
|
|
104
|
+
...data,
|
|
105
|
+
tweakSelections: result.appliedTweaks,
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (journaled) await acknowledgeOutboxEntry(entry);
|
|
112
|
+
|
|
113
|
+
const queuedAfterSuccess = clearCompletedTweakSave(
|
|
114
|
+
pendingTweakSaveRef.current,
|
|
115
|
+
pending.revision,
|
|
116
|
+
);
|
|
117
|
+
if (
|
|
118
|
+
queuedAfterSuccess &&
|
|
119
|
+
queuedAfterSuccess.revision > pending.revision
|
|
120
|
+
) {
|
|
121
|
+
const rebasedQueued = rebaseTweakSaveForSend(
|
|
122
|
+
queuedAfterSuccess,
|
|
123
|
+
confirmedHash,
|
|
124
|
+
);
|
|
125
|
+
pendingTweakSaveRef.current = rebasedQueued;
|
|
126
|
+
const rebasedEntry = createTweakSaveOutboxEntry(rebasedQueued);
|
|
127
|
+
if (rebasedEntry) void journalTweakOutboxEntry(rebasedEntry);
|
|
128
|
+
} else {
|
|
129
|
+
pendingTweakSaveRef.current = queuedAfterSuccess;
|
|
130
|
+
}
|
|
131
|
+
if (tweakSaveRevisionRef.current === pending.revision) {
|
|
132
|
+
setTweakSaveActive(false);
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
pendingTweakSaveRef.current = retainLatestFailedTweakSave(
|
|
136
|
+
pendingTweakSaveRef.current,
|
|
137
|
+
sendPending,
|
|
138
|
+
);
|
|
139
|
+
if (tweakSaveRevisionRef.current === pending.revision) {
|
|
140
|
+
setTweakSaveActive(true);
|
|
141
|
+
}
|
|
142
|
+
const failureKind = classifyTweakSaveFailure(error, journaled);
|
|
143
|
+
if (failureKind === "conflict") {
|
|
144
|
+
toast.error(t("designEditor.toasts.tweakConflict"));
|
|
145
|
+
} else if (failureKind === "durable-retry") {
|
|
146
|
+
warnChangesWillRetry();
|
|
147
|
+
} else {
|
|
148
|
+
toast.error(t("designEditor.toasts.tweakSaveNotDurable"));
|
|
149
|
+
}
|
|
150
|
+
} finally {
|
|
151
|
+
tweakSaveInFlightRef.current = false;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
tweakSaveChainRef.current = current;
|
|
155
|
+
void current.finally(() => {
|
|
156
|
+
if (tweakSaveChainRef.current === current) {
|
|
157
|
+
tweakSaveChainRef.current = Promise.resolve();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
2
|
+
import { flushSync } from "react-dom";
|
|
3
|
+
|
|
4
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
5
|
+
import {
|
|
6
|
+
isTextEditSessionOutcome,
|
|
7
|
+
scheduleBeginTextEditForScreen,
|
|
8
|
+
} from "@/pages/design-editor/text-edit-utils";
|
|
9
|
+
import type { DesignTool, EditorMode } from "@/pages/design-editor/types";
|
|
10
|
+
|
|
11
|
+
export interface PrimitiveCreatedArgs {
|
|
12
|
+
boardFileId: string | undefined;
|
|
13
|
+
clearPendingOverviewLayerSelectionTimer: () => void;
|
|
14
|
+
pendingEmptyTextEditRef: RefObject<{
|
|
15
|
+
screenId: string | null;
|
|
16
|
+
nodeId: string;
|
|
17
|
+
cancel: () => void;
|
|
18
|
+
settled: boolean;
|
|
19
|
+
} | null>;
|
|
20
|
+
pendingOverviewLayerSelectionRef: RefObject<string | null>;
|
|
21
|
+
pendingOverviewScreenSelectionRef: RefObject<string | null>;
|
|
22
|
+
pendingTextEditNodeIdRef: RefObject<string | null>;
|
|
23
|
+
removeEmptyTextNodeWithRetry: (
|
|
24
|
+
screenId: string | null,
|
|
25
|
+
nodeId: string,
|
|
26
|
+
) => void;
|
|
27
|
+
setActiveFileId: Dispatch<SetStateAction<string | null>>;
|
|
28
|
+
setActiveTool: Dispatch<SetStateAction<DesignTool>>;
|
|
29
|
+
setCreatedOverviewLayerSelection: Dispatch<
|
|
30
|
+
SetStateAction<{ screenId: string; layerId: string } | null>
|
|
31
|
+
>;
|
|
32
|
+
setHoveredElement: Dispatch<SetStateAction<ElementInfo | null>>;
|
|
33
|
+
setMode: Dispatch<SetStateAction<EditorMode>>;
|
|
34
|
+
setOverviewSelectedScreenIds: Dispatch<SetStateAction<string[]>>;
|
|
35
|
+
setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
|
|
36
|
+
setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function runPrimitiveCreated(
|
|
40
|
+
{
|
|
41
|
+
boardFileId,
|
|
42
|
+
clearPendingOverviewLayerSelectionTimer,
|
|
43
|
+
pendingEmptyTextEditRef,
|
|
44
|
+
pendingOverviewLayerSelectionRef,
|
|
45
|
+
pendingOverviewScreenSelectionRef,
|
|
46
|
+
pendingTextEditNodeIdRef,
|
|
47
|
+
removeEmptyTextNodeWithRetry,
|
|
48
|
+
setActiveFileId,
|
|
49
|
+
setActiveTool,
|
|
50
|
+
setCreatedOverviewLayerSelection,
|
|
51
|
+
setHoveredElement,
|
|
52
|
+
setMode,
|
|
53
|
+
setOverviewSelectedScreenIds,
|
|
54
|
+
setSelectedElement,
|
|
55
|
+
setSelectedLayerIdsState,
|
|
56
|
+
}: PrimitiveCreatedArgs,
|
|
57
|
+
screenId: string,
|
|
58
|
+
nodeId: string,
|
|
59
|
+
options?: {
|
|
60
|
+
nextTool?: "move" | "pen";
|
|
61
|
+
preserveActiveTool?: boolean;
|
|
62
|
+
},
|
|
63
|
+
) {
|
|
64
|
+
// B2/B4 fix: stay in overview mode after drawing a primitive. The user
|
|
65
|
+
// drew a shape on the board — they should remain on the board with the
|
|
66
|
+
// new primitive selected, matching Figma behaviour. We activate the
|
|
67
|
+
// target screen (so the layers panel shows its content) and select the
|
|
68
|
+
// new node, but do NOT switch to single/full view.
|
|
69
|
+
//
|
|
70
|
+
// Board guard (overview-zoom corruption fix): the board file is NOT a
|
|
71
|
+
// screen — it never appears in `overviewScreens` and has no
|
|
72
|
+
// `canvasFrames` entry, so activating it flipped the overview zoom
|
|
73
|
+
// basis onto double-fallback inputs (scale snapped to 0.25) while
|
|
74
|
+
// `explicitOverviewCanvasZoom` stayed pinned to the previous screen's
|
|
75
|
+
// scale — the displayed zoom showed garbage (observed: 10241.49%).
|
|
76
|
+
// For a board-created primitive we still select the node and honor the
|
|
77
|
+
// pending text-edit intent below (both are screen-id scoped and work
|
|
78
|
+
// for the board), but keep the previous active FILE and never put the
|
|
79
|
+
// board id into the overview screen-frame selection.
|
|
80
|
+
const isBoardTarget = Boolean(boardFileId && screenId === boardFileId);
|
|
81
|
+
pendingOverviewScreenSelectionRef.current = null;
|
|
82
|
+
pendingOverviewLayerSelectionRef.current = nodeId;
|
|
83
|
+
clearPendingOverviewLayerSelectionTimer();
|
|
84
|
+
flushSync(() => {
|
|
85
|
+
setCreatedOverviewLayerSelection({ screenId, layerId: nodeId });
|
|
86
|
+
if (!isBoardTarget) {
|
|
87
|
+
setActiveFileId(screenId);
|
|
88
|
+
}
|
|
89
|
+
setSelectedElement(null);
|
|
90
|
+
setHoveredElement(null);
|
|
91
|
+
setSelectedLayerIdsState([nodeId]);
|
|
92
|
+
// The new node is the selection; leaving its parent frame selected too
|
|
93
|
+
// hands Cmd+D and alt-drag to the screen-level duplicate instead.
|
|
94
|
+
setOverviewSelectedScreenIds([]);
|
|
95
|
+
if (!options?.preserveActiveTool) {
|
|
96
|
+
setActiveTool(options?.nextTool ?? "move");
|
|
97
|
+
}
|
|
98
|
+
setMode("edit");
|
|
99
|
+
});
|
|
100
|
+
// viewMode stays at "overview" — no setViewMode("single") call here.
|
|
101
|
+
|
|
102
|
+
// Immediately enter text-editing for newly created TEXT primitives. In
|
|
103
|
+
// overview mode the target iframe may become active and receive the
|
|
104
|
+
// inserted HTML over separate renders, so post directly to the target
|
|
105
|
+
// iframe with a few short retries instead of relying on a single global
|
|
106
|
+
// bridge callback.
|
|
107
|
+
const textNodeId = pendingTextEditNodeIdRef.current;
|
|
108
|
+
pendingTextEditNodeIdRef.current = null;
|
|
109
|
+
if (textNodeId) {
|
|
110
|
+
// Cancel any still-pending retry loop from a PREVIOUS text primitive
|
|
111
|
+
// (e.g. the user drew a second text box before the first one settled)
|
|
112
|
+
// before starting a new one, so stale timers can't fight over which
|
|
113
|
+
// node to clean up.
|
|
114
|
+
pendingEmptyTextEditRef.current?.cancel();
|
|
115
|
+
// Creation-race fast path: the retry loop below fires its FIRST
|
|
116
|
+
// begin-text-edit attempt at 180ms and its status-query round trips
|
|
117
|
+
// can leave a multi-second window where keystrokes still hit HOST
|
|
118
|
+
// shortcuts (Delete deleted the just-created text layer). The
|
|
119
|
+
// DesignCanvas runtime bridge's beginTextEdit (registered as
|
|
120
|
+
// window.__designCanvasBeginTextEdit by the active surface's canvas
|
|
121
|
+
// — the flushSync above just pointed activeFileId at this screenId
|
|
122
|
+
// for non-board targets) queues begin-text-edit through the
|
|
123
|
+
// bridge-ready one-shot queue AND arms the host keystroke buffer
|
|
124
|
+
// SYNCHRONOUSLY, so typing is captured from the very first keydown.
|
|
125
|
+
// Activation itself waits until the pointer gesture's trailing click:
|
|
126
|
+
// focusing during mouseup lets that click blur the empty editable one
|
|
127
|
+
// frame later, producing the visible caret blink and losing typing.
|
|
128
|
+
// Best-effort only: if the registered bridge still belongs to a
|
|
129
|
+
// different surface, its iframe no-ops on the unknown nodeId while
|
|
130
|
+
// the buffer still swallows destructive shortcuts, and the
|
|
131
|
+
// screen-id-scoped scheduleBeginTextEditForScreen loop below remains
|
|
132
|
+
// the authoritative per-iframe fallback (resolved through
|
|
133
|
+
// findCanvasIframeForScreen, so board-space text works too).
|
|
134
|
+
if (typeof window !== "undefined") {
|
|
135
|
+
const beginTextEditNow = (window as any).__designCanvasBeginTextEdit;
|
|
136
|
+
if (typeof beginTextEditNow === "function") {
|
|
137
|
+
beginTextEditNow(textNodeId, { afterPointerGesture: true });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const cancel = scheduleBeginTextEditForScreen(screenId, textNodeId, {
|
|
141
|
+
boardFileId,
|
|
142
|
+
onExhausted: (finalStatus) => {
|
|
143
|
+
const pending = pendingEmptyTextEditRef.current;
|
|
144
|
+
if (!pending || pending.nodeId !== textNodeId || pending.settled) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
pending.settled = true;
|
|
148
|
+
if (isTextEditSessionOutcome(finalStatus)) return;
|
|
149
|
+
if (finalStatus === "no-iframe" || finalStatus === "no-reply") {
|
|
150
|
+
// Never reached an editing surface at all. That is a targeting
|
|
151
|
+
// defect, not the user declining to type, so make it audible —
|
|
152
|
+
// but the persisted document, not this outcome, still decides
|
|
153
|
+
// whether the node is empty and should go.
|
|
154
|
+
console.warn(
|
|
155
|
+
`[design] text edit never reached a surface for ${screenId}/${textNodeId} (${finalStatus})`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
removeEmptyTextNodeWithRetry(screenId, textNodeId);
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
pendingEmptyTextEditRef.current = {
|
|
162
|
+
screenId,
|
|
163
|
+
nodeId: textNodeId,
|
|
164
|
+
cancel,
|
|
165
|
+
settled: false,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|