@agent-native/core 0.82.0 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +34 -0
- package/corpus/core/README.md +2 -2
- package/corpus/core/docs/content/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/ar-SA/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/de-DE/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/es-ES/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/fr-FR/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/hi-IN/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/ja-JP/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/ko-KR/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/pt-BR/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-CN/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-TW/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/template-chat.mdx +1 -1
- package/corpus/core/docs/content/template-chat.mdx +1 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builtin.ts +1 -1
- package/corpus/core/src/agent/engine/output-tokens.ts +1 -1
- package/corpus/core/src/agent/harness/acp-adapter.ts +28 -4
- package/corpus/core/src/agent/harness/ai-sdk-adapter.ts +3 -0
- package/corpus/core/src/agent/harness/translate.ts +3 -0
- package/corpus/core/src/agent/harness/types.ts +1 -0
- package/corpus/core/src/agent/model-config.ts +34 -13
- package/corpus/core/src/agent/production-agent.ts +87 -10
- package/corpus/core/src/agent/thread-data-builder.ts +29 -4
- package/corpus/core/src/agent/tool-call-journal.ts +109 -48
- package/corpus/core/src/agent/types.ts +6 -1
- package/corpus/core/src/cli/design-connect.ts +106 -0
- package/corpus/core/src/cli/gateway-helpers.ts +17 -0
- package/corpus/core/src/cli/recap.ts +0 -2
- package/corpus/core/src/cli/skills.ts +54 -16
- package/corpus/core/src/cli/workspace-dev.ts +29 -5
- package/corpus/core/src/client/AssistantChat.tsx +89 -42
- package/corpus/core/src/client/CommandMenu.tsx +34 -12
- package/corpus/core/src/client/DefaultSpinner.tsx +8 -1
- package/corpus/core/src/client/chat/message-components.tsx +49 -1
- package/corpus/core/src/client/chat/repo-helpers.ts +71 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +15 -5
- package/corpus/core/src/client/components/ApiKeySettings.tsx +8 -8
- package/corpus/core/src/client/components/CodeRequiredDialog.tsx +26 -15
- package/corpus/core/src/client/components/MissingKeyCard.tsx +13 -6
- package/corpus/core/src/client/composer/TiptapComposer.tsx +4 -3
- package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +0 -1
- package/corpus/core/src/client/guided-questions.tsx +21 -27
- package/corpus/core/src/client/integrations/IntegrationCard.tsx +1 -1
- package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +1 -1
- package/corpus/core/src/client/onboarding/OnboardingBanner.tsx +2 -2
- package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +23 -19
- package/corpus/core/src/client/resources/ResourceEditor.tsx +18 -10
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +5 -1
- package/corpus/core/src/client/settings/SettingsPanel.tsx +8 -3
- package/corpus/core/src/client/settings/useBuilderStatus.ts +8 -41
- package/corpus/core/src/client/useProductionAgent.ts +6 -2
- package/corpus/core/src/deploy/build.ts +1 -1
- package/corpus/core/src/observability/traces.ts +3 -3
- package/corpus/core/src/onboarding/default-steps.ts +1 -1
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +177 -81
- package/corpus/core/src/server/core-routes-plugin.ts +7 -1
- package/corpus/core/src/server/open-route.ts +59 -40
- package/corpus/core/src/shared/reasoning-effort.ts +2 -0
- package/corpus/core/src/sharing/access.ts +22 -2
- package/corpus/core/src/sharing/registry.ts +18 -0
- package/corpus/core/src/styles/agent-native.css +104 -20
- package/corpus/core/src/styles/rich-markdown-editor.css +11 -10
- package/corpus/templates/analytics/actions/hubspot-deals.ts +64 -4
- package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +10 -10
- package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +10 -10
- package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +6 -5
- package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +9 -9
- package/corpus/templates/analytics/app/global.css +4 -0
- package/corpus/templates/analytics/app/lib/chart-theme.ts +13 -0
- package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +22 -24
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-charts-now-use-theme-aware-tooltip-and-grid-colors.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-fixed-ask-composer-spacing-so-the-chat-field-has-a-comfortab.md +6 -0
- package/corpus/templates/calendar/AGENTS.md +4 -0
- package/corpus/templates/calendar/actions/connect-google-calendar.ts +80 -0
- package/corpus/templates/calendar/changelog/2026-06-30-calendar-now-gives-the-agent-a-safe-google-connection-link-i.md +6 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/content/.env.local.example +6 -0
- package/corpus/templates/content/AGENTS.md +10 -1
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +14 -1
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +43 -0
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +79 -7
- package/corpus/templates/content/actions/_database-source-utils.ts +987 -22
- package/corpus/templates/content/actions/_database-utils.ts +41 -0
- package/corpus/templates/content/actions/_local-file-documents.ts +74 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +33 -8
- package/corpus/templates/content/actions/change-content-database-source-role.ts +14 -0
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +3 -0
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +71 -3
- package/corpus/templates/content/actions/list-content-databases.ts +41 -33
- package/corpus/templates/content/actions/process-builder-body-hydration.ts +47 -0
- package/corpus/templates/content/actions/update-document.ts +93 -0
- package/corpus/templates/content/app/blocks/SourceComponentBlock.tsx +277 -0
- package/corpus/templates/content/app/blocks/contentBlockRegistry.tsx +2 -0
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +5 -5
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +45 -4
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +5 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +92 -0
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +5 -3
- package/corpus/templates/content/app/components/editor/extensions/registryBlocks.ts +41 -0
- package/corpus/templates/content/app/components/editor/registrySlashItems.ts +4 -0
- package/corpus/templates/content/app/global.css +16 -8
- package/corpus/templates/content/app/hooks/use-content-database.ts +24 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +24 -0
- package/corpus/templates/content/app/i18n-data.ts +212 -0
- package/corpus/templates/content/app/lib/content-command-search.ts +64 -0
- package/corpus/templates/content/app/root.tsx +236 -8
- package/corpus/templates/content/changelog/2026-06-29-builder-cms-sources-now-sync-article-bodies-through-content.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-builder-article-media-now-render-as-images-and-embeds-when-r.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-builder-source-components-now-render-as-preserved-preview-bl.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-cmd-k-search-now-opens-from-the-editor-and-finds-real-content.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-editor-floating-toolbars-now-use-theme-aware-colors-in-light.md +6 -0
- package/corpus/templates/content/package.json +3 -1
- package/corpus/templates/content/scripts/check-native-deps.mjs +57 -0
- package/corpus/templates/content/scripts/dev-database.mjs +83 -0
- package/corpus/templates/content/scripts/seed-demo-user.mjs +107 -0
- package/corpus/templates/content/server/db/schema.ts +27 -0
- package/corpus/templates/content/server/plugins/db.ts +33 -0
- package/corpus/templates/content/shared/api.ts +45 -0
- package/corpus/templates/content/shared/builder-mdx.ts +905 -5
- package/corpus/templates/content/shared/nfm-registry.ts +2 -0
- package/corpus/templates/content/shared/source-component-block.ts +141 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +6 -1
- package/corpus/templates/design/actions/add-localhost-screens.ts +63 -8
- package/corpus/templates/design/actions/apply-motion-edit.ts +12 -46
- package/corpus/templates/design/actions/list-design-native-assets.ts +2 -1
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +49 -1
- package/corpus/templates/design/actions/navigate.ts +3 -12
- package/corpus/templates/design/actions/open-visual-edit.ts +341 -0
- package/corpus/templates/design/actions/view-screen.ts +1 -1
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +1 -1
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +350 -15
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +71 -91
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +25 -27
- package/corpus/templates/design/app/components/design/DeviceFrame.tsx +1 -1
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +2 -2
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1111 -570
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +162 -32
- package/corpus/templates/design/app/components/design/MotionDock.tsx +268 -240
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1193 -207
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +1 -1
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +81 -102
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +6 -6
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +11 -11
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +9 -7
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1225 -154
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +130 -37
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +24 -11
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +184 -162
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +137 -79
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +13 -13
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +8 -8
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +2 -2
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +4 -4
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +3 -3
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -7
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +14 -11
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +4 -4
- package/corpus/templates/design/app/components/design/inspector/index.ts +2 -0
- package/corpus/templates/design/app/components/design/types.ts +19 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +61 -78
- package/corpus/templates/design/app/components/layout/Header.tsx +0 -1
- package/corpus/templates/design/app/components/layout/Layout.tsx +3 -2
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +0 -2
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +5 -4
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +2 -2
- package/corpus/templates/design/app/global.css +56 -0
- package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -5
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +6 -7
- package/corpus/templates/design/app/hooks/use-question-flow.ts +3 -2
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +6 -4
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +6 -3
- package/corpus/templates/design/app/i18n/zh-TW.ts +5 -14
- package/corpus/templates/design/app/i18n-data.ts +63 -188
- package/corpus/templates/design/app/lib/agent-chat.ts +13 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +1755 -401
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +9 -5
- package/corpus/templates/design/app/pages/DesignSystems.tsx +2 -2
- package/corpus/templates/design/app/pages/Index.tsx +49 -26
- package/corpus/templates/design/app/pages/NotFound.tsx +25 -8
- package/corpus/templates/design/app/pages/VisualEdit.tsx +17 -5
- package/corpus/templates/design/app/routes/examples.tsx +1 -7
- package/corpus/templates/design/app/routes/templates.tsx +7 -4
- package/corpus/templates/design/changelog/2026-06-30-board-shapes-and-auto-layout-controls-are-clearer.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-canvas-editing-now-keeps-undo-redo-and-cross-screen-layer-mo.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-selections-now-use-purple-component-chrome-and-sho.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-constraints-now-stay-tucked-into-a-compact-inspector-preview.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-creating-a-component-now-opens-in-a-compact-inspector-popove.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-cards-no-longer-show-legacy-project-type-badges.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-chat-now-keeps-new-turns-in-the-visible-design-agent-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-editor-panels-now-keep-their-compact-text-sizing-cons.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-no-longer-shows-the-connected-local-code-warning-bann.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-opens-to-the-files-panel-unless-a-generation-is-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-opens-with-a-cleaner-empty-canvas-and-loading-preview.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-tools-use-simpler-less-decorative-icons.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-dragging-absolute-elements-no-longer-shows-coordinate-labels.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-all-screens-opening-too-zoomed-in-by-default.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-the-public-design-assets-sidebar-so-native-components-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-the-screen-overview-board-so-empty-board-space-no-long.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-full-view-screens-can-now-scroll-while-remaining-editable.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-inspector-element-headers-show-the-selected-html-tag-and-the.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-inspector-inputs-stay-compact-across-desktop-breakpoints.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-layers-can-be-multi-selected-from-the-canvas-and-edited-toge.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-local-visual-edit-links-can-edit-localhost-frames-without-si.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-localhost-visual-edit-frames-can-now-be-selected-and-edited.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-marquee-selection-now-works-inside-screens-multi-selected-la.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-is-now-available-from-the-persistent-left-rail-and-th.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-timeline-collapse-and-reopen-animations-feel-smoother.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-board-shapes-now-start-with-neutral-gray-styling.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-designs-open-into-a-steadier-loading-state-before-the-ed.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-designs-show-a-loading-state-during-handoff-and-keep-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-public-visual-edit-design-links-now-open-directly-instead-of.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-removed-the-confusing-states-section-from-the-design-editor-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-removed-the-templates-gallery-from-design-navigation.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-stays-visible-as-soon-as-editable-designs-load.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-signed-out-save-and-share-controls-use-simpler-labels-and-op.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-text-added-on-the-canvas-now-focuses-cleanly-with-the-right-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-is-simpler-and-its-bottom.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-now-follows-the-current-t.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-child-layout-controls-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-the-ask-ai-section-abov.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-review-panel-no-longer-shows-a-separate-inline-au.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-empty-designs-page-now-focuses-on-a-single-primary-new-d.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-layers-panel-header-stays-as-layers-while-multiple-items.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-missing-design-screen-now-uses-a-neutral-back-button.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-now-closes-without-squeezing-the-canvas-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-new-design-connect-ai-setup-now-lines-up-with-the-compos.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-screen-overview-board-now-blends-into-the-canvas-and-new.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-drops-now-show-screen-guides.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-keep-a-transparent-backdrop-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-select-reliably-from-all-screens.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-edit-can-open-and-refresh-localhost-screens-in-one-au.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +15 -4
- package/corpus/templates/design/server/db/index.ts +52 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/design/server/plugins/auth.ts +1 -0
- package/corpus/templates/design/server/plugins/core-routes.ts +4 -0
- package/corpus/templates/design/shared/board-file.ts +380 -5
- package/corpus/templates/design/shared/board-objects.ts +2 -2
- package/corpus/templates/design/shared/code-layer.ts +50 -6
- package/corpus/templates/design/shared/motion-compiler.ts +27 -0
- package/corpus/templates/macros/app/components/DailyProgress.tsx +41 -37
- package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
- package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
- package/corpus/templates/macros/app/components/VoiceDictation.tsx +1 -1
- package/corpus/templates/macros/app/components/WeeklyCaloriesChart.tsx +1 -1
- package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
- package/corpus/templates/macros/app/components/WeightTracker.tsx +1 -1
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -1
- package/corpus/templates/macros/app/routes/_index.tsx +5 -5
- package/corpus/templates/macros/app/routes/analytics.tsx +1 -1
- package/corpus/templates/macros/changelog/2026-06-30-dashboard-loading-and-chart-surfaces-now-use-theme-aware-col.md +6 -0
- package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +14 -11
- package/corpus/templates/mail/app/global.css +3 -2
- package/corpus/templates/mail/changelog/2026-06-30-compose-floating-toolbars-now-use-theme-aware-colors-in-ligh.md +6 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +2 -2
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +3 -3
- package/corpus/templates/slides/app/global.css +10 -0
- package/corpus/templates/slides/app/pages/Index.tsx +55 -51
- package/corpus/templates/slides/changelog/2026-06-30-deck-thumbnails-collapse-cleanly-to-one-column-when-the-chat.md +6 -0
- package/dist/agent/engine/anthropic-engine.d.ts +2 -2
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts +2 -2
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builtin.js +1 -1
- package/dist/agent/engine/builtin.js.map +1 -1
- package/dist/agent/engine/output-tokens.js +1 -1
- package/dist/agent/engine/output-tokens.js.map +1 -1
- package/dist/agent/harness/acp-adapter.d.ts +4 -1
- package/dist/agent/harness/acp-adapter.d.ts.map +1 -1
- package/dist/agent/harness/acp-adapter.js +19 -2
- package/dist/agent/harness/acp-adapter.js.map +1 -1
- package/dist/agent/harness/ai-sdk-adapter.d.ts.map +1 -1
- package/dist/agent/harness/ai-sdk-adapter.js +3 -0
- package/dist/agent/harness/ai-sdk-adapter.js.map +1 -1
- package/dist/agent/harness/translate.d.ts.map +1 -1
- package/dist/agent/harness/translate.js +3 -0
- package/dist/agent/harness/translate.js.map +1 -1
- package/dist/agent/harness/types.d.ts +1 -0
- package/dist/agent/harness/types.d.ts.map +1 -1
- package/dist/agent/harness/types.js.map +1 -1
- package/dist/agent/model-config.d.ts +21 -20
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +31 -13
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +85 -10
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +27 -6
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/agent/tool-call-journal.d.ts +11 -9
- package/dist/agent/tool-call-journal.d.ts.map +1 -1
- package/dist/agent/tool-call-journal.js +92 -40
- package/dist/agent/tool-call-journal.js.map +1 -1
- package/dist/agent/types.d.ts +5 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +84 -0
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/gateway-helpers.d.ts +4 -0
- package/dist/cli/gateway-helpers.d.ts.map +1 -1
- package/dist/cli/gateway-helpers.js +9 -0
- package/dist/cli/gateway-helpers.js.map +1 -1
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +0 -2
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +54 -16
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/workspace-dev.d.ts.map +1 -1
- package/dist/cli/workspace-dev.js +33 -6
- package/dist/cli/workspace-dev.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +72 -26
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/CommandMenu.d.ts +6 -2
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +25 -14
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/DefaultSpinner.d.ts.map +1 -1
- package/dist/client/DefaultSpinner.js +8 -1
- package/dist/client/DefaultSpinner.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +7 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +37 -1
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/repo-helpers.d.ts +1 -0
- package/dist/client/chat/repo-helpers.d.ts.map +1 -1
- package/dist/client/chat/repo-helpers.js +59 -0
- package/dist/client/chat/repo-helpers.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +4 -4
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/components/ApiKeySettings.js +8 -8
- package/dist/client/components/ApiKeySettings.js.map +1 -1
- package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
- package/dist/client/components/CodeRequiredDialog.js +24 -15
- package/dist/client/components/CodeRequiredDialog.js.map +1 -1
- package/dist/client/components/MissingKeyCard.d.ts.map +1 -1
- package/dist/client/components/MissingKeyCard.js +11 -6
- package/dist/client/components/MissingKeyCard.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +4 -3
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +6 -6
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/integrations/IntegrationCard.js +1 -1
- package/dist/client/integrations/IntegrationCard.js.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.js +1 -1
- package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
- package/dist/client/onboarding/OnboardingBanner.js +2 -2
- package/dist/client/onboarding/OnboardingBanner.js.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.js +23 -19
- package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
- package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
- package/dist/client/resources/ResourceEditor.js +13 -10
- package/dist/client/resources/ResourceEditor.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +2 -1
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +8 -3
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +6 -38
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/useProductionAgent.d.ts +2 -1
- package/dist/client/useProductionAgent.d.ts.map +1 -1
- package/dist/client/useProductionAgent.js.map +1 -1
- package/dist/deploy/build.js +1 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js.map +1 -1
- package/dist/onboarding/default-steps.js +1 -1
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +3 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +134 -72
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +3 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +4 -1
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/open-route.d.ts +9 -0
- package/dist/server/open-route.d.ts.map +1 -1
- package/dist/server/open-route.js +48 -38
- package/dist/server/open-route.js.map +1 -1
- package/dist/shared/reasoning-effort.js +3 -0
- package/dist/shared/reasoning-effort.js.map +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +18 -2
- package/dist/sharing/access.js.map +1 -1
- package/dist/sharing/registry.d.ts +10 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +1 -1
- package/dist/styles/agent-native.css +104 -20
- package/dist/styles/rich-markdown-editor.css +11 -10
- package/docs/content/cloneable-saas.mdx +2 -2
- package/docs/content/faq.mdx +1 -1
- package/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
- package/docs/content/locales/ar-SA/faq.mdx +1 -1
- package/docs/content/locales/ar-SA/template-chat.mdx +1 -1
- package/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
- package/docs/content/locales/de-DE/faq.mdx +1 -1
- package/docs/content/locales/de-DE/template-chat.mdx +1 -1
- package/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
- package/docs/content/locales/es-ES/faq.mdx +1 -1
- package/docs/content/locales/es-ES/template-chat.mdx +1 -1
- package/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
- package/docs/content/locales/fr-FR/faq.mdx +1 -1
- package/docs/content/locales/fr-FR/template-chat.mdx +1 -1
- package/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
- package/docs/content/locales/hi-IN/faq.mdx +1 -1
- package/docs/content/locales/hi-IN/template-chat.mdx +1 -1
- package/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
- package/docs/content/locales/ja-JP/faq.mdx +1 -1
- package/docs/content/locales/ja-JP/template-chat.mdx +1 -1
- package/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
- package/docs/content/locales/ko-KR/faq.mdx +1 -1
- package/docs/content/locales/ko-KR/template-chat.mdx +1 -1
- package/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
- package/docs/content/locales/pt-BR/faq.mdx +1 -1
- package/docs/content/locales/pt-BR/template-chat.mdx +1 -1
- package/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
- package/docs/content/locales/zh-CN/faq.mdx +1 -1
- package/docs/content/locales/zh-CN/template-chat.mdx +1 -1
- package/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
- package/docs/content/locales/zh-TW/faq.mdx +1 -1
- package/docs/content/locales/zh-TW/template-chat.mdx +1 -1
- package/docs/content/template-chat.mdx +1 -1
- package/package.json +1 -1
- package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +0 -157
- package/corpus/templates/design/app/lib/design-templates.ts +0 -1305
- package/corpus/templates/design/app/pages/Templates.tsx +0 -279
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
appBasePath,
|
|
17
17
|
ensureEmbedAuthFetchInterceptor,
|
|
18
18
|
isEmbedAuthActive,
|
|
19
|
-
sendToAgentChat,
|
|
20
19
|
getBrowserTabId,
|
|
21
20
|
readClientAppState,
|
|
22
21
|
setClientAppState,
|
|
@@ -58,6 +57,10 @@ import {
|
|
|
58
57
|
hasCapability,
|
|
59
58
|
} from "@shared/design-source-capabilities";
|
|
60
59
|
import { shouldUseLiveFileContent } from "@shared/html-content";
|
|
60
|
+
import {
|
|
61
|
+
compile as compileMotionTimeline,
|
|
62
|
+
injectManagedMotionCss,
|
|
63
|
+
} from "@shared/motion-compiler";
|
|
61
64
|
import type { MotionTrack } from "@shared/motion-timeline";
|
|
62
65
|
import {
|
|
63
66
|
resolveTweaksToCssVars,
|
|
@@ -90,7 +93,7 @@ import {
|
|
|
90
93
|
IconPhoto,
|
|
91
94
|
IconRefresh,
|
|
92
95
|
IconChevronDown,
|
|
93
|
-
|
|
96
|
+
IconChevronUp,
|
|
94
97
|
IconCheck,
|
|
95
98
|
IconPointer,
|
|
96
99
|
IconTypography,
|
|
@@ -165,7 +168,6 @@ import {
|
|
|
165
168
|
LocalhostWriteConsentDialog,
|
|
166
169
|
type LocalhostWriteConsentPayload,
|
|
167
170
|
} from "@/components/design/LocalhostWriteConsentDialog";
|
|
168
|
-
import { LocalSourceEditBanner } from "@/components/design/LocalSourceEditBanner";
|
|
169
171
|
import {
|
|
170
172
|
MotionDock,
|
|
171
173
|
type MotionDockTrack,
|
|
@@ -173,13 +175,18 @@ import {
|
|
|
173
175
|
import {
|
|
174
176
|
MultiScreenCanvas,
|
|
175
177
|
OVERVIEW_FRAME_WIDTH,
|
|
178
|
+
type CanvasLayerMarqueeSelection,
|
|
176
179
|
type CanvasPrimitiveInsert,
|
|
177
180
|
type FrameGeometry,
|
|
178
181
|
} from "@/components/design/MultiScreenCanvas";
|
|
179
182
|
import { QuestionFlow } from "@/components/design/QuestionFlow";
|
|
180
183
|
import type { ReviewPanelProps } from "@/components/design/ReviewPanel";
|
|
181
184
|
import { TokensPanel } from "@/components/design/TokensPanel";
|
|
182
|
-
import type {
|
|
185
|
+
import type {
|
|
186
|
+
ElementInfo,
|
|
187
|
+
ElementSelectionIntent,
|
|
188
|
+
DeviceFrameType,
|
|
189
|
+
} from "@/components/design/types";
|
|
183
190
|
import {
|
|
184
191
|
DEVICE_FRAME_VIEWPORTS,
|
|
185
192
|
ZOOM_PRESETS,
|
|
@@ -232,7 +239,14 @@ import {
|
|
|
232
239
|
type DesignEditorCommand,
|
|
233
240
|
} from "@/hooks/use-navigation-state";
|
|
234
241
|
import { useQuestionFlow } from "@/hooks/use-question-flow";
|
|
235
|
-
import {
|
|
242
|
+
import {
|
|
243
|
+
isDesignHotkeyEditableTarget,
|
|
244
|
+
useDesignHotkeys,
|
|
245
|
+
} from "@/hooks/useDesignHotkeys";
|
|
246
|
+
import {
|
|
247
|
+
DESIGN_CHAT_STORAGE_KEY,
|
|
248
|
+
sendToDesignAgentChat,
|
|
249
|
+
} from "@/lib/agent-chat";
|
|
236
250
|
import {
|
|
237
251
|
clearPendingGeneration,
|
|
238
252
|
hasFreshPendingGeneration,
|
|
@@ -265,12 +279,16 @@ const STORED_RUN_LIVENESS_GRACE_MS = 20_000;
|
|
|
265
279
|
const MAX_DESIGN_UNDO_STACK = 50;
|
|
266
280
|
const OVERVIEW_ZOOM_THRESHOLD = 60;
|
|
267
281
|
const MOTION_DOCK_TRANSITION_MS = 200;
|
|
282
|
+
const MOTION_DOCK_EXIT_SETTLE_MS = 80;
|
|
283
|
+
const MOTION_DOCK_EXIT_FALLBACK_MS = MOTION_DOCK_TRANSITION_MS * 2 + 600;
|
|
268
284
|
const MOTION_AUTOSAVE_DELAY_MS = 500;
|
|
285
|
+
const BOARD_SURFACE_SIZE = 131_072;
|
|
269
286
|
/** Extensions that the localhost bridge allows to be written back to source. */
|
|
270
287
|
const LOCALHOST_WRITE_EXTENSIONS = new Set([".html", ".htm", ".css"]);
|
|
271
288
|
const NO_LOCALHOST_WRITE_CONTENT_MESSAGE =
|
|
272
289
|
"No content to write. Open the screen first." /* i18n-ignore */;
|
|
273
290
|
const FOCUSED_SCREEN_ZOOM = 100;
|
|
291
|
+
export const DEFAULT_OVERVIEW_ZOOM = 60;
|
|
274
292
|
const KEEPALIVE_FILE_SAVE_MAX_BYTES = 60_000;
|
|
275
293
|
const UNSUPPORTED_HTML2CANVAS_COLOR_RE =
|
|
276
294
|
/\b(?:color|color-mix|oklch|oklab|lab|lch)\(/i;
|
|
@@ -293,6 +311,14 @@ const HTML2CANVAS_UNSUPPORTED_VALUE_PROPERTIES = [
|
|
|
293
311
|
"list-style-image",
|
|
294
312
|
] as const;
|
|
295
313
|
|
|
314
|
+
function blurActiveDesignEditableTarget() {
|
|
315
|
+
if (typeof document === "undefined") return;
|
|
316
|
+
const active = document.activeElement;
|
|
317
|
+
if (active instanceof HTMLElement && isDesignHotkeyEditableTarget(active)) {
|
|
318
|
+
active.blur();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
296
322
|
function getContentSignature(content: string): string {
|
|
297
323
|
let hash = 2166136261;
|
|
298
324
|
for (let index = 0; index < content.length; index += 1) {
|
|
@@ -302,6 +328,10 @@ function getContentSignature(content: string): string {
|
|
|
302
328
|
return `${content.length}:${hash.toString(36)}`;
|
|
303
329
|
}
|
|
304
330
|
|
|
331
|
+
function dedupeStringIds(ids: string[]): string[] {
|
|
332
|
+
return Array.from(new Set(ids.filter(Boolean)));
|
|
333
|
+
}
|
|
334
|
+
|
|
305
335
|
export function isScreenRootElementInfo(info: ElementInfo | null | undefined) {
|
|
306
336
|
const tagName = info?.tagName?.toUpperCase();
|
|
307
337
|
return tagName === "BODY" || tagName === "HTML";
|
|
@@ -369,14 +399,26 @@ export function getSidebarCodeLayerSelectionState(args: {
|
|
|
369
399
|
currentViewMode: "single" | "overview";
|
|
370
400
|
ownerFileId?: string | null;
|
|
371
401
|
overviewSelectedScreenIds: string[];
|
|
402
|
+
screenFileIds?: string[];
|
|
372
403
|
}) {
|
|
373
|
-
const {
|
|
404
|
+
const {
|
|
405
|
+
currentViewMode,
|
|
406
|
+
ownerFileId,
|
|
407
|
+
overviewSelectedScreenIds,
|
|
408
|
+
screenFileIds,
|
|
409
|
+
} = args;
|
|
410
|
+
const ownerScreenId =
|
|
411
|
+
ownerFileId && (!screenFileIds || screenFileIds.includes(ownerFileId))
|
|
412
|
+
? ownerFileId
|
|
413
|
+
: null;
|
|
374
414
|
return {
|
|
375
415
|
viewMode: currentViewMode,
|
|
376
416
|
overviewSelectedScreenIds:
|
|
377
|
-
currentViewMode === "overview" &&
|
|
378
|
-
? [
|
|
379
|
-
:
|
|
417
|
+
currentViewMode === "overview" && ownerScreenId
|
|
418
|
+
? [ownerScreenId]
|
|
419
|
+
: currentViewMode === "overview" && ownerFileId
|
|
420
|
+
? []
|
|
421
|
+
: overviewSelectedScreenIds,
|
|
380
422
|
};
|
|
381
423
|
}
|
|
382
424
|
|
|
@@ -411,6 +453,10 @@ export function getOverviewCanvasZoom(
|
|
|
411
453
|
return displayZoom / scale;
|
|
412
454
|
}
|
|
413
455
|
|
|
456
|
+
export function getDefaultOverviewCanvasZoom(overviewZoomScale: number) {
|
|
457
|
+
return getOverviewCanvasZoom(DEFAULT_OVERVIEW_ZOOM, overviewZoomScale);
|
|
458
|
+
}
|
|
459
|
+
|
|
414
460
|
export function getDesignEditorShareUrl(
|
|
415
461
|
id: string,
|
|
416
462
|
origin: string,
|
|
@@ -423,6 +469,41 @@ export function getDesignEditorShareUrl(
|
|
|
423
469
|
return new URL(pathname, origin).toString();
|
|
424
470
|
}
|
|
425
471
|
|
|
472
|
+
function formatDesignEditorUrlZoom(zoom: number): string {
|
|
473
|
+
const rounded = Math.round(zoom * 100) / 100;
|
|
474
|
+
return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(2);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export function getDesignEditorStateUrlSearch(args: {
|
|
478
|
+
currentSearch: string;
|
|
479
|
+
viewMode: "single" | "overview";
|
|
480
|
+
screenId?: string | null;
|
|
481
|
+
selectionId?: string | null;
|
|
482
|
+
zoom?: number | null;
|
|
483
|
+
}) {
|
|
484
|
+
const params = new URLSearchParams(args.currentSearch);
|
|
485
|
+
params.set("view", args.viewMode);
|
|
486
|
+
if (args.screenId) {
|
|
487
|
+
params.set("screen", args.screenId);
|
|
488
|
+
} else {
|
|
489
|
+
params.delete("screen");
|
|
490
|
+
}
|
|
491
|
+
params.delete("fileId");
|
|
492
|
+
params.delete("filename");
|
|
493
|
+
if (args.selectionId) {
|
|
494
|
+
params.set("selection", args.selectionId);
|
|
495
|
+
} else {
|
|
496
|
+
params.delete("selection");
|
|
497
|
+
}
|
|
498
|
+
if (typeof args.zoom === "number" && Number.isFinite(args.zoom)) {
|
|
499
|
+
params.set("zoom", formatDesignEditorUrlZoom(args.zoom));
|
|
500
|
+
} else {
|
|
501
|
+
params.delete("zoom");
|
|
502
|
+
}
|
|
503
|
+
const query = params.toString();
|
|
504
|
+
return query ? `?${query}` : "";
|
|
505
|
+
}
|
|
506
|
+
|
|
426
507
|
export function getLocalhostRouteSourceFile(args: {
|
|
427
508
|
sourceFile?: string;
|
|
428
509
|
source?: string;
|
|
@@ -473,14 +554,25 @@ export function getFreshActiveFileContent(args: {
|
|
|
473
554
|
export function getFreshScreenContent(args: {
|
|
474
555
|
screenId: string;
|
|
475
556
|
activeFileId?: string | null;
|
|
557
|
+
freshActiveContentFileId?: string | null;
|
|
476
558
|
freshActiveContent: string;
|
|
477
559
|
fileContentById: ReadonlyMap<string, string>;
|
|
478
560
|
}) {
|
|
479
|
-
|
|
561
|
+
const freshActiveContentFileId =
|
|
562
|
+
args.freshActiveContentFileId ?? args.activeFileId;
|
|
563
|
+
return args.screenId === args.activeFileId &&
|
|
564
|
+
args.screenId === freshActiveContentFileId
|
|
480
565
|
? args.freshActiveContent
|
|
481
566
|
: (args.fileContentById.get(args.screenId) ?? "");
|
|
482
567
|
}
|
|
483
568
|
|
|
569
|
+
export function shouldReplacePreviewAfterVisualStyleCommit(args: {
|
|
570
|
+
runtimeApplied?: boolean;
|
|
571
|
+
runtimeStyleApplied: boolean;
|
|
572
|
+
}) {
|
|
573
|
+
return !args.runtimeApplied && !args.runtimeStyleApplied;
|
|
574
|
+
}
|
|
575
|
+
|
|
484
576
|
export function getLayerMoveIterationOrder<T>(
|
|
485
577
|
orderedIds: readonly T[],
|
|
486
578
|
placement: "before" | "after" | "inside",
|
|
@@ -495,25 +587,37 @@ export function removeUndoRedoOrderKind<T extends string>(
|
|
|
495
587
|
return order.filter((entry) => entry !== kind);
|
|
496
588
|
}
|
|
497
589
|
|
|
498
|
-
type UndoRedoOrderKind = "content" | "file-content" | "geometry";
|
|
590
|
+
export type UndoRedoOrderKind = "content" | "file-content" | "geometry";
|
|
499
591
|
|
|
500
|
-
function
|
|
501
|
-
|
|
502
|
-
):
|
|
503
|
-
|
|
592
|
+
export function getUndoRedoPriorityOrder(
|
|
593
|
+
preferred: UndoRedoOrderKind | undefined,
|
|
594
|
+
): UndoRedoOrderKind[] {
|
|
595
|
+
if (preferred === "geometry") return ["geometry", "content", "file-content"];
|
|
596
|
+
if (preferred === "file-content")
|
|
597
|
+
return ["file-content", "content", "geometry"];
|
|
598
|
+
return ["content", "file-content", "geometry"];
|
|
504
599
|
}
|
|
505
600
|
|
|
506
601
|
function resolveZoomUpdate(update: SetStateAction<number>, current: number) {
|
|
507
602
|
return typeof update === "function" ? update(current) : update;
|
|
508
603
|
}
|
|
509
604
|
|
|
510
|
-
export function
|
|
605
|
+
export function shouldLimitEditorChromeUntilContentReady(args: {
|
|
511
606
|
fileCount: number;
|
|
607
|
+
hasActiveCanvasContent: boolean;
|
|
512
608
|
generating: boolean;
|
|
513
609
|
pendingGenerationActive: boolean;
|
|
514
610
|
}) {
|
|
515
|
-
const {
|
|
516
|
-
|
|
611
|
+
const {
|
|
612
|
+
fileCount,
|
|
613
|
+
generating,
|
|
614
|
+
hasActiveCanvasContent,
|
|
615
|
+
pendingGenerationActive,
|
|
616
|
+
} = args;
|
|
617
|
+
return (
|
|
618
|
+
(fileCount === 0 || !hasActiveCanvasContent) &&
|
|
619
|
+
(generating || pendingGenerationActive)
|
|
620
|
+
);
|
|
517
621
|
}
|
|
518
622
|
|
|
519
623
|
export function shouldEscapeToOverview(args: {
|
|
@@ -755,6 +859,13 @@ interface DesignFile {
|
|
|
755
859
|
updatedAt: string;
|
|
756
860
|
}
|
|
757
861
|
|
|
862
|
+
interface LiveScreenSnapshot {
|
|
863
|
+
url: string;
|
|
864
|
+
html: string;
|
|
865
|
+
status?: number;
|
|
866
|
+
contentType?: string;
|
|
867
|
+
}
|
|
868
|
+
|
|
758
869
|
interface DesignData {
|
|
759
870
|
id: string;
|
|
760
871
|
title: string;
|
|
@@ -882,6 +993,32 @@ interface GeometryHistoryEntry {
|
|
|
882
993
|
after: CanvasFrameGeometryById;
|
|
883
994
|
}
|
|
884
995
|
|
|
996
|
+
function geometryHistoryEntryTouchesFrameIds(
|
|
997
|
+
entry: GeometryHistoryEntry,
|
|
998
|
+
frameIds: Set<string>,
|
|
999
|
+
) {
|
|
1000
|
+
for (const frameId of frameIds) {
|
|
1001
|
+
if (entry.before[frameId] || entry.after[frameId]) return true;
|
|
1002
|
+
}
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function removeRecentUndoRedoOrderKinds<T extends string>(
|
|
1007
|
+
order: T[],
|
|
1008
|
+
kind: T,
|
|
1009
|
+
count: number,
|
|
1010
|
+
): T[] {
|
|
1011
|
+
if (count <= 0) return order;
|
|
1012
|
+
const next = [...order];
|
|
1013
|
+
let remaining = count;
|
|
1014
|
+
for (let index = next.length - 1; index >= 0 && remaining > 0; index -= 1) {
|
|
1015
|
+
if (next[index] !== kind) continue;
|
|
1016
|
+
next.splice(index, 1);
|
|
1017
|
+
remaining -= 1;
|
|
1018
|
+
}
|
|
1019
|
+
return next;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
885
1022
|
export interface ContentHistoryChange {
|
|
886
1023
|
fileId: string;
|
|
887
1024
|
before: string;
|
|
@@ -911,6 +1048,17 @@ export function getAvailableContentHistoryChanges(
|
|
|
911
1048
|
);
|
|
912
1049
|
}
|
|
913
1050
|
|
|
1051
|
+
function findLastContentHistoryChangeIndex(
|
|
1052
|
+
stack: ContentHistoryChange[],
|
|
1053
|
+
fileId?: string | null,
|
|
1054
|
+
) {
|
|
1055
|
+
if (!fileId) return -1;
|
|
1056
|
+
for (let index = stack.length - 1; index >= 0; index -= 1) {
|
|
1057
|
+
if (stack[index]?.fileId === fileId) return index;
|
|
1058
|
+
}
|
|
1059
|
+
return -1;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
914
1062
|
type PatchProofStatus =
|
|
915
1063
|
| "runtime"
|
|
916
1064
|
| "queued"
|
|
@@ -1055,6 +1203,7 @@ function designEditorCommandFromSearchParams(
|
|
|
1055
1203
|
searchParams.get("screen") ??
|
|
1056
1204
|
searchParams.get("fileId") ??
|
|
1057
1205
|
searchParams.get("filename");
|
|
1206
|
+
const selection = searchParams.get("selection");
|
|
1058
1207
|
const rawZoom = searchParams.get("zoom");
|
|
1059
1208
|
const zoom = rawZoom !== null ? Number(rawZoom) : NaN;
|
|
1060
1209
|
const tool = normalizeDesignTool(searchParams.get("tool"));
|
|
@@ -1066,6 +1215,7 @@ function designEditorCommandFromSearchParams(
|
|
|
1066
1215
|
inspector !== "extensions" &&
|
|
1067
1216
|
!leftPanel &&
|
|
1068
1217
|
!screen &&
|
|
1218
|
+
!selection &&
|
|
1069
1219
|
!tool
|
|
1070
1220
|
) {
|
|
1071
1221
|
return null;
|
|
@@ -1086,6 +1236,7 @@ function designEditorCommandFromSearchParams(
|
|
|
1086
1236
|
}
|
|
1087
1237
|
if (leftPanel) command.leftPanel = leftPanel;
|
|
1088
1238
|
if (screen) command.screen = screen;
|
|
1239
|
+
if (selection) command.selection = selection;
|
|
1089
1240
|
if (Number.isFinite(zoom)) {
|
|
1090
1241
|
command.zoom = zoom;
|
|
1091
1242
|
} else if (editorView === "single") {
|
|
@@ -1276,6 +1427,35 @@ function removeAbsolutePositioningFromNodeInHtml(
|
|
|
1276
1427
|
}
|
|
1277
1428
|
}
|
|
1278
1429
|
|
|
1430
|
+
function setAbsolutePositioningForNodeInHtml(
|
|
1431
|
+
content: string,
|
|
1432
|
+
nodeAttrId: string,
|
|
1433
|
+
point: { x: number; y: number },
|
|
1434
|
+
pointerOffset?: { x: number; y: number },
|
|
1435
|
+
): string {
|
|
1436
|
+
if (typeof window === "undefined") return content;
|
|
1437
|
+
try {
|
|
1438
|
+
const doc = new DOMParser().parseFromString(content, "text/html");
|
|
1439
|
+
const element = doc.querySelector(
|
|
1440
|
+
`[data-agent-native-node-id="${CSS.escape(nodeAttrId)}"]`,
|
|
1441
|
+
) as HTMLElement | null;
|
|
1442
|
+
if (!element) return content;
|
|
1443
|
+
element.style.position = "absolute";
|
|
1444
|
+
element.style.left = `${Math.round(point.x - (pointerOffset?.x ?? 0))}px`;
|
|
1445
|
+
element.style.top = `${Math.round(point.y - (pointerOffset?.y ?? 0))}px`;
|
|
1446
|
+
element.style.removeProperty("right");
|
|
1447
|
+
element.style.removeProperty("bottom");
|
|
1448
|
+
return `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
|
1449
|
+
} catch {
|
|
1450
|
+
return content;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
function isAbsoluteCodeLayerNode(node: CodeLayerNode | null | undefined) {
|
|
1455
|
+
const position = String(node?.style.position ?? "").toLowerCase();
|
|
1456
|
+
return position === "absolute" || position === "fixed";
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1279
1459
|
function escapeHtmlText(value: string): string {
|
|
1280
1460
|
return value
|
|
1281
1461
|
.replace(/&/g, "&")
|
|
@@ -1457,14 +1637,19 @@ function primitiveLayerName(primitive: CanvasPrimitiveInsert): string {
|
|
|
1457
1637
|
function appendCanvasPrimitiveToHtml(
|
|
1458
1638
|
content: string,
|
|
1459
1639
|
primitive: CanvasPrimitiveInsert,
|
|
1640
|
+
options?: { preserveNegativePosition?: boolean },
|
|
1460
1641
|
): string | null {
|
|
1461
1642
|
if (typeof window === "undefined") return null;
|
|
1462
1643
|
try {
|
|
1463
1644
|
const doc = new DOMParser().parseFromString(content, "text/html");
|
|
1464
1645
|
if (!doc.body) return null;
|
|
1465
1646
|
const geometry = primitive.geometry;
|
|
1466
|
-
const left =
|
|
1467
|
-
|
|
1647
|
+
const left = options?.preserveNegativePosition
|
|
1648
|
+
? Math.round(geometry.x)
|
|
1649
|
+
: Math.max(0, Math.round(geometry.x));
|
|
1650
|
+
const top = options?.preserveNegativePosition
|
|
1651
|
+
? Math.round(geometry.y)
|
|
1652
|
+
: Math.max(0, Math.round(geometry.y));
|
|
1468
1653
|
const width = Math.max(1, Math.round(geometry.width));
|
|
1469
1654
|
const height = Math.max(1, Math.round(geometry.height));
|
|
1470
1655
|
const nodeId = primitive.nodeId ?? uniqueLayerId(primitive.kind);
|
|
@@ -1538,6 +1723,9 @@ function appendCanvasPrimitiveToHtml(
|
|
|
1538
1723
|
}
|
|
1539
1724
|
svg.setAttribute("data-agent-native-node-id", nodeId);
|
|
1540
1725
|
svg.setAttribute("data-agent-native-layer-name", layerName);
|
|
1726
|
+
// Kind marker so the layers panel shows a true vector/line/arrow icon for
|
|
1727
|
+
// this SVG primitive instead of falling through to the rectangle glyph.
|
|
1728
|
+
// Read by treeTypeForNode in shared/code-layer.ts.
|
|
1541
1729
|
svg.setAttribute("data-an-primitive", primitive.kind);
|
|
1542
1730
|
svg.setAttribute(
|
|
1543
1731
|
"viewBox",
|
|
@@ -1583,6 +1771,10 @@ function appendCanvasPrimitiveToHtml(
|
|
|
1583
1771
|
polygon.setAttribute("stroke-linejoin", "round");
|
|
1584
1772
|
svg.setAttribute("data-agent-native-node-id", nodeId);
|
|
1585
1773
|
svg.setAttribute("data-agent-native-layer-name", layerName);
|
|
1774
|
+
// Kind marker so the layers panel shows a true polygon/star icon for this
|
|
1775
|
+
// SVG primitive instead of falling through to the rectangle glyph.
|
|
1776
|
+
// Read by treeTypeForNode in shared/code-layer.ts.
|
|
1777
|
+
svg.setAttribute("data-an-primitive", primitive.kind);
|
|
1586
1778
|
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
1587
1779
|
svg.setAttribute(
|
|
1588
1780
|
"style",
|
|
@@ -1637,7 +1829,7 @@ function appendCanvasPrimitiveToHtml(
|
|
|
1637
1829
|
element.style.borderRadius = canonical.borderRadius;
|
|
1638
1830
|
element.style.overflow = "hidden";
|
|
1639
1831
|
} else if (primitive.kind === "text") {
|
|
1640
|
-
element.textContent = primitive.text ?? "
|
|
1832
|
+
element.textContent = primitive.text ?? "";
|
|
1641
1833
|
element.style.display = primitive.autoSize ? "inline-block" : "flex";
|
|
1642
1834
|
if (!primitive.autoSize) {
|
|
1643
1835
|
element.style.alignItems = "center";
|
|
@@ -2061,12 +2253,38 @@ function layerTypeForCodeLayer(
|
|
|
2061
2253
|
): LayersPanelNode["type"] {
|
|
2062
2254
|
if (node.type === "group") return "group";
|
|
2063
2255
|
if (node.type === "component") return "component";
|
|
2256
|
+
if (node.type === "ellipse") return "ellipse";
|
|
2064
2257
|
if (node.type === "shape") return "shape";
|
|
2258
|
+
if (node.type === "vector") return "vector";
|
|
2259
|
+
if (node.type === "line") return "line";
|
|
2260
|
+
if (node.type === "arrow") return "arrow";
|
|
2261
|
+
if (node.type === "polygon") return "polygon";
|
|
2262
|
+
if (node.type === "star") return "star";
|
|
2065
2263
|
if (node.type === "text") return "text";
|
|
2066
2264
|
if (node.type === "image") return "image";
|
|
2067
2265
|
return "element";
|
|
2068
2266
|
}
|
|
2069
2267
|
|
|
2268
|
+
function codeLayerNodeLooksLikeComponent(
|
|
2269
|
+
node: CodeLayerNode | null | undefined,
|
|
2270
|
+
): boolean {
|
|
2271
|
+
if (!node) return false;
|
|
2272
|
+
if (isComponentInstance(node)) return true;
|
|
2273
|
+
const tag = node.tag.toLowerCase();
|
|
2274
|
+
if (
|
|
2275
|
+
tag === "button" ||
|
|
2276
|
+
tag === "input" ||
|
|
2277
|
+
tag === "select" ||
|
|
2278
|
+
tag === "textarea"
|
|
2279
|
+
) {
|
|
2280
|
+
return true;
|
|
2281
|
+
}
|
|
2282
|
+
if (/component|card|button|control/i.test(node.layerName)) return true;
|
|
2283
|
+
return node.classes.some((item) =>
|
|
2284
|
+
/component|card|button|control/i.test(item),
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2070
2288
|
function preferredCodeLayerSelector(node: CodeLayerNode): string {
|
|
2071
2289
|
return (
|
|
2072
2290
|
node.selectors.find((selector) =>
|
|
@@ -2236,6 +2454,7 @@ function codeLayerTreeToPanelNodes(
|
|
|
2236
2454
|
id: node.id,
|
|
2237
2455
|
name: resolvedLayerName(node),
|
|
2238
2456
|
type: layerTypeForCodeLayer(node),
|
|
2457
|
+
tagName: node.tag,
|
|
2239
2458
|
layout: node.layout,
|
|
2240
2459
|
detail: node.detail,
|
|
2241
2460
|
badge: node.badge,
|
|
@@ -2255,6 +2474,14 @@ interface EffectiveCodeLayerState {
|
|
|
2255
2474
|
hiddenIds: Set<string>;
|
|
2256
2475
|
}
|
|
2257
2476
|
|
|
2477
|
+
interface SelectedLayerTarget {
|
|
2478
|
+
layerId: string;
|
|
2479
|
+
fileId: string;
|
|
2480
|
+
node: CodeLayerNode;
|
|
2481
|
+
tree: CodeLayerTreeNode[];
|
|
2482
|
+
elementInfo: ElementInfo;
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2258
2485
|
function collectEffectiveCodeLayerState(
|
|
2259
2486
|
nodes: CodeLayerTreeNode[],
|
|
2260
2487
|
lockedIds: Set<string>,
|
|
@@ -2316,6 +2543,7 @@ function elementInfoFromCodeLayerNode(node: CodeLayerNode): ElementInfo {
|
|
|
2316
2543
|
),
|
|
2317
2544
|
boundingRect: { x: 0, y: 0, width: 0, height: 0 },
|
|
2318
2545
|
textContent: node.textSnippet ?? undefined,
|
|
2546
|
+
childElementCount: node.children.length,
|
|
2319
2547
|
isFlexChild: node.layout.parentDisplay?.includes("flex") ? true : false,
|
|
2320
2548
|
isFlexContainer: node.layout.isFlexContainer,
|
|
2321
2549
|
parentDisplay: node.layout.parentDisplay,
|
|
@@ -2444,6 +2672,7 @@ function canonicalElementInfoForCodeLayerNode(
|
|
|
2444
2672
|
selector: preferredCodeLayerSelector(node),
|
|
2445
2673
|
classes: node.classes,
|
|
2446
2674
|
confidence: node.confidence,
|
|
2675
|
+
childElementCount: node.children.length,
|
|
2447
2676
|
editCapabilities: info.editCapabilities?.some((capability) =>
|
|
2448
2677
|
capability.kind.startsWith("deterministic"),
|
|
2449
2678
|
)
|
|
@@ -2510,6 +2739,7 @@ export function refreshElementInfoFromContent(
|
|
|
2510
2739
|
sourceInfo.classes,
|
|
2511
2740
|
),
|
|
2512
2741
|
textContent: sourceInfo.textContent,
|
|
2742
|
+
childElementCount: sourceInfo.childElementCount,
|
|
2513
2743
|
isFlexChild: sourceInfo.isFlexChild,
|
|
2514
2744
|
isFlexContainer: sourceInfo.isFlexContainer,
|
|
2515
2745
|
parentDisplay: sourceInfo.parentDisplay,
|
|
@@ -2530,6 +2760,7 @@ export function refreshElementInfoFromContent(
|
|
|
2530
2760
|
classes,
|
|
2531
2761
|
),
|
|
2532
2762
|
textContent: element.textContent?.slice(0, 200) ?? info.textContent,
|
|
2763
|
+
childElementCount: element.children.length,
|
|
2533
2764
|
};
|
|
2534
2765
|
} catch {
|
|
2535
2766
|
return null;
|
|
@@ -2687,6 +2918,13 @@ function AgentNativeMenuMark({ className }: { className?: string }) {
|
|
|
2687
2918
|
|
|
2688
2919
|
type DesignLeftPanel = "file" | "agent" | "assets" | "tools" | "tokens";
|
|
2689
2920
|
|
|
2921
|
+
const INITIAL_GENERATION_DISABLED_LEFT_PANELS = new Set<DesignLeftPanel>([
|
|
2922
|
+
"file",
|
|
2923
|
+
"assets",
|
|
2924
|
+
"tools",
|
|
2925
|
+
"tokens",
|
|
2926
|
+
]);
|
|
2927
|
+
|
|
2690
2928
|
function normalizeDesignLeftPanel(value: unknown): DesignLeftPanel | undefined {
|
|
2691
2929
|
if (value === "extensions") return "tools";
|
|
2692
2930
|
return value === "file" ||
|
|
@@ -2700,11 +2938,19 @@ function normalizeDesignLeftPanel(value: unknown): DesignLeftPanel | undefined {
|
|
|
2700
2938
|
|
|
2701
2939
|
function DesignWorkspaceRail({
|
|
2702
2940
|
activePanel,
|
|
2941
|
+
disabledPanels,
|
|
2942
|
+
motionOpen,
|
|
2943
|
+
motionDisabled,
|
|
2703
2944
|
projectMenu,
|
|
2945
|
+
onMotionToggle,
|
|
2704
2946
|
onPanelChange,
|
|
2705
2947
|
}: {
|
|
2706
2948
|
activePanel: DesignLeftPanel;
|
|
2949
|
+
disabledPanels?: ReadonlySet<DesignLeftPanel>;
|
|
2950
|
+
motionOpen?: boolean;
|
|
2951
|
+
motionDisabled?: boolean;
|
|
2707
2952
|
projectMenu: ReactNode;
|
|
2953
|
+
onMotionToggle?: () => void;
|
|
2708
2954
|
onPanelChange: (panel: DesignLeftPanel) => void;
|
|
2709
2955
|
}) {
|
|
2710
2956
|
const t = useT();
|
|
@@ -2752,16 +2998,27 @@ function DesignWorkspaceRail({
|
|
|
2752
2998
|
<div className="flex min-h-0 flex-1 flex-col items-center gap-4">
|
|
2753
2999
|
{items.map((item) => {
|
|
2754
3000
|
const active = item.panel === activePanel;
|
|
3001
|
+
const disabled = disabledPanels?.has(item.panel) ?? false;
|
|
2755
3002
|
return (
|
|
2756
3003
|
<Tooltip key={item.panel}>
|
|
2757
3004
|
<TooltipTrigger asChild>
|
|
2758
3005
|
<button
|
|
2759
3006
|
type="button"
|
|
2760
3007
|
aria-label={item.label}
|
|
3008
|
+
aria-disabled={disabled || undefined}
|
|
2761
3009
|
aria-current={active ? "page" : undefined}
|
|
2762
|
-
|
|
3010
|
+
tabIndex={disabled ? -1 : undefined}
|
|
3011
|
+
onClick={(event) => {
|
|
3012
|
+
if (disabled) {
|
|
3013
|
+
event.preventDefault();
|
|
3014
|
+
return;
|
|
3015
|
+
}
|
|
3016
|
+
onPanelChange(item.panel);
|
|
3017
|
+
}}
|
|
2763
3018
|
className={cn(
|
|
2764
|
-
"group flex w-12 cursor-pointer flex-col items-center justify-start gap-1
|
|
3019
|
+
"group flex w-12 cursor-pointer flex-col items-center justify-start gap-1 rounded-none text-[10px] font-[450] leading-none text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
|
|
3020
|
+
disabled &&
|
|
3021
|
+
"cursor-default opacity-35 hover:text-muted-foreground",
|
|
2765
3022
|
active && "text-foreground",
|
|
2766
3023
|
)}
|
|
2767
3024
|
>
|
|
@@ -2771,6 +3028,8 @@ function DesignWorkspaceRail({
|
|
|
2771
3028
|
active
|
|
2772
3029
|
? "bg-[var(--design-editor-selection-color)] text-[var(--design-editor-accent-color)]"
|
|
2773
3030
|
: "text-muted-foreground group-hover:bg-[var(--design-editor-layer-hover-color)] group-hover:text-foreground",
|
|
3031
|
+
disabled &&
|
|
3032
|
+
"group-hover:bg-transparent group-hover:text-muted-foreground",
|
|
2774
3033
|
)}
|
|
2775
3034
|
>
|
|
2776
3035
|
{item.icon}
|
|
@@ -2785,6 +3044,53 @@ function DesignWorkspaceRail({
|
|
|
2785
3044
|
);
|
|
2786
3045
|
})}
|
|
2787
3046
|
</div>
|
|
3047
|
+
{onMotionToggle ? (
|
|
3048
|
+
<div className="mt-4 flex w-full flex-col items-center border-t border-border/70 pt-3">
|
|
3049
|
+
<Tooltip>
|
|
3050
|
+
<TooltipTrigger asChild>
|
|
3051
|
+
<button
|
|
3052
|
+
type="button"
|
|
3053
|
+
aria-label={"Motion" /* i18n-ignore */}
|
|
3054
|
+
aria-disabled={motionDisabled || undefined}
|
|
3055
|
+
aria-pressed={motionOpen || undefined}
|
|
3056
|
+
tabIndex={motionDisabled ? -1 : undefined}
|
|
3057
|
+
onClick={(event) => {
|
|
3058
|
+
if (motionDisabled) {
|
|
3059
|
+
event.preventDefault();
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
onMotionToggle();
|
|
3063
|
+
}}
|
|
3064
|
+
className={cn(
|
|
3065
|
+
"group flex w-12 cursor-pointer flex-col items-center justify-start gap-1 rounded-none !text-[10px] font-[450] leading-none text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
|
|
3066
|
+
motionDisabled &&
|
|
3067
|
+
"cursor-default opacity-35 hover:text-muted-foreground",
|
|
3068
|
+
motionOpen && "text-foreground",
|
|
3069
|
+
)}
|
|
3070
|
+
>
|
|
3071
|
+
<span
|
|
3072
|
+
className={cn(
|
|
3073
|
+
"flex size-8 items-center justify-center rounded-lg transition-colors",
|
|
3074
|
+
motionOpen
|
|
3075
|
+
? "bg-[var(--design-editor-selection-color)] text-[var(--design-editor-accent-color)]"
|
|
3076
|
+
: "text-muted-foreground group-hover:bg-[var(--design-editor-layer-hover-color)] group-hover:text-foreground",
|
|
3077
|
+
motionDisabled &&
|
|
3078
|
+
"group-hover:bg-transparent group-hover:text-muted-foreground",
|
|
3079
|
+
)}
|
|
3080
|
+
>
|
|
3081
|
+
<IconChevronUp className="size-[15px]" />
|
|
3082
|
+
</span>
|
|
3083
|
+
<span className="max-w-full truncate leading-none">
|
|
3084
|
+
{"Motion" /* i18n-ignore */}
|
|
3085
|
+
</span>
|
|
3086
|
+
</button>
|
|
3087
|
+
</TooltipTrigger>
|
|
3088
|
+
<TooltipContent side="right">
|
|
3089
|
+
{"Motion" /* i18n-ignore */}
|
|
3090
|
+
</TooltipContent>
|
|
3091
|
+
</Tooltip>
|
|
3092
|
+
</div>
|
|
3093
|
+
) : null}
|
|
2788
3094
|
</nav>
|
|
2789
3095
|
);
|
|
2790
3096
|
}
|
|
@@ -3598,6 +3904,25 @@ export default function DesignEditor() {
|
|
|
3598
3904
|
const { id } = useParams<{ id: string }>();
|
|
3599
3905
|
const navigate = useNavigate();
|
|
3600
3906
|
const location = useLocation();
|
|
3907
|
+
const initialEditorUrlRef = useRef<{
|
|
3908
|
+
designId: string | undefined;
|
|
3909
|
+
searchParams: URLSearchParams;
|
|
3910
|
+
} | null>(null);
|
|
3911
|
+
if (
|
|
3912
|
+
!initialEditorUrlRef.current ||
|
|
3913
|
+
initialEditorUrlRef.current.designId !== id
|
|
3914
|
+
) {
|
|
3915
|
+
initialEditorUrlRef.current = {
|
|
3916
|
+
designId: id,
|
|
3917
|
+
searchParams: new URLSearchParams(location.search),
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
const initialSearchParams = initialEditorUrlRef.current.searchParams;
|
|
3921
|
+
const initialRouteScreenTarget =
|
|
3922
|
+
initialSearchParams.get("screen") ??
|
|
3923
|
+
initialSearchParams.get("fileId") ??
|
|
3924
|
+
initialSearchParams.get("filename");
|
|
3925
|
+
const initialRouteSelectionId = initialSearchParams.get("selection") || null;
|
|
3601
3926
|
const searchParams = useMemo(
|
|
3602
3927
|
() => new URLSearchParams(location.search),
|
|
3603
3928
|
[location.search],
|
|
@@ -3632,7 +3957,9 @@ export default function DesignEditor() {
|
|
|
3632
3957
|
const [titleEditing, setTitleEditing] = useState(false);
|
|
3633
3958
|
const [titleDraft, setTitleDraft] = useState("");
|
|
3634
3959
|
const [screenZoom, setScreenZoom] = useState(FOCUSED_SCREEN_ZOOM);
|
|
3635
|
-
const [
|
|
3960
|
+
const [explicitOverviewCanvasZoom, setExplicitOverviewCanvasZoom] = useState<
|
|
3961
|
+
number | null
|
|
3962
|
+
>(null);
|
|
3636
3963
|
const [deviceFrame, setDeviceFrame] = useState<DeviceFrameType>("none");
|
|
3637
3964
|
const [viewMode, setViewMode] = useState<"single" | "overview">("overview");
|
|
3638
3965
|
const viewModeRef = useRef<"single" | "overview">("overview");
|
|
@@ -3660,6 +3987,8 @@ export default function DesignEditor() {
|
|
|
3660
3987
|
useState<InspectorTab>("design");
|
|
3661
3988
|
const [activeLeftPanel, setActiveLeftPanel] =
|
|
3662
3989
|
useState<DesignLeftPanel>("file");
|
|
3990
|
+
const initialSearchCommandAppliedForIdRef = useRef<string | null>(null);
|
|
3991
|
+
const initialUrlSelectionHydratedForIdRef = useRef<string | null>(null);
|
|
3663
3992
|
const [leftSidebarWidth, setLeftSidebarWidth] = useState(256);
|
|
3664
3993
|
const [rightSidebarWidth, setRightSidebarWidth] = useState(256);
|
|
3665
3994
|
const [layersSearchQuery, setLayersSearchQuery] = useState("");
|
|
@@ -3667,6 +3996,11 @@ export default function DesignEditor() {
|
|
|
3667
3996
|
const [selectedLayerIdsState, setSelectedLayerIdsState] = useState<string[]>(
|
|
3668
3997
|
[],
|
|
3669
3998
|
);
|
|
3999
|
+
const selectedLayerTargetsRef = useRef<SelectedLayerTarget[]>([]);
|
|
4000
|
+
const effectiveCodeLayerStateRef = useRef<EffectiveCodeLayerState>({
|
|
4001
|
+
lockedIds: new Set(),
|
|
4002
|
+
hiddenIds: new Set(),
|
|
4003
|
+
});
|
|
3670
4004
|
const [overviewSelectedScreenIds, setOverviewSelectedScreenIds] = useState<
|
|
3671
4005
|
string[]
|
|
3672
4006
|
>([]);
|
|
@@ -3817,10 +4151,22 @@ export default function DesignEditor() {
|
|
|
3817
4151
|
motionDockUnmountTimerRef.current = window.setTimeout(() => {
|
|
3818
4152
|
setMotionDockMounted(false);
|
|
3819
4153
|
motionDockUnmountTimerRef.current = null;
|
|
3820
|
-
},
|
|
4154
|
+
}, MOTION_DOCK_EXIT_FALLBACK_MS);
|
|
3821
4155
|
},
|
|
3822
4156
|
[clearMotionDockUnmountTimer],
|
|
3823
4157
|
);
|
|
4158
|
+
const handleMotionDockExitComplete = useCallback(() => {
|
|
4159
|
+
if (motionDockOpen) return;
|
|
4160
|
+
clearMotionDockUnmountTimer();
|
|
4161
|
+
if (typeof window === "undefined") {
|
|
4162
|
+
setMotionDockMounted(false);
|
|
4163
|
+
return;
|
|
4164
|
+
}
|
|
4165
|
+
motionDockUnmountTimerRef.current = window.setTimeout(() => {
|
|
4166
|
+
setMotionDockMounted(false);
|
|
4167
|
+
motionDockUnmountTimerRef.current = null;
|
|
4168
|
+
}, MOTION_DOCK_EXIT_SETTLE_MS);
|
|
4169
|
+
}, [clearMotionDockUnmountTimer, motionDockOpen]);
|
|
3824
4170
|
useEffect(
|
|
3825
4171
|
() => () => clearMotionDockUnmountTimer(),
|
|
3826
4172
|
[clearMotionDockUnmountTimer],
|
|
@@ -3973,22 +4319,50 @@ export default function DesignEditor() {
|
|
|
3973
4319
|
const undoManagerRef = useRef<Y.UndoManager | null>(null);
|
|
3974
4320
|
const contentUndoStackRef = useRef<ContentHistoryEntry[]>([]);
|
|
3975
4321
|
const contentRedoStackRef = useRef<ContentHistoryEntry[]>([]);
|
|
4322
|
+
const localContentUndoStackRef = useRef<ContentHistoryChange[]>([]);
|
|
4323
|
+
const localContentRedoStackRef = useRef<ContentHistoryChange[]>([]);
|
|
4324
|
+
const activeFileIdForUndoRef = useRef<string | null>(null);
|
|
3976
4325
|
const suppressContentHistoryRef = useRef(false);
|
|
3977
4326
|
const geometryUndoStackRef = useRef<GeometryHistoryEntry[]>([]);
|
|
3978
4327
|
const geometryRedoStackRef = useRef<GeometryHistoryEntry[]>([]);
|
|
3979
4328
|
const historyOrderRef = useRef<UndoRedoOrderKind[]>([]);
|
|
3980
4329
|
const redoOrderRef = useRef<UndoRedoOrderKind[]>([]);
|
|
4330
|
+
const clearRedoStacks = useCallback(() => {
|
|
4331
|
+
contentRedoStackRef.current = [];
|
|
4332
|
+
localContentRedoStackRef.current = [];
|
|
4333
|
+
geometryRedoStackRef.current = [];
|
|
4334
|
+
redoOrderRef.current = [];
|
|
4335
|
+
undoManagerRef.current?.clear(false, true);
|
|
4336
|
+
}, []);
|
|
3981
4337
|
const syncUndoRedoState = useCallback(() => {
|
|
3982
4338
|
const undoManager = undoManagerRef.current;
|
|
4339
|
+
const canUseOverviewHistory = viewModeRef.current === "overview";
|
|
4340
|
+
const activeHistoryFileId = activeFileIdForUndoRef.current;
|
|
4341
|
+
const hasLocalUndo =
|
|
4342
|
+
!canUseOverviewHistory &&
|
|
4343
|
+
findLastContentHistoryChangeIndex(
|
|
4344
|
+
localContentUndoStackRef.current,
|
|
4345
|
+
activeHistoryFileId,
|
|
4346
|
+
) !== -1;
|
|
4347
|
+
const hasLocalRedo =
|
|
4348
|
+
!canUseOverviewHistory &&
|
|
4349
|
+
findLastContentHistoryChangeIndex(
|
|
4350
|
+
localContentRedoStackRef.current,
|
|
4351
|
+
activeHistoryFileId,
|
|
4352
|
+
) !== -1;
|
|
3983
4353
|
setCanUndo(
|
|
3984
4354
|
Boolean(undoManager?.canUndo()) ||
|
|
3985
|
-
|
|
3986
|
-
|
|
4355
|
+
hasLocalUndo ||
|
|
4356
|
+
(canUseOverviewHistory &&
|
|
4357
|
+
(contentUndoStackRef.current.length > 0 ||
|
|
4358
|
+
geometryUndoStackRef.current.length > 0)),
|
|
3987
4359
|
);
|
|
3988
4360
|
setCanRedo(
|
|
3989
4361
|
Boolean(undoManager?.canRedo()) ||
|
|
3990
|
-
|
|
3991
|
-
|
|
4362
|
+
hasLocalRedo ||
|
|
4363
|
+
(canUseOverviewHistory &&
|
|
4364
|
+
(contentRedoStackRef.current.length > 0 ||
|
|
4365
|
+
geometryRedoStackRef.current.length > 0)),
|
|
3992
4366
|
);
|
|
3993
4367
|
}, []);
|
|
3994
4368
|
const recordContentHistoryEntry = useCallback(
|
|
@@ -3997,23 +4371,59 @@ export default function DesignEditor() {
|
|
|
3997
4371
|
(change) => change.before !== change.after,
|
|
3998
4372
|
);
|
|
3999
4373
|
if (changes.length === 0) return;
|
|
4374
|
+
const activeHistoryFileId = activeFileIdForUndoRef.current;
|
|
4375
|
+
if (
|
|
4376
|
+
activeHistoryFileId &&
|
|
4377
|
+
changes.some((change) => change.fileId === activeHistoryFileId)
|
|
4378
|
+
) {
|
|
4379
|
+
undoManagerRef.current?.clear(true, false);
|
|
4380
|
+
localContentUndoStackRef.current =
|
|
4381
|
+
localContentUndoStackRef.current.filter(
|
|
4382
|
+
(change) => change.fileId !== activeHistoryFileId,
|
|
4383
|
+
);
|
|
4384
|
+
localContentRedoStackRef.current =
|
|
4385
|
+
localContentRedoStackRef.current.filter(
|
|
4386
|
+
(change) => change.fileId !== activeHistoryFileId,
|
|
4387
|
+
);
|
|
4388
|
+
historyOrderRef.current = removeUndoRedoOrderKind(
|
|
4389
|
+
historyOrderRef.current,
|
|
4390
|
+
"content",
|
|
4391
|
+
);
|
|
4392
|
+
redoOrderRef.current = removeUndoRedoOrderKind(
|
|
4393
|
+
redoOrderRef.current,
|
|
4394
|
+
"content",
|
|
4395
|
+
);
|
|
4396
|
+
}
|
|
4000
4397
|
contentUndoStackRef.current = [
|
|
4001
4398
|
...contentUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
4002
4399
|
changes.length === 1 ? changes[0] : { changes },
|
|
4003
4400
|
];
|
|
4004
|
-
|
|
4401
|
+
clearRedoStacks();
|
|
4005
4402
|
historyOrderRef.current = [
|
|
4006
4403
|
...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
4007
4404
|
"file-content",
|
|
4008
4405
|
];
|
|
4009
|
-
redoOrderRef.current = [];
|
|
4010
4406
|
syncUndoRedoState();
|
|
4011
4407
|
},
|
|
4012
|
-
[syncUndoRedoState],
|
|
4408
|
+
[clearRedoStacks, syncUndoRedoState],
|
|
4409
|
+
);
|
|
4410
|
+
const recordLocalContentHistoryEntry = useCallback(
|
|
4411
|
+
(change: ContentHistoryChange) => {
|
|
4412
|
+
if (change.before === change.after) return;
|
|
4413
|
+
localContentUndoStackRef.current = [
|
|
4414
|
+
...localContentUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
4415
|
+
change,
|
|
4416
|
+
];
|
|
4417
|
+
clearRedoStacks();
|
|
4418
|
+
syncUndoRedoState();
|
|
4419
|
+
},
|
|
4420
|
+
[clearRedoStacks, syncUndoRedoState],
|
|
4013
4421
|
);
|
|
4014
4422
|
const clearLocalUndoRedoStacks = useCallback(() => {
|
|
4015
4423
|
contentUndoStackRef.current = [];
|
|
4016
4424
|
contentRedoStackRef.current = [];
|
|
4425
|
+
localContentUndoStackRef.current = [];
|
|
4426
|
+
localContentRedoStackRef.current = [];
|
|
4017
4427
|
geometryUndoStackRef.current = [];
|
|
4018
4428
|
geometryRedoStackRef.current = [];
|
|
4019
4429
|
historyOrderRef.current = [];
|
|
@@ -4023,9 +4433,6 @@ export default function DesignEditor() {
|
|
|
4023
4433
|
const designSelectionOwnerIdRef = useRef(`${TAB_ID}:${generateTabId()}`);
|
|
4024
4434
|
const frameGeometrySaveTimerRef = useRef<number | null>(null);
|
|
4025
4435
|
const [tweakSaveActive, setTweakSaveActive] = useState(false);
|
|
4026
|
-
// Dismissible localhost-source banner (reset per session).
|
|
4027
|
-
const [localSourceBannerDismissed, setLocalSourceBannerDismissed] =
|
|
4028
|
-
useState(false);
|
|
4029
4436
|
// Localhost write-consent dialog state. When the agent wants to write a local
|
|
4030
4437
|
// file and no valid grant exists for the active connection, we show the dialog
|
|
4031
4438
|
// with a pending payload; the user clicks "Allow writes" to mint a grant.
|
|
@@ -4218,7 +4625,9 @@ export default function DesignEditor() {
|
|
|
4218
4625
|
onComplete: handleGenerationComplete,
|
|
4219
4626
|
onStale: markGenerationStale,
|
|
4220
4627
|
shouldAdoptRunningTab: () =>
|
|
4221
|
-
Boolean(id) &&
|
|
4628
|
+
Boolean(id) &&
|
|
4629
|
+
!generationOutputReadyRef.current &&
|
|
4630
|
+
hasFreshPendingGeneration(id),
|
|
4222
4631
|
onAdoptRunningTab: (tabId) => {
|
|
4223
4632
|
generationRunConfirmedRef.current = true;
|
|
4224
4633
|
setGenerationChatTabId(tabId);
|
|
@@ -4271,8 +4680,9 @@ export default function DesignEditor() {
|
|
|
4271
4680
|
pendingQuestions && pendingQuestions.length > 0,
|
|
4272
4681
|
);
|
|
4273
4682
|
|
|
4274
|
-
const { session } = useSession();
|
|
4683
|
+
const { session, isLoading: sessionLoading } = useSession();
|
|
4275
4684
|
const isSignedIn = Boolean(session?.email);
|
|
4685
|
+
const sessionResolved = !sessionLoading;
|
|
4276
4686
|
|
|
4277
4687
|
useEffect(() => {
|
|
4278
4688
|
return () => clearGenerationCompleteTimer();
|
|
@@ -4310,12 +4720,11 @@ export default function DesignEditor() {
|
|
|
4310
4720
|
: undefined,
|
|
4311
4721
|
[session?.email, session?.name],
|
|
4312
4722
|
);
|
|
4723
|
+
const signInToSaveHref = buildSignInHrefForDesignIntent("save");
|
|
4724
|
+
const signInToShareHref = buildSignInHrefForDesignIntent("share");
|
|
4313
4725
|
const handleSignInToSave = useCallback(() => {
|
|
4314
4726
|
window.location.href = buildSignInHrefForDesignIntent("save");
|
|
4315
4727
|
}, []);
|
|
4316
|
-
const handleSignInToShare = useCallback(() => {
|
|
4317
|
-
window.location.href = buildSignInHrefForDesignIntent("share");
|
|
4318
|
-
}, []);
|
|
4319
4728
|
|
|
4320
4729
|
// Data fetching
|
|
4321
4730
|
useEffect(() => {
|
|
@@ -4362,6 +4771,7 @@ export default function DesignEditor() {
|
|
|
4362
4771
|
const canShareDesign =
|
|
4363
4772
|
designAccessRole === "owner" || designAccessRole === "admin";
|
|
4364
4773
|
const canEditDesign = canShareDesign || designAccessRole === "editor";
|
|
4774
|
+
const canRenderAuthenticatedShare = isSignedIn || canEditDesign;
|
|
4365
4775
|
const canEditDesignRef = useRef(canEditDesign);
|
|
4366
4776
|
const pendingLocalFileContentsRef = useRef<
|
|
4367
4777
|
Map<
|
|
@@ -4909,6 +5319,24 @@ export default function DesignEditor() {
|
|
|
4909
5319
|
return pending ? { ...file, content: pending.content } : file;
|
|
4910
5320
|
});
|
|
4911
5321
|
}, [pendingLocalFileContentsSnapshot, serverFiles]);
|
|
5322
|
+
const [liveScreenSnapshotsById, setLiveScreenSnapshotsById] = useState<
|
|
5323
|
+
Record<string, LiveScreenSnapshot>
|
|
5324
|
+
>({});
|
|
5325
|
+
useEffect(() => {
|
|
5326
|
+
const liveFileIds = new Set(serverFiles.map((file) => file.id));
|
|
5327
|
+
setLiveScreenSnapshotsById((current) => {
|
|
5328
|
+
let changed = false;
|
|
5329
|
+
const next: Record<string, LiveScreenSnapshot> = {};
|
|
5330
|
+
Object.entries(current).forEach(([fileId, snapshot]) => {
|
|
5331
|
+
if (!liveFileIds.has(fileId)) {
|
|
5332
|
+
changed = true;
|
|
5333
|
+
return;
|
|
5334
|
+
}
|
|
5335
|
+
next[fileId] = snapshot;
|
|
5336
|
+
});
|
|
5337
|
+
return changed ? next : current;
|
|
5338
|
+
});
|
|
5339
|
+
}, [serverFiles]);
|
|
4912
5340
|
const designDataJson = useMemo(
|
|
4913
5341
|
() => parseDesignDataJson(design?.data),
|
|
4914
5342
|
[design?.data],
|
|
@@ -5024,6 +5452,7 @@ export default function DesignEditor() {
|
|
|
5024
5452
|
height: numberValue("height"),
|
|
5025
5453
|
url: stringValue("url"),
|
|
5026
5454
|
previewUrl: stringValue("previewUrl"),
|
|
5455
|
+
bridgeUrl: stringValue("bridgeUrl"),
|
|
5027
5456
|
// Breakpoint preview widths (§6.4). When non-empty, MultiScreenCanvas
|
|
5028
5457
|
// renders one iframe per width to the right of the primary frame.
|
|
5029
5458
|
breakpointWidths: bpWidths,
|
|
@@ -5046,28 +5475,19 @@ export default function DesignEditor() {
|
|
|
5046
5475
|
return typeof boardFile?.content === "string" ? boardFile.content : "";
|
|
5047
5476
|
}, [boardFileId, files]);
|
|
5048
5477
|
|
|
5049
|
-
// Logical canvas-space bounding box of the board iframe.
|
|
5050
|
-
//
|
|
5051
|
-
//
|
|
5478
|
+
// Logical canvas-space bounding box of the board iframe. The board is an
|
|
5479
|
+
// invisible editing layer behind screen frames, not a finite artboard, so keep
|
|
5480
|
+
// it at the canvas-safe maximum instead of clipping it to the screen union.
|
|
5052
5481
|
const boardFrameGeometry = useMemo((): FrameGeometry | undefined => {
|
|
5053
5482
|
if (!boardFileId) return undefined;
|
|
5054
|
-
const
|
|
5055
|
-
if (entries.length === 0) return { x: 0, y: 0, width: 1920, height: 1080 };
|
|
5056
|
-
let maxRight = 0;
|
|
5057
|
-
let maxBottom = 0;
|
|
5058
|
-
for (const geo of entries) {
|
|
5059
|
-
const right = (geo.x ?? 0) + (geo.width ?? 0);
|
|
5060
|
-
const bottom = (geo.y ?? 0) + (geo.height ?? 0);
|
|
5061
|
-
if (right > maxRight) maxRight = right;
|
|
5062
|
-
if (bottom > maxBottom) maxBottom = bottom;
|
|
5063
|
-
}
|
|
5483
|
+
const origin = -BOARD_SURFACE_SIZE / 2;
|
|
5064
5484
|
return {
|
|
5065
|
-
x:
|
|
5066
|
-
y:
|
|
5067
|
-
width:
|
|
5068
|
-
height:
|
|
5485
|
+
x: origin,
|
|
5486
|
+
y: origin,
|
|
5487
|
+
width: BOARD_SURFACE_SIZE,
|
|
5488
|
+
height: BOARD_SURFACE_SIZE,
|
|
5069
5489
|
};
|
|
5070
|
-
}, [boardFileId
|
|
5490
|
+
}, [boardFileId]);
|
|
5071
5491
|
|
|
5072
5492
|
const queueFrameGeometrySave = useCallback(
|
|
5073
5493
|
(geometryById: CanvasFrameGeometryById) => {
|
|
@@ -5160,14 +5580,11 @@ export default function DesignEditor() {
|
|
|
5160
5580
|
after: afterSnapshot,
|
|
5161
5581
|
},
|
|
5162
5582
|
];
|
|
5163
|
-
|
|
5164
|
-
contentRedoStackRef.current = [];
|
|
5165
|
-
undoManagerRef.current?.clear(false, true);
|
|
5583
|
+
clearRedoStacks();
|
|
5166
5584
|
historyOrderRef.current = [
|
|
5167
5585
|
...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
5168
5586
|
"geometry",
|
|
5169
5587
|
];
|
|
5170
|
-
redoOrderRef.current = [];
|
|
5171
5588
|
const resizedFrameIds = viewportChangedFrameIds(
|
|
5172
5589
|
beforeSnapshot,
|
|
5173
5590
|
afterSnapshot,
|
|
@@ -5180,7 +5597,7 @@ export default function DesignEditor() {
|
|
|
5180
5597
|
);
|
|
5181
5598
|
syncUndoRedoState();
|
|
5182
5599
|
},
|
|
5183
|
-
[syncUndoRedoState, writeFrameGeometrySnapshot],
|
|
5600
|
+
[clearRedoStacks, syncUndoRedoState, writeFrameGeometrySnapshot],
|
|
5184
5601
|
);
|
|
5185
5602
|
|
|
5186
5603
|
// §6.6 — "Make this a real app" handler.
|
|
@@ -5356,6 +5773,7 @@ export default function DesignEditor() {
|
|
|
5356
5773
|
}, [files, activeFileId]);
|
|
5357
5774
|
|
|
5358
5775
|
const activeFile = files.find((f) => f.id === activeFileId) ?? files[0];
|
|
5776
|
+
activeFileIdForUndoRef.current = activeFile?.id ?? null;
|
|
5359
5777
|
const motionTimelineQueryParams =
|
|
5360
5778
|
id && activeFile?.id
|
|
5361
5779
|
? { designId: id, sourceRef: activeFile.id }
|
|
@@ -5384,12 +5802,6 @@ export default function DesignEditor() {
|
|
|
5384
5802
|
setReviewAuditLoading(false);
|
|
5385
5803
|
}, [activeFile?.id, reviewFileId]);
|
|
5386
5804
|
|
|
5387
|
-
const initialGenerationReadOnly = shouldLockInspectorForInitialGeneration({
|
|
5388
|
-
fileCount: files.length,
|
|
5389
|
-
generating,
|
|
5390
|
-
pendingGenerationActive,
|
|
5391
|
-
});
|
|
5392
|
-
|
|
5393
5805
|
const selectedScreenIds = useMemo(
|
|
5394
5806
|
() =>
|
|
5395
5807
|
getSelectedScreenIdsForEditorState({
|
|
@@ -5408,6 +5820,10 @@ export default function DesignEditor() {
|
|
|
5408
5820
|
: undefined,
|
|
5409
5821
|
[activeOverviewScreenId, overviewScreens],
|
|
5410
5822
|
);
|
|
5823
|
+
const activeScreenBridgeUrl = activeOverviewScreen?.bridgeUrl;
|
|
5824
|
+
const activeScreenExternalSnapshotHtml = activeFile?.id
|
|
5825
|
+
? liveScreenSnapshotsById[activeFile.id]?.html
|
|
5826
|
+
: undefined;
|
|
5411
5827
|
const activeOverviewSourceWidth =
|
|
5412
5828
|
deviceFrame === "none"
|
|
5413
5829
|
? activeOverviewScreen?.width
|
|
@@ -5425,6 +5841,9 @@ export default function DesignEditor() {
|
|
|
5425
5841
|
overviewZoomScaleRef.current = overviewZoomScale;
|
|
5426
5842
|
}, [overviewZoomScale]);
|
|
5427
5843
|
|
|
5844
|
+
const overviewCanvasZoom =
|
|
5845
|
+
explicitOverviewCanvasZoom ??
|
|
5846
|
+
getDefaultOverviewCanvasZoom(overviewZoomScale);
|
|
5428
5847
|
const overviewZoom = getOverviewDisplayZoom(
|
|
5429
5848
|
overviewCanvasZoom,
|
|
5430
5849
|
overviewZoomScale,
|
|
@@ -5433,10 +5852,12 @@ export default function DesignEditor() {
|
|
|
5433
5852
|
const setZoomForView = useCallback(
|
|
5434
5853
|
(targetView: "single" | "overview", update: SetStateAction<number>) => {
|
|
5435
5854
|
if (targetView === "overview") {
|
|
5436
|
-
|
|
5855
|
+
setExplicitOverviewCanvasZoom((currentCanvasZoom) => {
|
|
5437
5856
|
const scale = overviewZoomScaleRef.current;
|
|
5857
|
+
const resolvedCanvasZoom =
|
|
5858
|
+
currentCanvasZoom ?? getDefaultOverviewCanvasZoom(scale);
|
|
5438
5859
|
const currentDisplayZoom = getOverviewDisplayZoom(
|
|
5439
|
-
|
|
5860
|
+
resolvedCanvasZoom,
|
|
5440
5861
|
scale,
|
|
5441
5862
|
);
|
|
5442
5863
|
const nextDisplayZoom = resolveZoomUpdate(update, currentDisplayZoom);
|
|
@@ -5463,6 +5884,7 @@ export default function DesignEditor() {
|
|
|
5463
5884
|
const applyDesignEditorCommand = useCallback(
|
|
5464
5885
|
(command: DesignEditorCommand | Record<string, unknown>) => {
|
|
5465
5886
|
if (!id || command.designId !== id) return true;
|
|
5887
|
+
const commandRecord = command as Record<string, unknown>;
|
|
5466
5888
|
const editorView =
|
|
5467
5889
|
command.editorView === "overview" || command.editorView === "single"
|
|
5468
5890
|
? command.editorView
|
|
@@ -5479,6 +5901,14 @@ export default function DesignEditor() {
|
|
|
5479
5901
|
: typeof command.screen === "string"
|
|
5480
5902
|
? command.screen
|
|
5481
5903
|
: null;
|
|
5904
|
+
const selectionId =
|
|
5905
|
+
typeof command.selection === "string"
|
|
5906
|
+
? command.selection
|
|
5907
|
+
: typeof commandRecord.nodeId === "string"
|
|
5908
|
+
? commandRecord.nodeId
|
|
5909
|
+
: typeof commandRecord.layerId === "string"
|
|
5910
|
+
? commandRecord.layerId
|
|
5911
|
+
: null;
|
|
5482
5912
|
const targetFile = findDesignFileByScreenTarget(files, target);
|
|
5483
5913
|
// A navigate command can name a screen the agent just created that the
|
|
5484
5914
|
// get-design query hasn't refetched yet. Treat any unresolved named target
|
|
@@ -5526,6 +5956,9 @@ export default function DesignEditor() {
|
|
|
5526
5956
|
if (targetFile) {
|
|
5527
5957
|
setActiveFileId(targetFile.id);
|
|
5528
5958
|
}
|
|
5959
|
+
if (selectionId) {
|
|
5960
|
+
setSelectedLayerIdsState([selectionId]);
|
|
5961
|
+
}
|
|
5529
5962
|
|
|
5530
5963
|
const commandZoom =
|
|
5531
5964
|
typeof command.zoom === "number" && Number.isFinite(command.zoom)
|
|
@@ -5537,12 +5970,12 @@ export default function DesignEditor() {
|
|
|
5537
5970
|
|
|
5538
5971
|
if (editorView === "overview") {
|
|
5539
5972
|
viewModeRef.current = "overview";
|
|
5540
|
-
setSelectedElement(null);
|
|
5973
|
+
if (!selectionId) setSelectedElement(null);
|
|
5541
5974
|
applyCommandTool("move");
|
|
5542
5975
|
setViewMode("overview");
|
|
5543
5976
|
} else if (editorView === "single") {
|
|
5544
5977
|
viewModeRef.current = "single";
|
|
5545
|
-
setSelectedElement(null);
|
|
5978
|
+
if (!selectionId) setSelectedElement(null);
|
|
5546
5979
|
applyCommandTool("move");
|
|
5547
5980
|
if (commandZoom === null) {
|
|
5548
5981
|
setScreenZoom(FOCUSED_SCREEN_ZOOM);
|
|
@@ -5559,10 +5992,20 @@ export default function DesignEditor() {
|
|
|
5559
5992
|
|
|
5560
5993
|
useEffect(() => {
|
|
5561
5994
|
if (!id) return;
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5995
|
+
if (initialSearchCommandAppliedForIdRef.current === id) return;
|
|
5996
|
+
const command = designEditorCommandFromSearchParams(
|
|
5997
|
+
id,
|
|
5998
|
+
initialSearchParams,
|
|
5999
|
+
);
|
|
6000
|
+
if (!command) {
|
|
6001
|
+
initialSearchCommandAppliedForIdRef.current = id;
|
|
6002
|
+
return;
|
|
6003
|
+
}
|
|
6004
|
+
const applied = applyDesignEditorCommand(command);
|
|
6005
|
+
if (applied) {
|
|
6006
|
+
initialSearchCommandAppliedForIdRef.current = id;
|
|
6007
|
+
}
|
|
6008
|
+
}, [applyDesignEditorCommand, id, initialSearchParams]);
|
|
5566
6009
|
|
|
5567
6010
|
useEffect(() => {
|
|
5568
6011
|
if (!id) return;
|
|
@@ -5862,6 +6305,7 @@ export default function DesignEditor() {
|
|
|
5862
6305
|
const [collabContentFileId, setCollabContentFileId] = useState<string | null>(
|
|
5863
6306
|
null,
|
|
5864
6307
|
);
|
|
6308
|
+
const previousDesignIdForHistoryRef = useRef<string | null>(null);
|
|
5865
6309
|
const prevActiveFileIdRef = useRef<string | null>(null);
|
|
5866
6310
|
// `updatedAt` of the DB content this preview currently reflects. A poll that
|
|
5867
6311
|
// returns an older-or-equal value is a stale snapshot and is ignored; a newer
|
|
@@ -5907,7 +6351,16 @@ export default function DesignEditor() {
|
|
|
5907
6351
|
};
|
|
5908
6352
|
}, [awareness, ydoc]);
|
|
5909
6353
|
|
|
5910
|
-
|
|
6354
|
+
useEffect(() => {
|
|
6355
|
+
if (previousDesignIdForHistoryRef.current === id) return;
|
|
6356
|
+
previousDesignIdForHistoryRef.current = id ?? null;
|
|
6357
|
+
clearLocalUndoRedoStacks();
|
|
6358
|
+
syncUndoRedoState();
|
|
6359
|
+
}, [clearLocalUndoRedoStacks, id, syncUndoRedoState]);
|
|
6360
|
+
|
|
6361
|
+
// Reset per-file reconcile state when switching files.
|
|
6362
|
+
// Keep undo/redo content + geometry stacks intact: overview mode needs one
|
|
6363
|
+
// chronological history across all screens, board edits, and frame geometry.
|
|
5911
6364
|
useEffect(() => {
|
|
5912
6365
|
if (viewMode === "overview") {
|
|
5913
6366
|
prevActiveFileIdRef.current = activeFileId;
|
|
@@ -5916,9 +6369,7 @@ export default function DesignEditor() {
|
|
|
5916
6369
|
lastAppliedFileUpdatedAtRef.current = null;
|
|
5917
6370
|
lastLocalContentRef.current = null;
|
|
5918
6371
|
latestActiveContentRef.current = null;
|
|
5919
|
-
clearLocalUndoRedoStacks();
|
|
5920
6372
|
clearStaleAgentCollabRecovery();
|
|
5921
|
-
syncUndoRedoState();
|
|
5922
6373
|
return;
|
|
5923
6374
|
}
|
|
5924
6375
|
if (activeFileId !== prevActiveFileIdRef.current) {
|
|
@@ -5928,17 +6379,9 @@ export default function DesignEditor() {
|
|
|
5928
6379
|
lastAppliedFileUpdatedAtRef.current = null;
|
|
5929
6380
|
lastLocalContentRef.current = null;
|
|
5930
6381
|
latestActiveContentRef.current = null;
|
|
5931
|
-
clearLocalUndoRedoStacks();
|
|
5932
6382
|
clearStaleAgentCollabRecovery();
|
|
5933
|
-
syncUndoRedoState();
|
|
5934
6383
|
}
|
|
5935
|
-
}, [
|
|
5936
|
-
activeFileId,
|
|
5937
|
-
clearLocalUndoRedoStacks,
|
|
5938
|
-
clearStaleAgentCollabRecovery,
|
|
5939
|
-
syncUndoRedoState,
|
|
5940
|
-
viewMode,
|
|
5941
|
-
]);
|
|
6384
|
+
}, [activeFileId, clearStaleAgentCollabRecovery, viewMode]);
|
|
5942
6385
|
|
|
5943
6386
|
useEffect(() => {
|
|
5944
6387
|
return clearStaleAgentCollabRecovery;
|
|
@@ -6102,12 +6545,19 @@ export default function DesignEditor() {
|
|
|
6102
6545
|
});
|
|
6103
6546
|
|
|
6104
6547
|
const syncState = () => syncUndoRedoState();
|
|
6105
|
-
const handleStackItemAdded = (
|
|
6548
|
+
const handleStackItemAdded = (event: {
|
|
6549
|
+
origin?: unknown;
|
|
6550
|
+
type?: "undo" | "redo";
|
|
6551
|
+
}) => {
|
|
6552
|
+
if (event.origin !== LOCAL_EDIT_ORIGIN || event.type !== "undo") {
|
|
6553
|
+
syncUndoRedoState();
|
|
6554
|
+
return;
|
|
6555
|
+
}
|
|
6106
6556
|
historyOrderRef.current = [
|
|
6107
6557
|
...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
6108
6558
|
"content",
|
|
6109
6559
|
];
|
|
6110
|
-
|
|
6560
|
+
clearRedoStacks();
|
|
6111
6561
|
syncUndoRedoState();
|
|
6112
6562
|
};
|
|
6113
6563
|
um.on("stack-item-added", handleStackItemAdded);
|
|
@@ -6135,7 +6585,7 @@ export default function DesignEditor() {
|
|
|
6135
6585
|
);
|
|
6136
6586
|
syncUndoRedoState();
|
|
6137
6587
|
};
|
|
6138
|
-
}, [ydoc, isSynced, syncUndoRedoState]);
|
|
6588
|
+
}, [clearRedoStacks, ydoc, isSynced, syncUndoRedoState]);
|
|
6139
6589
|
|
|
6140
6590
|
// Reconcile authoritative external DB content (agent edit / peer-via-SQL) into
|
|
6141
6591
|
// the live preview. This is the robustness fallback the Yjs observe path can't
|
|
@@ -6485,9 +6935,20 @@ export default function DesignEditor() {
|
|
|
6485
6935
|
: (activeFile?.content ?? ""));
|
|
6486
6936
|
const activeContent =
|
|
6487
6937
|
typeof activeContentSource === "string" ? activeContentSource : "";
|
|
6938
|
+
const initialGenerationChromeLimited =
|
|
6939
|
+
shouldLimitEditorChromeUntilContentReady({
|
|
6940
|
+
fileCount: files.length,
|
|
6941
|
+
generating,
|
|
6942
|
+
hasActiveCanvasContent: Boolean(activeFile && activeContent.trim()),
|
|
6943
|
+
pendingGenerationActive,
|
|
6944
|
+
});
|
|
6488
6945
|
useLayoutEffect(() => {
|
|
6489
6946
|
latestActiveContentRef.current = activeContent;
|
|
6490
6947
|
}, [activeContent]);
|
|
6948
|
+
useEffect(() => {
|
|
6949
|
+
if (!initialGenerationChromeLimited || activeLeftPanel === "agent") return;
|
|
6950
|
+
setActiveLeftPanel("agent");
|
|
6951
|
+
}, [activeLeftPanel, initialGenerationChromeLimited]);
|
|
6491
6952
|
const fileContentById = useMemo(() => {
|
|
6492
6953
|
const map = new Map<string, string>();
|
|
6493
6954
|
for (const file of files) {
|
|
@@ -6500,22 +6961,58 @@ export default function DesignEditor() {
|
|
|
6500
6961
|
getFreshScreenContent({
|
|
6501
6962
|
screenId,
|
|
6502
6963
|
activeFileId: activeFile?.id,
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
latestContent: latestActiveContentRef.current,
|
|
6506
|
-
lastLocalContent: lastLocalContentRef.current,
|
|
6507
|
-
}),
|
|
6964
|
+
freshActiveContentFileId: activeFile?.id,
|
|
6965
|
+
freshActiveContent: activeContent,
|
|
6508
6966
|
fileContentById,
|
|
6509
6967
|
}),
|
|
6510
6968
|
[activeContent, activeFile?.id, fileContentById],
|
|
6511
6969
|
);
|
|
6970
|
+
const getProjectionContentForScreen = useCallback(
|
|
6971
|
+
(screenId: string) =>
|
|
6972
|
+
liveScreenSnapshotsById[screenId]?.html ?? getScreenContent(screenId),
|
|
6973
|
+
[getScreenContent, liveScreenSnapshotsById],
|
|
6974
|
+
);
|
|
6975
|
+
const handleScreenExternalContentSnapshot = useCallback(
|
|
6976
|
+
(screenId: string, snapshot: LiveScreenSnapshot) => {
|
|
6977
|
+
setLiveScreenSnapshotsById((current) => {
|
|
6978
|
+
const existing = current[screenId];
|
|
6979
|
+
if (
|
|
6980
|
+
existing?.url === snapshot.url &&
|
|
6981
|
+
existing.html === snapshot.html &&
|
|
6982
|
+
existing.status === snapshot.status &&
|
|
6983
|
+
existing.contentType === snapshot.contentType
|
|
6984
|
+
) {
|
|
6985
|
+
return current;
|
|
6986
|
+
}
|
|
6987
|
+
return { ...current, [screenId]: snapshot };
|
|
6988
|
+
});
|
|
6989
|
+
},
|
|
6990
|
+
[],
|
|
6991
|
+
);
|
|
6992
|
+
const updateLiveScreenSnapshotContent = useCallback(
|
|
6993
|
+
(screenId: string, html: string) => {
|
|
6994
|
+
const existing = liveScreenSnapshotsById[screenId];
|
|
6995
|
+
if (!existing) return false;
|
|
6996
|
+
if (existing.html === html) return true;
|
|
6997
|
+
setLiveScreenSnapshotsById((current) => ({
|
|
6998
|
+
...current,
|
|
6999
|
+
[screenId]: { ...existing, html },
|
|
7000
|
+
}));
|
|
7001
|
+
return true;
|
|
7002
|
+
},
|
|
7003
|
+
[liveScreenSnapshotsById],
|
|
7004
|
+
);
|
|
7005
|
+
const activeProjectionContent =
|
|
7006
|
+
activeFile?.id !== undefined
|
|
7007
|
+
? getProjectionContentForScreen(activeFile.id)
|
|
7008
|
+
: activeContent;
|
|
6512
7009
|
const pageStyles = useMemo(
|
|
6513
7010
|
() => getBodyInlineStyles(activeContent),
|
|
6514
7011
|
[activeContent],
|
|
6515
7012
|
);
|
|
6516
7013
|
const activeCodeLayerProjection = useMemo(
|
|
6517
|
-
() => buildCodeLayerProjection(
|
|
6518
|
-
[
|
|
7014
|
+
() => buildCodeLayerProjection(activeProjectionContent),
|
|
7015
|
+
[activeProjectionContent],
|
|
6519
7016
|
);
|
|
6520
7017
|
const activeMotionTimeline = motionTimelineResult?.timelines?.[0] ?? null;
|
|
6521
7018
|
const activeMotionHydrationFingerprint = activeFile?.id
|
|
@@ -6621,9 +7118,13 @@ export default function DesignEditor() {
|
|
|
6621
7118
|
const designSourceType = useMemo(
|
|
6622
7119
|
() =>
|
|
6623
7120
|
normalizeDesignSourceType(designDataJson.sourceType as unknown) ??
|
|
7121
|
+
normalizeDesignSourceType(designDataJson.sourceMode as unknown) ??
|
|
6624
7122
|
"inline",
|
|
6625
|
-
[designDataJson.sourceType],
|
|
7123
|
+
[designDataJson.sourceMode, designDataJson.sourceType],
|
|
6626
7124
|
);
|
|
7125
|
+
const activeCanvasSourceType =
|
|
7126
|
+
normalizeDesignSourceType(activeOverviewScreen?.sourceType) ??
|
|
7127
|
+
designSourceType;
|
|
6627
7128
|
const sourceCapabilities = useMemo(() => {
|
|
6628
7129
|
const caps = resolveSourceCapabilities(designSourceType);
|
|
6629
7130
|
return DESIGN_CAPABILITY_NAMES.filter((name) => hasCapability(caps, name));
|
|
@@ -6666,6 +7167,11 @@ export default function DesignEditor() {
|
|
|
6666
7167
|
? bridgeSourceIdForCodeLayerNode(selectedCodeLayerNode)
|
|
6667
7168
|
: undefined;
|
|
6668
7169
|
}, [selectedCodeLayerNode]);
|
|
7170
|
+
const selectedElementAlreadyComponent = useMemo(() => {
|
|
7171
|
+
if (!selectedElement) return false;
|
|
7172
|
+
if (selectedElement.componentName?.trim()) return true;
|
|
7173
|
+
return codeLayerNodeLooksLikeComponent(selectedCodeLayerNode);
|
|
7174
|
+
}, [selectedCodeLayerNode, selectedElement]);
|
|
6669
7175
|
|
|
6670
7176
|
useEffect(() => {
|
|
6671
7177
|
setShaderFillPreview(null);
|
|
@@ -6686,11 +7192,8 @@ export default function DesignEditor() {
|
|
|
6686
7192
|
// Outer HTML of the selection — backs the inline/Alpine "Inspect code" view.
|
|
6687
7193
|
const selectedElementOuterHtml = useMemo(() => {
|
|
6688
7194
|
if (!selectedElement?.selector) return null;
|
|
6689
|
-
return (
|
|
6690
|
-
|
|
6691
|
-
getElementOuterHtml(activeContent, selectedElement.selector)
|
|
6692
|
-
);
|
|
6693
|
-
}, [activeContent, selectedElement?.selector, selectedElement?.htmlContent]);
|
|
7195
|
+
return getElementOuterHtml(activeContent, selectedElement.selector);
|
|
7196
|
+
}, [activeContent, selectedElement?.selector]);
|
|
6694
7197
|
|
|
6695
7198
|
// §6.3 — the motion-dock target: the selected element's literal
|
|
6696
7199
|
// `data-agent-native-node-id` (the value the motion compiler + preview bridge
|
|
@@ -6757,7 +7260,13 @@ export default function DesignEditor() {
|
|
|
6757
7260
|
// resolveNodeToFile bridge op, which is wired through open-component-source
|
|
6758
7261
|
// when an external file path is available; until that round-trip is hooked
|
|
6759
7262
|
// up here the popover shows the projected HTML for all source types.
|
|
6760
|
-
return {
|
|
7263
|
+
return {
|
|
7264
|
+
html: selectedElementOuterHtml,
|
|
7265
|
+
tagName: selectedElement.tagName,
|
|
7266
|
+
id: selectedElement.id,
|
|
7267
|
+
classes: selectedElement.classes,
|
|
7268
|
+
sourceLocation: null,
|
|
7269
|
+
};
|
|
6761
7270
|
}, [selectedElement, selectedElementOuterHtml]);
|
|
6762
7271
|
|
|
6763
7272
|
const handleCreateComponent = useCallback(
|
|
@@ -6783,7 +7292,7 @@ export default function DesignEditor() {
|
|
|
6783
7292
|
// Follow-up: ask the Design agent to extract props and replace repeated
|
|
6784
7293
|
// instances with this component. The deterministic annotate above is the
|
|
6785
7294
|
// core; this is an enhancement that runs in the agent chat.
|
|
6786
|
-
|
|
7295
|
+
sendToDesignAgentChat({
|
|
6787
7296
|
message: `Extract props for the "${name}" component and replace repeated instances on this design with it.`,
|
|
6788
7297
|
context: [
|
|
6789
7298
|
`Design id: "${id}".`,
|
|
@@ -6838,18 +7347,22 @@ export default function DesignEditor() {
|
|
|
6838
7347
|
(screenId: string) => {
|
|
6839
7348
|
if (screenId === activeFile?.id) return activeCodeLayerProjection;
|
|
6840
7349
|
if (!fileContentById.has(screenId)) return null;
|
|
6841
|
-
return buildCodeLayerProjection(
|
|
7350
|
+
return buildCodeLayerProjection(getProjectionContentForScreen(screenId));
|
|
6842
7351
|
},
|
|
6843
7352
|
[
|
|
6844
7353
|
activeCodeLayerProjection,
|
|
6845
7354
|
activeFile?.id,
|
|
6846
7355
|
fileContentById,
|
|
6847
|
-
|
|
7356
|
+
getProjectionContentForScreen,
|
|
6848
7357
|
],
|
|
6849
7358
|
);
|
|
6850
7359
|
|
|
6851
7360
|
const replacePreviewContent = useCallback(
|
|
6852
|
-
(
|
|
7361
|
+
(
|
|
7362
|
+
nextContent: string,
|
|
7363
|
+
selector?: string | null,
|
|
7364
|
+
options: { forceFullDocument?: boolean } = {},
|
|
7365
|
+
) => {
|
|
6853
7366
|
const replaceContent = (window as any).__designCanvasReplaceContent;
|
|
6854
7367
|
if (typeof replaceContent !== "function") return false;
|
|
6855
7368
|
return Boolean(
|
|
@@ -6857,7 +7370,9 @@ export default function DesignEditor() {
|
|
|
6857
7370
|
nextContent,
|
|
6858
7371
|
selector ?? selectedCanvasSelector,
|
|
6859
7372
|
selectedCanvasSelectorCandidates,
|
|
6860
|
-
|
|
7373
|
+
{
|
|
7374
|
+
forceFullDocument: options.forceFullDocument === true,
|
|
7375
|
+
},
|
|
6861
7376
|
),
|
|
6862
7377
|
);
|
|
6863
7378
|
},
|
|
@@ -6884,9 +7399,11 @@ export default function DesignEditor() {
|
|
|
6884
7399
|
options: {
|
|
6885
7400
|
refreshPreview?: boolean;
|
|
6886
7401
|
skipPreview?: boolean;
|
|
7402
|
+
forcePreviewFullDocument?: boolean;
|
|
6887
7403
|
immediateSave?: boolean;
|
|
6888
7404
|
persist?: boolean;
|
|
6889
7405
|
recordHistory?: boolean;
|
|
7406
|
+
historyBeforeContent?: string;
|
|
6890
7407
|
updatedAt?: string;
|
|
6891
7408
|
} = {},
|
|
6892
7409
|
) => {
|
|
@@ -6894,12 +7411,18 @@ export default function DesignEditor() {
|
|
|
6894
7411
|
const shouldRecordHistory =
|
|
6895
7412
|
options.recordHistory !== false && !options.updatedAt;
|
|
6896
7413
|
const previousContent =
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
7414
|
+
typeof options.historyBeforeContent === "string"
|
|
7415
|
+
? options.historyBeforeContent
|
|
7416
|
+
: collabContentFileIdRef.current === activeFile.id &&
|
|
7417
|
+
typeof collabContentRef.current === "string"
|
|
7418
|
+
? collabContentRef.current
|
|
7419
|
+
: (activeFile.content ?? "");
|
|
6901
7420
|
const yjsHistoryAvailable = Boolean(
|
|
6902
|
-
shouldRecordHistory &&
|
|
7421
|
+
shouldRecordHistory &&
|
|
7422
|
+
viewModeRef.current !== "overview" &&
|
|
7423
|
+
ydoc &&
|
|
7424
|
+
isSynced &&
|
|
7425
|
+
undoManagerRef.current,
|
|
6903
7426
|
);
|
|
6904
7427
|
if (
|
|
6905
7428
|
!suppressContentHistoryRef.current &&
|
|
@@ -6907,11 +7430,16 @@ export default function DesignEditor() {
|
|
|
6907
7430
|
!yjsHistoryAvailable &&
|
|
6908
7431
|
previousContent !== nextContent
|
|
6909
7432
|
) {
|
|
6910
|
-
|
|
7433
|
+
const change = {
|
|
6911
7434
|
fileId: activeFile.id,
|
|
6912
7435
|
before: previousContent,
|
|
6913
7436
|
after: nextContent,
|
|
6914
|
-
}
|
|
7437
|
+
};
|
|
7438
|
+
if (viewModeRef.current === "overview") {
|
|
7439
|
+
recordContentHistoryEntry(change);
|
|
7440
|
+
} else {
|
|
7441
|
+
recordLocalContentHistoryEntry(change);
|
|
7442
|
+
}
|
|
6915
7443
|
}
|
|
6916
7444
|
if (options.updatedAt) {
|
|
6917
7445
|
clearPendingLocalFileContent(activeFile.id);
|
|
@@ -6960,8 +7488,18 @@ export default function DesignEditor() {
|
|
|
6960
7488
|
? true
|
|
6961
7489
|
: forceRefresh
|
|
6962
7490
|
? false
|
|
6963
|
-
: replacePreviewContent(
|
|
6964
|
-
|
|
7491
|
+
: replacePreviewContent(
|
|
7492
|
+
nextContent,
|
|
7493
|
+
null,
|
|
7494
|
+
options.forcePreviewFullDocument
|
|
7495
|
+
? { forceFullDocument: true }
|
|
7496
|
+
: undefined,
|
|
7497
|
+
);
|
|
7498
|
+
if (
|
|
7499
|
+
forceRefresh ||
|
|
7500
|
+
options.forcePreviewFullDocument ||
|
|
7501
|
+
!replacedPreview
|
|
7502
|
+
) {
|
|
6965
7503
|
setContentRenderRevision((revision) => revision + 1);
|
|
6966
7504
|
}
|
|
6967
7505
|
if (ydoc && isSynced) {
|
|
@@ -6972,7 +7510,7 @@ export default function DesignEditor() {
|
|
|
6972
7510
|
ytext.delete(0, ytext.length);
|
|
6973
7511
|
ytext.insert(0, nextContent);
|
|
6974
7512
|
},
|
|
6975
|
-
|
|
7513
|
+
yjsHistoryAvailable ? LOCAL_EDIT_ORIGIN : TAB_ID,
|
|
6976
7514
|
);
|
|
6977
7515
|
}
|
|
6978
7516
|
}
|
|
@@ -6996,6 +7534,7 @@ export default function DesignEditor() {
|
|
|
6996
7534
|
queueFileContentSave,
|
|
6997
7535
|
replacePreviewContent,
|
|
6998
7536
|
recordContentHistoryEntry,
|
|
7537
|
+
recordLocalContentHistoryEntry,
|
|
6999
7538
|
syncUndoRedoState,
|
|
7000
7539
|
ydoc,
|
|
7001
7540
|
],
|
|
@@ -7008,6 +7547,7 @@ export default function DesignEditor() {
|
|
|
7008
7547
|
options: {
|
|
7009
7548
|
refreshPreview?: boolean;
|
|
7010
7549
|
skipPreview?: boolean;
|
|
7550
|
+
forcePreviewFullDocument?: boolean;
|
|
7011
7551
|
persist?: boolean;
|
|
7012
7552
|
recordHistory?: boolean;
|
|
7013
7553
|
updatedAt?: string;
|
|
@@ -7107,27 +7647,47 @@ export default function DesignEditor() {
|
|
|
7107
7647
|
motionAutosaveTimerRef.current = window.setTimeout(() => {
|
|
7108
7648
|
motionAutosaveTimerRef.current = null;
|
|
7109
7649
|
if (motionAutosaveRevisionRef.current !== revisionAtSchedule) return;
|
|
7650
|
+
const tracksForSave = motionTracks.map(
|
|
7651
|
+
({ label: _label, ...track }) => track,
|
|
7652
|
+
);
|
|
7653
|
+
const currentContent = getFreshActiveFileContent({
|
|
7654
|
+
activeContent,
|
|
7655
|
+
latestContent: latestActiveContentRef.current,
|
|
7656
|
+
lastLocalContent: lastLocalContentRef.current,
|
|
7657
|
+
});
|
|
7658
|
+
const localMotionCss = compileMotionTimeline({
|
|
7659
|
+
id: motionTimelineId ?? "",
|
|
7660
|
+
designId: id,
|
|
7661
|
+
sourceRef: activeFile.id,
|
|
7662
|
+
filePath: null,
|
|
7663
|
+
tracks: tracksForSave,
|
|
7664
|
+
durationMs: motionDurationMs,
|
|
7665
|
+
defaultEase: "ease",
|
|
7666
|
+
compiledHash: null,
|
|
7667
|
+
createdAt: "",
|
|
7668
|
+
updatedAt: "",
|
|
7669
|
+
}).css;
|
|
7670
|
+
const localPatchedContent = injectManagedMotionCss(
|
|
7671
|
+
currentContent,
|
|
7672
|
+
localMotionCss,
|
|
7673
|
+
);
|
|
7110
7674
|
applyMotionEdit(
|
|
7111
7675
|
{
|
|
7112
7676
|
designId: id,
|
|
7113
7677
|
fileId: activeFile.id,
|
|
7114
7678
|
timelineId: motionTimelineId ?? undefined,
|
|
7115
7679
|
sourceRef: activeFile.id,
|
|
7116
|
-
tracks:
|
|
7680
|
+
tracks: tracksForSave,
|
|
7117
7681
|
durationMs: motionDurationMs,
|
|
7118
|
-
currentContent
|
|
7119
|
-
|
|
7120
|
-
latestContent: latestActiveContentRef.current,
|
|
7121
|
-
lastLocalContent: lastLocalContentRef.current,
|
|
7122
|
-
}),
|
|
7123
|
-
includeContent: true,
|
|
7682
|
+
currentContent,
|
|
7683
|
+
includeContent: false,
|
|
7124
7684
|
},
|
|
7125
7685
|
{
|
|
7126
7686
|
onSuccess: (result) => {
|
|
7127
7687
|
const response = result as {
|
|
7128
7688
|
fileId?: unknown;
|
|
7129
7689
|
timelineId?: unknown;
|
|
7130
|
-
|
|
7690
|
+
updatedAt?: unknown;
|
|
7131
7691
|
};
|
|
7132
7692
|
if (typeof response.timelineId === "string") {
|
|
7133
7693
|
setMotionTimelineId(response.timelineId);
|
|
@@ -7139,10 +7699,14 @@ export default function DesignEditor() {
|
|
|
7139
7699
|
}
|
|
7140
7700
|
if (
|
|
7141
7701
|
typeof response.fileId === "string" &&
|
|
7142
|
-
|
|
7702
|
+
response.fileId === activeFile.id
|
|
7143
7703
|
) {
|
|
7144
|
-
applyFileContentUpdate(response.fileId,
|
|
7145
|
-
refreshPreview:
|
|
7704
|
+
applyFileContentUpdate(response.fileId, localPatchedContent, {
|
|
7705
|
+
refreshPreview: true,
|
|
7706
|
+
persist: false,
|
|
7707
|
+
...(typeof response.updatedAt === "string"
|
|
7708
|
+
? { updatedAt: response.updatedAt }
|
|
7709
|
+
: {}),
|
|
7146
7710
|
});
|
|
7147
7711
|
}
|
|
7148
7712
|
void queryClient.invalidateQueries({
|
|
@@ -7273,7 +7837,9 @@ export default function DesignEditor() {
|
|
|
7273
7837
|
: storedContent;
|
|
7274
7838
|
})()
|
|
7275
7839
|
: storedContent);
|
|
7276
|
-
const nextContent = appendCanvasPrimitiveToHtml(baseContent, primitive
|
|
7840
|
+
const nextContent = appendCanvasPrimitiveToHtml(baseContent, primitive, {
|
|
7841
|
+
preserveNegativePosition: targetFile.id === boardFileId,
|
|
7842
|
+
});
|
|
7277
7843
|
if (!nextContent) {
|
|
7278
7844
|
toast.error(t("designEditor.toasts.primitiveInsertFailed"));
|
|
7279
7845
|
return false;
|
|
@@ -7287,7 +7853,11 @@ export default function DesignEditor() {
|
|
|
7287
7853
|
: null;
|
|
7288
7854
|
|
|
7289
7855
|
if (targetFile.id === activeFile?.id) {
|
|
7290
|
-
applyLocalContentUpdate(nextContent, {
|
|
7856
|
+
applyLocalContentUpdate(nextContent, {
|
|
7857
|
+
forcePreviewFullDocument: true,
|
|
7858
|
+
historyBeforeContent: baseContent,
|
|
7859
|
+
immediateSave: true,
|
|
7860
|
+
});
|
|
7291
7861
|
} else {
|
|
7292
7862
|
recordContentHistoryEntry({
|
|
7293
7863
|
fileId: targetFile.id,
|
|
@@ -7335,6 +7905,7 @@ export default function DesignEditor() {
|
|
|
7335
7905
|
activeContent,
|
|
7336
7906
|
activeFile?.id,
|
|
7337
7907
|
applyLocalContentUpdate,
|
|
7908
|
+
boardFileId,
|
|
7338
7909
|
canEditDesign,
|
|
7339
7910
|
files,
|
|
7340
7911
|
id,
|
|
@@ -7348,7 +7919,7 @@ export default function DesignEditor() {
|
|
|
7348
7919
|
);
|
|
7349
7920
|
|
|
7350
7921
|
const handlePrimitiveCreated = useCallback(
|
|
7351
|
-
(screenId: string, nodeId: string) => {
|
|
7922
|
+
(screenId: string, nodeId: string, options?: { selectFrame?: boolean }) => {
|
|
7352
7923
|
// B2/B4 fix: stay in overview mode after drawing a primitive. The user
|
|
7353
7924
|
// drew a shape on the board — they should remain on the board with the
|
|
7354
7925
|
// new primitive selected, matching Figma behaviour. We activate the
|
|
@@ -7363,7 +7934,9 @@ export default function DesignEditor() {
|
|
|
7363
7934
|
setSelectedElement(null);
|
|
7364
7935
|
setHoveredElement(null);
|
|
7365
7936
|
setSelectedLayerIdsState([nodeId]);
|
|
7366
|
-
setOverviewSelectedScreenIds(
|
|
7937
|
+
setOverviewSelectedScreenIds(
|
|
7938
|
+
options?.selectFrame === false ? [] : [screenId],
|
|
7939
|
+
);
|
|
7367
7940
|
setActiveTool("move");
|
|
7368
7941
|
setMode("edit");
|
|
7369
7942
|
});
|
|
@@ -7380,7 +7953,7 @@ export default function DesignEditor() {
|
|
|
7380
7953
|
scheduleBeginTextEditForScreen(screenId, textNodeId);
|
|
7381
7954
|
}
|
|
7382
7955
|
},
|
|
7383
|
-
[clearPendingOverviewLayerSelectionTimer],
|
|
7956
|
+
[clearPendingOverviewLayerSelectionTimer, scheduleBeginTextEditForScreen],
|
|
7384
7957
|
);
|
|
7385
7958
|
|
|
7386
7959
|
/**
|
|
@@ -7394,22 +7967,14 @@ export default function DesignEditor() {
|
|
|
7394
7967
|
if (!boardFileId || !canEditDesign) return false;
|
|
7395
7968
|
const result = handleCreatePrimitive(boardFileId, primitive);
|
|
7396
7969
|
if (!result) return false;
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
// so use the same iframe-targeted retry path as screen primitives.
|
|
7401
|
-
if (primitive.kind === "text") {
|
|
7402
|
-
const textNodeId =
|
|
7403
|
-
pendingTextEditNodeIdRef.current ?? primitive.nodeId ?? null;
|
|
7404
|
-
pendingTextEditNodeIdRef.current = null;
|
|
7405
|
-
if (textNodeId) {
|
|
7406
|
-
scheduleBeginTextEditForScreen(boardFileId, textNodeId);
|
|
7407
|
-
}
|
|
7970
|
+
const nodeId = typeof result === "string" ? result : primitive.nodeId;
|
|
7971
|
+
if (nodeId) {
|
|
7972
|
+
handlePrimitiveCreated(boardFileId, nodeId, { selectFrame: false });
|
|
7408
7973
|
}
|
|
7409
7974
|
|
|
7410
7975
|
return result;
|
|
7411
7976
|
},
|
|
7412
|
-
[boardFileId, canEditDesign, handleCreatePrimitive],
|
|
7977
|
+
[boardFileId, canEditDesign, handleCreatePrimitive, handlePrimitiveCreated],
|
|
7413
7978
|
);
|
|
7414
7979
|
|
|
7415
7980
|
const handleOverviewScreenSelectionChange = useCallback(
|
|
@@ -7447,6 +8012,7 @@ export default function DesignEditor() {
|
|
|
7447
8012
|
|
|
7448
8013
|
const handleMoveTool = useCallback(() => {
|
|
7449
8014
|
if (!canEditDesign) return;
|
|
8015
|
+
blurActiveDesignEditableTarget();
|
|
7450
8016
|
setActiveTool("move");
|
|
7451
8017
|
setMode("edit");
|
|
7452
8018
|
setDrawMode(false);
|
|
@@ -7455,6 +8021,7 @@ export default function DesignEditor() {
|
|
|
7455
8021
|
|
|
7456
8022
|
const handleFrameTool = useCallback(() => {
|
|
7457
8023
|
if (!canEditDesign) return;
|
|
8024
|
+
blurActiveDesignEditableTarget();
|
|
7458
8025
|
flushSync(() => {
|
|
7459
8026
|
setActiveTool("frame");
|
|
7460
8027
|
setMode("edit");
|
|
@@ -7468,6 +8035,7 @@ export default function DesignEditor() {
|
|
|
7468
8035
|
|
|
7469
8036
|
const handleTextTool = useCallback(() => {
|
|
7470
8037
|
if (!canEditDesign) return;
|
|
8038
|
+
blurActiveDesignEditableTarget();
|
|
7471
8039
|
flushSync(() => {
|
|
7472
8040
|
setActiveTool("text");
|
|
7473
8041
|
viewModeRef.current = "overview";
|
|
@@ -7482,6 +8050,7 @@ export default function DesignEditor() {
|
|
|
7482
8050
|
const handleShapeTool = useCallback(
|
|
7483
8051
|
(tool: ShapeTool) => {
|
|
7484
8052
|
if (!canEditDesign) return;
|
|
8053
|
+
blurActiveDesignEditableTarget();
|
|
7485
8054
|
flushSync(() => {
|
|
7486
8055
|
setActiveTool(tool);
|
|
7487
8056
|
viewModeRef.current = "overview";
|
|
@@ -7501,6 +8070,7 @@ export default function DesignEditor() {
|
|
|
7501
8070
|
|
|
7502
8071
|
const handlePenTool = useCallback(() => {
|
|
7503
8072
|
if (!canEditDesign) return;
|
|
8073
|
+
blurActiveDesignEditableTarget();
|
|
7504
8074
|
flushSync(() => {
|
|
7505
8075
|
setActiveTool("pen");
|
|
7506
8076
|
viewModeRef.current = "overview";
|
|
@@ -7514,6 +8084,7 @@ export default function DesignEditor() {
|
|
|
7514
8084
|
|
|
7515
8085
|
const handleHandTool = useCallback(() => {
|
|
7516
8086
|
if (!canEditDesign) return;
|
|
8087
|
+
blurActiveDesignEditableTarget();
|
|
7517
8088
|
setActiveTool("hand");
|
|
7518
8089
|
setMode("edit");
|
|
7519
8090
|
setDrawMode(false);
|
|
@@ -7524,6 +8095,7 @@ export default function DesignEditor() {
|
|
|
7524
8095
|
|
|
7525
8096
|
const handleScaleTool = useCallback(() => {
|
|
7526
8097
|
if (!activeFile || !canEditDesign) return;
|
|
8098
|
+
blurActiveDesignEditableTarget();
|
|
7527
8099
|
setActiveTool("scale");
|
|
7528
8100
|
setMode("edit");
|
|
7529
8101
|
setDrawMode(false);
|
|
@@ -7630,7 +8202,7 @@ export default function DesignEditor() {
|
|
|
7630
8202
|
"For tiny source changes, prefer `edit-design`, but make sure the tweak definitions are saved so the Tweaks panel updates.",
|
|
7631
8203
|
].join("\n");
|
|
7632
8204
|
|
|
7633
|
-
|
|
8205
|
+
sendToDesignAgentChat({
|
|
7634
8206
|
message: `Add tweak controls to "${design.title}": ${trimmed}`,
|
|
7635
8207
|
context,
|
|
7636
8208
|
submit: true,
|
|
@@ -7894,7 +8466,7 @@ export default function DesignEditor() {
|
|
|
7894
8466
|
);
|
|
7895
8467
|
|
|
7896
8468
|
const handleScreenElementSelect = useCallback(
|
|
7897
|
-
(screenId: string, info: ElementInfo) => {
|
|
8469
|
+
(screenId: string, info: ElementInfo, intent?: ElementSelectionIntent) => {
|
|
7898
8470
|
const pendingLayerId = pendingOverviewLayerSelectionRef.current;
|
|
7899
8471
|
const pendingScreenId = pendingOverviewScreenSelectionRef.current;
|
|
7900
8472
|
const createdSelection = createdOverviewLayerSelection;
|
|
@@ -7927,11 +8499,33 @@ export default function DesignEditor() {
|
|
|
7927
8499
|
) {
|
|
7928
8500
|
return;
|
|
7929
8501
|
}
|
|
8502
|
+
const additiveSelection = Boolean(
|
|
8503
|
+
node &&
|
|
8504
|
+
(intent?.additive ||
|
|
8505
|
+
intent?.range ||
|
|
8506
|
+
intent?.shiftKey ||
|
|
8507
|
+
intent?.metaKey ||
|
|
8508
|
+
intent?.ctrlKey),
|
|
8509
|
+
);
|
|
7930
8510
|
setActiveFileId(screenId);
|
|
7931
8511
|
setSelectedElement(canonical);
|
|
7932
8512
|
setHoveredElement(null);
|
|
7933
8513
|
setHoveredElementScreenId(null);
|
|
7934
|
-
|
|
8514
|
+
if (node && additiveSelection) {
|
|
8515
|
+
setSelectedLayerIdsState((current) => {
|
|
8516
|
+
const removeExisting =
|
|
8517
|
+
Boolean(intent?.metaKey || intent?.ctrlKey) &&
|
|
8518
|
+
!intent?.shiftKey &&
|
|
8519
|
+
current.includes(node.id);
|
|
8520
|
+
if (removeExisting) {
|
|
8521
|
+
const next = current.filter((layerId) => layerId !== node.id);
|
|
8522
|
+
return next.length > 0 ? next : [node.id];
|
|
8523
|
+
}
|
|
8524
|
+
return dedupeStringIds([...current, node.id]);
|
|
8525
|
+
});
|
|
8526
|
+
} else {
|
|
8527
|
+
setSelectedLayerIdsState(node ? [node.id] : []);
|
|
8528
|
+
}
|
|
7935
8529
|
if (viewModeRef.current === "overview") {
|
|
7936
8530
|
setOverviewSelectedScreenIds([]);
|
|
7937
8531
|
}
|
|
@@ -7984,10 +8578,10 @@ export default function DesignEditor() {
|
|
|
7984
8578
|
);
|
|
7985
8579
|
|
|
7986
8580
|
const handleElementSelect = useCallback(
|
|
7987
|
-
(info: ElementInfo) => {
|
|
8581
|
+
(info: ElementInfo, intent?: ElementSelectionIntent) => {
|
|
7988
8582
|
const screenId = activeFile?.id ?? activeFileId;
|
|
7989
8583
|
if (screenId) {
|
|
7990
|
-
handleScreenElementSelect(screenId, info);
|
|
8584
|
+
handleScreenElementSelect(screenId, info, intent);
|
|
7991
8585
|
return;
|
|
7992
8586
|
}
|
|
7993
8587
|
setSelectedElement(
|
|
@@ -8172,7 +8766,11 @@ export default function DesignEditor() {
|
|
|
8172
8766
|
// advance lastLocalContentRef.current to resolvedNextContent below, the
|
|
8173
8767
|
// next synchronous call reads the previous call's result and the patches
|
|
8174
8768
|
// compose. Falls back to activeContent when the ref is unset (file switch).
|
|
8769
|
+
const activeLiveSnapshot = activeFile
|
|
8770
|
+
? liveScreenSnapshotsById[activeFile.id]
|
|
8771
|
+
: undefined;
|
|
8175
8772
|
const baseContent =
|
|
8773
|
+
activeLiveSnapshot?.html ??
|
|
8176
8774
|
latestActiveContentRef.current ??
|
|
8177
8775
|
lastLocalContentRef.current ??
|
|
8178
8776
|
activeContent;
|
|
@@ -8369,37 +8967,78 @@ export default function DesignEditor() {
|
|
|
8369
8967
|
);
|
|
8370
8968
|
})
|
|
8371
8969
|
: null;
|
|
8970
|
+
const liveSnapshotUpdated = activeLiveSnapshot
|
|
8971
|
+
? updateLiveScreenSnapshotContent(activeFile.id, resolvedNextContent)
|
|
8972
|
+
: false;
|
|
8973
|
+
if (liveSnapshotUpdated) {
|
|
8974
|
+
setPatchProof((prev) =>
|
|
8975
|
+
prev?.id === proofId ? { ...prev, status: "queued" } : prev,
|
|
8976
|
+
);
|
|
8977
|
+
if (!runtimeStyleApplied) {
|
|
8978
|
+
setContentRenderRevision((revision) => revision + 1);
|
|
8979
|
+
}
|
|
8980
|
+
} else {
|
|
8981
|
+
const yjsHistoryAvailable = Boolean(
|
|
8982
|
+
viewModeRef.current !== "overview" &&
|
|
8983
|
+
ydoc &&
|
|
8984
|
+
isSynced &&
|
|
8985
|
+
undoManagerRef.current,
|
|
8986
|
+
);
|
|
8987
|
+
if (
|
|
8988
|
+
!yjsHistoryAvailable &&
|
|
8989
|
+
!suppressContentHistoryRef.current &&
|
|
8990
|
+
baseContent !== resolvedNextContent
|
|
8991
|
+
) {
|
|
8992
|
+
const change = {
|
|
8993
|
+
fileId: activeFile.id,
|
|
8994
|
+
before: baseContent,
|
|
8995
|
+
after: resolvedNextContent,
|
|
8996
|
+
};
|
|
8997
|
+
if (viewModeRef.current === "overview") {
|
|
8998
|
+
recordContentHistoryEntry(change);
|
|
8999
|
+
} else {
|
|
9000
|
+
recordLocalContentHistoryEntry(change);
|
|
9001
|
+
}
|
|
9002
|
+
}
|
|
8372
9003
|
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
9004
|
+
setCollabContent(resolvedNextContent);
|
|
9005
|
+
setCollabContentFileId(activeFile.id);
|
|
9006
|
+
setPatchProof((prev) =>
|
|
9007
|
+
prev?.id === proofId ? { ...prev, status: "queued" } : prev,
|
|
9008
|
+
);
|
|
9009
|
+
// Mark as our own write so the get-design reconcile + Yjs observe don't
|
|
9010
|
+
// treat the echo as an external edit and fight the live value.
|
|
9011
|
+
lastLocalContentRef.current = resolvedNextContent;
|
|
9012
|
+
latestActiveContentRef.current = resolvedNextContent;
|
|
9013
|
+
// Write the edit into the shared Y.Doc so other open clients see it live
|
|
9014
|
+
// through Yjs (not only via the slower update-file → applyText round-trip).
|
|
9015
|
+
// Single-screen edits use the active-file UndoManager. Overview edits are
|
|
9016
|
+
// tracked in the global file-content stack so all screens share one order.
|
|
9017
|
+
if (ydoc && isSynced) {
|
|
9018
|
+
const ytext = ydoc.getText("content");
|
|
9019
|
+
if (ytext.toString() !== resolvedNextContent) {
|
|
9020
|
+
ydoc.transact(
|
|
9021
|
+
() => {
|
|
9022
|
+
ytext.delete(0, ytext.length);
|
|
9023
|
+
ytext.insert(0, resolvedNextContent);
|
|
9024
|
+
},
|
|
9025
|
+
yjsHistoryAvailable ? LOCAL_EDIT_ORIGIN : TAB_ID,
|
|
9026
|
+
);
|
|
9027
|
+
}
|
|
9028
|
+
}
|
|
9029
|
+
queueFileContentSave(activeFile.id, resolvedNextContent, {
|
|
9030
|
+
syncCollab: !(ydoc && isSynced),
|
|
9031
|
+
});
|
|
9032
|
+
if (
|
|
9033
|
+
shouldReplacePreviewAfterVisualStyleCommit({
|
|
9034
|
+
runtimeApplied: options.runtimeApplied,
|
|
9035
|
+
runtimeStyleApplied,
|
|
9036
|
+
}) &&
|
|
9037
|
+
!replacePreviewContent(resolvedNextContent, selector)
|
|
9038
|
+
) {
|
|
9039
|
+
setContentRenderRevision((revision) => revision + 1);
|
|
8392
9040
|
}
|
|
8393
9041
|
}
|
|
8394
|
-
queueFileContentSave(activeFile.id, resolvedNextContent, {
|
|
8395
|
-
syncCollab: !(ydoc && isSynced),
|
|
8396
|
-
});
|
|
8397
|
-
if (
|
|
8398
|
-
!runtimeStyleApplied &&
|
|
8399
|
-
!replacePreviewContent(resolvedNextContent, selector)
|
|
8400
|
-
) {
|
|
8401
|
-
setContentRenderRevision((revision) => revision + 1);
|
|
8402
|
-
}
|
|
8403
9042
|
if (resolvedNode) setSelectedLayerIdsState([resolvedNode.id]);
|
|
8404
9043
|
setSelectedElement((prev) => {
|
|
8405
9044
|
if (options.elementInfo) return options.elementInfo;
|
|
@@ -8426,15 +9065,128 @@ export default function DesignEditor() {
|
|
|
8426
9065
|
activeFile,
|
|
8427
9066
|
activeBreakpointWidthState,
|
|
8428
9067
|
canEditDesign,
|
|
9068
|
+
liveScreenSnapshotsById,
|
|
8429
9069
|
queueFileContentSave,
|
|
9070
|
+
recordContentHistoryEntry,
|
|
9071
|
+
recordLocalContentHistoryEntry,
|
|
8430
9072
|
replacePreviewContent,
|
|
8431
9073
|
selectedElement,
|
|
8432
9074
|
t,
|
|
9075
|
+
updateLiveScreenSnapshotContent,
|
|
8433
9076
|
ydoc,
|
|
8434
9077
|
isSynced,
|
|
8435
9078
|
],
|
|
8436
9079
|
);
|
|
8437
9080
|
|
|
9081
|
+
const commitStylesToSelectedLayers = useCallback(
|
|
9082
|
+
(styles: Record<string, string>) => {
|
|
9083
|
+
if (!canEditDesign) return false;
|
|
9084
|
+
const entries = Object.entries(styles).filter(
|
|
9085
|
+
([, value]) => value !== undefined,
|
|
9086
|
+
);
|
|
9087
|
+
if (entries.length === 0) return false;
|
|
9088
|
+
const effectiveLayerState = effectiveCodeLayerStateRef.current;
|
|
9089
|
+
const targets = selectedLayerTargetsRef.current.filter(
|
|
9090
|
+
(target) =>
|
|
9091
|
+
!effectiveLayerState.lockedIds.has(target.fileId) &&
|
|
9092
|
+
!effectiveLayerState.hiddenIds.has(target.fileId) &&
|
|
9093
|
+
!effectiveLayerState.lockedIds.has(target.layerId) &&
|
|
9094
|
+
!effectiveLayerState.hiddenIds.has(target.layerId),
|
|
9095
|
+
);
|
|
9096
|
+
if (targets.length <= 1) return false;
|
|
9097
|
+
|
|
9098
|
+
const targetsByFile = new Map<string, SelectedLayerTarget[]>();
|
|
9099
|
+
targets.forEach((target) => {
|
|
9100
|
+
targetsByFile.set(target.fileId, [
|
|
9101
|
+
...(targetsByFile.get(target.fileId) ?? []),
|
|
9102
|
+
target,
|
|
9103
|
+
]);
|
|
9104
|
+
});
|
|
9105
|
+
|
|
9106
|
+
let appliedAny = false;
|
|
9107
|
+
targetsByFile.forEach((fileTargets, fileId) => {
|
|
9108
|
+
const baseContent =
|
|
9109
|
+
fileId === activeFile?.id
|
|
9110
|
+
? getFreshActiveFileContent({
|
|
9111
|
+
activeContent,
|
|
9112
|
+
latestContent: latestActiveContentRef.current,
|
|
9113
|
+
lastLocalContent: lastLocalContentRef.current,
|
|
9114
|
+
})
|
|
9115
|
+
: getScreenContent(fileId);
|
|
9116
|
+
if (!baseContent) return;
|
|
9117
|
+
let nextContent = baseContent;
|
|
9118
|
+
let projection = buildCodeLayerProjection(nextContent);
|
|
9119
|
+
fileTargets.forEach((target) => {
|
|
9120
|
+
const sourceId = bridgeSourceIdForCodeLayerNode(target.node);
|
|
9121
|
+
const selector = preferredCodeLayerSelector(target.node);
|
|
9122
|
+
const node =
|
|
9123
|
+
projection.nodes.find((candidate) =>
|
|
9124
|
+
codeLayerNodeMatchesBridgeTarget(candidate, selector, sourceId),
|
|
9125
|
+
) ??
|
|
9126
|
+
projection.nodes.find(
|
|
9127
|
+
(candidate) => candidate.id === target.node.id,
|
|
9128
|
+
);
|
|
9129
|
+
if (!node) return;
|
|
9130
|
+
|
|
9131
|
+
entries.forEach(([property, value]) => {
|
|
9132
|
+
const patch = applyVisualEdit(nextContent, {
|
|
9133
|
+
kind: "style",
|
|
9134
|
+
target: { nodeId: node.id },
|
|
9135
|
+
property,
|
|
9136
|
+
value,
|
|
9137
|
+
});
|
|
9138
|
+
if (patch.result.status !== "applied") return;
|
|
9139
|
+
nextContent = patch.content;
|
|
9140
|
+
projection = patch.projection;
|
|
9141
|
+
});
|
|
9142
|
+
});
|
|
9143
|
+
if (nextContent === baseContent) return;
|
|
9144
|
+
appliedAny = true;
|
|
9145
|
+
applyFileContentUpdate(fileId, nextContent, {
|
|
9146
|
+
refreshPreview: fileId === activeFile?.id,
|
|
9147
|
+
});
|
|
9148
|
+
});
|
|
9149
|
+
|
|
9150
|
+
if (appliedAny) {
|
|
9151
|
+
const stylePatch = Object.fromEntries(entries);
|
|
9152
|
+
const primaryTarget = targets[targets.length - 1];
|
|
9153
|
+
if (primaryTarget) {
|
|
9154
|
+
setSelectedElement((previous) => {
|
|
9155
|
+
const previousMatches =
|
|
9156
|
+
previous &&
|
|
9157
|
+
codeLayerNodeMatchesBridgeTarget(
|
|
9158
|
+
primaryTarget.node,
|
|
9159
|
+
previous.selector,
|
|
9160
|
+
previous.sourceId ?? previous.id,
|
|
9161
|
+
);
|
|
9162
|
+
const base = previousMatches
|
|
9163
|
+
? canonicalElementInfoForCodeLayerNode(
|
|
9164
|
+
previous,
|
|
9165
|
+
primaryTarget.node,
|
|
9166
|
+
)
|
|
9167
|
+
: primaryTarget.elementInfo;
|
|
9168
|
+
return {
|
|
9169
|
+
...base,
|
|
9170
|
+
computedStyles: {
|
|
9171
|
+
...base.computedStyles,
|
|
9172
|
+
...stylePatch,
|
|
9173
|
+
},
|
|
9174
|
+
};
|
|
9175
|
+
});
|
|
9176
|
+
}
|
|
9177
|
+
}
|
|
9178
|
+
|
|
9179
|
+
return true;
|
|
9180
|
+
},
|
|
9181
|
+
[
|
|
9182
|
+
activeContent,
|
|
9183
|
+
activeFile?.id,
|
|
9184
|
+
applyFileContentUpdate,
|
|
9185
|
+
canEditDesign,
|
|
9186
|
+
getScreenContent,
|
|
9187
|
+
],
|
|
9188
|
+
);
|
|
9189
|
+
|
|
8438
9190
|
const handleStyleChange = useCallback(
|
|
8439
9191
|
(property: string, value: string) => {
|
|
8440
9192
|
const selector = selectedElement?.selector ?? "body";
|
|
@@ -8452,9 +9204,11 @@ export default function DesignEditor() {
|
|
|
8452
9204
|
return;
|
|
8453
9205
|
}
|
|
8454
9206
|
}
|
|
9207
|
+
if (commitStylesToSelectedLayers({ [property]: value })) return;
|
|
8455
9208
|
commitVisualStyles(selector, { [property]: value });
|
|
8456
9209
|
},
|
|
8457
9210
|
[
|
|
9211
|
+
commitStylesToSelectedLayers,
|
|
8458
9212
|
commitVisualStyles,
|
|
8459
9213
|
selectedElement?.selector,
|
|
8460
9214
|
selectedElement?.sourceId,
|
|
@@ -8472,9 +9226,14 @@ export default function DesignEditor() {
|
|
|
8472
9226
|
Boolean(value),
|
|
8473
9227
|
);
|
|
8474
9228
|
if (entries.length === 0) return;
|
|
9229
|
+
if (commitStylesToSelectedLayers(Object.fromEntries(entries))) return;
|
|
8475
9230
|
commitVisualStyles(selector, Object.fromEntries(entries));
|
|
8476
9231
|
},
|
|
8477
|
-
[
|
|
9232
|
+
[
|
|
9233
|
+
commitStylesToSelectedLayers,
|
|
9234
|
+
commitVisualStyles,
|
|
9235
|
+
selectedElement?.selector,
|
|
9236
|
+
],
|
|
8478
9237
|
);
|
|
8479
9238
|
|
|
8480
9239
|
const getFreshActiveContent = useCallback(
|
|
@@ -8551,14 +9310,38 @@ export default function DesignEditor() {
|
|
|
8551
9310
|
);
|
|
8552
9311
|
return false;
|
|
8553
9312
|
}
|
|
8554
|
-
const
|
|
9313
|
+
const movedNodeAttrId =
|
|
9314
|
+
targetNode?.dataAttributes["data-agent-native-node-id"] ??
|
|
9315
|
+
details?.sourceId ??
|
|
9316
|
+
elementInfo?.sourceId ??
|
|
8555
9317
|
(patch.result.after?.nodeId
|
|
8556
9318
|
? patch.projection.nodes.find(
|
|
8557
9319
|
(node) => node.id === patch.result.after?.nodeId,
|
|
9320
|
+
)?.dataAttributes["data-agent-native-node-id"]
|
|
9321
|
+
: undefined);
|
|
9322
|
+
const nextContent =
|
|
9323
|
+
isAbsoluteCodeLayerNode(targetNode) && movedNodeAttrId
|
|
9324
|
+
? removeAbsolutePositioningFromNodeInHtml(
|
|
9325
|
+
patch.content,
|
|
9326
|
+
movedNodeAttrId,
|
|
9327
|
+
)
|
|
9328
|
+
: patch.content;
|
|
9329
|
+
const nextProjection = buildCodeLayerProjection(nextContent);
|
|
9330
|
+
const movedNode =
|
|
9331
|
+
(movedNodeAttrId
|
|
9332
|
+
? nextProjection.nodes.find(
|
|
9333
|
+
(node) =>
|
|
9334
|
+
node.dataAttributes["data-agent-native-node-id"] ===
|
|
9335
|
+
movedNodeAttrId,
|
|
9336
|
+
)
|
|
9337
|
+
: null) ??
|
|
9338
|
+
(patch.result.after?.nodeId
|
|
9339
|
+
? nextProjection.nodes.find(
|
|
9340
|
+
(node) => node.id === patch.result.after?.nodeId,
|
|
8558
9341
|
)
|
|
8559
9342
|
: null) ??
|
|
8560
9343
|
resolveCodeLayerNodeFromBridge(
|
|
8561
|
-
|
|
9344
|
+
nextProjection,
|
|
8562
9345
|
selector,
|
|
8563
9346
|
details?.sourceId ??
|
|
8564
9347
|
elementInfo?.sourceId ??
|
|
@@ -8566,7 +9349,7 @@ export default function DesignEditor() {
|
|
|
8566
9349
|
? bridgeSourceIdForCodeLayerNode(targetNode)
|
|
8567
9350
|
: undefined),
|
|
8568
9351
|
);
|
|
8569
|
-
applyLocalContentUpdate(
|
|
9352
|
+
applyLocalContentUpdate(nextContent, { skipPreview: true });
|
|
8570
9353
|
if (movedNode) setSelectedLayerIdsState([movedNode.id]);
|
|
8571
9354
|
if (elementInfo) {
|
|
8572
9355
|
setSelectedElement({
|
|
@@ -8677,7 +9460,8 @@ export default function DesignEditor() {
|
|
|
8677
9460
|
) => {
|
|
8678
9461
|
if (!canEditDesign) return;
|
|
8679
9462
|
if (!activeFile) return;
|
|
8680
|
-
const
|
|
9463
|
+
const activeLiveSnapshot = liveScreenSnapshotsById[activeFile.id];
|
|
9464
|
+
const baseContent = activeLiveSnapshot?.html ?? getFreshActiveContent();
|
|
8681
9465
|
const projection = buildCodeLayerProjection(baseContent);
|
|
8682
9466
|
const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
|
|
8683
9467
|
const targetNode = targetInfo
|
|
@@ -8710,7 +9494,11 @@ export default function DesignEditor() {
|
|
|
8710
9494
|
);
|
|
8711
9495
|
return;
|
|
8712
9496
|
}
|
|
8713
|
-
|
|
9497
|
+
if (activeLiveSnapshot) {
|
|
9498
|
+
updateLiveScreenSnapshotContent(activeFile.id, nextContent);
|
|
9499
|
+
} else {
|
|
9500
|
+
applyLocalContentUpdate(nextContent, { skipPreview: true });
|
|
9501
|
+
}
|
|
8714
9502
|
setActiveTool("text");
|
|
8715
9503
|
setMode("edit");
|
|
8716
9504
|
if (removedContent) {
|
|
@@ -8753,7 +9541,9 @@ export default function DesignEditor() {
|
|
|
8753
9541
|
applyLocalContentUpdate,
|
|
8754
9542
|
canEditDesign,
|
|
8755
9543
|
getFreshActiveContent,
|
|
9544
|
+
liveScreenSnapshotsById,
|
|
8756
9545
|
t,
|
|
9546
|
+
updateLiveScreenSnapshotContent,
|
|
8757
9547
|
],
|
|
8758
9548
|
);
|
|
8759
9549
|
|
|
@@ -8773,7 +9563,8 @@ export default function DesignEditor() {
|
|
|
8773
9563
|
([, value]) => value !== undefined,
|
|
8774
9564
|
);
|
|
8775
9565
|
if (entries.length === 0) return;
|
|
8776
|
-
const
|
|
9566
|
+
const liveSnapshot = liveScreenSnapshotsById[screenId];
|
|
9567
|
+
const baseContent = liveSnapshot?.html ?? getScreenContent(screenId);
|
|
8777
9568
|
const projection = buildCodeLayerProjection(baseContent);
|
|
8778
9569
|
const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
|
|
8779
9570
|
const targetNode = targetInfo
|
|
@@ -8817,6 +9608,10 @@ export default function DesignEditor() {
|
|
|
8817
9608
|
);
|
|
8818
9609
|
return;
|
|
8819
9610
|
}
|
|
9611
|
+
if (liveSnapshot) {
|
|
9612
|
+
updateLiveScreenSnapshotContent(screenId, nextContent);
|
|
9613
|
+
return;
|
|
9614
|
+
}
|
|
8820
9615
|
applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
|
|
8821
9616
|
},
|
|
8822
9617
|
[
|
|
@@ -8825,7 +9620,9 @@ export default function DesignEditor() {
|
|
|
8825
9620
|
canEditDesign,
|
|
8826
9621
|
getScreenContent,
|
|
8827
9622
|
handleVisualStyleChange,
|
|
9623
|
+
liveScreenSnapshotsById,
|
|
8828
9624
|
t,
|
|
9625
|
+
updateLiveScreenSnapshotContent,
|
|
8829
9626
|
],
|
|
8830
9627
|
);
|
|
8831
9628
|
|
|
@@ -9013,7 +9810,8 @@ export default function DesignEditor() {
|
|
|
9013
9810
|
return;
|
|
9014
9811
|
}
|
|
9015
9812
|
if (!canEditDesign) return;
|
|
9016
|
-
const
|
|
9813
|
+
const liveSnapshot = liveScreenSnapshotsById[screenId];
|
|
9814
|
+
const baseContent = liveSnapshot?.html ?? getScreenContent(screenId);
|
|
9017
9815
|
const projection = buildCodeLayerProjection(baseContent);
|
|
9018
9816
|
const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
|
|
9019
9817
|
const targetNode = targetInfo
|
|
@@ -9046,7 +9844,11 @@ export default function DesignEditor() {
|
|
|
9046
9844
|
);
|
|
9047
9845
|
return;
|
|
9048
9846
|
}
|
|
9049
|
-
|
|
9847
|
+
if (liveSnapshot) {
|
|
9848
|
+
updateLiveScreenSnapshotContent(screenId, nextContent);
|
|
9849
|
+
} else {
|
|
9850
|
+
applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
|
|
9851
|
+
}
|
|
9050
9852
|
setActiveFileId(screenId);
|
|
9051
9853
|
setActiveTool("text");
|
|
9052
9854
|
setMode("edit");
|
|
@@ -9091,7 +9893,9 @@ export default function DesignEditor() {
|
|
|
9091
9893
|
canEditDesign,
|
|
9092
9894
|
getScreenContent,
|
|
9093
9895
|
handleTextContentChange,
|
|
9896
|
+
liveScreenSnapshotsById,
|
|
9094
9897
|
t,
|
|
9898
|
+
updateLiveScreenSnapshotContent,
|
|
9095
9899
|
],
|
|
9096
9900
|
);
|
|
9097
9901
|
|
|
@@ -9272,7 +10076,9 @@ export default function DesignEditor() {
|
|
|
9272
10076
|
});
|
|
9273
10077
|
if (result) {
|
|
9274
10078
|
pasteCascadeRef.current += 1;
|
|
9275
|
-
applyLocalContentUpdate(result.content
|
|
10079
|
+
applyLocalContentUpdate(result.content, {
|
|
10080
|
+
forcePreviewFullDocument: true,
|
|
10081
|
+
});
|
|
9276
10082
|
selectInsertedLayers(
|
|
9277
10083
|
activeFile.id,
|
|
9278
10084
|
result.content,
|
|
@@ -9324,7 +10130,9 @@ export default function DesignEditor() {
|
|
|
9324
10130
|
});
|
|
9325
10131
|
if (!result) return;
|
|
9326
10132
|
if (!position) pasteCascadeRef.current += 1;
|
|
9327
|
-
applyLocalContentUpdate(result.content
|
|
10133
|
+
applyLocalContentUpdate(result.content, {
|
|
10134
|
+
forcePreviewFullDocument: true,
|
|
10135
|
+
});
|
|
9328
10136
|
selectInsertedLayers(activeFile.id, result.content, result.rootNodeIds);
|
|
9329
10137
|
},
|
|
9330
10138
|
[
|
|
@@ -9356,7 +10164,9 @@ export default function DesignEditor() {
|
|
|
9356
10164
|
},
|
|
9357
10165
|
);
|
|
9358
10166
|
if (!result) return;
|
|
9359
|
-
applyLocalContentUpdate(result.content
|
|
10167
|
+
applyLocalContentUpdate(result.content, {
|
|
10168
|
+
forcePreviewFullDocument: true,
|
|
10169
|
+
});
|
|
9360
10170
|
selectInsertedLayers(activeFile.id, result.content, result.rootNodeIds);
|
|
9361
10171
|
} else {
|
|
9362
10172
|
handlePasteSelection();
|
|
@@ -9407,7 +10217,10 @@ export default function DesignEditor() {
|
|
|
9407
10217
|
insertedRootNodeIds.unshift(...result.rootNodeIds);
|
|
9408
10218
|
}
|
|
9409
10219
|
if (insertedRootNodeIds.length === 0) continue;
|
|
9410
|
-
applyFileContentUpdate(file.id, content, {
|
|
10220
|
+
applyFileContentUpdate(file.id, content, {
|
|
10221
|
+
forcePreviewFullDocument: true,
|
|
10222
|
+
refreshPreview: false,
|
|
10223
|
+
});
|
|
9411
10224
|
selectedScreenIds.push(file.id);
|
|
9412
10225
|
const finalProjection = buildCodeLayerProjection(content);
|
|
9413
10226
|
insertedRootNodeIds.forEach((rootNodeId) => {
|
|
@@ -9472,7 +10285,9 @@ export default function DesignEditor() {
|
|
|
9472
10285
|
placement: "after",
|
|
9473
10286
|
});
|
|
9474
10287
|
if (nextContent) {
|
|
9475
|
-
applyLocalContentUpdate(nextContent
|
|
10288
|
+
applyLocalContentUpdate(nextContent, {
|
|
10289
|
+
forcePreviewFullDocument: true,
|
|
10290
|
+
});
|
|
9476
10291
|
} else {
|
|
9477
10292
|
toast.error(t("designEditor.toasts.duplicateElementFailed"));
|
|
9478
10293
|
}
|
|
@@ -9683,57 +10498,6 @@ export default function DesignEditor() {
|
|
|
9683
10498
|
t,
|
|
9684
10499
|
]);
|
|
9685
10500
|
|
|
9686
|
-
/**
|
|
9687
|
-
* Convert the selected container to full auto-layout. Applies the
|
|
9688
|
-
* { kind: "autoLayout", enabled: true } substrate intent which sets
|
|
9689
|
-
* display:flex on the target AND strips position:absolute/left/top/right/bottom
|
|
9690
|
-
* from its direct children so they become flow children.
|
|
9691
|
-
*/
|
|
9692
|
-
const handleAutoLayoutConvert = useCallback(
|
|
9693
|
-
(
|
|
9694
|
-
targetNodeId: string,
|
|
9695
|
-
opts?: { direction?: "row" | "column"; gap?: string },
|
|
9696
|
-
) => {
|
|
9697
|
-
if (!canEditDesign || !activeFile) return;
|
|
9698
|
-
const baseContent = getFreshActiveContent();
|
|
9699
|
-
const patch = applyVisualEdit(baseContent, {
|
|
9700
|
-
kind: "autoLayout",
|
|
9701
|
-
targetId: targetNodeId,
|
|
9702
|
-
enabled: true,
|
|
9703
|
-
direction: opts?.direction ?? "row",
|
|
9704
|
-
gap: opts?.gap ?? "8px",
|
|
9705
|
-
});
|
|
9706
|
-
if (patch.result.status !== "applied") {
|
|
9707
|
-
toast.error(
|
|
9708
|
-
codeLayerPatchMessage(
|
|
9709
|
-
patch.result.message,
|
|
9710
|
-
t("designEditor.toasts.layerMoveFailed"),
|
|
9711
|
-
),
|
|
9712
|
-
{ duration: 4000 },
|
|
9713
|
-
);
|
|
9714
|
-
return;
|
|
9715
|
-
}
|
|
9716
|
-
applyLocalContentUpdate(patch.content, { skipPreview: true });
|
|
9717
|
-
// Re-select the container so the inspector refreshes its layout state.
|
|
9718
|
-
const containerNode = patch.projection.nodes.find(
|
|
9719
|
-
(n) =>
|
|
9720
|
-
n.dataAttributes["data-agent-native-node-id"] === targetNodeId ||
|
|
9721
|
-
n.id === targetNodeId,
|
|
9722
|
-
);
|
|
9723
|
-
if (containerNode) {
|
|
9724
|
-
setSelectedLayerIdsState([containerNode.id]);
|
|
9725
|
-
setSelectedElement(elementInfoFromCodeLayerNode(containerNode));
|
|
9726
|
-
}
|
|
9727
|
-
},
|
|
9728
|
-
[
|
|
9729
|
-
activeFile,
|
|
9730
|
-
applyLocalContentUpdate,
|
|
9731
|
-
canEditDesign,
|
|
9732
|
-
getFreshActiveContent,
|
|
9733
|
-
t,
|
|
9734
|
-
],
|
|
9735
|
-
);
|
|
9736
|
-
|
|
9737
10501
|
/**
|
|
9738
10502
|
* Handle a primitive being drag-dropped onto another primitive in the
|
|
9739
10503
|
* MultiScreenCanvas overview (CONTRACT: onPrimitiveReparent prop).
|
|
@@ -9931,6 +10695,8 @@ export default function DesignEditor() {
|
|
|
9931
10695
|
targetScreenId,
|
|
9932
10696
|
targetAnchorNodeId,
|
|
9933
10697
|
targetAnchorPlacement,
|
|
10698
|
+
targetLocalPoint,
|
|
10699
|
+
sourcePointerOffset,
|
|
9934
10700
|
}: {
|
|
9935
10701
|
sourceSelector: string;
|
|
9936
10702
|
sourceNodeId?: string;
|
|
@@ -9938,6 +10704,9 @@ export default function DesignEditor() {
|
|
|
9938
10704
|
targetScreenId: string;
|
|
9939
10705
|
targetAnchorNodeId?: string;
|
|
9940
10706
|
targetAnchorPlacement?: "before" | "after" | "inside";
|
|
10707
|
+
targetCanvasPoint?: { x: number; y: number };
|
|
10708
|
+
targetLocalPoint?: { x: number; y: number };
|
|
10709
|
+
sourcePointerOffset?: { x: number; y: number };
|
|
9941
10710
|
}) => {
|
|
9942
10711
|
if (!canEditDesign) return;
|
|
9943
10712
|
if (sourceScreenId === targetScreenId) return;
|
|
@@ -9966,14 +10735,24 @@ export default function DesignEditor() {
|
|
|
9966
10735
|
resolvedSourceNode?.dataAttributes["data-agent-native-node-id"] ??
|
|
9967
10736
|
sourceNodeId ??
|
|
9968
10737
|
sourceSelector;
|
|
10738
|
+
const destProjection = buildCodeLayerProjection(destContent);
|
|
10739
|
+
const resolvedTargetAnchor = targetAnchorNodeId
|
|
10740
|
+
? resolveCodeLayerNodeFromBridge(
|
|
10741
|
+
destProjection,
|
|
10742
|
+
undefined,
|
|
10743
|
+
targetAnchorNodeId,
|
|
10744
|
+
)
|
|
10745
|
+
: null;
|
|
10746
|
+
const targetAnchorAttrId =
|
|
10747
|
+
resolvedTargetAnchor?.dataAttributes["data-agent-native-node-id"];
|
|
9969
10748
|
|
|
9970
10749
|
// Use hit-test anchor when the canvas supplied one; fall back to
|
|
9971
10750
|
// top-level body append ("inside" with no anchor = existing behaviour).
|
|
9972
10751
|
const result = moveNodeBetweenDocuments(sourceContent, destContent, {
|
|
9973
10752
|
nodeId: nodeAttrId,
|
|
9974
|
-
...(
|
|
10753
|
+
...(targetAnchorAttrId
|
|
9975
10754
|
? {
|
|
9976
|
-
anchorNodeId:
|
|
10755
|
+
anchorNodeId: targetAnchorAttrId,
|
|
9977
10756
|
placement: targetAnchorPlacement ?? "inside",
|
|
9978
10757
|
}
|
|
9979
10758
|
: { placement: "inside" }),
|
|
@@ -9989,16 +10768,23 @@ export default function DesignEditor() {
|
|
|
9989
10768
|
return;
|
|
9990
10769
|
}
|
|
9991
10770
|
|
|
9992
|
-
//
|
|
9993
|
-
//
|
|
9994
|
-
//
|
|
9995
|
-
// nodeAttrId when a collision triggered an id re-stamp) so the strip and
|
|
9996
|
-
// re-selection always find the correct element.
|
|
10771
|
+
// Hit-test anchors are emitted only for auto-layout insertion targets. If
|
|
10772
|
+
// there is no anchor, preserve absolute mode and rebase left/top to the
|
|
10773
|
+
// release point so screen↔board moves behave like Figma absolute layers.
|
|
9997
10774
|
const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
|
|
9998
|
-
const
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10775
|
+
const nextDestContent = targetAnchorAttrId
|
|
10776
|
+
? removeAbsolutePositioningFromNodeInHtml(
|
|
10777
|
+
result.destHtml,
|
|
10778
|
+
destNodeAttrId,
|
|
10779
|
+
)
|
|
10780
|
+
: targetLocalPoint
|
|
10781
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
10782
|
+
result.destHtml,
|
|
10783
|
+
destNodeAttrId,
|
|
10784
|
+
targetLocalPoint,
|
|
10785
|
+
sourcePointerOffset,
|
|
10786
|
+
)
|
|
10787
|
+
: result.destHtml;
|
|
10002
10788
|
|
|
10003
10789
|
recordContentHistoryEntry({
|
|
10004
10790
|
changes: [
|
|
@@ -10010,7 +10796,7 @@ export default function DesignEditor() {
|
|
|
10010
10796
|
{
|
|
10011
10797
|
fileId: targetScreenId,
|
|
10012
10798
|
before: destContent,
|
|
10013
|
-
after:
|
|
10799
|
+
after: nextDestContent,
|
|
10014
10800
|
},
|
|
10015
10801
|
],
|
|
10016
10802
|
});
|
|
@@ -10019,7 +10805,7 @@ export default function DesignEditor() {
|
|
|
10019
10805
|
recordHistory: false,
|
|
10020
10806
|
refreshPreview: true,
|
|
10021
10807
|
});
|
|
10022
|
-
applyFileContentUpdate(targetScreenId,
|
|
10808
|
+
applyFileContentUpdate(targetScreenId, nextDestContent, {
|
|
10023
10809
|
recordHistory: false,
|
|
10024
10810
|
refreshPreview: true,
|
|
10025
10811
|
});
|
|
@@ -10027,7 +10813,7 @@ export default function DesignEditor() {
|
|
|
10027
10813
|
// Switch active screen to the target and select the moved node; viewMode
|
|
10028
10814
|
// stays "overview" (no setViewMode call).
|
|
10029
10815
|
setActiveFileId(targetScreenId);
|
|
10030
|
-
const finalProjection = buildCodeLayerProjection(
|
|
10816
|
+
const finalProjection = buildCodeLayerProjection(nextDestContent);
|
|
10031
10817
|
const movedNodeFinal = finalProjection.nodes.find(
|
|
10032
10818
|
(n) => n.dataAttributes["data-agent-native-node-id"] === destNodeAttrId,
|
|
10033
10819
|
);
|
|
@@ -10077,6 +10863,38 @@ export default function DesignEditor() {
|
|
|
10077
10863
|
delete nextGeometry[file.id];
|
|
10078
10864
|
});
|
|
10079
10865
|
|
|
10866
|
+
const nextGeometryUndoStack: GeometryHistoryEntry[] = [];
|
|
10867
|
+
let removedGeometryUndoEntries = 0;
|
|
10868
|
+
geometryUndoStackRef.current.forEach((entry) => {
|
|
10869
|
+
if (geometryHistoryEntryTouchesFrameIds(entry, deleteIds)) {
|
|
10870
|
+
removedGeometryUndoEntries += 1;
|
|
10871
|
+
return;
|
|
10872
|
+
}
|
|
10873
|
+
nextGeometryUndoStack.push(entry);
|
|
10874
|
+
});
|
|
10875
|
+
geometryUndoStackRef.current = nextGeometryUndoStack;
|
|
10876
|
+
historyOrderRef.current = removeRecentUndoRedoOrderKinds(
|
|
10877
|
+
historyOrderRef.current,
|
|
10878
|
+
"geometry",
|
|
10879
|
+
removedGeometryUndoEntries,
|
|
10880
|
+
);
|
|
10881
|
+
|
|
10882
|
+
const nextGeometryRedoStack: GeometryHistoryEntry[] = [];
|
|
10883
|
+
let removedGeometryRedoEntries = 0;
|
|
10884
|
+
geometryRedoStackRef.current.forEach((entry) => {
|
|
10885
|
+
if (geometryHistoryEntryTouchesFrameIds(entry, deleteIds)) {
|
|
10886
|
+
removedGeometryRedoEntries += 1;
|
|
10887
|
+
return;
|
|
10888
|
+
}
|
|
10889
|
+
nextGeometryRedoStack.push(entry);
|
|
10890
|
+
});
|
|
10891
|
+
geometryRedoStackRef.current = nextGeometryRedoStack;
|
|
10892
|
+
redoOrderRef.current = removeRecentUndoRedoOrderKinds(
|
|
10893
|
+
redoOrderRef.current,
|
|
10894
|
+
"geometry",
|
|
10895
|
+
removedGeometryRedoEntries,
|
|
10896
|
+
);
|
|
10897
|
+
|
|
10080
10898
|
writeFrameGeometrySnapshot(nextGeometry);
|
|
10081
10899
|
queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
|
|
10082
10900
|
if (!old || typeof old !== "object" || !Array.isArray(old.files)) {
|
|
@@ -10109,7 +10927,12 @@ export default function DesignEditor() {
|
|
|
10109
10927
|
});
|
|
10110
10928
|
});
|
|
10111
10929
|
|
|
10112
|
-
|
|
10930
|
+
// File-backed screen deletion is not a geometry-only edit. The screen rows
|
|
10931
|
+
// are hard-deleted, so suppress MultiScreenCanvas' local frame-history
|
|
10932
|
+
// entry; otherwise undo would restore geometry for files that no longer
|
|
10933
|
+
// exist.
|
|
10934
|
+
syncUndoRedoState();
|
|
10935
|
+
return false;
|
|
10113
10936
|
},
|
|
10114
10937
|
[
|
|
10115
10938
|
activeFile,
|
|
@@ -10119,6 +10942,7 @@ export default function DesignEditor() {
|
|
|
10119
10942
|
files,
|
|
10120
10943
|
id,
|
|
10121
10944
|
queryClient,
|
|
10945
|
+
syncUndoRedoState,
|
|
10122
10946
|
t,
|
|
10123
10947
|
writeFrameGeometrySnapshot,
|
|
10124
10948
|
],
|
|
@@ -10213,8 +11037,10 @@ export default function DesignEditor() {
|
|
|
10213
11037
|
const handleUndo = useCallback(() => {
|
|
10214
11038
|
if (!canEditDesign) return;
|
|
10215
11039
|
const um = undoManagerRef.current;
|
|
10216
|
-
const
|
|
10217
|
-
|
|
11040
|
+
const canUseOverviewHistory = viewModeRef.current === "overview";
|
|
11041
|
+
let prunedUndoHistory = false;
|
|
11042
|
+
const undoContent = (scope: "any" | "local" | "global" = "any") => {
|
|
11043
|
+
if (scope !== "global" && um?.canUndo()) {
|
|
10218
11044
|
um.undo();
|
|
10219
11045
|
if (ydoc && activeFile) {
|
|
10220
11046
|
const next = ydoc.getText("content").toString();
|
|
@@ -10227,9 +11053,8 @@ export default function DesignEditor() {
|
|
|
10227
11053
|
queueFileContentSave(activeFile.id, next, {
|
|
10228
11054
|
syncCollab: !(ydoc && isSynced),
|
|
10229
11055
|
});
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
}
|
|
11056
|
+
replacePreviewContent(next, null, { forceFullDocument: true });
|
|
11057
|
+
setContentRenderRevision((revision) => revision + 1);
|
|
10233
11058
|
// Clear stale selection if the undo removed the selected element.
|
|
10234
11059
|
setSelectedElement((prev) => {
|
|
10235
11060
|
if (!prev) return prev;
|
|
@@ -10247,6 +11072,44 @@ export default function DesignEditor() {
|
|
|
10247
11072
|
return true;
|
|
10248
11073
|
}
|
|
10249
11074
|
|
|
11075
|
+
if (!canUseOverviewHistory && scope !== "global" && activeFile?.id) {
|
|
11076
|
+
const localIndex = findLastContentHistoryChangeIndex(
|
|
11077
|
+
localContentUndoStackRef.current,
|
|
11078
|
+
activeFile.id,
|
|
11079
|
+
);
|
|
11080
|
+
if (localIndex !== -1) {
|
|
11081
|
+
const [entry] = localContentUndoStackRef.current.splice(
|
|
11082
|
+
localIndex,
|
|
11083
|
+
1,
|
|
11084
|
+
);
|
|
11085
|
+
if (entry) {
|
|
11086
|
+
localContentRedoStackRef.current = [
|
|
11087
|
+
...localContentRedoStackRef.current.slice(
|
|
11088
|
+
-(MAX_DESIGN_UNDO_STACK - 1),
|
|
11089
|
+
),
|
|
11090
|
+
entry,
|
|
11091
|
+
];
|
|
11092
|
+
applyLocalContentUpdate(entry.before, {
|
|
11093
|
+
refreshPreview: false,
|
|
11094
|
+
forcePreviewFullDocument: true,
|
|
11095
|
+
immediateSave: true,
|
|
11096
|
+
recordHistory: false,
|
|
11097
|
+
});
|
|
11098
|
+
setSelectedElement((prev) => {
|
|
11099
|
+
if (!prev) return prev;
|
|
11100
|
+
return refreshElementInfoFromContent(entry.before, prev);
|
|
11101
|
+
});
|
|
11102
|
+
setHoveredElement((prev) => {
|
|
11103
|
+
if (!prev) return prev;
|
|
11104
|
+
return refreshElementInfoFromContent(entry.before, prev);
|
|
11105
|
+
});
|
|
11106
|
+
return true;
|
|
11107
|
+
}
|
|
11108
|
+
}
|
|
11109
|
+
}
|
|
11110
|
+
|
|
11111
|
+
if (scope === "local") return false;
|
|
11112
|
+
if (!canUseOverviewHistory) return false;
|
|
10250
11113
|
const entry =
|
|
10251
11114
|
contentUndoStackRef.current[contentUndoStackRef.current.length - 1];
|
|
10252
11115
|
if (!entry) return false;
|
|
@@ -10255,7 +11118,11 @@ export default function DesignEditor() {
|
|
|
10255
11118
|
files.map((file) => file.id),
|
|
10256
11119
|
activeFile?.id,
|
|
10257
11120
|
);
|
|
10258
|
-
if (changes.length === 0)
|
|
11121
|
+
if (changes.length === 0) {
|
|
11122
|
+
contentUndoStackRef.current.pop();
|
|
11123
|
+
prunedUndoHistory = true;
|
|
11124
|
+
return false;
|
|
11125
|
+
}
|
|
10259
11126
|
contentUndoStackRef.current.pop();
|
|
10260
11127
|
contentRedoStackRef.current = [
|
|
10261
11128
|
...contentRedoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
@@ -10271,6 +11138,7 @@ export default function DesignEditor() {
|
|
|
10271
11138
|
if (change.fileId === activeFile?.id) {
|
|
10272
11139
|
applyLocalContentUpdate(change.before, {
|
|
10273
11140
|
refreshPreview: false,
|
|
11141
|
+
forcePreviewFullDocument: true,
|
|
10274
11142
|
immediateSave: true,
|
|
10275
11143
|
recordHistory: false,
|
|
10276
11144
|
});
|
|
@@ -10300,6 +11168,7 @@ export default function DesignEditor() {
|
|
|
10300
11168
|
return true;
|
|
10301
11169
|
};
|
|
10302
11170
|
const undoGeometry = () => {
|
|
11171
|
+
if (!canUseOverviewHistory) return false;
|
|
10303
11172
|
const entry = geometryUndoStackRef.current.pop();
|
|
10304
11173
|
if (!entry) return false;
|
|
10305
11174
|
geometryRedoStackRef.current = [
|
|
@@ -10319,14 +11188,25 @@ export default function DesignEditor() {
|
|
|
10319
11188
|
return true;
|
|
10320
11189
|
};
|
|
10321
11190
|
|
|
10322
|
-
const
|
|
10323
|
-
const didUndo =
|
|
11191
|
+
const undoByOrder = (preferred?: UndoRedoOrderKind) =>
|
|
10324
11192
|
preferred === "geometry"
|
|
10325
11193
|
? undoGeometry() || undoContent()
|
|
10326
|
-
:
|
|
10327
|
-
? undoContent() || undoGeometry()
|
|
10328
|
-
:
|
|
10329
|
-
|
|
11194
|
+
: preferred === "file-content"
|
|
11195
|
+
? undoContent("global") || undoGeometry()
|
|
11196
|
+
: preferred === "content"
|
|
11197
|
+
? undoContent("local") || undoContent("global") || undoGeometry()
|
|
11198
|
+
: undoContent() || undoGeometry();
|
|
11199
|
+
let didUndo = false;
|
|
11200
|
+
if (canUseOverviewHistory) {
|
|
11201
|
+
while (!didUndo) {
|
|
11202
|
+
const preferred = historyOrderRef.current.pop();
|
|
11203
|
+
didUndo = undoByOrder(preferred);
|
|
11204
|
+
if (didUndo || preferred === undefined) break;
|
|
11205
|
+
}
|
|
11206
|
+
} else {
|
|
11207
|
+
didUndo = undoContent("local");
|
|
11208
|
+
}
|
|
11209
|
+
if (didUndo || prunedUndoHistory) {
|
|
10330
11210
|
syncUndoRedoState();
|
|
10331
11211
|
}
|
|
10332
11212
|
}, [
|
|
@@ -10347,8 +11227,10 @@ export default function DesignEditor() {
|
|
|
10347
11227
|
const handleRedo = useCallback(() => {
|
|
10348
11228
|
if (!canEditDesign) return;
|
|
10349
11229
|
const um = undoManagerRef.current;
|
|
10350
|
-
const
|
|
10351
|
-
|
|
11230
|
+
const canUseOverviewHistory = viewModeRef.current === "overview";
|
|
11231
|
+
let prunedRedoHistory = false;
|
|
11232
|
+
const redoContent = (scope: "any" | "local" | "global" = "any") => {
|
|
11233
|
+
if (scope !== "global" && um?.canRedo()) {
|
|
10352
11234
|
um.redo();
|
|
10353
11235
|
if (ydoc && activeFile) {
|
|
10354
11236
|
const next = ydoc.getText("content").toString();
|
|
@@ -10361,9 +11243,8 @@ export default function DesignEditor() {
|
|
|
10361
11243
|
queueFileContentSave(activeFile.id, next, {
|
|
10362
11244
|
syncCollab: !(ydoc && isSynced),
|
|
10363
11245
|
});
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
}
|
|
11246
|
+
replacePreviewContent(next, null, { forceFullDocument: true });
|
|
11247
|
+
setContentRenderRevision((revision) => revision + 1);
|
|
10367
11248
|
// Clear stale selection if the redo removed the selected element.
|
|
10368
11249
|
setSelectedElement((prev) => {
|
|
10369
11250
|
if (!prev) return prev;
|
|
@@ -10381,6 +11262,44 @@ export default function DesignEditor() {
|
|
|
10381
11262
|
return true;
|
|
10382
11263
|
}
|
|
10383
11264
|
|
|
11265
|
+
if (!canUseOverviewHistory && scope !== "global" && activeFile?.id) {
|
|
11266
|
+
const localIndex = findLastContentHistoryChangeIndex(
|
|
11267
|
+
localContentRedoStackRef.current,
|
|
11268
|
+
activeFile.id,
|
|
11269
|
+
);
|
|
11270
|
+
if (localIndex !== -1) {
|
|
11271
|
+
const [entry] = localContentRedoStackRef.current.splice(
|
|
11272
|
+
localIndex,
|
|
11273
|
+
1,
|
|
11274
|
+
);
|
|
11275
|
+
if (entry) {
|
|
11276
|
+
localContentUndoStackRef.current = [
|
|
11277
|
+
...localContentUndoStackRef.current.slice(
|
|
11278
|
+
-(MAX_DESIGN_UNDO_STACK - 1),
|
|
11279
|
+
),
|
|
11280
|
+
entry,
|
|
11281
|
+
];
|
|
11282
|
+
applyLocalContentUpdate(entry.after, {
|
|
11283
|
+
refreshPreview: false,
|
|
11284
|
+
forcePreviewFullDocument: true,
|
|
11285
|
+
immediateSave: true,
|
|
11286
|
+
recordHistory: false,
|
|
11287
|
+
});
|
|
11288
|
+
setSelectedElement((prev) => {
|
|
11289
|
+
if (!prev) return prev;
|
|
11290
|
+
return refreshElementInfoFromContent(entry.after, prev);
|
|
11291
|
+
});
|
|
11292
|
+
setHoveredElement((prev) => {
|
|
11293
|
+
if (!prev) return prev;
|
|
11294
|
+
return refreshElementInfoFromContent(entry.after, prev);
|
|
11295
|
+
});
|
|
11296
|
+
return true;
|
|
11297
|
+
}
|
|
11298
|
+
}
|
|
11299
|
+
}
|
|
11300
|
+
|
|
11301
|
+
if (scope === "local") return false;
|
|
11302
|
+
if (!canUseOverviewHistory) return false;
|
|
10384
11303
|
const entry =
|
|
10385
11304
|
contentRedoStackRef.current[contentRedoStackRef.current.length - 1];
|
|
10386
11305
|
if (!entry) return false;
|
|
@@ -10389,7 +11308,11 @@ export default function DesignEditor() {
|
|
|
10389
11308
|
files.map((file) => file.id),
|
|
10390
11309
|
activeFile?.id,
|
|
10391
11310
|
);
|
|
10392
|
-
if (changes.length === 0)
|
|
11311
|
+
if (changes.length === 0) {
|
|
11312
|
+
contentRedoStackRef.current.pop();
|
|
11313
|
+
prunedRedoHistory = true;
|
|
11314
|
+
return false;
|
|
11315
|
+
}
|
|
10393
11316
|
contentRedoStackRef.current.pop();
|
|
10394
11317
|
contentUndoStackRef.current = [
|
|
10395
11318
|
...contentUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
@@ -10405,6 +11328,7 @@ export default function DesignEditor() {
|
|
|
10405
11328
|
if (change.fileId === activeFile?.id) {
|
|
10406
11329
|
applyLocalContentUpdate(change.after, {
|
|
10407
11330
|
refreshPreview: false,
|
|
11331
|
+
forcePreviewFullDocument: true,
|
|
10408
11332
|
immediateSave: true,
|
|
10409
11333
|
recordHistory: false,
|
|
10410
11334
|
});
|
|
@@ -10434,6 +11358,7 @@ export default function DesignEditor() {
|
|
|
10434
11358
|
return true;
|
|
10435
11359
|
};
|
|
10436
11360
|
const redoGeometry = () => {
|
|
11361
|
+
if (!canUseOverviewHistory) return false;
|
|
10437
11362
|
const entry = geometryRedoStackRef.current.pop();
|
|
10438
11363
|
if (!entry) return false;
|
|
10439
11364
|
geometryUndoStackRef.current = [
|
|
@@ -10453,14 +11378,25 @@ export default function DesignEditor() {
|
|
|
10453
11378
|
return true;
|
|
10454
11379
|
};
|
|
10455
11380
|
|
|
10456
|
-
const
|
|
10457
|
-
const didRedo =
|
|
11381
|
+
const redoByOrder = (preferred?: UndoRedoOrderKind) =>
|
|
10458
11382
|
preferred === "geometry"
|
|
10459
11383
|
? redoGeometry() || redoContent()
|
|
10460
|
-
:
|
|
10461
|
-
? redoContent() || redoGeometry()
|
|
10462
|
-
:
|
|
10463
|
-
|
|
11384
|
+
: preferred === "file-content"
|
|
11385
|
+
? redoContent("global") || redoGeometry()
|
|
11386
|
+
: preferred === "content"
|
|
11387
|
+
? redoContent("local") || redoContent("global") || redoGeometry()
|
|
11388
|
+
: redoContent() || redoGeometry();
|
|
11389
|
+
let didRedo = false;
|
|
11390
|
+
if (canUseOverviewHistory) {
|
|
11391
|
+
while (!didRedo) {
|
|
11392
|
+
const preferred = redoOrderRef.current.pop();
|
|
11393
|
+
didRedo = redoByOrder(preferred);
|
|
11394
|
+
if (didRedo || preferred === undefined) break;
|
|
11395
|
+
}
|
|
11396
|
+
} else {
|
|
11397
|
+
didRedo = redoContent("local");
|
|
11398
|
+
}
|
|
11399
|
+
if (didRedo || prunedRedoHistory) {
|
|
10464
11400
|
syncUndoRedoState();
|
|
10465
11401
|
}
|
|
10466
11402
|
}, [
|
|
@@ -10496,7 +11432,7 @@ export default function DesignEditor() {
|
|
|
10496
11432
|
viewModeRef.current = "overview";
|
|
10497
11433
|
setViewMode("overview");
|
|
10498
11434
|
setActiveTool("move");
|
|
10499
|
-
|
|
11435
|
+
setExplicitOverviewCanvasZoom(100);
|
|
10500
11436
|
}, []);
|
|
10501
11437
|
|
|
10502
11438
|
const runEditorViewTransition = useCallback((update: () => void) => {
|
|
@@ -11539,7 +12475,7 @@ ${serializedHtml}
|
|
|
11539
12475
|
: codingHandoffPreviewFallback);
|
|
11540
12476
|
const shareExportTab = (
|
|
11541
12477
|
<div className="space-y-3">
|
|
11542
|
-
<div className="text-[11px] font-semibold uppercase text-muted-foreground">
|
|
12478
|
+
<div className="!text-[11px] font-semibold uppercase text-muted-foreground">
|
|
11543
12479
|
{"Format" /* i18n-ignore share export section label */}
|
|
11544
12480
|
</div>
|
|
11545
12481
|
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
|
|
@@ -11564,11 +12500,11 @@ ${serializedHtml}
|
|
|
11564
12500
|
<span className="min-w-0 flex-1">
|
|
11565
12501
|
<span className="block truncate text-[12px] font-semibold text-foreground">
|
|
11566
12502
|
{option.title}{" "}
|
|
11567
|
-
<span className="text-[11px] font-medium text-muted-foreground">
|
|
12503
|
+
<span className="!text-[11px] font-medium text-muted-foreground">
|
|
11568
12504
|
{option.extension}
|
|
11569
12505
|
</span>
|
|
11570
12506
|
</span>
|
|
11571
|
-
<span className="mt-0.5 block text-[11px] leading-4 text-muted-foreground">
|
|
12507
|
+
<span className="mt-0.5 block !text-[11px] leading-4 text-muted-foreground">
|
|
11572
12508
|
{option.description}
|
|
11573
12509
|
</span>
|
|
11574
12510
|
</span>
|
|
@@ -11592,7 +12528,7 @@ ${serializedHtml}
|
|
|
11592
12528
|
<div className="text-[12px] font-medium text-foreground">
|
|
11593
12529
|
{selectedShareExportOption.title}
|
|
11594
12530
|
</div>
|
|
11595
|
-
<div className="text-[11px] text-muted-foreground">
|
|
12531
|
+
<div className="!text-[11px] text-muted-foreground">
|
|
11596
12532
|
{selectedShareExportOption.description}
|
|
11597
12533
|
</div>
|
|
11598
12534
|
</div>
|
|
@@ -11664,7 +12600,7 @@ ${serializedHtml}
|
|
|
11664
12600
|
<div className="text-[12px] font-medium text-foreground">
|
|
11665
12601
|
{"Download zip instead" /* i18n-ignore share send option */}
|
|
11666
12602
|
</div>
|
|
11667
|
-
<div className="mt-0.5 text-[11px] leading-4 text-muted-foreground">
|
|
12603
|
+
<div className="mt-0.5 !text-[11px] leading-4 text-muted-foreground">
|
|
11668
12604
|
{
|
|
11669
12605
|
"For agents without the Design connector, drop the bundle into your agent's chat manually." /* i18n-ignore share send option description */
|
|
11670
12606
|
}
|
|
@@ -11694,7 +12630,7 @@ ${serializedHtml}
|
|
|
11694
12630
|
const designSharePopoverClassName =
|
|
11695
12631
|
"z-[100010] !w-[min(620px,calc(100vw-32px))] !p-3 " +
|
|
11696
12632
|
"[&_[role=tablist]]:!inline-flex [&_[role=tablist]]:!w-fit [&_[role=tablist]]:!self-start [&_[role=tablist]]:justify-start [&_[role=tablist]]:gap-0.5 [&_[role=tablist]]:rounded-none [&_[role=tablist]]:bg-transparent [&_[role=tablist]]:p-0 " +
|
|
11697
|
-
"[&_[role=tab]]:!h-6 [&_[role=tab]]:!flex-none [&_[role=tab]]:rounded-md [&_[role=tab]]:px-2 [&_[role=tab]]
|
|
12633
|
+
"[&_[role=tab]]:!h-6 [&_[role=tab]]:!flex-none [&_[role=tab]]:rounded-md [&_[role=tab]]:px-2 [&_[role=tab]]:!text-[11px] [&_[role=tab]]:font-semibold [&_[role=tab]]:shadow-none [&_[role=tab]]:ring-0 " +
|
|
11698
12634
|
"[&_[role=tab]:hover]:bg-[var(--design-editor-panel-raised-bg)] [&_[role=tab]:hover]:text-foreground [&_[role=tab][aria-selected=true]]:bg-[var(--design-editor-panel-raised-bg)] [&_[role=tab][aria-selected=true]]:text-foreground [&_[role=tab][aria-selected=true]]:ring-0";
|
|
11699
12635
|
const designShareTabs = {
|
|
11700
12636
|
shareLabel: "Share link" /* i18n-ignore share tab label */,
|
|
@@ -11725,7 +12661,7 @@ ${serializedHtml}
|
|
|
11725
12661
|
},
|
|
11726
12662
|
});
|
|
11727
12663
|
if (!stamped.changed || stamped.content === activeContent) return;
|
|
11728
|
-
applyLocalContentUpdate(stamped.content);
|
|
12664
|
+
applyLocalContentUpdate(stamped.content, { recordHistory: false });
|
|
11729
12665
|
}, [activeContent, activeFile, applyLocalContentUpdate, id, viewMode]);
|
|
11730
12666
|
const activeCodeLayerTree = useMemo(
|
|
11731
12667
|
() => buildCodeLayerTree(activeCodeLayerProjection),
|
|
@@ -11740,7 +12676,9 @@ ${serializedHtml}
|
|
|
11740
12676
|
() =>
|
|
11741
12677
|
files.map((file) => {
|
|
11742
12678
|
const content =
|
|
11743
|
-
file.id === activeFile?.id
|
|
12679
|
+
file.id === activeFile?.id
|
|
12680
|
+
? activeProjectionContent
|
|
12681
|
+
: getProjectionContentForScreen(file.id);
|
|
11744
12682
|
const projection =
|
|
11745
12683
|
file.id === activeFile?.id
|
|
11746
12684
|
? activeCodeLayerProjection
|
|
@@ -11759,9 +12697,10 @@ ${serializedHtml}
|
|
|
11759
12697
|
[
|
|
11760
12698
|
activeCodeLayerProjection,
|
|
11761
12699
|
activeCodeLayerTree,
|
|
11762
|
-
|
|
12700
|
+
activeProjectionContent,
|
|
11763
12701
|
activeFile?.id,
|
|
11764
12702
|
files,
|
|
12703
|
+
getProjectionContentForScreen,
|
|
11765
12704
|
],
|
|
11766
12705
|
);
|
|
11767
12706
|
const codeLayerModelByFileId = useMemo(
|
|
@@ -11805,6 +12744,7 @@ ${serializedHtml}
|
|
|
11805
12744
|
});
|
|
11806
12745
|
return state;
|
|
11807
12746
|
}, [codeLayerModelsByFile, hiddenLayerIds, lockedLayerIds]);
|
|
12747
|
+
effectiveCodeLayerStateRef.current = effectiveCodeLayerState;
|
|
11808
12748
|
useEffect(() => {
|
|
11809
12749
|
shouldPreserveBlockedOverviewLayerSelectionRef.current = (
|
|
11810
12750
|
screenId: string,
|
|
@@ -12048,12 +12988,191 @@ ${serializedHtml}
|
|
|
12048
12988
|
selectedLayerIdsState,
|
|
12049
12989
|
viewMode,
|
|
12050
12990
|
]);
|
|
12991
|
+
const selectedUrlSelectionId = useMemo(
|
|
12992
|
+
() =>
|
|
12993
|
+
selectedElementLayerId ??
|
|
12994
|
+
[...selectedLayerIds]
|
|
12995
|
+
.reverse()
|
|
12996
|
+
.find((layerId) => codeLayerOwnerByNodeId.has(layerId)) ??
|
|
12997
|
+
null,
|
|
12998
|
+
[codeLayerOwnerByNodeId, selectedElementLayerId, selectedLayerIds],
|
|
12999
|
+
);
|
|
12051
13000
|
const selectedLayerIdsRef = useRef<string[]>(selectedLayerIds);
|
|
12052
13001
|
|
|
12053
13002
|
useLayoutEffect(() => {
|
|
12054
13003
|
selectedLayerIdsRef.current = selectedLayerIds;
|
|
12055
13004
|
}, [selectedLayerIds]);
|
|
12056
13005
|
|
|
13006
|
+
useEffect(() => {
|
|
13007
|
+
if (!id) return;
|
|
13008
|
+
if (initialUrlSelectionHydratedForIdRef.current === id) return;
|
|
13009
|
+
if (!initialRouteSelectionId) {
|
|
13010
|
+
initialUrlSelectionHydratedForIdRef.current = id;
|
|
13011
|
+
return;
|
|
13012
|
+
}
|
|
13013
|
+
if (
|
|
13014
|
+
selectedUrlSelectionId &&
|
|
13015
|
+
selectedUrlSelectionId !== initialRouteSelectionId
|
|
13016
|
+
) {
|
|
13017
|
+
initialUrlSelectionHydratedForIdRef.current = id;
|
|
13018
|
+
return;
|
|
13019
|
+
}
|
|
13020
|
+
const owner = codeLayerOwnerByNodeId.get(initialRouteSelectionId);
|
|
13021
|
+
if (!owner) return;
|
|
13022
|
+
const selectionBlocked =
|
|
13023
|
+
effectiveCodeLayerState.lockedIds.has(owner.fileId) ||
|
|
13024
|
+
effectiveCodeLayerState.hiddenIds.has(owner.fileId) ||
|
|
13025
|
+
effectiveCodeLayerState.lockedIds.has(initialRouteSelectionId) ||
|
|
13026
|
+
effectiveCodeLayerState.hiddenIds.has(initialRouteSelectionId);
|
|
13027
|
+
if (
|
|
13028
|
+
activeFileId === owner.fileId &&
|
|
13029
|
+
selectedLayerIds.includes(initialRouteSelectionId) &&
|
|
13030
|
+
(selectionBlocked || selectedElementLayerId === initialRouteSelectionId)
|
|
13031
|
+
) {
|
|
13032
|
+
initialUrlSelectionHydratedForIdRef.current = id;
|
|
13033
|
+
return;
|
|
13034
|
+
}
|
|
13035
|
+
|
|
13036
|
+
pendingOverviewScreenSelectionRef.current = null;
|
|
13037
|
+
pendingOverviewLayerSelectionRef.current = null;
|
|
13038
|
+
clearPendingOverviewLayerSelectionTimer();
|
|
13039
|
+
setCreatedOverviewLayerSelection(null);
|
|
13040
|
+
setActiveFileId(owner.fileId);
|
|
13041
|
+
setSelectedLayerIdsState([initialRouteSelectionId]);
|
|
13042
|
+
if (viewModeRef.current === "overview") {
|
|
13043
|
+
setOverviewSelectedScreenIds([]);
|
|
13044
|
+
}
|
|
13045
|
+
setSelectedElement(
|
|
13046
|
+
selectionBlocked ? null : elementInfoFromCodeLayerNode(owner.node),
|
|
13047
|
+
);
|
|
13048
|
+
setHoveredElement(null);
|
|
13049
|
+
setHoveredElementScreenId(null);
|
|
13050
|
+
setActiveTool("move");
|
|
13051
|
+
setMode("edit");
|
|
13052
|
+
if (!selectionBlocked) {
|
|
13053
|
+
focusDesignInspectorForSelection();
|
|
13054
|
+
}
|
|
13055
|
+
initialUrlSelectionHydratedForIdRef.current = id;
|
|
13056
|
+
}, [
|
|
13057
|
+
activeFileId,
|
|
13058
|
+
clearPendingOverviewLayerSelectionTimer,
|
|
13059
|
+
codeLayerOwnerByNodeId,
|
|
13060
|
+
effectiveCodeLayerState,
|
|
13061
|
+
focusDesignInspectorForSelection,
|
|
13062
|
+
id,
|
|
13063
|
+
initialRouteSelectionId,
|
|
13064
|
+
selectedElementLayerId,
|
|
13065
|
+
selectedLayerIds,
|
|
13066
|
+
selectedUrlSelectionId,
|
|
13067
|
+
]);
|
|
13068
|
+
|
|
13069
|
+
useEffect(() => {
|
|
13070
|
+
if (!id || files.length === 0) return;
|
|
13071
|
+
if (
|
|
13072
|
+
initialRouteScreenTarget &&
|
|
13073
|
+
!findDesignFileByScreenTarget(files, initialRouteScreenTarget) &&
|
|
13074
|
+
!activeFileId
|
|
13075
|
+
) {
|
|
13076
|
+
return;
|
|
13077
|
+
}
|
|
13078
|
+
const preserveInitialRouteSelection = Boolean(
|
|
13079
|
+
initialRouteSelectionId &&
|
|
13080
|
+
initialUrlSelectionHydratedForIdRef.current !== id &&
|
|
13081
|
+
initialRouteSelectionId !== selectedUrlSelectionId &&
|
|
13082
|
+
codeLayerOwnerByNodeId.size === 0,
|
|
13083
|
+
);
|
|
13084
|
+
const nextSearch = getDesignEditorStateUrlSearch({
|
|
13085
|
+
currentSearch: location.search,
|
|
13086
|
+
viewMode,
|
|
13087
|
+
screenId: activeFile?.id ?? activeFileId,
|
|
13088
|
+
selectionId:
|
|
13089
|
+
selectedUrlSelectionId ??
|
|
13090
|
+
(preserveInitialRouteSelection ? initialRouteSelectionId : null),
|
|
13091
|
+
zoom,
|
|
13092
|
+
});
|
|
13093
|
+
if (nextSearch === location.search) return;
|
|
13094
|
+
navigate(
|
|
13095
|
+
{
|
|
13096
|
+
pathname: location.pathname,
|
|
13097
|
+
search: nextSearch,
|
|
13098
|
+
hash: location.hash,
|
|
13099
|
+
},
|
|
13100
|
+
{ replace: true, preventScrollReset: true },
|
|
13101
|
+
);
|
|
13102
|
+
}, [
|
|
13103
|
+
activeFile?.id,
|
|
13104
|
+
activeFileId,
|
|
13105
|
+
codeLayerOwnerByNodeId.size,
|
|
13106
|
+
files,
|
|
13107
|
+
id,
|
|
13108
|
+
location.hash,
|
|
13109
|
+
location.pathname,
|
|
13110
|
+
location.search,
|
|
13111
|
+
navigate,
|
|
13112
|
+
initialRouteScreenTarget,
|
|
13113
|
+
initialRouteSelectionId,
|
|
13114
|
+
selectedUrlSelectionId,
|
|
13115
|
+
viewMode,
|
|
13116
|
+
zoom,
|
|
13117
|
+
]);
|
|
13118
|
+
|
|
13119
|
+
const selectedLayerTargets = useMemo<SelectedLayerTarget[]>(
|
|
13120
|
+
() =>
|
|
13121
|
+
selectedLayerIds
|
|
13122
|
+
.map((layerId) => {
|
|
13123
|
+
const owner = codeLayerOwnerByNodeId.get(layerId);
|
|
13124
|
+
if (!owner) return null;
|
|
13125
|
+
const selectedMatches =
|
|
13126
|
+
selectedElement &&
|
|
13127
|
+
codeLayerNodeMatchesBridgeTarget(
|
|
13128
|
+
owner.node,
|
|
13129
|
+
selectedElement.selector,
|
|
13130
|
+
selectedElement.sourceId ?? selectedElement.id,
|
|
13131
|
+
);
|
|
13132
|
+
return {
|
|
13133
|
+
layerId,
|
|
13134
|
+
fileId: owner.fileId,
|
|
13135
|
+
node: owner.node,
|
|
13136
|
+
tree: owner.tree,
|
|
13137
|
+
elementInfo: selectedMatches
|
|
13138
|
+
? canonicalElementInfoForCodeLayerNode(
|
|
13139
|
+
selectedElement,
|
|
13140
|
+
owner.node,
|
|
13141
|
+
)
|
|
13142
|
+
: elementInfoFromCodeLayerNode(owner.node),
|
|
13143
|
+
};
|
|
13144
|
+
})
|
|
13145
|
+
.filter((target): target is SelectedLayerTarget => Boolean(target)),
|
|
13146
|
+
[codeLayerOwnerByNodeId, selectedElement, selectedLayerIds],
|
|
13147
|
+
);
|
|
13148
|
+
|
|
13149
|
+
useLayoutEffect(() => {
|
|
13150
|
+
selectedLayerTargetsRef.current = selectedLayerTargets;
|
|
13151
|
+
}, [selectedLayerTargets]);
|
|
13152
|
+
|
|
13153
|
+
const selectedLayerSelectorGroupsByScreen = useMemo(() => {
|
|
13154
|
+
const groupsByScreen: Record<string, string[][]> = {};
|
|
13155
|
+
selectedLayerTargets.forEach((target) => {
|
|
13156
|
+
const selectorGroup = codeLayerSelectorAliases(target.node);
|
|
13157
|
+
if (selectorGroup.length === 0) return;
|
|
13158
|
+
groupsByScreen[target.fileId] = [
|
|
13159
|
+
...(groupsByScreen[target.fileId] ?? []),
|
|
13160
|
+
selectorGroup,
|
|
13161
|
+
];
|
|
13162
|
+
});
|
|
13163
|
+
return groupsByScreen;
|
|
13164
|
+
}, [selectedLayerTargets]);
|
|
13165
|
+
|
|
13166
|
+
const selectedInspectorElements = useMemo(
|
|
13167
|
+
() =>
|
|
13168
|
+
selectedLayerTargets.length > 0
|
|
13169
|
+
? selectedLayerTargets.map((target) => target.elementInfo)
|
|
13170
|
+
: selectedElement
|
|
13171
|
+
? [selectedElement]
|
|
13172
|
+
: [],
|
|
13173
|
+
[selectedElement, selectedLayerTargets],
|
|
13174
|
+
);
|
|
13175
|
+
|
|
12057
13176
|
const layerPanelSelectedIds = useMemo(
|
|
12058
13177
|
() =>
|
|
12059
13178
|
viewMode === "overview" && createdOverviewLayerSelection
|
|
@@ -12848,7 +13967,7 @@ ${serializedHtml}
|
|
|
12848
13967
|
const handleLayerSelectionChange = useCallback(
|
|
12849
13968
|
(
|
|
12850
13969
|
ids: string[],
|
|
12851
|
-
|
|
13970
|
+
_intent: {
|
|
12852
13971
|
additive: boolean;
|
|
12853
13972
|
currentSelectedIds?: string[];
|
|
12854
13973
|
id: string;
|
|
@@ -12860,40 +13979,30 @@ ${serializedHtml}
|
|
|
12860
13979
|
pendingOverviewLayerSelectionRef.current = null;
|
|
12861
13980
|
clearPendingOverviewLayerSelectionTimer();
|
|
12862
13981
|
setCreatedOverviewLayerSelection(null);
|
|
12863
|
-
if (intent.additive && !intent.range) {
|
|
12864
|
-
const currentLayerIds = (
|
|
12865
|
-
intent.currentSelectedIds && intent.currentSelectedIds.length > 0
|
|
12866
|
-
? intent.currentSelectedIds
|
|
12867
|
-
: selectedLayerIdsRef.current
|
|
12868
|
-
).filter((layerId) => !layerId.startsWith("__"));
|
|
12869
|
-
const additiveLayerIds = currentLayerIds.includes(intent.id)
|
|
12870
|
-
? currentLayerIds.filter((layerId) => layerId !== intent.id)
|
|
12871
|
-
: [...currentLayerIds, intent.id];
|
|
12872
|
-
setSelectedLayerIdsState(additiveLayerIds);
|
|
12873
|
-
if (viewModeRef.current === "overview") {
|
|
12874
|
-
const fileIds = files.map((file) => file.id);
|
|
12875
|
-
const selectedScreenIds = getOverviewScreenIdsFromLayerSelection({
|
|
12876
|
-
fileIds,
|
|
12877
|
-
layerIds: additiveLayerIds,
|
|
12878
|
-
});
|
|
12879
|
-
setOverviewSelectedScreenIds(selectedScreenIds);
|
|
12880
|
-
}
|
|
12881
|
-
setSelectedElement(null);
|
|
12882
|
-
focusDesignInspectorForSelection();
|
|
12883
|
-
setActiveTool("move");
|
|
12884
|
-
setMode("edit");
|
|
12885
|
-
return;
|
|
12886
|
-
}
|
|
12887
13982
|
setSelectedLayerIdsState(nextLayerIds);
|
|
12888
|
-
const
|
|
13983
|
+
const screenFileIds = files
|
|
13984
|
+
.filter((file) => !isBoardFile(file.filename))
|
|
13985
|
+
.map((file) => file.id);
|
|
13986
|
+
if (viewModeRef.current === "overview") {
|
|
13987
|
+
setOverviewSelectedScreenIds(
|
|
13988
|
+
getOverviewScreenIdsFromLayerSelection({
|
|
13989
|
+
fileIds: screenFileIds,
|
|
13990
|
+
layerIds: nextLayerIds,
|
|
13991
|
+
}),
|
|
13992
|
+
);
|
|
13993
|
+
}
|
|
13994
|
+
const selectedId = nextLayerIds[nextLayerIds.length - 1];
|
|
12889
13995
|
if (!selectedId) {
|
|
12890
13996
|
setSelectedElement(null);
|
|
12891
13997
|
return;
|
|
12892
13998
|
}
|
|
12893
13999
|
const codeLayerOwner = codeLayerOwnerByNodeId.get(selectedId);
|
|
12894
14000
|
if (codeLayerOwner) {
|
|
14001
|
+
const ownerIsScreenFile = screenFileIds.includes(codeLayerOwner.fileId);
|
|
12895
14002
|
if (viewModeRef.current === "overview") {
|
|
12896
|
-
pendingOverviewScreenSelectionRef.current =
|
|
14003
|
+
pendingOverviewScreenSelectionRef.current = ownerIsScreenFile
|
|
14004
|
+
? codeLayerOwner.fileId
|
|
14005
|
+
: null;
|
|
12897
14006
|
pendingOverviewLayerSelectionRef.current = selectedId;
|
|
12898
14007
|
}
|
|
12899
14008
|
if (codeLayerOwner.fileId !== activeFile?.id) {
|
|
@@ -12903,6 +14012,7 @@ ${serializedHtml}
|
|
|
12903
14012
|
currentViewMode: viewModeRef.current,
|
|
12904
14013
|
ownerFileId: codeLayerOwner.fileId,
|
|
12905
14014
|
overviewSelectedScreenIds,
|
|
14015
|
+
screenFileIds,
|
|
12906
14016
|
});
|
|
12907
14017
|
viewModeRef.current = nextSelectionState.viewMode;
|
|
12908
14018
|
setViewMode(nextSelectionState.viewMode);
|
|
@@ -12933,11 +14043,19 @@ ${serializedHtml}
|
|
|
12933
14043
|
const fileId = selectedId.startsWith("code:")
|
|
12934
14044
|
? selectedId.slice("code:".length)
|
|
12935
14045
|
: selectedId;
|
|
12936
|
-
if (
|
|
14046
|
+
if (
|
|
14047
|
+
files.some((file) => file.id === fileId && !isBoardFile(file.filename))
|
|
14048
|
+
) {
|
|
12937
14049
|
setOverviewSelectedScreenIds([fileId]);
|
|
12938
14050
|
setActiveFileId(fileId);
|
|
12939
14051
|
setSelectedElement(null);
|
|
12940
|
-
setSelectedLayerIdsState(
|
|
14052
|
+
setSelectedLayerIdsState(
|
|
14053
|
+
nextLayerIds.some((layerId) =>
|
|
14054
|
+
files.some((file) => file.id === layerId),
|
|
14055
|
+
)
|
|
14056
|
+
? nextLayerIds
|
|
14057
|
+
: [fileId],
|
|
14058
|
+
);
|
|
12941
14059
|
setActiveTool("move");
|
|
12942
14060
|
setMode("edit");
|
|
12943
14061
|
viewModeRef.current = "overview";
|
|
@@ -12955,6 +14073,113 @@ ${serializedHtml}
|
|
|
12955
14073
|
],
|
|
12956
14074
|
);
|
|
12957
14075
|
|
|
14076
|
+
const handleLayerMarqueeSelectionChange = useCallback(
|
|
14077
|
+
(
|
|
14078
|
+
selection: CanvasLayerMarqueeSelection[],
|
|
14079
|
+
intent: ElementSelectionIntent,
|
|
14080
|
+
) => {
|
|
14081
|
+
pendingOverviewScreenSelectionRef.current = null;
|
|
14082
|
+
pendingOverviewLayerSelectionRef.current = null;
|
|
14083
|
+
clearPendingOverviewLayerSelectionTimer();
|
|
14084
|
+
setCreatedOverviewLayerSelection(null);
|
|
14085
|
+
|
|
14086
|
+
const resolved = selection
|
|
14087
|
+
.map((item) => {
|
|
14088
|
+
const projection = getCodeLayerProjectionForScreen(item.screenId);
|
|
14089
|
+
if (!projection) return null;
|
|
14090
|
+
const canonical = canonicalizeElementInfoFromProjection(
|
|
14091
|
+
projection,
|
|
14092
|
+
item.info,
|
|
14093
|
+
);
|
|
14094
|
+
const node = resolveCodeLayerNodeFromElementInfo(
|
|
14095
|
+
projection,
|
|
14096
|
+
canonical,
|
|
14097
|
+
);
|
|
14098
|
+
if (!node || isScreenRootElementInfo(canonical)) return null;
|
|
14099
|
+
return {
|
|
14100
|
+
screenId: item.screenId,
|
|
14101
|
+
node,
|
|
14102
|
+
elementInfo: canonical,
|
|
14103
|
+
};
|
|
14104
|
+
})
|
|
14105
|
+
.filter(
|
|
14106
|
+
(
|
|
14107
|
+
item,
|
|
14108
|
+
): item is {
|
|
14109
|
+
screenId: string;
|
|
14110
|
+
node: CodeLayerNode;
|
|
14111
|
+
elementInfo: ElementInfo;
|
|
14112
|
+
} => Boolean(item),
|
|
14113
|
+
);
|
|
14114
|
+
|
|
14115
|
+
const hitLayerIds = dedupeStringIds(resolved.map((item) => item.node.id));
|
|
14116
|
+
setSelectedLayerIdsState((current) =>
|
|
14117
|
+
intent.additive
|
|
14118
|
+
? dedupeStringIds([
|
|
14119
|
+
...current.filter((layerId) => !layerId.startsWith("__")),
|
|
14120
|
+
...hitLayerIds,
|
|
14121
|
+
])
|
|
14122
|
+
: hitLayerIds,
|
|
14123
|
+
);
|
|
14124
|
+
if (viewModeRef.current === "overview") {
|
|
14125
|
+
setOverviewSelectedScreenIds([]);
|
|
14126
|
+
}
|
|
14127
|
+
|
|
14128
|
+
const primary = resolved[resolved.length - 1];
|
|
14129
|
+
if (primary) {
|
|
14130
|
+
setActiveFileId(primary.screenId);
|
|
14131
|
+
setSelectedElement(primary.elementInfo);
|
|
14132
|
+
focusDesignInspectorForSelection();
|
|
14133
|
+
} else if (!intent.additive) {
|
|
14134
|
+
setSelectedElement(null);
|
|
14135
|
+
}
|
|
14136
|
+
|
|
14137
|
+
setActiveTool("move");
|
|
14138
|
+
setMode("edit");
|
|
14139
|
+
},
|
|
14140
|
+
[
|
|
14141
|
+
clearPendingOverviewLayerSelectionTimer,
|
|
14142
|
+
focusDesignInspectorForSelection,
|
|
14143
|
+
getCodeLayerProjectionForScreen,
|
|
14144
|
+
],
|
|
14145
|
+
);
|
|
14146
|
+
|
|
14147
|
+
const handleScreenElementMarqueeSelect = useCallback(
|
|
14148
|
+
(
|
|
14149
|
+
screenId: string,
|
|
14150
|
+
infos: ElementInfo[],
|
|
14151
|
+
intent?: ElementSelectionIntent,
|
|
14152
|
+
) => {
|
|
14153
|
+
handleLayerMarqueeSelectionChange(
|
|
14154
|
+
infos.map((info) => ({ screenId, info })),
|
|
14155
|
+
{
|
|
14156
|
+
additive: Boolean(
|
|
14157
|
+
intent?.additive ||
|
|
14158
|
+
intent?.range ||
|
|
14159
|
+
intent?.shiftKey ||
|
|
14160
|
+
intent?.metaKey ||
|
|
14161
|
+
intent?.ctrlKey,
|
|
14162
|
+
),
|
|
14163
|
+
range: Boolean(intent?.range || intent?.shiftKey),
|
|
14164
|
+
source: "marquee",
|
|
14165
|
+
shiftKey: Boolean(intent?.shiftKey),
|
|
14166
|
+
metaKey: Boolean(intent?.metaKey),
|
|
14167
|
+
ctrlKey: Boolean(intent?.ctrlKey),
|
|
14168
|
+
},
|
|
14169
|
+
);
|
|
14170
|
+
},
|
|
14171
|
+
[handleLayerMarqueeSelectionChange],
|
|
14172
|
+
);
|
|
14173
|
+
|
|
14174
|
+
const handleElementMarqueeSelect = useCallback(
|
|
14175
|
+
(infos: ElementInfo[], intent?: ElementSelectionIntent) => {
|
|
14176
|
+
const screenId = activeFile?.id ?? activeFileId;
|
|
14177
|
+
if (!screenId) return;
|
|
14178
|
+
handleScreenElementMarqueeSelect(screenId, infos, intent);
|
|
14179
|
+
},
|
|
14180
|
+
[activeFile?.id, activeFileId, handleScreenElementMarqueeSelect],
|
|
14181
|
+
);
|
|
14182
|
+
|
|
12958
14183
|
const handleLayerRename = useCallback(
|
|
12959
14184
|
(layerId: string, name: string) => {
|
|
12960
14185
|
if (!canEditDesign) return;
|
|
@@ -13208,7 +14433,12 @@ ${serializedHtml}
|
|
|
13208
14433
|
if (!id) return null;
|
|
13209
14434
|
|
|
13210
14435
|
if (designLoading || (!design && pendingGenerationActive)) {
|
|
13211
|
-
return
|
|
14436
|
+
return (
|
|
14437
|
+
<DesignEditorSkeleton
|
|
14438
|
+
embedded={embedded}
|
|
14439
|
+
pendingGeneration={pendingGenerationActive}
|
|
14440
|
+
/>
|
|
14441
|
+
);
|
|
13212
14442
|
}
|
|
13213
14443
|
|
|
13214
14444
|
if (!design) {
|
|
@@ -13223,7 +14453,7 @@ ${serializedHtml}
|
|
|
13223
14453
|
className="absolute inset-x-0 top-0 h-px bg-[var(--design-editor-panel-divider-color)]"
|
|
13224
14454
|
/>
|
|
13225
14455
|
<div className="relative flex w-full max-w-sm flex-col items-center text-center">
|
|
13226
|
-
<div className="mb-2 text-[11px] font-medium uppercase text-muted-foreground">
|
|
14456
|
+
<div className="mb-2 !text-[11px] font-medium uppercase text-muted-foreground">
|
|
13227
14457
|
404
|
|
13228
14458
|
</div>
|
|
13229
14459
|
<h1 className="text-xl font-semibold text-foreground">
|
|
@@ -13232,7 +14462,7 @@ ${serializedHtml}
|
|
|
13232
14462
|
<Button
|
|
13233
14463
|
variant="default"
|
|
13234
14464
|
onClick={() => navigate("/")}
|
|
13235
|
-
className="mt-7 h-9 cursor-pointer gap-2 rounded-md border border-
|
|
14465
|
+
className="mt-7 h-9 cursor-pointer gap-2 rounded-md border border-foreground bg-foreground px-3.5 text-background shadow-sm hover:border-foreground/90 hover:bg-foreground/90 hover:text-background focus-visible:ring-foreground"
|
|
13236
14466
|
>
|
|
13237
14467
|
<IconArrowLeft className="size-4 rtl:-scale-x-100" />
|
|
13238
14468
|
{t("designEditor.backToDesigns")}
|
|
@@ -13537,30 +14767,39 @@ ${serializedHtml}
|
|
|
13537
14767
|
<Tooltip>
|
|
13538
14768
|
<TooltipTrigger asChild>
|
|
13539
14769
|
<Button
|
|
14770
|
+
asChild
|
|
13540
14771
|
variant="outline"
|
|
13541
14772
|
size="sm"
|
|
13542
|
-
|
|
13543
|
-
className="h-8 max-w-[13rem] cursor-pointer gap-1.5 truncate rounded-md bg-[var(--design-editor-panel-raised-bg)] px-2 text-xs shadow-none"
|
|
14773
|
+
className="h-8 cursor-pointer gap-1.5 rounded-md bg-[var(--design-editor-panel-raised-bg)] px-3 text-sm shadow-none"
|
|
13544
14774
|
aria-label={t("designEditor.signUpToSave")}
|
|
13545
14775
|
>
|
|
13546
|
-
<
|
|
13547
|
-
|
|
14776
|
+
<a
|
|
14777
|
+
href={signInToSaveHref}
|
|
14778
|
+
target="_blank"
|
|
14779
|
+
rel="noopener noreferrer"
|
|
14780
|
+
>
|
|
14781
|
+
<span>{t("home.save")}</span>
|
|
14782
|
+
</a>
|
|
13548
14783
|
</Button>
|
|
13549
14784
|
</TooltipTrigger>
|
|
13550
|
-
<TooltipContent>
|
|
13551
|
-
{t("designEditor.signUpToSaveDescription")}
|
|
13552
|
-
</TooltipContent>
|
|
14785
|
+
<TooltipContent>{t("designEditor.signUpToSave")}</TooltipContent>
|
|
13553
14786
|
</Tooltip>
|
|
13554
14787
|
<Tooltip>
|
|
13555
14788
|
<TooltipTrigger asChild>
|
|
13556
14789
|
<Button
|
|
14790
|
+
asChild
|
|
13557
14791
|
variant="default"
|
|
13558
14792
|
size="sm"
|
|
13559
|
-
onClick={handleSignInToShare}
|
|
13560
14793
|
className="h-8 cursor-pointer gap-1.5 rounded-md !border-[var(--design-editor-accent-color)] !bg-[var(--design-editor-accent-color)] px-3 text-sm !text-[var(--design-editor-accent-contrast-color)] shadow-none hover:!border-[var(--design-editor-accent-hover-color)] hover:!bg-[var(--design-editor-accent-hover-color)] hover:!text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
14794
|
+
aria-label={t("designEditor.signUpToShare")}
|
|
13561
14795
|
>
|
|
13562
|
-
<
|
|
13563
|
-
|
|
14796
|
+
<a
|
|
14797
|
+
href={signInToShareHref}
|
|
14798
|
+
target="_blank"
|
|
14799
|
+
rel="noopener noreferrer"
|
|
14800
|
+
>
|
|
14801
|
+
<span>{t("designEditor.share")}</span>
|
|
14802
|
+
</a>
|
|
13564
14803
|
</Button>
|
|
13565
14804
|
</TooltipTrigger>
|
|
13566
14805
|
<TooltipContent>{t("designEditor.signUpToShare")}</TooltipContent>
|
|
@@ -13701,7 +14940,7 @@ ${serializedHtml}
|
|
|
13701
14940
|
</PopoverContent>
|
|
13702
14941
|
</Popover>
|
|
13703
14942
|
|
|
13704
|
-
{
|
|
14943
|
+
{canRenderAuthenticatedShare ? (
|
|
13705
14944
|
<ShareButton
|
|
13706
14945
|
resourceType="design"
|
|
13707
14946
|
resourceId={id}
|
|
@@ -13715,40 +14954,15 @@ ${serializedHtml}
|
|
|
13715
14954
|
popoverClassName={designSharePopoverClassName}
|
|
13716
14955
|
triggerClassName="h-8 rounded-md !border-[var(--design-editor-accent-color)] !bg-[var(--design-editor-accent-color)] px-3 text-sm !text-[var(--design-editor-accent-contrast-color)] shadow-none hover:!border-[var(--design-editor-accent-hover-color)] hover:!bg-[var(--design-editor-accent-hover-color)] hover:!text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)] [&_svg]:!text-[var(--design-editor-accent-contrast-color)]"
|
|
13717
14956
|
/>
|
|
13718
|
-
) : (
|
|
14957
|
+
) : sessionResolved ? (
|
|
13719
14958
|
signedOutPersistenceActions
|
|
13720
|
-
)}
|
|
14959
|
+
) : null}
|
|
13721
14960
|
</div>
|
|
13722
14961
|
</div>
|
|
13723
14962
|
</div>
|
|
13724
14963
|
);
|
|
13725
14964
|
|
|
13726
14965
|
const leftContentWidth = Math.max(leftSidebarWidth, 320);
|
|
13727
|
-
const motionDockLauncherHidden = motionDockMounted || motionDockOpen;
|
|
13728
|
-
const motionDockLauncher =
|
|
13729
|
-
!embedded && activeFile ? (
|
|
13730
|
-
<div
|
|
13731
|
-
aria-hidden={motionDockLauncherHidden ? true : undefined}
|
|
13732
|
-
className={cn(
|
|
13733
|
-
"overflow-hidden border-t border-[var(--design-editor-panel-divider-color)] transition-[max-height,opacity,border-color] duration-200 ease-out",
|
|
13734
|
-
motionDockLauncherHidden
|
|
13735
|
-
? "max-h-0 border-transparent opacity-0"
|
|
13736
|
-
: "max-h-9 opacity-100",
|
|
13737
|
-
)}
|
|
13738
|
-
>
|
|
13739
|
-
<button
|
|
13740
|
-
type="button"
|
|
13741
|
-
aria-label={"Expand motion dock" /* i18n-ignore */}
|
|
13742
|
-
className="flex h-8 w-full cursor-pointer items-center gap-2 bg-[var(--design-editor-panel-bg)] px-3 text-left text-[11px] font-medium uppercase tracking-wide text-muted-foreground outline-none transition-colors hover:bg-[var(--design-editor-panel-raised-bg)] hover:text-foreground focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-[var(--design-editor-accent-color)] disabled:cursor-default"
|
|
13743
|
-
disabled={motionDockLauncherHidden}
|
|
13744
|
-
onClick={() => setMotionDockOpenAnimated(true)}
|
|
13745
|
-
>
|
|
13746
|
-
<IconChevronRight className="size-3.5 shrink-0" />
|
|
13747
|
-
<span className="min-w-0 flex-1 truncate">Motion</span>
|
|
13748
|
-
</button>
|
|
13749
|
-
</div>
|
|
13750
|
-
) : null;
|
|
13751
|
-
|
|
13752
14966
|
return (
|
|
13753
14967
|
// h-full not flex-1: the parent <main> uses overflow-y-auto, not flex,
|
|
13754
14968
|
// so flex-1 on the child doesn't resolve to the available height. h-full
|
|
@@ -14124,7 +15338,7 @@ ${serializedHtml}
|
|
|
14124
15338
|
/>
|
|
14125
15339
|
)}
|
|
14126
15340
|
|
|
14127
|
-
{!embedded &&
|
|
15341
|
+
{!embedded && canRenderAuthenticatedShare ? (
|
|
14128
15342
|
<ShareButton
|
|
14129
15343
|
resourceType="design"
|
|
14130
15344
|
resourceId={id}
|
|
@@ -14139,7 +15353,7 @@ ${serializedHtml}
|
|
|
14139
15353
|
popoverClassName={designSharePopoverClassName}
|
|
14140
15354
|
triggerClassName="h-8 rounded-md !border-[var(--design-editor-accent-color)] !bg-[var(--design-editor-accent-color)] px-3 !text-[var(--design-editor-accent-contrast-color)] shadow-none hover:!border-[var(--design-editor-accent-hover-color)] hover:!bg-[var(--design-editor-accent-hover-color)] hover:!text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)] [&_svg]:!text-[var(--design-editor-accent-contrast-color)]"
|
|
14141
15355
|
/>
|
|
14142
|
-
) : !embedded ? (
|
|
15356
|
+
) : !embedded && sessionResolved ? (
|
|
14143
15357
|
signedOutPersistenceActions
|
|
14144
15358
|
) : null}
|
|
14145
15359
|
</div>
|
|
@@ -14152,7 +15366,15 @@ ${serializedHtml}
|
|
|
14152
15366
|
<div className="relative flex min-h-0 shrink-0 border-r border-[var(--design-editor-panel-divider-color)] bg-[var(--design-editor-panel-bg)]">
|
|
14153
15367
|
<DesignWorkspaceRail
|
|
14154
15368
|
activePanel={activeLeftPanel}
|
|
15369
|
+
disabledPanels={
|
|
15370
|
+
initialGenerationChromeLimited
|
|
15371
|
+
? INITIAL_GENERATION_DISABLED_LEFT_PANELS
|
|
15372
|
+
: undefined
|
|
15373
|
+
}
|
|
15374
|
+
motionOpen={motionDockOpen}
|
|
15375
|
+
motionDisabled={!activeFile}
|
|
14155
15376
|
projectMenu={projectMenu}
|
|
15377
|
+
onMotionToggle={() => setMotionDockOpenAnimated(!motionDockOpen)}
|
|
14156
15378
|
onPanelChange={setActiveLeftPanel}
|
|
14157
15379
|
/>
|
|
14158
15380
|
<div
|
|
@@ -14186,7 +15408,6 @@ ${serializedHtml}
|
|
|
14186
15408
|
selectedIds={layerPanelSelectedIds}
|
|
14187
15409
|
expandedIds={layerPanelExpandedIds}
|
|
14188
15410
|
searchQuery={layersSearchQuery}
|
|
14189
|
-
footer={motionDockLauncher}
|
|
14190
15411
|
onScreenSelect={handleSidebarScreenSelect}
|
|
14191
15412
|
onScreenOverview={handleSidebarScreenOverview}
|
|
14192
15413
|
onAddScreen={handleAddScreen}
|
|
@@ -14216,6 +15437,7 @@ ${serializedHtml}
|
|
|
14216
15437
|
<AgentChatSurface
|
|
14217
15438
|
mode="panel"
|
|
14218
15439
|
className="min-h-0 flex-1 border-0 bg-transparent shadow-none"
|
|
15440
|
+
storageKey={DESIGN_CHAT_STORAGE_KEY}
|
|
14219
15441
|
emptyStateText={t("chat.emptyState")}
|
|
14220
15442
|
suggestions={[
|
|
14221
15443
|
t("chat.suggestionLandingPage"),
|
|
@@ -14398,18 +15620,6 @@ ${serializedHtml}
|
|
|
14398
15620
|
>
|
|
14399
15621
|
{activeFile ? (
|
|
14400
15622
|
<div className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
|
14401
|
-
{/* Banner for screens connected to a local dev server: edits
|
|
14402
|
-
route through the agent rather than being applied inline. */}
|
|
14403
|
-
{activeScreenIsLocalSource &&
|
|
14404
|
-
!localSourceBannerDismissed &&
|
|
14405
|
-
id && (
|
|
14406
|
-
<LocalSourceEditBanner
|
|
14407
|
-
designId={id}
|
|
14408
|
-
fileId={activeFile.id}
|
|
14409
|
-
routeSourceFile={activeScreenRouteSourceFile}
|
|
14410
|
-
onDismiss={() => setLocalSourceBannerDismissed(true)}
|
|
14411
|
-
/>
|
|
14412
|
-
)}
|
|
14413
15623
|
{/* "Apply to source" affordance: write the current editor
|
|
14414
15624
|
content back to the local HTML/CSS file via the bridge.
|
|
14415
15625
|
Only shown for localhost-backed screens where the route
|
|
@@ -14420,7 +15630,7 @@ ${serializedHtml}
|
|
|
14420
15630
|
<Button
|
|
14421
15631
|
size="sm"
|
|
14422
15632
|
variant="outline"
|
|
14423
|
-
className="h-6 gap-1.5 px-2 text-[11px]"
|
|
15633
|
+
className="h-6 gap-1.5 px-2 !text-[11px]"
|
|
14424
15634
|
disabled={applyToSourcePending}
|
|
14425
15635
|
onClick={handleApplyToSource}
|
|
14426
15636
|
>
|
|
@@ -14459,7 +15669,7 @@ ${serializedHtml}
|
|
|
14459
15669
|
<MultiScreenCanvas
|
|
14460
15670
|
screens={overviewScreens}
|
|
14461
15671
|
zoom={overviewCanvasZoom}
|
|
14462
|
-
onZoomChange={
|
|
15672
|
+
onZoomChange={setExplicitOverviewCanvasZoom}
|
|
14463
15673
|
activeId={activeFileId}
|
|
14464
15674
|
selectedScreenIds={overviewSelectedScreenIds}
|
|
14465
15675
|
fullViewScreenIds={fullViewScreenIds}
|
|
@@ -14488,16 +15698,68 @@ ${serializedHtml}
|
|
|
14488
15698
|
onPrimitiveReparent={handleOverviewPrimitiveReparent}
|
|
14489
15699
|
onCrossScreenElementDrop={handleCrossScreenElementDrop}
|
|
14490
15700
|
boardFileId={boardFileId}
|
|
15701
|
+
boardIsActive={activeFileId === boardFileId}
|
|
14491
15702
|
boardFileContent={boardFileContent}
|
|
14492
15703
|
boardFrameGeometry={boardFrameGeometry}
|
|
15704
|
+
boardClearSelectionRequest={overviewClearSelectionRequest}
|
|
15705
|
+
boardSelectedSelector={
|
|
15706
|
+
activeFileId === boardFileId
|
|
15707
|
+
? selectedCanvasSelector
|
|
15708
|
+
: null
|
|
15709
|
+
}
|
|
15710
|
+
boardSelectedSelectorCandidates={
|
|
15711
|
+
activeFileId === boardFileId
|
|
15712
|
+
? selectedCanvasSelectorCandidates
|
|
15713
|
+
: []
|
|
15714
|
+
}
|
|
15715
|
+
boardHoveredSelector={
|
|
15716
|
+
hoveredElementScreenId === boardFileId
|
|
15717
|
+
? hoveredCanvasSelector
|
|
15718
|
+
: null
|
|
15719
|
+
}
|
|
15720
|
+
boardHoveredSelectorCandidates={
|
|
15721
|
+
hoveredElementScreenId === boardFileId
|
|
15722
|
+
? hoveredCanvasSelectorCandidates
|
|
15723
|
+
: []
|
|
15724
|
+
}
|
|
15725
|
+
boardLockedSelectors={
|
|
15726
|
+
boardFileId
|
|
15727
|
+
? getLayerSelectorsForFile(
|
|
15728
|
+
boardFileId,
|
|
15729
|
+
lockedLayerIds,
|
|
15730
|
+
)
|
|
15731
|
+
: []
|
|
15732
|
+
}
|
|
15733
|
+
boardHiddenSelectors={
|
|
15734
|
+
boardFileId
|
|
15735
|
+
? getLayerSelectorsForFile(
|
|
15736
|
+
boardFileId,
|
|
15737
|
+
hiddenLayerIds,
|
|
15738
|
+
)
|
|
15739
|
+
: []
|
|
15740
|
+
}
|
|
14493
15741
|
onBoardDrawPrimitive={
|
|
14494
15742
|
canEditDesign ? handleBoardDrawPrimitive : undefined
|
|
14495
15743
|
}
|
|
14496
15744
|
boardEditMode={canEditDesign}
|
|
14497
15745
|
onBoardElementSelect={
|
|
14498
15746
|
boardFileId
|
|
14499
|
-
? (info) =>
|
|
14500
|
-
handleScreenElementSelect(
|
|
15747
|
+
? (info, intent) =>
|
|
15748
|
+
handleScreenElementSelect(
|
|
15749
|
+
boardFileId,
|
|
15750
|
+
info,
|
|
15751
|
+
intent,
|
|
15752
|
+
)
|
|
15753
|
+
: undefined
|
|
15754
|
+
}
|
|
15755
|
+
onBoardElementMarqueeSelect={
|
|
15756
|
+
boardFileId
|
|
15757
|
+
? (infos, intent) =>
|
|
15758
|
+
handleScreenElementMarqueeSelect(
|
|
15759
|
+
boardFileId,
|
|
15760
|
+
infos,
|
|
15761
|
+
intent,
|
|
15762
|
+
)
|
|
14501
15763
|
: undefined
|
|
14502
15764
|
}
|
|
14503
15765
|
onBoardElementHover={
|
|
@@ -14506,6 +15768,20 @@ ${serializedHtml}
|
|
|
14506
15768
|
handleScreenElementHover(boardFileId, info)
|
|
14507
15769
|
: undefined
|
|
14508
15770
|
}
|
|
15771
|
+
onBoardElementClear={
|
|
15772
|
+
boardFileId
|
|
15773
|
+
? () => handleScreenElementClear(boardFileId)
|
|
15774
|
+
: undefined
|
|
15775
|
+
}
|
|
15776
|
+
onBoardIframeHotkey={handleIframeHotkey}
|
|
15777
|
+
onBoardIframeContextMenu={handleIframeContextMenu}
|
|
15778
|
+
onBoardTextEditingStateChange={setTextEditingState}
|
|
15779
|
+
onBoardElementDblClickText={
|
|
15780
|
+
boardFileId
|
|
15781
|
+
? (info) =>
|
|
15782
|
+
handleScreenElementDblClickText(boardFileId, info)
|
|
15783
|
+
: undefined
|
|
15784
|
+
}
|
|
14509
15785
|
onBoardVisualStyleChange={
|
|
14510
15786
|
boardFileId
|
|
14511
15787
|
? (selector, styles, info) =>
|
|
@@ -14563,6 +15839,12 @@ ${serializedHtml}
|
|
|
14563
15839
|
onCreateScreenFrame={handleCreateScreenFrame}
|
|
14564
15840
|
onDeleteSelection={handleDeleteOverviewSelection}
|
|
14565
15841
|
onSelectionChange={handleOverviewScreenSelectionChange}
|
|
15842
|
+
onLayerMarqueeSelectionChange={
|
|
15843
|
+
handleLayerMarqueeSelectionChange
|
|
15844
|
+
}
|
|
15845
|
+
selectedLayerSelectorGroupsByScreen={
|
|
15846
|
+
selectedLayerSelectorGroupsByScreen
|
|
15847
|
+
}
|
|
14566
15848
|
onPick={(id) => {
|
|
14567
15849
|
pendingOverviewScreenSelectionRef.current = null;
|
|
14568
15850
|
pendingOverviewLayerSelectionRef.current = null;
|
|
@@ -14631,6 +15913,13 @@ ${serializedHtml}
|
|
|
14631
15913
|
renderScreenContent={(screen, metadata, geometry) => {
|
|
14632
15914
|
const screenIsActive = screen.id === activeFile?.id;
|
|
14633
15915
|
const screenContent = getScreenContent(screen.id);
|
|
15916
|
+
const screenSourceType =
|
|
15917
|
+
normalizeDesignSourceType(screen.sourceType) ??
|
|
15918
|
+
metadata.source ??
|
|
15919
|
+
designSourceType;
|
|
15920
|
+
const screenBridgeUrl = screen.bridgeUrl;
|
|
15921
|
+
const screenSnapshot =
|
|
15922
|
+
liveScreenSnapshotsById[screen.id]?.html;
|
|
14634
15923
|
const screenContentKey = screenIsActive
|
|
14635
15924
|
? [screen.id, contentRenderRevision].join(":")
|
|
14636
15925
|
: [
|
|
@@ -14647,7 +15936,15 @@ ${serializedHtml}
|
|
|
14647
15936
|
screenId={screen.id}
|
|
14648
15937
|
zoom={100}
|
|
14649
15938
|
deviceFrame="none"
|
|
14650
|
-
sourceType={
|
|
15939
|
+
sourceType={screenSourceType}
|
|
15940
|
+
bridgeUrl={screenBridgeUrl}
|
|
15941
|
+
externalSnapshotHtml={screenSnapshot}
|
|
15942
|
+
onExternalContentSnapshot={(snapshot) =>
|
|
15943
|
+
handleScreenExternalContentSnapshot(
|
|
15944
|
+
screen.id,
|
|
15945
|
+
snapshot,
|
|
15946
|
+
)
|
|
15947
|
+
}
|
|
14651
15948
|
fusionUrl={designFusionUrl}
|
|
14652
15949
|
onComponentSourceJump={handleComponentSourceJump}
|
|
14653
15950
|
embeddedFrame={{
|
|
@@ -14673,7 +15970,7 @@ ${serializedHtml}
|
|
|
14673
15970
|
editorChromeScaleY={overviewCanvasZoom / 100}
|
|
14674
15971
|
editMode={mode === "edit"}
|
|
14675
15972
|
interactMode={false}
|
|
14676
|
-
readOnly={!
|
|
15973
|
+
readOnly={!canEditDesign}
|
|
14677
15974
|
scaleMode={screenIsActive && activeTool === "scale"}
|
|
14678
15975
|
clearSelectionRequest={
|
|
14679
15976
|
overviewClearSelectionRequest
|
|
@@ -14687,6 +15984,10 @@ ${serializedHtml}
|
|
|
14687
15984
|
? selectedCanvasSelectorCandidates
|
|
14688
15985
|
: []
|
|
14689
15986
|
}
|
|
15987
|
+
selectedSelectorGroups={
|
|
15988
|
+
selectedLayerSelectorGroupsByScreen[screen.id] ??
|
|
15989
|
+
[]
|
|
15990
|
+
}
|
|
14690
15991
|
hoveredSelector={
|
|
14691
15992
|
hoveredElementScreenId === screen.id
|
|
14692
15993
|
? hoveredCanvasSelector
|
|
@@ -14705,8 +16006,15 @@ ${serializedHtml}
|
|
|
14705
16006
|
screen.id,
|
|
14706
16007
|
hiddenLayerIds,
|
|
14707
16008
|
)}
|
|
14708
|
-
onElementSelect={(info) =>
|
|
14709
|
-
handleScreenElementSelect(screen.id, info)
|
|
16009
|
+
onElementSelect={(info, intent) =>
|
|
16010
|
+
handleScreenElementSelect(screen.id, info, intent)
|
|
16011
|
+
}
|
|
16012
|
+
onElementMarqueeSelect={(infos, intent) =>
|
|
16013
|
+
handleScreenElementMarqueeSelect(
|
|
16014
|
+
screen.id,
|
|
16015
|
+
infos,
|
|
16016
|
+
intent,
|
|
16017
|
+
)
|
|
14710
16018
|
}
|
|
14711
16019
|
onElementHover={(info) =>
|
|
14712
16020
|
handleScreenElementHover(screen.id, info)
|
|
@@ -14791,7 +16099,16 @@ ${serializedHtml}
|
|
|
14791
16099
|
zoom={zoom}
|
|
14792
16100
|
onZoomChange={setZoom}
|
|
14793
16101
|
deviceFrame={deviceFrame}
|
|
14794
|
-
sourceType={
|
|
16102
|
+
sourceType={activeCanvasSourceType}
|
|
16103
|
+
bridgeUrl={activeScreenBridgeUrl}
|
|
16104
|
+
externalSnapshotHtml={activeScreenExternalSnapshotHtml}
|
|
16105
|
+
onExternalContentSnapshot={(snapshot) => {
|
|
16106
|
+
if (!activeFile?.id) return;
|
|
16107
|
+
handleScreenExternalContentSnapshot(
|
|
16108
|
+
activeFile.id,
|
|
16109
|
+
snapshot,
|
|
16110
|
+
);
|
|
16111
|
+
}}
|
|
14795
16112
|
fusionUrl={designFusionUrl}
|
|
14796
16113
|
previewWidthPx={activeBreakpointWidthState}
|
|
14797
16114
|
shaderFillPreview={shaderFillPreview}
|
|
@@ -14806,6 +16123,13 @@ ${serializedHtml}
|
|
|
14806
16123
|
selectedSelectorCandidates={
|
|
14807
16124
|
selectedCanvasSelectorCandidates
|
|
14808
16125
|
}
|
|
16126
|
+
selectedSelectorGroups={
|
|
16127
|
+
activeFile
|
|
16128
|
+
? (selectedLayerSelectorGroupsByScreen[
|
|
16129
|
+
activeFile.id
|
|
16130
|
+
] ?? [])
|
|
16131
|
+
: []
|
|
16132
|
+
}
|
|
14809
16133
|
hoveredSelector={hoveredCanvasSelector}
|
|
14810
16134
|
hoveredSelectorCandidates={
|
|
14811
16135
|
hoveredCanvasSelectorCandidates
|
|
@@ -14813,6 +16137,7 @@ ${serializedHtml}
|
|
|
14813
16137
|
lockedSelectors={lockedLayerSelectors}
|
|
14814
16138
|
hiddenSelectors={hiddenLayerSelectors}
|
|
14815
16139
|
onElementSelect={handleElementSelect}
|
|
16140
|
+
onElementMarqueeSelect={handleElementMarqueeSelect}
|
|
14816
16141
|
onElementHover={handleElementHover}
|
|
14817
16142
|
onClearSelection={() => {
|
|
14818
16143
|
setSelectedElement(null);
|
|
@@ -14881,18 +16206,43 @@ ${serializedHtml}
|
|
|
14881
16206
|
</div>
|
|
14882
16207
|
</div>
|
|
14883
16208
|
) : (
|
|
14884
|
-
<div className="flex flex-1 items-center justify-center">
|
|
14885
|
-
<div className="text-center">
|
|
16209
|
+
<div className="flex min-h-0 flex-1 items-center justify-center px-8 py-10">
|
|
16210
|
+
<div className="flex w-full max-w-md flex-col items-center text-center">
|
|
14886
16211
|
{generating || pendingGenerationActive ? (
|
|
14887
16212
|
<>
|
|
14888
|
-
<
|
|
16213
|
+
<div className="mb-4 flex size-12 items-center justify-center rounded-xl border border-[var(--design-editor-panel-divider-color)] bg-[var(--design-editor-panel-bg)] shadow-[0_18px_50px_-34px_rgba(0,0,0,0.8)]">
|
|
16214
|
+
<Spinner className="size-5 text-foreground/40" />
|
|
16215
|
+
</div>
|
|
14889
16216
|
<p className="text-sm text-muted-foreground">
|
|
14890
16217
|
{t("designEditor.generating")}
|
|
14891
16218
|
</p>
|
|
14892
16219
|
</>
|
|
14893
16220
|
) : (
|
|
14894
16221
|
<>
|
|
14895
|
-
<
|
|
16222
|
+
<div
|
|
16223
|
+
aria-hidden="true"
|
|
16224
|
+
className="mb-5 w-full max-w-sm rounded-xl bg-[#f7f8fb] p-3 dark:bg-[#f4f6f8]"
|
|
16225
|
+
>
|
|
16226
|
+
<div className="flex h-7 items-center justify-between px-1 pb-2">
|
|
16227
|
+
<div className="flex gap-1.5">
|
|
16228
|
+
<span className="size-2 rounded-full bg-slate-950/[0.12]" />
|
|
16229
|
+
<span className="size-2 rounded-full bg-slate-950/[0.1]" />
|
|
16230
|
+
<span className="size-2 rounded-full bg-slate-950/[0.08]" />
|
|
16231
|
+
</div>
|
|
16232
|
+
<span className="h-2 w-16 rounded bg-slate-950/[0.08]" />
|
|
16233
|
+
</div>
|
|
16234
|
+
<div className="space-y-3 pt-4">
|
|
16235
|
+
<span className="block h-5 w-2/3 rounded bg-slate-950/[0.085]" />
|
|
16236
|
+
<span className="block h-4 w-1/2 rounded bg-slate-950/[0.07]" />
|
|
16237
|
+
<div className="grid grid-cols-3 gap-2 pt-2">
|
|
16238
|
+
<span className="h-12 rounded-md bg-slate-950/[0.07]" />
|
|
16239
|
+
<span className="h-12 rounded-md bg-slate-950/[0.07]" />
|
|
16240
|
+
<span className="h-12 rounded-md bg-slate-950/[0.07]" />
|
|
16241
|
+
</div>
|
|
16242
|
+
<span className="block h-20 rounded-lg bg-slate-950/[0.07]" />
|
|
16243
|
+
</div>
|
|
16244
|
+
</div>
|
|
16245
|
+
<p className="mb-3 text-sm font-medium text-foreground/85">
|
|
14896
16246
|
{generationIssue ?? t("designEditor.noFiles")}
|
|
14897
16247
|
</p>
|
|
14898
16248
|
{retryablePrompt ? (
|
|
@@ -14904,7 +16254,7 @@ ${serializedHtml}
|
|
|
14904
16254
|
{retryablePrompt ? (
|
|
14905
16255
|
<Button
|
|
14906
16256
|
size="sm"
|
|
14907
|
-
className="cursor-pointer"
|
|
16257
|
+
className="h-8 cursor-pointer rounded-md"
|
|
14908
16258
|
onClick={handleRetryGeneration}
|
|
14909
16259
|
>
|
|
14910
16260
|
<IconRefresh className="h-3.5 w-3.5" />
|
|
@@ -14915,7 +16265,7 @@ ${serializedHtml}
|
|
|
14915
16265
|
ref={generateBtnRef}
|
|
14916
16266
|
variant={retryablePrompt ? "ghost" : "outline"}
|
|
14917
16267
|
size="sm"
|
|
14918
|
-
className="cursor-pointer"
|
|
16268
|
+
className="h-8 cursor-pointer rounded-md"
|
|
14919
16269
|
onClick={() => {
|
|
14920
16270
|
setRetryablePrompt(null);
|
|
14921
16271
|
handlePromptOpenChange(true);
|
|
@@ -14936,7 +16286,7 @@ ${serializedHtml}
|
|
|
14936
16286
|
)}
|
|
14937
16287
|
|
|
14938
16288
|
{/* Right rail */}
|
|
14939
|
-
{!embedded ? (
|
|
16289
|
+
{!embedded && !initialGenerationChromeLimited ? (
|
|
14940
16290
|
<div
|
|
14941
16291
|
className="relative flex h-full min-h-0 shrink-0 flex-col border-l border-[var(--design-editor-panel-divider-color)] bg-[var(--design-editor-panel-bg)]"
|
|
14942
16292
|
style={{ width: rightSidebarWidth }}
|
|
@@ -14953,6 +16303,7 @@ ${serializedHtml}
|
|
|
14953
16303
|
<div className="min-h-0 flex-1">
|
|
14954
16304
|
<EditPanel
|
|
14955
16305
|
selectedElement={selectedElement}
|
|
16306
|
+
selectedElements={selectedInspectorElements}
|
|
14956
16307
|
pageStyles={pageStyles}
|
|
14957
16308
|
zoom={zoom}
|
|
14958
16309
|
headerTrailing={zoomControl}
|
|
@@ -14961,7 +16312,6 @@ ${serializedHtml}
|
|
|
14961
16312
|
onActiveTabChange={setActiveInspectorTab}
|
|
14962
16313
|
tweaks={tweaks}
|
|
14963
16314
|
tweakValues={tweakSelections}
|
|
14964
|
-
readOnly={initialGenerationReadOnly}
|
|
14965
16315
|
activeContent={activeContent}
|
|
14966
16316
|
activeFileUpdatedAt={activeFile?.updatedAt ?? null}
|
|
14967
16317
|
onComponentPropApplied={handleComponentPropApplied}
|
|
@@ -14976,16 +16326,19 @@ ${serializedHtml}
|
|
|
14976
16326
|
onRequestTweaks={handleRequestTweaks}
|
|
14977
16327
|
onStyleChange={handleStyleChange}
|
|
14978
16328
|
onStylesChange={handleStylesChange}
|
|
14979
|
-
onAutoLayoutConvert={handleAutoLayoutConvert}
|
|
14980
16329
|
onExport={handleInspectorExport}
|
|
14981
16330
|
exporting={pngExporting || svgExporting}
|
|
14982
16331
|
designId={id}
|
|
14983
16332
|
fileId={activeFile?.id}
|
|
14984
|
-
filename={activeFile?.filename}
|
|
14985
16333
|
componentNodeId={selectedComponentNodeId}
|
|
14986
16334
|
sourceCapabilities={sourceCapabilities}
|
|
16335
|
+
selectedElementAlreadyComponent={
|
|
16336
|
+
selectedElementAlreadyComponent
|
|
16337
|
+
}
|
|
14987
16338
|
onCreateComponent={
|
|
14988
|
-
id && selectedElement
|
|
16339
|
+
id && selectedElement && !selectedElementAlreadyComponent
|
|
16340
|
+
? handleCreateComponent
|
|
16341
|
+
: undefined
|
|
14989
16342
|
}
|
|
14990
16343
|
defaultComponentName={defaultComponentName}
|
|
14991
16344
|
inspectCode={inspectCodeData}
|
|
@@ -15056,6 +16409,7 @@ ${serializedHtml}
|
|
|
15056
16409
|
durationMs={motionDurationMs}
|
|
15057
16410
|
open={motionDockOpen}
|
|
15058
16411
|
onOpenChange={setMotionDockOpenAnimated}
|
|
16412
|
+
onExitComplete={handleMotionDockExitComplete}
|
|
15059
16413
|
onTracksChange={handleMotionTracksChange}
|
|
15060
16414
|
onDurationChange={handleMotionDurationChange}
|
|
15061
16415
|
canvasIframeRef={canvasIframeRef}
|