@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,212 @@
|
|
|
1
|
+
import type { CodeLayerNode, CodeLayerTreeNode } from "@shared/code-layer";
|
|
2
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
3
|
+
|
|
4
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
5
|
+
import {
|
|
6
|
+
collectCodeLayerAncestors,
|
|
7
|
+
elementInfoFromCodeLayerNode,
|
|
8
|
+
} from "@/pages/design-editor/code-layer-state";
|
|
9
|
+
import {
|
|
10
|
+
hasSelectableCodeLayerParent,
|
|
11
|
+
resolveEscapePopSelectionAction,
|
|
12
|
+
shouldEscapeToOverview,
|
|
13
|
+
} from "@/pages/design-editor/selection-state";
|
|
14
|
+
import type { DesignTool, EditorMode } from "@/pages/design-editor/types";
|
|
15
|
+
|
|
16
|
+
export interface EscapeHotkeyArgs {
|
|
17
|
+
activeBreakpointWidthStateRef: RefObject<number | undefined>;
|
|
18
|
+
activeTool: DesignTool;
|
|
19
|
+
cancelActiveEditorDrag: () => boolean;
|
|
20
|
+
codeLayerOwnerByNodeIdRef: RefObject<
|
|
21
|
+
Map<
|
|
22
|
+
string,
|
|
23
|
+
{
|
|
24
|
+
fileId: string;
|
|
25
|
+
node: CodeLayerNode;
|
|
26
|
+
tree: CodeLayerTreeNode[];
|
|
27
|
+
runtimeOnly: boolean;
|
|
28
|
+
}
|
|
29
|
+
>
|
|
30
|
+
>;
|
|
31
|
+
drawMode: boolean;
|
|
32
|
+
enterOverviewFromZoom: (nextMode?: EditorMode) => void;
|
|
33
|
+
focusedAnnotationSending: boolean;
|
|
34
|
+
handleBreakpointBarSelect: (widthPx: number | undefined) => void;
|
|
35
|
+
handleCloseKeyboardShortcuts: () => void;
|
|
36
|
+
handleExitFocusedDrawMode: () => void;
|
|
37
|
+
handleExitOverviewDrawMode: () => void;
|
|
38
|
+
keyboardShortcutsOpen: boolean;
|
|
39
|
+
mode: EditorMode;
|
|
40
|
+
overviewAnnotationSending: boolean;
|
|
41
|
+
pinMode: boolean;
|
|
42
|
+
selectedElement: ElementInfo | null;
|
|
43
|
+
selectedLayerIdsState: string[];
|
|
44
|
+
setActiveFileId: Dispatch<SetStateAction<string | null>>;
|
|
45
|
+
setActiveTool: Dispatch<SetStateAction<DesignTool>>;
|
|
46
|
+
setDrawMode: Dispatch<SetStateAction<boolean>>;
|
|
47
|
+
setExpandedLayerIds: Dispatch<SetStateAction<string[]>>;
|
|
48
|
+
setHoveredElement: Dispatch<SetStateAction<ElementInfo | null>>;
|
|
49
|
+
setMode: Dispatch<SetStateAction<EditorMode>>;
|
|
50
|
+
setOverviewClearSelectionRequest: Dispatch<SetStateAction<number>>;
|
|
51
|
+
setOverviewSelectedScreenIds: Dispatch<SetStateAction<string[]>>;
|
|
52
|
+
setPinMode: Dispatch<SetStateAction<boolean>>;
|
|
53
|
+
setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
|
|
54
|
+
setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
|
|
55
|
+
viewMode: "single" | "overview";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function runEscapeHotkey({
|
|
59
|
+
activeBreakpointWidthStateRef,
|
|
60
|
+
activeTool,
|
|
61
|
+
cancelActiveEditorDrag,
|
|
62
|
+
codeLayerOwnerByNodeIdRef,
|
|
63
|
+
drawMode,
|
|
64
|
+
enterOverviewFromZoom,
|
|
65
|
+
focusedAnnotationSending,
|
|
66
|
+
handleBreakpointBarSelect,
|
|
67
|
+
handleCloseKeyboardShortcuts,
|
|
68
|
+
handleExitFocusedDrawMode,
|
|
69
|
+
handleExitOverviewDrawMode,
|
|
70
|
+
keyboardShortcutsOpen,
|
|
71
|
+
mode,
|
|
72
|
+
overviewAnnotationSending,
|
|
73
|
+
pinMode,
|
|
74
|
+
selectedElement,
|
|
75
|
+
selectedLayerIdsState,
|
|
76
|
+
setActiveFileId,
|
|
77
|
+
setActiveTool,
|
|
78
|
+
setDrawMode,
|
|
79
|
+
setExpandedLayerIds,
|
|
80
|
+
setHoveredElement,
|
|
81
|
+
setMode,
|
|
82
|
+
setOverviewClearSelectionRequest,
|
|
83
|
+
setOverviewSelectedScreenIds,
|
|
84
|
+
setPinMode,
|
|
85
|
+
setSelectedElement,
|
|
86
|
+
setSelectedLayerIdsState,
|
|
87
|
+
viewMode,
|
|
88
|
+
}: EscapeHotkeyArgs) {
|
|
89
|
+
if (keyboardShortcutsOpen) {
|
|
90
|
+
handleCloseKeyboardShortcuts();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (cancelActiveEditorDrag()) return;
|
|
94
|
+
// A delivery-confirmation wait freezes the complete annotation batch.
|
|
95
|
+
// Escape must not clear it while the submitted snapshot is in flight.
|
|
96
|
+
if (overviewAnnotationSending || focusedAnnotationSending) return;
|
|
97
|
+
// Escape is a deliberate annotate-mode exit, same as the overlay's X.
|
|
98
|
+
// Route it through the same per-surface clear semantics instead of merely
|
|
99
|
+
// hiding the overlay and leaving a stale batch to reappear later.
|
|
100
|
+
if (drawMode && mode === "annotate") {
|
|
101
|
+
if (viewMode === "overview") handleExitOverviewDrawMode();
|
|
102
|
+
else handleExitFocusedDrawMode();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// ReviewCanvasPins owns Escape while comment mode is active so it can
|
|
106
|
+
// dismiss in context: first an open draft/thread, then pin mode itself.
|
|
107
|
+
// Letting this global handler continue would exit the tool on the same
|
|
108
|
+
// keypress that only meant to close the composer.
|
|
109
|
+
if (pinMode) return;
|
|
110
|
+
// BP-DEEP item 5 — Framer-style click-to-target: Escape's first job when
|
|
111
|
+
// a breakpoint is the active edit target is to return to Base, matching
|
|
112
|
+
// "click the base frame / empty canvas" — mirrors the other early-return
|
|
113
|
+
// priority checks above/below (cancelActiveEditorDrag,
|
|
114
|
+
// shouldEscapeToOverview) that let one Escape press consume the single
|
|
115
|
+
// most contextually-relevant action instead of stacking every effect.
|
|
116
|
+
// Gated on the ref (not the state) so this callback doesn't need
|
|
117
|
+
// activeBreakpointWidthState as a dep and doesn't get recreated (and
|
|
118
|
+
// re-registered with useDesignHotkeys) on every breakpoint switch.
|
|
119
|
+
if (activeBreakpointWidthStateRef.current !== undefined) {
|
|
120
|
+
handleBreakpointBarSelect(undefined);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (
|
|
124
|
+
shouldEscapeToOverview({
|
|
125
|
+
activeTool,
|
|
126
|
+
drawMode,
|
|
127
|
+
mode,
|
|
128
|
+
pinMode,
|
|
129
|
+
selectedElement,
|
|
130
|
+
viewMode,
|
|
131
|
+
})
|
|
132
|
+
) {
|
|
133
|
+
enterOverviewFromZoom();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Figma parity — pop the plain-canvas selection one level at a time
|
|
137
|
+
// (child layer -> parent layer -> containing screen/frame -> fully
|
|
138
|
+
// deselected) instead of deselecting everything on the first Escape.
|
|
139
|
+
// The decision itself is a pure, unit-tested function
|
|
140
|
+
// (resolveEscapePopSelectionAction in design-editor/selection-state.ts);
|
|
141
|
+
// this reuses the same codeLayerOwnerByNodeIdRef/parentId ancestor walk
|
|
142
|
+
// handleSelectParentLayer (Shift+Enter / "\\") already uses, inlined
|
|
143
|
+
// here (rather than calling selectCodeLayerNodesForHotkey directly)
|
|
144
|
+
// since that helper is declared later in this component and referencing
|
|
145
|
+
// it from this callback's deps would hit its temporal-dead-zone before
|
|
146
|
+
// this render's declarations run.
|
|
147
|
+
const escapeSelectedLayerId =
|
|
148
|
+
selectedLayerIdsState[selectedLayerIdsState.length - 1];
|
|
149
|
+
const escapeOwner = escapeSelectedLayerId
|
|
150
|
+
? codeLayerOwnerByNodeIdRef.current.get(escapeSelectedLayerId)
|
|
151
|
+
: undefined;
|
|
152
|
+
// BUG-ESCAPE-SHELL: the flat codeLayerOwnerByNodeIdRef map (built from
|
|
153
|
+
// projection.nodes) still contains <html>/<body> entries with real
|
|
154
|
+
// parentId links even though the VISUAL layers tree collapses them away
|
|
155
|
+
// (shared/code-layer.ts's isCollapsibleDocumentShellNode). A bare
|
|
156
|
+
// Boolean(escapeOwner?.node.parentId) check treats a top-level layer's
|
|
157
|
+
// collapsed <body> ancestor as a selectable parent, so popping from the
|
|
158
|
+
// screen root would select <body> then <html> instead of stopping at the
|
|
159
|
+
// screen/frame level. hasSelectableCodeLayerParent excludes those shell
|
|
160
|
+
// nodes so the pop walk matches what the layers panel shows.
|
|
161
|
+
const escapeParentOwner = escapeOwner?.node.parentId
|
|
162
|
+
? codeLayerOwnerByNodeIdRef.current.get(escapeOwner.node.parentId)
|
|
163
|
+
: undefined;
|
|
164
|
+
const popAction = resolveEscapePopSelectionAction({
|
|
165
|
+
hasSelectedLayer: Boolean(escapeOwner),
|
|
166
|
+
hasLayerParent: hasSelectableCodeLayerParent({
|
|
167
|
+
parentNode: escapeParentOwner?.node,
|
|
168
|
+
}),
|
|
169
|
+
viewMode,
|
|
170
|
+
});
|
|
171
|
+
if (popAction.kind === "pop-to-parent-layer" && escapeOwner) {
|
|
172
|
+
const parentOwner = escapeParentOwner;
|
|
173
|
+
if (parentOwner && parentOwner.fileId === escapeOwner.fileId) {
|
|
174
|
+
setActiveFileId(parentOwner.fileId);
|
|
175
|
+
setOverviewSelectedScreenIds([]);
|
|
176
|
+
setSelectedLayerIdsState([parentOwner.node.id]);
|
|
177
|
+
setSelectedElement(elementInfoFromCodeLayerNode(parentOwner.node));
|
|
178
|
+
setExpandedLayerIds((current) => {
|
|
179
|
+
const next = new Set(current);
|
|
180
|
+
next.add(parentOwner.fileId);
|
|
181
|
+
collectCodeLayerAncestors(
|
|
182
|
+
parentOwner.tree,
|
|
183
|
+
parentOwner.node.id,
|
|
184
|
+
).forEach((ancestorId) => next.add(ancestorId));
|
|
185
|
+
return next.size === current.length ? current : Array.from(next);
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Parent couldn't be resolved (e.g. cross-file) — fall through to a
|
|
190
|
+
// full deselect below rather than silently doing nothing.
|
|
191
|
+
} else if (popAction.kind === "pop-to-screen-frame" && escapeOwner) {
|
|
192
|
+
setSelectedElement(null);
|
|
193
|
+
setSelectedLayerIdsState([]);
|
|
194
|
+
setOverviewSelectedScreenIds([escapeOwner.fileId]);
|
|
195
|
+
setOverviewClearSelectionRequest((request) => request + 1);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
setSelectedElement(null);
|
|
199
|
+
setHoveredElement(null);
|
|
200
|
+
setOverviewSelectedScreenIds([]);
|
|
201
|
+
// Item 10 — Escape must also clear a multi-layer selection
|
|
202
|
+
// (selectedLayerIdsState), not just the single selectedElement /
|
|
203
|
+
// overviewSelectedScreenIds above. Without this, pressing Escape after a
|
|
204
|
+
// multi-layer marquee/shift-click selection left the layers panel
|
|
205
|
+
// showing stale selected rows even though the canvas selection was gone.
|
|
206
|
+
setSelectedLayerIdsState([]);
|
|
207
|
+
setOverviewClearSelectionRequest((request) => request + 1);
|
|
208
|
+
setDrawMode(false);
|
|
209
|
+
setPinMode(false);
|
|
210
|
+
setActiveTool("move");
|
|
211
|
+
setMode("edit");
|
|
212
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { applyVisualEdit, buildCodeLayerProjection } from "@shared/code-layer";
|
|
2
|
+
import type { Dispatch, SetStateAction } from "react";
|
|
3
|
+
import { toast } from "sonner";
|
|
4
|
+
|
|
5
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
6
|
+
import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
|
|
7
|
+
import {
|
|
8
|
+
codeLayerPatchMessage,
|
|
9
|
+
elementInfoFromCodeLayerNode,
|
|
10
|
+
} from "@/pages/design-editor/code-layer-state";
|
|
11
|
+
import { setCodeLayerAttributeInHtml } from "@/pages/design-editor/html-layer-positioning";
|
|
12
|
+
import { buildActiveFileNodeIdSet } from "@/pages/design-editor/selection-state";
|
|
13
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
14
|
+
|
|
15
|
+
export interface FrameSelectionArgs {
|
|
16
|
+
activeFile: DesignFile;
|
|
17
|
+
applyLocalContentUpdate: (
|
|
18
|
+
nextContent: string,
|
|
19
|
+
options?: {
|
|
20
|
+
refreshPreview?: boolean;
|
|
21
|
+
skipPreview?: boolean;
|
|
22
|
+
forcePreviewFullDocument?: boolean;
|
|
23
|
+
immediateSave?: boolean;
|
|
24
|
+
persist?: boolean;
|
|
25
|
+
recordHistory?: boolean;
|
|
26
|
+
historyBeforeContent?: string;
|
|
27
|
+
updatedAt?: string;
|
|
28
|
+
clipboardMutation?: ClipboardContentMutationPublication;
|
|
29
|
+
},
|
|
30
|
+
) => void;
|
|
31
|
+
canEditDesign: boolean;
|
|
32
|
+
files: DesignFile[];
|
|
33
|
+
getFreshActiveContent: () => string;
|
|
34
|
+
selectedLayerIdsState: string[];
|
|
35
|
+
setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
|
|
36
|
+
setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
|
|
37
|
+
t: (key: string, options?: Record<string, unknown>) => string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function runFrameSelection({
|
|
41
|
+
activeFile,
|
|
42
|
+
applyLocalContentUpdate,
|
|
43
|
+
canEditDesign,
|
|
44
|
+
files,
|
|
45
|
+
getFreshActiveContent,
|
|
46
|
+
selectedLayerIdsState,
|
|
47
|
+
setSelectedElement,
|
|
48
|
+
setSelectedLayerIdsState,
|
|
49
|
+
t,
|
|
50
|
+
}: FrameSelectionArgs) {
|
|
51
|
+
if (!canEditDesign || !activeFile) return;
|
|
52
|
+
const baseContent = getFreshActiveContent();
|
|
53
|
+
const fileIds = new Set(files.map((f) => f.id));
|
|
54
|
+
const activeNodeIdSet = buildActiveFileNodeIdSet(
|
|
55
|
+
buildCodeLayerProjection(baseContent),
|
|
56
|
+
);
|
|
57
|
+
const nodeIds = selectedLayerIdsState.filter(
|
|
58
|
+
(id) => !id.startsWith("__") && !fileIds.has(id) && activeNodeIdSet.has(id),
|
|
59
|
+
);
|
|
60
|
+
if (nodeIds.length < 1) return;
|
|
61
|
+
const patch = applyVisualEdit(baseContent, {
|
|
62
|
+
kind: "wrapNodes",
|
|
63
|
+
targetIds: nodeIds,
|
|
64
|
+
autoLayout: false,
|
|
65
|
+
});
|
|
66
|
+
if (patch.result.status !== "applied") {
|
|
67
|
+
toast.error(
|
|
68
|
+
codeLayerPatchMessage(
|
|
69
|
+
patch.result.message,
|
|
70
|
+
t("designEditor.toasts.layerMoveFailed"),
|
|
71
|
+
),
|
|
72
|
+
{ duration: 4000 },
|
|
73
|
+
);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let nextContent = patch.content;
|
|
77
|
+
let wrapperNode = patch.result.wrapperNodeId
|
|
78
|
+
? patch.projection.nodes.find(
|
|
79
|
+
(n) =>
|
|
80
|
+
n.dataAttributes["data-agent-native-node-id"] ===
|
|
81
|
+
patch.result.wrapperNodeId,
|
|
82
|
+
)
|
|
83
|
+
: undefined;
|
|
84
|
+
if (wrapperNode) {
|
|
85
|
+
const renamed = setCodeLayerAttributeInHtml(
|
|
86
|
+
nextContent,
|
|
87
|
+
wrapperNode,
|
|
88
|
+
"data-agent-native-layer-name",
|
|
89
|
+
"Frame",
|
|
90
|
+
);
|
|
91
|
+
if (renamed) nextContent = renamed;
|
|
92
|
+
const taggedProjection = buildCodeLayerProjection(nextContent);
|
|
93
|
+
const taggedNode = taggedProjection.nodes.find(
|
|
94
|
+
(n) =>
|
|
95
|
+
n.dataAttributes["data-agent-native-node-id"] ===
|
|
96
|
+
patch.result.wrapperNodeId,
|
|
97
|
+
);
|
|
98
|
+
if (taggedNode) {
|
|
99
|
+
const tagged = setCodeLayerAttributeInHtml(
|
|
100
|
+
nextContent,
|
|
101
|
+
taggedNode,
|
|
102
|
+
"data-an-primitive",
|
|
103
|
+
"frame",
|
|
104
|
+
);
|
|
105
|
+
if (tagged) nextContent = tagged;
|
|
106
|
+
}
|
|
107
|
+
wrapperNode =
|
|
108
|
+
buildCodeLayerProjection(nextContent).nodes.find(
|
|
109
|
+
(n) =>
|
|
110
|
+
n.dataAttributes["data-agent-native-node-id"] ===
|
|
111
|
+
patch.result.wrapperNodeId,
|
|
112
|
+
) ?? wrapperNode;
|
|
113
|
+
}
|
|
114
|
+
applyLocalContentUpdate(nextContent, { skipPreview: true });
|
|
115
|
+
if (wrapperNode) {
|
|
116
|
+
setSelectedLayerIdsState([wrapperNode.id]);
|
|
117
|
+
setSelectedElement(elementInfoFromCodeLayerNode(wrapperNode));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
|
|
2
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
3
|
+
import type { RefObject } from "react";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
cloneCanvasFrameGeometry,
|
|
7
|
+
viewportChangedFrameIds,
|
|
8
|
+
} from "@/pages/design-editor/design-data-geometry-utils";
|
|
9
|
+
import type { UndoRedoOrderKind } from "@/pages/design-editor/editor-state";
|
|
10
|
+
import {
|
|
11
|
+
geometrySnapshotsEqual,
|
|
12
|
+
sanitizeCanvasFrameGeometryForPersist,
|
|
13
|
+
} from "@/pages/design-editor/geometry-persistence";
|
|
14
|
+
import type {
|
|
15
|
+
GeometryHistoryEntry,
|
|
16
|
+
GeometryHistorySelection,
|
|
17
|
+
} from "@/pages/design-editor/history";
|
|
18
|
+
import { MAX_DESIGN_UNDO_STACK } from "@/pages/design-editor/history";
|
|
19
|
+
|
|
20
|
+
export interface GeometryCommitArgs {
|
|
21
|
+
boardFileId: string | undefined;
|
|
22
|
+
captureCurrentSelection: () => GeometryHistorySelection;
|
|
23
|
+
clearRedoStacks: () => void;
|
|
24
|
+
designDataJsonRef: RefObject<Record<string, unknown>>;
|
|
25
|
+
geometryUndoStackRef: RefObject<GeometryHistoryEntry[]>;
|
|
26
|
+
historyOrderRef: RefObject<UndoRedoOrderKind[]>;
|
|
27
|
+
id: string | undefined;
|
|
28
|
+
lastGeometryCommitAtRef: RefObject<number>;
|
|
29
|
+
locallyPinnedHeightIdsRef: RefObject<Set<string>>;
|
|
30
|
+
queryClient: QueryClient;
|
|
31
|
+
queueFrameGeometrySave: (geometryById: CanvasFrameGeometryById) => void;
|
|
32
|
+
syncUndoRedoState: () => void;
|
|
33
|
+
writeFrameGeometrySnapshot: (
|
|
34
|
+
geometryById: CanvasFrameGeometryById,
|
|
35
|
+
options?: { syncViewportFrameIds?: string[]; pinHeightFrameIds?: string[] },
|
|
36
|
+
) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function runGeometryCommit(
|
|
40
|
+
{
|
|
41
|
+
boardFileId,
|
|
42
|
+
captureCurrentSelection,
|
|
43
|
+
clearRedoStacks,
|
|
44
|
+
designDataJsonRef,
|
|
45
|
+
geometryUndoStackRef,
|
|
46
|
+
historyOrderRef,
|
|
47
|
+
id,
|
|
48
|
+
lastGeometryCommitAtRef,
|
|
49
|
+
locallyPinnedHeightIdsRef,
|
|
50
|
+
queryClient,
|
|
51
|
+
queueFrameGeometrySave,
|
|
52
|
+
syncUndoRedoState,
|
|
53
|
+
writeFrameGeometrySnapshot,
|
|
54
|
+
}: GeometryCommitArgs,
|
|
55
|
+
before: CanvasFrameGeometryById,
|
|
56
|
+
after: CanvasFrameGeometryById,
|
|
57
|
+
options?: { source?: "pointer" | "keyboard" },
|
|
58
|
+
) {
|
|
59
|
+
const beforeSnapshot = cloneCanvasFrameGeometry(before);
|
|
60
|
+
// Geometry-persist guard: refuse absurd committed geometry HERE (not
|
|
61
|
+
// only inside the save functions) so the undo stack and the mid-gesture
|
|
62
|
+
// query-cache write below stay consistent with what actually persists —
|
|
63
|
+
// an insane frame falls back to its own pre-gesture geometry, and a
|
|
64
|
+
// commit whose every change was refused becomes a no-op.
|
|
65
|
+
const { geometryById: afterSnapshot } = sanitizeCanvasFrameGeometryForPersist(
|
|
66
|
+
cloneCanvasFrameGeometry(after),
|
|
67
|
+
beforeSnapshot,
|
|
68
|
+
boardFileId ? [boardFileId] : [],
|
|
69
|
+
);
|
|
70
|
+
if (geometrySnapshotsEqual(beforeSnapshot, afterSnapshot)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// U9: keyboard nudge (arrow-key auto-repeat) fires one onGeometryCommit
|
|
74
|
+
// per tick, each previously pushing its own undo entry AND its own
|
|
75
|
+
// immediate (non-debounced) server write — a held arrow key could evict
|
|
76
|
+
// the 50-entry undo cap in well under a second and hammer the server.
|
|
77
|
+
// Coalesce consecutive commits into one undo entry (mirroring Yjs's own
|
|
78
|
+
// captureTimeout) when the new commit continues straight from the last
|
|
79
|
+
// one (same "before" as the prior "after") within a ~800ms window, and
|
|
80
|
+
// route the write through the same debounced save queue used for drags
|
|
81
|
+
// instead of firing an immediate mutation per tick.
|
|
82
|
+
//
|
|
83
|
+
// This coalescing must only apply to KEYBOARD nudge auto-repeat, not to
|
|
84
|
+
// two independent pointer gestures (e.g. two separate drags) that
|
|
85
|
+
// happen to land within the same 800ms window — those are discrete
|
|
86
|
+
// user actions and each must be its own undo step, matching Figma.
|
|
87
|
+
// MultiScreenCanvas's onGeometryCommit callback (a real pointer drag)
|
|
88
|
+
// omits `options`, so it defaults to "pointer" and never coalesces;
|
|
89
|
+
// only handleNudgeSelection's overview branch passes "keyboard".
|
|
90
|
+
const source = options?.source ?? "pointer";
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const lastEntry =
|
|
93
|
+
geometryUndoStackRef.current[geometryUndoStackRef.current.length - 1];
|
|
94
|
+
const continuesLastGesture =
|
|
95
|
+
source === "keyboard" &&
|
|
96
|
+
lastEntry &&
|
|
97
|
+
now - lastGeometryCommitAtRef.current < 800 &&
|
|
98
|
+
geometrySnapshotsEqual(lastEntry.after, beforeSnapshot);
|
|
99
|
+
lastGeometryCommitAtRef.current = now;
|
|
100
|
+
// Figma-parity undo/redo selection restore: selectionAfter always
|
|
101
|
+
// reflects the CURRENT selection at this commit tick (so redo restores
|
|
102
|
+
// whatever was selected when the gesture finished), while
|
|
103
|
+
// selectionBefore is only captured on the FIRST tick of a gesture and
|
|
104
|
+
// then carried forward unchanged through every coalesced continuation —
|
|
105
|
+
// otherwise a held arrow key would keep overwriting selectionBefore
|
|
106
|
+
// with the selection at the START of each individual tick instead of
|
|
107
|
+
// the whole gesture's actual starting selection.
|
|
108
|
+
const selectionAfter = captureCurrentSelection();
|
|
109
|
+
if (continuesLastGesture) {
|
|
110
|
+
geometryUndoStackRef.current = [
|
|
111
|
+
...geometryUndoStackRef.current.slice(0, -1),
|
|
112
|
+
{
|
|
113
|
+
before: lastEntry.before,
|
|
114
|
+
after: afterSnapshot,
|
|
115
|
+
selectionBefore: lastEntry.selectionBefore,
|
|
116
|
+
selectionAfter,
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
} else {
|
|
120
|
+
geometryUndoStackRef.current = [
|
|
121
|
+
...geometryUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
122
|
+
{
|
|
123
|
+
before: beforeSnapshot,
|
|
124
|
+
after: afterSnapshot,
|
|
125
|
+
selectionBefore: selectionAfter,
|
|
126
|
+
selectionAfter,
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
clearRedoStacks();
|
|
131
|
+
historyOrderRef.current = continuesLastGesture
|
|
132
|
+
? historyOrderRef.current
|
|
133
|
+
: [
|
|
134
|
+
...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
135
|
+
"geometry",
|
|
136
|
+
];
|
|
137
|
+
const resizedFrameIds = viewportChangedFrameIds(
|
|
138
|
+
beforeSnapshot,
|
|
139
|
+
afterSnapshot,
|
|
140
|
+
);
|
|
141
|
+
if (continuesLastGesture) {
|
|
142
|
+
// Mid-gesture tick: keep the query cache current for a responsive
|
|
143
|
+
// canvas, but debounce the actual network write so a held key
|
|
144
|
+
// doesn't send one mutation per tick.
|
|
145
|
+
queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
|
|
146
|
+
if (!old || typeof old !== "object") return old;
|
|
147
|
+
const nextData = {
|
|
148
|
+
...designDataJsonRef.current,
|
|
149
|
+
canvasFrames: afterSnapshot,
|
|
150
|
+
};
|
|
151
|
+
return { ...old, data: JSON.stringify(nextData) };
|
|
152
|
+
});
|
|
153
|
+
queueFrameGeometrySave(afterSnapshot);
|
|
154
|
+
} else {
|
|
155
|
+
writeFrameGeometrySnapshot(
|
|
156
|
+
afterSnapshot,
|
|
157
|
+
resizedFrameIds.length > 0
|
|
158
|
+
? (resizedFrameIds.forEach((frameId) =>
|
|
159
|
+
locallyPinnedHeightIdsRef.current.add(frameId),
|
|
160
|
+
),
|
|
161
|
+
{
|
|
162
|
+
syncViewportFrameIds: resizedFrameIds,
|
|
163
|
+
pinHeightFrameIds: resizedFrameIds,
|
|
164
|
+
})
|
|
165
|
+
: undefined,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
syncUndoRedoState();
|
|
169
|
+
}
|
package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildCodeLayerProjection,
|
|
3
|
+
buildCodeLayerTree,
|
|
4
|
+
} from "@shared/code-layer";
|
|
5
|
+
|
|
6
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
7
|
+
import { extractDesignClipboardManagedStyles } from "@/lib/design-clipboard-managed-styles";
|
|
8
|
+
import { resolveClipboardLayerSourceHtml } from "@/pages/design-editor/clipboard-layer-source";
|
|
9
|
+
import { getElementOuterHtml } from "@/pages/design-editor/clone-and-pen-edit";
|
|
10
|
+
import {
|
|
11
|
+
collectCodeLayerAncestors,
|
|
12
|
+
resolveCodeLayerNodeFromElementInfo,
|
|
13
|
+
} from "@/pages/design-editor/code-layer-state";
|
|
14
|
+
import type {
|
|
15
|
+
LiveScreenSnapshot,
|
|
16
|
+
RuntimeLayerSnapshot,
|
|
17
|
+
SelectedCanvasLayerSnapshot,
|
|
18
|
+
} from "@/pages/design-editor/command-types";
|
|
19
|
+
import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
|
|
20
|
+
import { shouldUseRuntimeLayerProjection } from "@/pages/design-editor/pending-edits";
|
|
21
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
22
|
+
|
|
23
|
+
export interface GetSelectedLayerSnapshotsArgs {
|
|
24
|
+
activeFile: DesignFile;
|
|
25
|
+
designSourceType: "inline" | "localhost" | "fusion";
|
|
26
|
+
files: DesignFile[];
|
|
27
|
+
getFreshActiveContent: () => string;
|
|
28
|
+
getScreenContent: (screenId: string) => string;
|
|
29
|
+
liveScreenSnapshotsById: Record<string, LiveScreenSnapshot>;
|
|
30
|
+
overviewScreens: OverviewScreen[];
|
|
31
|
+
runtimeLayerSnapshotsById: Record<string, RuntimeLayerSnapshot>;
|
|
32
|
+
selectedElement: ElementInfo | null;
|
|
33
|
+
selectedElementLayerId: string | null;
|
|
34
|
+
selectedLayerIdsState: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function runGetSelectedLayerSnapshots({
|
|
38
|
+
activeFile,
|
|
39
|
+
designSourceType,
|
|
40
|
+
files,
|
|
41
|
+
getFreshActiveContent,
|
|
42
|
+
getScreenContent,
|
|
43
|
+
liveScreenSnapshotsById,
|
|
44
|
+
overviewScreens,
|
|
45
|
+
runtimeLayerSnapshotsById,
|
|
46
|
+
selectedElement,
|
|
47
|
+
selectedElementLayerId,
|
|
48
|
+
selectedLayerIdsState,
|
|
49
|
+
}: GetSelectedLayerSnapshotsArgs) {
|
|
50
|
+
const fileIds = new Set(files.map((file) => file.id));
|
|
51
|
+
const candidateIds = selectedLayerIdsState.filter(
|
|
52
|
+
(layerId) => layerId && !layerId.startsWith("__") && !fileIds.has(layerId),
|
|
53
|
+
);
|
|
54
|
+
if (
|
|
55
|
+
selectedElementLayerId &&
|
|
56
|
+
!candidateIds.includes(selectedElementLayerId)
|
|
57
|
+
) {
|
|
58
|
+
candidateIds.push(selectedElementLayerId);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const snapshots: SelectedCanvasLayerSnapshot[] = [];
|
|
62
|
+
for (const file of files) {
|
|
63
|
+
// A hydrated localhost app has two snapshots: `/snapshot` is the source
|
|
64
|
+
// or SSR shell, while the runtime layer snapshot is the DOM the user can
|
|
65
|
+
// actually see and select. Layers already prefers that rendered tree, so
|
|
66
|
+
// Copy must resolve against the same id namespace or client-rendered
|
|
67
|
+
// React/Vue/Svelte nodes silently produce an empty clipboard.
|
|
68
|
+
const runtimeProjectionEligible = shouldUseRuntimeLayerProjection({
|
|
69
|
+
screen: overviewScreens.find((screen) => screen.id === file.id),
|
|
70
|
+
fallbackSourceType: designSourceType,
|
|
71
|
+
content: file.content ?? "",
|
|
72
|
+
});
|
|
73
|
+
const runtimeSnapshot = runtimeProjectionEligible
|
|
74
|
+
? runtimeLayerSnapshotsById[file.id]
|
|
75
|
+
: undefined;
|
|
76
|
+
const content = resolveClipboardLayerSourceHtml({
|
|
77
|
+
runtimeProjectionEligible,
|
|
78
|
+
runtimeSnapshot,
|
|
79
|
+
liveSnapshotHtml: liveScreenSnapshotsById[file.id]?.html,
|
|
80
|
+
storedContent: getScreenContent(file.id),
|
|
81
|
+
});
|
|
82
|
+
if (!content) continue;
|
|
83
|
+
const projection = buildCodeLayerProjection(content);
|
|
84
|
+
const tree = buildCodeLayerTree(projection);
|
|
85
|
+
for (const layerId of candidateIds) {
|
|
86
|
+
const node = projection.nodes.find(
|
|
87
|
+
(candidate) =>
|
|
88
|
+
candidate.id === layerId ||
|
|
89
|
+
candidate.dataAttributes["data-agent-native-node-id"] === layerId,
|
|
90
|
+
);
|
|
91
|
+
if (!node?.source) continue;
|
|
92
|
+
const html = content.slice(node.source.start, node.source.end);
|
|
93
|
+
const portableStyleSnapshot =
|
|
94
|
+
selectedElementLayerId &&
|
|
95
|
+
node.id === selectedElementLayerId &&
|
|
96
|
+
selectedElement?.portableStyleSnapshot
|
|
97
|
+
? selectedElement.portableStyleSnapshot
|
|
98
|
+
: undefined;
|
|
99
|
+
snapshots.push({
|
|
100
|
+
html,
|
|
101
|
+
rootNodeId: node.dataAttributes["data-agent-native-node-id"] ?? node.id,
|
|
102
|
+
sourceFileId: file.id,
|
|
103
|
+
portableStyleSnapshot,
|
|
104
|
+
managedStyleSnapshot: extractDesignClipboardManagedStyles(
|
|
105
|
+
content,
|
|
106
|
+
html,
|
|
107
|
+
),
|
|
108
|
+
node,
|
|
109
|
+
sourceIndex: node.source.start,
|
|
110
|
+
tree,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (snapshots.length === 0 && activeFile && selectedElement?.selector) {
|
|
116
|
+
const runtimeProjectionEligible = shouldUseRuntimeLayerProjection({
|
|
117
|
+
screen: overviewScreens.find((screen) => screen.id === activeFile.id),
|
|
118
|
+
fallbackSourceType: designSourceType,
|
|
119
|
+
content: activeFile.content ?? "",
|
|
120
|
+
});
|
|
121
|
+
const runtimeSnapshot = runtimeProjectionEligible
|
|
122
|
+
? runtimeLayerSnapshotsById[activeFile.id]
|
|
123
|
+
: undefined;
|
|
124
|
+
const content = resolveClipboardLayerSourceHtml({
|
|
125
|
+
runtimeProjectionEligible,
|
|
126
|
+
runtimeSnapshot,
|
|
127
|
+
liveSnapshotHtml: liveScreenSnapshotsById[activeFile.id]?.html,
|
|
128
|
+
storedContent: getFreshActiveContent(),
|
|
129
|
+
});
|
|
130
|
+
const projection = buildCodeLayerProjection(content);
|
|
131
|
+
const tree = buildCodeLayerTree(projection);
|
|
132
|
+
const node = resolveCodeLayerNodeFromElementInfo(
|
|
133
|
+
projection,
|
|
134
|
+
selectedElement,
|
|
135
|
+
);
|
|
136
|
+
const html = node?.source
|
|
137
|
+
? content.slice(node.source.start, node.source.end)
|
|
138
|
+
: getElementOuterHtml(content, selectedElement.selector);
|
|
139
|
+
if (html && node) {
|
|
140
|
+
snapshots.push({
|
|
141
|
+
html,
|
|
142
|
+
rootNodeId:
|
|
143
|
+
node.dataAttributes["data-agent-native-node-id"] ??
|
|
144
|
+
selectedElement.sourceId ??
|
|
145
|
+
selectedElement.id,
|
|
146
|
+
sourceFileId: activeFile.id,
|
|
147
|
+
portableStyleSnapshot: selectedElement.portableStyleSnapshot,
|
|
148
|
+
managedStyleSnapshot: extractDesignClipboardManagedStyles(
|
|
149
|
+
content,
|
|
150
|
+
html,
|
|
151
|
+
),
|
|
152
|
+
node,
|
|
153
|
+
sourceIndex: node.source?.start ?? Number.MAX_SAFE_INTEGER,
|
|
154
|
+
tree,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const selectedKeys = new Set(
|
|
160
|
+
snapshots.map((snapshot) => `${snapshot.sourceFileId}:${snapshot.node.id}`),
|
|
161
|
+
);
|
|
162
|
+
const topLevelSnapshots = snapshots.filter(
|
|
163
|
+
(snapshot) =>
|
|
164
|
+
!collectCodeLayerAncestors(snapshot.tree, snapshot.node.id).some(
|
|
165
|
+
(ancestorId) =>
|
|
166
|
+
selectedKeys.has(`${snapshot.sourceFileId}:${ancestorId}`),
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
const fileOrder = new Map(files.map((file, index) => [file.id, index]));
|
|
170
|
+
return topLevelSnapshots.sort((a, b) => {
|
|
171
|
+
const fileDelta =
|
|
172
|
+
(fileOrder.get(a.sourceFileId) ?? 0) -
|
|
173
|
+
(fileOrder.get(b.sourceFileId) ?? 0);
|
|
174
|
+
if (fileDelta !== 0) return fileDelta;
|
|
175
|
+
return a.sourceIndex - b.sourceIndex;
|
|
176
|
+
});
|
|
177
|
+
}
|