@agent-native/core 0.80.2 → 0.80.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +66 -2
- package/corpus/core/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +1 -1
- package/corpus/core/docs/content/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/template-design.mdx +1 -1
- package/corpus/core/package.json +3 -7
- package/corpus/core/src/action.ts +32 -0
- package/corpus/core/src/agent/durable-background.ts +32 -11
- package/corpus/core/src/agent/production-agent.ts +14 -1
- package/corpus/core/src/agent/run-loop-with-resume.ts +3 -1
- package/corpus/core/src/brand-kit/fig/index.ts +17 -37
- package/corpus/core/src/cli/index.ts +1 -1
- package/corpus/core/src/cli/mcp-config-writers.ts +187 -51
- package/corpus/core/src/cli/plan-local.ts +445 -108
- package/corpus/core/src/cli/skills.ts +32 -8
- package/corpus/core/src/client/AssistantChat.tsx +11 -1
- package/corpus/core/src/client/agent-chat.ts +12 -1
- package/corpus/core/src/client/composer/PromptComposer.tsx +100 -41
- package/corpus/core/src/client/org/OrgSwitcher.tsx +1 -1
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -27
- package/corpus/core/src/collab/presence.ts +1 -1
- package/corpus/core/src/mcp/connect-route.ts +34 -5
- package/corpus/core/src/server/action-discovery.ts +4 -0
- package/corpus/core/src/server/action-routes.ts +14 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +20 -0
- package/corpus/core/src/vite/client.ts +15 -1
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +2 -0
- package/corpus/templates/analytics/actions/update-dashboard.ts +8 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +117 -33
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +7 -0
- package/corpus/templates/analytics/changelog/2026-06-27-dashboards-can-now-nest-under-a-parent-in-the-sidebar-via-a-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-pages-now-sit-closer-under-the-header-with-tighter.md +6 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +3 -0
- package/corpus/templates/assets/actions/generate-image.ts +3 -0
- package/corpus/templates/assets/changelog/2026-06-29-longer-agent-generation-window.md +6 -0
- package/corpus/templates/assets/netlify.toml +3 -3
- package/corpus/templates/assets/server/plugins/agent-chat.ts +4 -1
- package/corpus/templates/clips/app/components/capture-install-options.tsx +7 -8
- package/corpus/templates/clips/app/components/player/timestamped-comment-button.tsx +162 -0
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +1 -1
- package/corpus/templates/clips/app/i18n/ar-SA.ts +4 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +4 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +4 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +4 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +4 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +4 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +4 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +4 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +4 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +42 -18
- package/corpus/templates/content/AGENTS.md +12 -0
- package/corpus/templates/content/app/blocks/BuilderDocsBlocks.tsx +10 -0
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +1 -0
- package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +185 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -0
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +19 -0
- package/corpus/templates/content/app/components/editor/extensions/ContentReferenceNode.tsx +82 -0
- package/corpus/templates/content/app/global.css +9 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +12 -0
- package/corpus/templates/content/app/i18n-data.ts +140 -0
- package/corpus/templates/content/changelog/2026-06-29-reusable-mdx-references-now-preview-linked-local-documents-i.md +6 -0
- package/corpus/templates/content/shared/builder-docs-blocks.ts +8 -0
- package/corpus/templates/content/shared/builder-mdx.ts +141 -4
- package/corpus/templates/content/shared/nfm.ts +62 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +10 -11
- package/corpus/templates/design/AGENTS.md +5 -0
- package/corpus/templates/design/_gitignore +7 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +22 -12
- package/corpus/templates/design/actions/create-design-system.ts +15 -4
- package/corpus/templates/design/actions/create-file.ts +20 -1
- package/corpus/templates/design/actions/delete-design-system.ts +2 -2
- package/corpus/templates/design/actions/duplicate-design.ts +35 -9
- package/corpus/templates/design/actions/generate-design.ts +146 -69
- package/corpus/templates/design/actions/generate-screens.ts +51 -19
- package/corpus/templates/design/actions/get-design-snapshot.ts +5 -1
- package/corpus/templates/design/actions/get-design.ts +2 -0
- package/corpus/templates/design/actions/import-design-project.ts +28 -9
- package/corpus/templates/design/actions/import-document.ts +22 -8
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +6 -153
- package/corpus/templates/design/actions/navigate.ts +105 -54
- package/corpus/templates/design/actions/set-default-design-system.ts +17 -3
- package/corpus/templates/design/actions/show-design-questions.ts +2 -0
- package/corpus/templates/design/actions/update-design.ts +2 -1
- package/corpus/templates/design/actions/view-screen.ts +12 -6
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +4 -4
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +159 -41
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +8 -4
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +37 -42
- package/corpus/templates/design/app/components/design/EditPanel.tsx +336 -183
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +153 -34
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +135 -33
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +6 -0
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +2 -1
- package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +4 -10
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +84 -43
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +4 -4
- package/corpus/templates/design/app/components/design/inspector/{FigmaColorPicker.tsx → DesignColorPicker.tsx} +89 -62
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +14 -26
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +40 -12
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -4
- package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +13 -13
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -9
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +13 -4
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +6 -6
- package/corpus/templates/design/app/components/design/inspector/{figma-icons.tsx → design-icons.tsx} +2 -2
- package/corpus/templates/design/app/components/design/inspector/index.ts +12 -12
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +5 -2
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +3 -3
- package/corpus/templates/design/app/components/layout/Layout.tsx +5 -2
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +167 -91
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +16 -3
- package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +15 -0
- package/corpus/templates/design/app/global.css +4 -0
- package/corpus/templates/design/app/hooks/use-design-system.ts +4 -1
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +38 -3
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +9 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +22 -0
- package/corpus/templates/design/app/i18n-data.ts +527 -0
- package/corpus/templates/design/app/lib/design-systems.ts +6 -4
- package/corpus/templates/design/app/lib/design-templates.ts +15 -3
- package/corpus/templates/design/app/pages/DesignEditor.tsx +641 -158
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +42 -34
- package/corpus/templates/design/app/pages/VisualEdit.tsx +100 -0
- package/corpus/templates/design/app/root.tsx +42 -9
- package/corpus/templates/design/app/routes/visual-edit.tsx +7 -0
- package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +1 -1
- package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +1 -1
- package/corpus/templates/design/changelog/2026-06-28-visual-code-design-mode.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-generation-prompts-now-show-connect-ai-before-sending.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +1 -1
- package/corpus/templates/design/changelog/2026-06-29-fixed-design-editor-exports-and-gradient-edits-so-multi-scre.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-png-export-handles-modern-css-color-functions-more-reliably.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-png-export-now-ignores-duplicate-clicks-while-a-download-is-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-public-visual-edit-and-shared-design-links-now-invite-visito.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-visual-edit-actions-now-reject-missing-design-targets-before.md +6 -0
- package/corpus/templates/design/e2e/README.md +71 -0
- package/corpus/templates/design/e2e/global-setup.ts +157 -0
- package/corpus/templates/design/e2e/helpers.ts +148 -0
- package/corpus/templates/design/netlify.toml +3 -4
- package/corpus/templates/design/package.json +5 -1
- package/corpus/templates/design/playwright.config.ts +51 -0
- package/corpus/templates/design/server/handlers/index-design-system-with-builder.ts +121 -0
- package/corpus/templates/design/server/handlers/uploads.ts +89 -38
- package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +181 -0
- package/corpus/templates/design/server/lib/coding-handoff.ts +33 -4
- package/corpus/templates/design/server/lib/design-export.ts +35 -8
- package/corpus/templates/design/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/design/server/plugins/auth.ts +10 -1
- package/corpus/templates/design/server/routes/api/index-design-system-with-builder.post.ts +1 -0
- package/corpus/templates/design/shared/api.ts +2 -0
- package/corpus/templates/design/shared/apply-edits.ts +12 -8
- package/corpus/templates/design/shared/canvas-math.ts +19 -1
- package/corpus/templates/design/shared/code-layer.ts +87 -28
- package/corpus/templates/design/shared/generation-session.ts +1 -0
- package/corpus/templates/design/shared/resolve-tweaks.ts +4 -3
- package/corpus/templates/design/shared/shader-presets.ts +10 -0
- package/corpus/templates/design/shared/source-mode.ts +9 -0
- package/corpus/templates/design/vitest.config.ts +2 -1
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +1 -1
- package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +1 -1
- package/corpus/templates/plan/.agents/skills/visual-plan/references/local-files.md +14 -5
- package/corpus/templates/plan/.agents/skills/visual-recap/references/local-files.md +14 -5
- package/corpus/templates/plan/AGENTS.md +1 -1
- package/corpus/templates/plan/actions/create-plan-design.ts +1 -1
- package/corpus/templates/plan/actions/validate-local-plan-source.ts +110 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -0
- package/corpus/templates/plan/docs/commenting-ux-plan.md +2 -2
- package/corpus/templates/plan/server/lib/plan-connector-catalog.ts +1 -0
- package/corpus/templates/plan/server/lib/public-action-paths.ts +1 -0
- package/corpus/templates/plan/server/plan-content.ts +1 -1
- package/corpus/templates/plan/server/ui-plan-html.ts +2 -2
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +6 -153
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +53 -38
- package/corpus/templates/slides/app/components/design-system/{fig-import-response.ts → builder-index-response.ts} +9 -6
- package/corpus/templates/slides/app/components/visual-editor/MultiSelectChip.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-06-29-design-system-setup-now-indexes-uploaded-design-files-throug.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
- package/corpus/templates/slides/netlify.toml +3 -6
- package/corpus/templates/slides/server/handlers/index-design-system-with-builder.ts +116 -0
- package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +179 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +4 -1
- package/corpus/templates/slides/server/routes/api/index-design-system-with-builder.post.ts +1 -0
- package/dist/action.d.ts +23 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +9 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/durable-background.d.ts +9 -7
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +27 -11
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts +10 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +4 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +2 -1
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +2 -2
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/brand-kit/fig/index.d.ts +4 -4
- package/dist/brand-kit/fig/index.d.ts.map +1 -1
- package/dist/brand-kit/fig/index.js +14 -27
- package/dist/brand-kit/fig/index.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-config-writers.d.ts +15 -7
- package/dist/cli/mcp-config-writers.d.ts.map +1 -1
- package/dist/cli/mcp-config-writers.js +198 -45
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/cli/plan-local.d.ts +30 -0
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +281 -56
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/skills.d.ts +2 -2
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +32 -8
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +11 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +15 -2
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +35 -1
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/org/OrgSwitcher.js +1 -1
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +2 -15
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/collab/presence.d.ts +1 -1
- package/dist/collab/presence.js +1 -1
- package/dist/collab/presence.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/mcp/connect-route.d.ts.map +1 -1
- package/dist/mcp/connect-route.js +34 -5
- package/dist/mcp/connect-route.js.map +1 -1
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +5 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +14 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +6 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +12 -3
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +12 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/ar-SA/template-design.mdx +1 -1
- package/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
- package/docs/content/locales/de-DE/template-design.mdx +1 -1
- package/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/es-ES/template-design.mdx +1 -1
- package/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/fr-FR/template-design.mdx +1 -1
- package/docs/content/locales/hi-IN/template-design.mdx +1 -1
- package/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/ja-JP/template-design.mdx +1 -1
- package/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/ko-KR/template-design.mdx +1 -1
- package/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/pt-BR/template-design.mdx +1 -1
- package/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/zh-CN/template-design.mdx +1 -1
- package/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/zh-TW/template-design.mdx +1 -1
- package/docs/content/real-time-collaboration.mdx +2 -2
- package/docs/content/template-design.mdx +1 -1
- package/package.json +3 -7
- package/corpus/core/src/brand-kit/fig/decode.ts +0 -410
- package/corpus/core/src/brand-kit/fig/extract-design-system.ts +0 -947
- package/corpus/core/src/brand-kit/fig/fig-to-html.ts +0 -1906
- package/corpus/templates/design/actions/import-figma.ts +0 -55
- package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +0 -6
- package/corpus/templates/design/server/handlers/import-figma-system.ts +0 -86
- package/corpus/templates/design/server/routes/api/import-figma-system.post.ts +0 -1
- package/corpus/templates/slides/server/handlers/import-figma-system.ts +0 -101
- package/corpus/templates/slides/server/routes/api/import-figma-system.post.ts +0 -1
- package/dist/brand-kit/fig/decode.d.ts +0 -33
- package/dist/brand-kit/fig/decode.d.ts.map +0 -1
- package/dist/brand-kit/fig/decode.js +0 -358
- package/dist/brand-kit/fig/decode.js.map +0 -1
- package/dist/brand-kit/fig/extract-design-system.d.ts +0 -44
- package/dist/brand-kit/fig/extract-design-system.d.ts.map +0 -1
- package/dist/brand-kit/fig/extract-design-system.js +0 -752
- package/dist/brand-kit/fig/extract-design-system.js.map +0 -1
- package/dist/brand-kit/fig/fig-to-html.d.ts +0 -246
- package/dist/brand-kit/fig/fig-to-html.d.ts.map +0 -1
- package/dist/brand-kit/fig/fig-to-html.js +0 -1506
- package/dist/brand-kit/fig/fig-to-html.js.map +0 -1
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
LiveCursorOverlay,
|
|
25
25
|
useT,
|
|
26
26
|
useChangeVersion,
|
|
27
|
-
|
|
27
|
+
setAgentChatContextItem,
|
|
28
|
+
removeAgentChatContextItem,
|
|
28
29
|
useAvatarUrl,
|
|
29
30
|
type CollabUser,
|
|
30
31
|
type PromptComposerSubmitOptions,
|
|
@@ -88,6 +89,7 @@ import {
|
|
|
88
89
|
IconDownload,
|
|
89
90
|
IconFileExport,
|
|
90
91
|
IconPlayerPlay,
|
|
92
|
+
IconDeviceFloppy,
|
|
91
93
|
} from "@tabler/icons-react";
|
|
92
94
|
import { useQueryClient } from "@tanstack/react-query";
|
|
93
95
|
import {
|
|
@@ -206,6 +208,28 @@ const KEEPALIVE_FILE_SAVE_MAX_BYTES = 60_000;
|
|
|
206
208
|
// Higher than the toolbar presets so overview zooming still feels like canvas
|
|
207
209
|
// work; trackpad/pinch zooming past this commits to editing that screen.
|
|
208
210
|
const OVERVIEW_EDIT_ZOOM_THRESHOLD = 250;
|
|
211
|
+
const UNSUPPORTED_HTML2CANVAS_COLOR_RE =
|
|
212
|
+
/\b(?:color|color-mix|oklch|oklab|lab|lch)\(/i;
|
|
213
|
+
const HTML2CANVAS_COLOR_PROPERTIES = [
|
|
214
|
+
"color",
|
|
215
|
+
"background-color",
|
|
216
|
+
"border-top-color",
|
|
217
|
+
"border-right-color",
|
|
218
|
+
"border-bottom-color",
|
|
219
|
+
"border-left-color",
|
|
220
|
+
"outline-color",
|
|
221
|
+
"text-decoration-color",
|
|
222
|
+
"fill",
|
|
223
|
+
"stroke",
|
|
224
|
+
] as const;
|
|
225
|
+
const HTML2CANVAS_SHADOW_PROPERTIES = ["box-shadow", "text-shadow"] as const;
|
|
226
|
+
const HTML2CANVAS_UNSUPPORTED_VALUE_PROPERTIES = [
|
|
227
|
+
"background-image",
|
|
228
|
+
"border-image-source",
|
|
229
|
+
"list-style-image",
|
|
230
|
+
] as const;
|
|
231
|
+
|
|
232
|
+
let html2CanvasColorContext: CanvasRenderingContext2D | null | undefined;
|
|
209
233
|
|
|
210
234
|
interface FileContentSaveRequest {
|
|
211
235
|
id: string;
|
|
@@ -213,6 +237,115 @@ interface FileContentSaveRequest {
|
|
|
213
237
|
syncCollab: boolean;
|
|
214
238
|
}
|
|
215
239
|
|
|
240
|
+
function getHtml2CanvasColorContext(): CanvasRenderingContext2D | null {
|
|
241
|
+
if (html2CanvasColorContext !== undefined) return html2CanvasColorContext;
|
|
242
|
+
if (typeof document === "undefined") {
|
|
243
|
+
html2CanvasColorContext = null;
|
|
244
|
+
return html2CanvasColorContext;
|
|
245
|
+
}
|
|
246
|
+
html2CanvasColorContext = document.createElement("canvas").getContext("2d");
|
|
247
|
+
return html2CanvasColorContext;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function parseColorFunctionComponent(component: string): number {
|
|
251
|
+
const trimmed = component.trim();
|
|
252
|
+
if (trimmed.endsWith("%")) {
|
|
253
|
+
return (Number(trimmed.slice(0, -1)) / 100) * 255;
|
|
254
|
+
}
|
|
255
|
+
const value = Number(trimmed);
|
|
256
|
+
if (!Number.isFinite(value)) return 0;
|
|
257
|
+
return Math.abs(value) <= 1 ? value * 255 : value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function parseColorFunctionAlpha(alpha: string | undefined): number {
|
|
261
|
+
if (!alpha) return 1;
|
|
262
|
+
const trimmed = alpha.trim();
|
|
263
|
+
if (trimmed.endsWith("%")) return Number(trimmed.slice(0, -1)) / 100;
|
|
264
|
+
const value = Number(trimmed);
|
|
265
|
+
return Number.isFinite(value) ? value : 1;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function parseRgbLikeColorFunction(value: string): string | null {
|
|
269
|
+
const match = value.match(/color\(\s*[\w-]+\s+([^)]+)\)/i);
|
|
270
|
+
if (!match) return null;
|
|
271
|
+
const [componentsPart, alphaPart] = match[1].split("/");
|
|
272
|
+
const channels = componentsPart.trim().split(/\s+/).slice(0, 3);
|
|
273
|
+
if (channels.length < 3) return null;
|
|
274
|
+
const [red, green, blue] = channels
|
|
275
|
+
.map(parseColorFunctionComponent)
|
|
276
|
+
.map((channel) => Math.round(Math.max(0, Math.min(255, channel))));
|
|
277
|
+
const alpha = Math.max(0, Math.min(1, parseColorFunctionAlpha(alphaPart)));
|
|
278
|
+
return alpha < 1
|
|
279
|
+
? `rgba(${red}, ${green}, ${blue}, ${alpha})`
|
|
280
|
+
: `rgb(${red}, ${green}, ${blue})`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function normalizeHtml2CanvasColor(value: string): string {
|
|
284
|
+
if (!UNSUPPORTED_HTML2CANVAS_COLOR_RE.test(value)) return value;
|
|
285
|
+
const context = getHtml2CanvasColorContext();
|
|
286
|
+
if (context) {
|
|
287
|
+
try {
|
|
288
|
+
context.fillStyle = "#000";
|
|
289
|
+
context.fillStyle = value;
|
|
290
|
+
const normalized = String(context.fillStyle);
|
|
291
|
+
if (normalized && !UNSUPPORTED_HTML2CANVAS_COLOR_RE.test(normalized)) {
|
|
292
|
+
return normalized;
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
// Fall back to small parser below.
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return parseRgbLikeColorFunction(value) ?? "rgb(0, 0, 0)";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function elementInlineStyle(
|
|
302
|
+
element: Element | undefined,
|
|
303
|
+
): CSSStyleDeclaration | null {
|
|
304
|
+
if (!element) return null;
|
|
305
|
+
const style = (element as Element & { style?: CSSStyleDeclaration }).style;
|
|
306
|
+
return style && typeof style.setProperty === "function" ? style : null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function sanitizeHtml2CanvasClone(
|
|
310
|
+
sourceDocument: Document,
|
|
311
|
+
clonedDocument: Document,
|
|
312
|
+
) {
|
|
313
|
+
const sourceView = sourceDocument.defaultView;
|
|
314
|
+
if (!sourceView) return;
|
|
315
|
+
const sourceElements = [
|
|
316
|
+
sourceDocument.documentElement,
|
|
317
|
+
...Array.from(sourceDocument.documentElement.querySelectorAll("*")),
|
|
318
|
+
];
|
|
319
|
+
const clonedElements = [
|
|
320
|
+
clonedDocument.documentElement,
|
|
321
|
+
...Array.from(clonedDocument.documentElement.querySelectorAll("*")),
|
|
322
|
+
];
|
|
323
|
+
sourceElements.forEach((sourceElement, index) => {
|
|
324
|
+
const clonedStyle = elementInlineStyle(clonedElements[index]);
|
|
325
|
+
if (!clonedStyle) return;
|
|
326
|
+
const computed = sourceView.getComputedStyle(sourceElement);
|
|
327
|
+
for (const property of HTML2CANVAS_COLOR_PROPERTIES) {
|
|
328
|
+
const value = computed.getPropertyValue(property);
|
|
329
|
+
if (!value || !UNSUPPORTED_HTML2CANVAS_COLOR_RE.test(value)) continue;
|
|
330
|
+
clonedStyle.setProperty(
|
|
331
|
+
property,
|
|
332
|
+
normalizeHtml2CanvasColor(value),
|
|
333
|
+
"important",
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
for (const property of HTML2CANVAS_SHADOW_PROPERTIES) {
|
|
337
|
+
const value = computed.getPropertyValue(property);
|
|
338
|
+
if (!value || !UNSUPPORTED_HTML2CANVAS_COLOR_RE.test(value)) continue;
|
|
339
|
+
clonedStyle.setProperty(property, "none", "important");
|
|
340
|
+
}
|
|
341
|
+
for (const property of HTML2CANVAS_UNSUPPORTED_VALUE_PROPERTIES) {
|
|
342
|
+
const value = computed.getPropertyValue(property);
|
|
343
|
+
if (!value || !UNSUPPORTED_HTML2CANVAS_COLOR_RE.test(value)) continue;
|
|
344
|
+
clonedStyle.setProperty(property, "none", "important");
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
216
349
|
function byteLength(value: string): number {
|
|
217
350
|
if (typeof TextEncoder === "undefined") return value.length;
|
|
218
351
|
return new TextEncoder().encode(value).length;
|
|
@@ -257,6 +390,34 @@ type DesignTool =
|
|
|
257
390
|
| "scale";
|
|
258
391
|
type ShapeTool = "rect" | "line" | "arrow" | "ellipse" | "polygon" | "star";
|
|
259
392
|
|
|
393
|
+
const DESIGN_EDITOR_TOOLS = new Set<DesignTool>([
|
|
394
|
+
"move",
|
|
395
|
+
"frame",
|
|
396
|
+
"rect",
|
|
397
|
+
"line",
|
|
398
|
+
"arrow",
|
|
399
|
+
"ellipse",
|
|
400
|
+
"polygon",
|
|
401
|
+
"star",
|
|
402
|
+
"text",
|
|
403
|
+
"pen",
|
|
404
|
+
"hand",
|
|
405
|
+
"comment",
|
|
406
|
+
"draw",
|
|
407
|
+
"scale",
|
|
408
|
+
]);
|
|
409
|
+
|
|
410
|
+
function normalizeDesignTool(value: unknown): DesignTool | null {
|
|
411
|
+
return typeof value === "string" &&
|
|
412
|
+
DESIGN_EDITOR_TOOLS.has(value as DesignTool)
|
|
413
|
+
? (value as DesignTool)
|
|
414
|
+
: null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function isSingleScreenAnnotationTool(tool: DesignTool): boolean {
|
|
418
|
+
return tool === "draw" || tool === "comment";
|
|
419
|
+
}
|
|
420
|
+
|
|
260
421
|
interface DesignFile {
|
|
261
422
|
id: string;
|
|
262
423
|
filename: string;
|
|
@@ -273,9 +434,23 @@ interface DesignData {
|
|
|
273
434
|
projectType: string;
|
|
274
435
|
designSystemId?: string | null;
|
|
275
436
|
data?: string | null;
|
|
437
|
+
accessRole?: DesignAccessRole;
|
|
276
438
|
files: DesignFile[];
|
|
277
439
|
}
|
|
278
440
|
|
|
441
|
+
type DesignAccessRole = "owner" | "admin" | "editor" | "viewer";
|
|
442
|
+
type PostAuthDesignIntent = "save" | "share";
|
|
443
|
+
|
|
444
|
+
function buildSignInHrefForDesignIntent(intent: PostAuthDesignIntent): string {
|
|
445
|
+
const base = agentNativePath("/_agent-native/sign-in");
|
|
446
|
+
if (typeof window === "undefined") return base;
|
|
447
|
+
|
|
448
|
+
const returnUrl = new URL(window.location.href);
|
|
449
|
+
returnUrl.searchParams.set("intent", intent);
|
|
450
|
+
const ret = returnUrl.pathname + returnUrl.search + returnUrl.hash;
|
|
451
|
+
return `${base}?return=${encodeURIComponent(ret)}`;
|
|
452
|
+
}
|
|
453
|
+
|
|
279
454
|
interface GeometryHistoryEntry {
|
|
280
455
|
before: CanvasFrameGeometryById;
|
|
281
456
|
after: CanvasFrameGeometryById;
|
|
@@ -424,14 +599,17 @@ function designEditorCommandFromSearchParams(
|
|
|
424
599
|
searchParams.get("screen") ??
|
|
425
600
|
searchParams.get("fileId") ??
|
|
426
601
|
searchParams.get("filename");
|
|
427
|
-
const
|
|
602
|
+
const rawZoom = searchParams.get("zoom");
|
|
603
|
+
const zoom = rawZoom !== null ? Number(rawZoom) : NaN;
|
|
604
|
+
const tool = normalizeDesignTool(searchParams.get("tool"));
|
|
428
605
|
if (
|
|
429
606
|
editorView !== "overview" &&
|
|
430
607
|
editorView !== "single" &&
|
|
431
608
|
inspector !== "design" &&
|
|
432
609
|
inspector !== "tweaks" &&
|
|
433
610
|
inspector !== "extensions" &&
|
|
434
|
-
!screen
|
|
611
|
+
!screen &&
|
|
612
|
+
!tool
|
|
435
613
|
) {
|
|
436
614
|
return null;
|
|
437
615
|
}
|
|
@@ -455,6 +633,7 @@ function designEditorCommandFromSearchParams(
|
|
|
455
633
|
} else if (editorView === "single") {
|
|
456
634
|
command.zoom = FOCUSED_SCREEN_ZOOM;
|
|
457
635
|
}
|
|
636
|
+
if (tool) command.tool = tool;
|
|
458
637
|
return command;
|
|
459
638
|
}
|
|
460
639
|
|
|
@@ -662,7 +841,7 @@ function appendCanvasPrimitiveToHtml(
|
|
|
662
841
|
const top = Math.max(0, Math.round(geometry.y));
|
|
663
842
|
const width = Math.max(1, Math.round(geometry.width));
|
|
664
843
|
const height = Math.max(1, Math.round(geometry.height));
|
|
665
|
-
const nodeId = uniqueLayerId(primitive.kind);
|
|
844
|
+
const nodeId = primitive.nodeId ?? uniqueLayerId(primitive.kind);
|
|
666
845
|
const layerName = primitiveLayerName(primitive);
|
|
667
846
|
|
|
668
847
|
if (
|
|
@@ -1191,8 +1370,8 @@ function codeLayerTreeToPanelNodes(
|
|
|
1191
1370
|
renamable: node.renamable,
|
|
1192
1371
|
lockable: true,
|
|
1193
1372
|
hideable: true,
|
|
1194
|
-
locked
|
|
1195
|
-
hidden
|
|
1373
|
+
locked,
|
|
1374
|
+
hidden,
|
|
1196
1375
|
children: codeLayerTreeToPanelNodes(
|
|
1197
1376
|
node.children,
|
|
1198
1377
|
lockedIds,
|
|
@@ -1204,6 +1383,36 @@ function codeLayerTreeToPanelNodes(
|
|
|
1204
1383
|
});
|
|
1205
1384
|
}
|
|
1206
1385
|
|
|
1386
|
+
interface EffectiveCodeLayerState {
|
|
1387
|
+
lockedIds: Set<string>;
|
|
1388
|
+
hiddenIds: Set<string>;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
function collectEffectiveCodeLayerState(
|
|
1392
|
+
nodes: CodeLayerTreeNode[],
|
|
1393
|
+
lockedIds: Set<string>,
|
|
1394
|
+
hiddenIds: Set<string>,
|
|
1395
|
+
inheritedLocked: boolean,
|
|
1396
|
+
inheritedHidden: boolean,
|
|
1397
|
+
state: EffectiveCodeLayerState,
|
|
1398
|
+
): EffectiveCodeLayerState {
|
|
1399
|
+
nodes.forEach((node) => {
|
|
1400
|
+
const locked = inheritedLocked || lockedIds.has(node.id);
|
|
1401
|
+
const hidden = inheritedHidden || hiddenIds.has(node.id);
|
|
1402
|
+
if (locked) state.lockedIds.add(node.id);
|
|
1403
|
+
if (hidden) state.hiddenIds.add(node.id);
|
|
1404
|
+
collectEffectiveCodeLayerState(
|
|
1405
|
+
node.children,
|
|
1406
|
+
lockedIds,
|
|
1407
|
+
hiddenIds,
|
|
1408
|
+
locked,
|
|
1409
|
+
hidden,
|
|
1410
|
+
state,
|
|
1411
|
+
);
|
|
1412
|
+
});
|
|
1413
|
+
return state;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1207
1416
|
function bridgeSourceIdForCodeLayerNode(node: CodeLayerNode): string {
|
|
1208
1417
|
return (
|
|
1209
1418
|
node.dataAttributes["data-agent-native-node-id"] ??
|
|
@@ -1477,7 +1686,7 @@ function fullPreviewHtml(content: string): string {
|
|
|
1477
1686
|
return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head><body>${content}</body></html>`;
|
|
1478
1687
|
}
|
|
1479
1688
|
|
|
1480
|
-
type
|
|
1689
|
+
type DesignToolbarOption = {
|
|
1481
1690
|
key: string;
|
|
1482
1691
|
label: string;
|
|
1483
1692
|
icon: ReactNode;
|
|
@@ -1487,7 +1696,7 @@ type FigmaToolbarOption = {
|
|
|
1487
1696
|
onSelect: () => void;
|
|
1488
1697
|
};
|
|
1489
1698
|
|
|
1490
|
-
function
|
|
1699
|
+
function DesignToolbarTool({
|
|
1491
1700
|
active,
|
|
1492
1701
|
label,
|
|
1493
1702
|
icon,
|
|
@@ -1497,7 +1706,7 @@ function FigmaToolbarTool({
|
|
|
1497
1706
|
active: boolean;
|
|
1498
1707
|
label: string;
|
|
1499
1708
|
icon: ReactNode;
|
|
1500
|
-
options:
|
|
1709
|
+
options: DesignToolbarOption[];
|
|
1501
1710
|
onPrimary: () => void;
|
|
1502
1711
|
}) {
|
|
1503
1712
|
const hasOptionsMenu = options.length > 1;
|
|
@@ -1506,7 +1715,7 @@ function FigmaToolbarTool({
|
|
|
1506
1715
|
className={cn(
|
|
1507
1716
|
"flex h-8 items-center overflow-hidden rounded-md text-neutral-200 transition-colors",
|
|
1508
1717
|
active
|
|
1509
|
-
? "bg-[
|
|
1718
|
+
? "bg-[var(--design-editor-accent-color)] text-white"
|
|
1510
1719
|
: "hover:bg-white/10 hover:text-white",
|
|
1511
1720
|
)}
|
|
1512
1721
|
>
|
|
@@ -1574,7 +1783,7 @@ function FigmaToolbarTool({
|
|
|
1574
1783
|
);
|
|
1575
1784
|
}
|
|
1576
1785
|
|
|
1577
|
-
function
|
|
1786
|
+
function DesignModeTab({
|
|
1578
1787
|
active,
|
|
1579
1788
|
disabled,
|
|
1580
1789
|
label,
|
|
@@ -1610,11 +1819,13 @@ function FigmaModeTab({
|
|
|
1610
1819
|
);
|
|
1611
1820
|
}
|
|
1612
1821
|
|
|
1613
|
-
function
|
|
1822
|
+
function DesignBottomToolbar({
|
|
1614
1823
|
mode,
|
|
1615
1824
|
pinMode,
|
|
1616
1825
|
drawMode,
|
|
1617
1826
|
activeTool,
|
|
1827
|
+
isOverview,
|
|
1828
|
+
hasActiveFile,
|
|
1618
1829
|
onMove,
|
|
1619
1830
|
onFrame,
|
|
1620
1831
|
onShape,
|
|
@@ -1630,6 +1841,8 @@ function FigmaBottomToolbar({
|
|
|
1630
1841
|
pinMode: boolean;
|
|
1631
1842
|
drawMode: boolean;
|
|
1632
1843
|
activeTool: DesignTool;
|
|
1844
|
+
isOverview: boolean;
|
|
1845
|
+
hasActiveFile: boolean;
|
|
1633
1846
|
onMove: () => void;
|
|
1634
1847
|
onFrame: () => void;
|
|
1635
1848
|
onShape: (tool: ShapeTool) => void;
|
|
@@ -1670,7 +1883,7 @@ function FigmaBottomToolbar({
|
|
|
1670
1883
|
return <IconSquare className={className} />;
|
|
1671
1884
|
}
|
|
1672
1885
|
};
|
|
1673
|
-
const shapeOptions:
|
|
1886
|
+
const shapeOptions: DesignToolbarOption[] = [
|
|
1674
1887
|
{
|
|
1675
1888
|
key: "rect",
|
|
1676
1889
|
label: t("designEditor.tools.rect"),
|
|
@@ -1735,7 +1948,7 @@ function FigmaBottomToolbar({
|
|
|
1735
1948
|
label: string;
|
|
1736
1949
|
icon: ReactNode;
|
|
1737
1950
|
onClick: () => void;
|
|
1738
|
-
options:
|
|
1951
|
+
options: DesignToolbarOption[];
|
|
1739
1952
|
}> = [
|
|
1740
1953
|
{
|
|
1741
1954
|
key: "move",
|
|
@@ -1832,6 +2045,7 @@ function FigmaBottomToolbar({
|
|
|
1832
2045
|
label: t("designEditor.modes.draw"),
|
|
1833
2046
|
icon: <IconBrush className="size-4" />,
|
|
1834
2047
|
active: activeTool === "draw" && mode === "annotate" && drawMode,
|
|
2048
|
+
disabled: !hasActiveFile || isOverview,
|
|
1835
2049
|
onSelect: onDraw,
|
|
1836
2050
|
},
|
|
1837
2051
|
],
|
|
@@ -1849,6 +2063,7 @@ function FigmaBottomToolbar({
|
|
|
1849
2063
|
icon: <IconMessage className="size-4" />,
|
|
1850
2064
|
shortcut: "C",
|
|
1851
2065
|
active: activeTool === "comment" && mode === "annotate" && pinMode,
|
|
2066
|
+
disabled: !hasActiveFile || isOverview,
|
|
1852
2067
|
onSelect: onCommentPin,
|
|
1853
2068
|
},
|
|
1854
2069
|
{
|
|
@@ -1856,6 +2071,7 @@ function FigmaBottomToolbar({
|
|
|
1856
2071
|
label: t("designEditor.modes.draw"),
|
|
1857
2072
|
icon: <IconBrush className="size-4" />,
|
|
1858
2073
|
active: activeTool === "draw" && mode === "annotate" && drawMode,
|
|
2074
|
+
disabled: !hasActiveFile || isOverview,
|
|
1859
2075
|
onSelect: onDraw,
|
|
1860
2076
|
},
|
|
1861
2077
|
],
|
|
@@ -1896,7 +2112,7 @@ function FigmaBottomToolbar({
|
|
|
1896
2112
|
<div className="absolute bottom-4 left-1/2 z-[70] flex max-w-[calc(100%-2rem)] -translate-x-1/2 items-center gap-1.5 overflow-hidden rounded-xl border border-white/10 bg-[#2c2c2c]/95 p-1.5 text-neutral-100 shadow-[0_22px_55px_-24px_rgba(0,0,0,0.9),0_0_0_1px_rgba(0,0,0,0.25)] backdrop-blur">
|
|
1897
2113
|
<div className="flex min-w-0 items-center gap-0.5">
|
|
1898
2114
|
{tools.map((tool) => (
|
|
1899
|
-
<
|
|
2115
|
+
<DesignToolbarTool
|
|
1900
2116
|
key={tool.key}
|
|
1901
2117
|
active={tool.active}
|
|
1902
2118
|
label={tool.label}
|
|
@@ -1911,7 +2127,7 @@ function FigmaBottomToolbar({
|
|
|
1911
2127
|
|
|
1912
2128
|
<div className="flex shrink-0 items-center gap-0.5 rounded-md bg-white/10 p-0.5">
|
|
1913
2129
|
{modes.map((item) => (
|
|
1914
|
-
<
|
|
2130
|
+
<DesignModeTab
|
|
1915
2131
|
key={item.key}
|
|
1916
2132
|
active={item.active}
|
|
1917
2133
|
label={item.label}
|
|
@@ -2002,6 +2218,10 @@ export default function DesignEditor() {
|
|
|
2002
2218
|
() => new URLSearchParams(location.search),
|
|
2003
2219
|
[location.search],
|
|
2004
2220
|
);
|
|
2221
|
+
const postAuthIntent = useMemo<PostAuthDesignIntent | null>(() => {
|
|
2222
|
+
const value = searchParams.get("intent");
|
|
2223
|
+
return value === "save" || value === "share" ? value : null;
|
|
2224
|
+
}, [searchParams]);
|
|
2005
2225
|
const queryClient = useQueryClient();
|
|
2006
2226
|
const appStateVersion = useChangeVersion("app-state");
|
|
2007
2227
|
const browserTabId = getBrowserTabId();
|
|
@@ -2027,6 +2247,10 @@ export default function DesignEditor() {
|
|
|
2027
2247
|
const [deviceFrame, setDeviceFrame] = useState<DeviceFrameType>("none");
|
|
2028
2248
|
const [viewMode, setViewMode] = useState<"single" | "overview">("overview");
|
|
2029
2249
|
const viewModeRef = useRef<"single" | "overview">("overview");
|
|
2250
|
+
// Trusted parent origin captured from the first validated inbound message.
|
|
2251
|
+
// Used to restrict outgoing postMessage calls that carry user data so they
|
|
2252
|
+
// are never broadcast to an arbitrary embedding page.
|
|
2253
|
+
const parentOriginRef = useRef<string | null>(null);
|
|
2030
2254
|
const [selectedElement, setSelectedElement] = useState<ElementInfo | null>(
|
|
2031
2255
|
null,
|
|
2032
2256
|
);
|
|
@@ -2063,13 +2287,12 @@ export default function DesignEditor() {
|
|
|
2063
2287
|
const copiedLayerHtmlRef = useRef<string | null>(null);
|
|
2064
2288
|
const copiedStylePropsRef = useRef<Record<string, string> | null>(null);
|
|
2065
2289
|
const spaceHandPreviousToolRef = useRef<DesignTool | null>(null);
|
|
2066
|
-
const { set: setAgentChatContextItem, remove: removeAgentChatContextItem } =
|
|
2067
|
-
useAgentChatContext();
|
|
2068
2290
|
const hasSelectedElement = Boolean(selectedElement);
|
|
2069
2291
|
|
|
2070
2292
|
useEffect(() => {
|
|
2071
2293
|
if (!isBuilderDesignEmbed) return;
|
|
2072
|
-
// Announce ready to Builder
|
|
2294
|
+
// Announce ready to Builder. The trusted origin is not yet known at this
|
|
2295
|
+
// point so we use "*" — this message carries no user data.
|
|
2073
2296
|
window.parent.postMessage({ type: "agentNative.appReady" }, "*");
|
|
2074
2297
|
|
|
2075
2298
|
function handleDesignHostMessage(event: MessageEvent) {
|
|
@@ -2093,6 +2316,12 @@ export default function DesignEditor() {
|
|
|
2093
2316
|
if (!data || typeof data.type !== "string") return;
|
|
2094
2317
|
|
|
2095
2318
|
if (data.type === "design:init") {
|
|
2319
|
+
// Capture the trusted parent origin on the first validated message so
|
|
2320
|
+
// outgoing postMessage calls that carry user data can restrict the
|
|
2321
|
+
// target instead of broadcasting to "*".
|
|
2322
|
+
if (!parentOriginRef.current) {
|
|
2323
|
+
parentOriginRef.current = origin;
|
|
2324
|
+
}
|
|
2096
2325
|
const { previewUrl, themeVars } = data.data ?? {};
|
|
2097
2326
|
// Apply theme vars
|
|
2098
2327
|
if (themeVars && typeof themeVars === "object") {
|
|
@@ -2213,7 +2442,9 @@ export default function DesignEditor() {
|
|
|
2213
2442
|
const [pinMode, setPinMode] = useState(false);
|
|
2214
2443
|
const [showPrompt, setShowPrompt] = useState(false);
|
|
2215
2444
|
const [showTweakPrompt, setShowTweakPrompt] = useState(false);
|
|
2445
|
+
const [pngExporting, setPngExporting] = useState(false);
|
|
2216
2446
|
const [svgExporting, setSvgExporting] = useState(false);
|
|
2447
|
+
const pngExportingRef = useRef(false);
|
|
2217
2448
|
const generateBtnRef = useRef<HTMLButtonElement | null>(null);
|
|
2218
2449
|
const promptAnchorRef = useRef<HTMLElement | null>(null);
|
|
2219
2450
|
const tweakPromptAnchorRef = useRef<HTMLElement | null>(null);
|
|
@@ -2438,6 +2669,7 @@ export default function DesignEditor() {
|
|
|
2438
2669
|
);
|
|
2439
2670
|
|
|
2440
2671
|
const { session } = useSession();
|
|
2672
|
+
const isSignedIn = Boolean(session?.email);
|
|
2441
2673
|
const pendingVariantKey = useMemo(
|
|
2442
2674
|
() =>
|
|
2443
2675
|
pendingVariants
|
|
@@ -2492,6 +2724,12 @@ export default function DesignEditor() {
|
|
|
2492
2724
|
: undefined,
|
|
2493
2725
|
[session?.email, session?.name],
|
|
2494
2726
|
);
|
|
2727
|
+
const handleSignInToSave = useCallback(() => {
|
|
2728
|
+
window.location.href = buildSignInHrefForDesignIntent("save");
|
|
2729
|
+
}, []);
|
|
2730
|
+
const handleSignInToShare = useCallback(() => {
|
|
2731
|
+
window.location.href = buildSignInHrefForDesignIntent("share");
|
|
2732
|
+
}, []);
|
|
2495
2733
|
|
|
2496
2734
|
// Data fetching
|
|
2497
2735
|
useEffect(() => {
|
|
@@ -2565,22 +2803,22 @@ export default function DesignEditor() {
|
|
|
2565
2803
|
const deleteFileMutation = useActionMutation("delete-file");
|
|
2566
2804
|
const updateDesignMutation = useActionMutation("update-design");
|
|
2567
2805
|
const applyTweaksMutation = useActionMutation("apply-tweaks");
|
|
2806
|
+
const duplicateDesignMutation = useActionMutation("duplicate-design");
|
|
2568
2807
|
const createCodingHandoffMutation = useActionMutation(
|
|
2569
2808
|
"export-coding-handoff",
|
|
2570
2809
|
);
|
|
2571
2810
|
const exportHtmlMutation = useActionMutation("export-html");
|
|
2572
2811
|
const exportZipMutation = useActionMutation("export-zip");
|
|
2573
2812
|
const [, setPatchProof] = useState<PatchProofState | null>(null);
|
|
2574
|
-
const
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
syncCollab: boolean;
|
|
2578
|
-
} | null>(null);
|
|
2813
|
+
const pendingFileSavesRef = useRef<Record<string, FileContentSaveRequest>>(
|
|
2814
|
+
{},
|
|
2815
|
+
);
|
|
2579
2816
|
const fileSaveChainsRef = useRef<Record<string, Promise<void>>>({});
|
|
2580
2817
|
const latestFileSaveForUnloadRef = useRef<
|
|
2581
2818
|
Record<string, FileContentSaveRequest>
|
|
2582
2819
|
>({});
|
|
2583
|
-
const
|
|
2820
|
+
const fileSaveTimersRef = useRef<Record<string, number>>({});
|
|
2821
|
+
const postAuthSaveRef = useRef<string | null>(null);
|
|
2584
2822
|
|
|
2585
2823
|
const saveFileContent = useCallback(
|
|
2586
2824
|
(pending: FileContentSaveRequest) => {
|
|
@@ -2639,22 +2877,24 @@ export default function DesignEditor() {
|
|
|
2639
2877
|
};
|
|
2640
2878
|
latestFileSaveForUnloadRef.current[fileId] = pending;
|
|
2641
2879
|
if (options.immediate) {
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2880
|
+
const timer = fileSaveTimersRef.current[fileId];
|
|
2881
|
+
if (timer) {
|
|
2882
|
+
window.clearTimeout(timer);
|
|
2883
|
+
delete fileSaveTimersRef.current[fileId];
|
|
2645
2884
|
}
|
|
2646
|
-
|
|
2885
|
+
delete pendingFileSavesRef.current[fileId];
|
|
2647
2886
|
saveFileContent(pending);
|
|
2648
2887
|
return;
|
|
2649
2888
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2889
|
+
pendingFileSavesRef.current[fileId] = pending;
|
|
2890
|
+
const timer = fileSaveTimersRef.current[fileId];
|
|
2891
|
+
if (timer) {
|
|
2892
|
+
window.clearTimeout(timer);
|
|
2653
2893
|
}
|
|
2654
|
-
|
|
2655
|
-
const pending =
|
|
2656
|
-
|
|
2657
|
-
|
|
2894
|
+
fileSaveTimersRef.current[fileId] = window.setTimeout(() => {
|
|
2895
|
+
const pending = pendingFileSavesRef.current[fileId];
|
|
2896
|
+
delete pendingFileSavesRef.current[fileId];
|
|
2897
|
+
delete fileSaveTimersRef.current[fileId];
|
|
2658
2898
|
if (!pending) return;
|
|
2659
2899
|
saveFileContent(pending);
|
|
2660
2900
|
}, 400);
|
|
@@ -2663,21 +2903,26 @@ export default function DesignEditor() {
|
|
|
2663
2903
|
);
|
|
2664
2904
|
|
|
2665
2905
|
useEffect(() => {
|
|
2666
|
-
const
|
|
2667
|
-
|
|
2668
|
-
latestFileSaveForUnloadRef.current[
|
|
2669
|
-
pendingFileSaveRef.current;
|
|
2906
|
+
const sendPendingKeepaliveSaves = () => {
|
|
2907
|
+
for (const pending of Object.values(pendingFileSavesRef.current)) {
|
|
2908
|
+
latestFileSaveForUnloadRef.current[pending.id] = pending;
|
|
2670
2909
|
}
|
|
2671
2910
|
Object.values(latestFileSaveForUnloadRef.current).forEach(
|
|
2672
2911
|
sendFileContentSaveKeepalive,
|
|
2673
2912
|
);
|
|
2674
2913
|
};
|
|
2914
|
+
const handlePageHide = () => {
|
|
2915
|
+
sendPendingKeepaliveSaves();
|
|
2916
|
+
};
|
|
2675
2917
|
window.addEventListener("pagehide", handlePageHide);
|
|
2676
2918
|
return () => {
|
|
2677
2919
|
window.removeEventListener("pagehide", handlePageHide);
|
|
2678
|
-
|
|
2679
|
-
|
|
2920
|
+
sendPendingKeepaliveSaves();
|
|
2921
|
+
for (const timer of Object.values(fileSaveTimersRef.current)) {
|
|
2922
|
+
window.clearTimeout(timer);
|
|
2680
2923
|
}
|
|
2924
|
+
fileSaveTimersRef.current = {};
|
|
2925
|
+
pendingFileSavesRef.current = {};
|
|
2681
2926
|
};
|
|
2682
2927
|
}, []);
|
|
2683
2928
|
|
|
@@ -2733,12 +2978,55 @@ export default function DesignEditor() {
|
|
|
2733
2978
|
}, []);
|
|
2734
2979
|
|
|
2735
2980
|
const design = isDesignData(designResult) ? designResult : null;
|
|
2981
|
+
const designAccessRole = design?.accessRole;
|
|
2982
|
+
const canShareDesign =
|
|
2983
|
+
designAccessRole === "owner" || designAccessRole === "admin";
|
|
2984
|
+
const canEditDesign = canShareDesign || designAccessRole === "editor";
|
|
2985
|
+
const shouldOpenShare = postAuthIntent === "share" && canShareDesign;
|
|
2986
|
+
const editorShareUrl = useMemo(() => {
|
|
2987
|
+
if (!id || typeof window === "undefined") return undefined;
|
|
2988
|
+
return `${window.location.origin}/design/${encodeURIComponent(id)}`;
|
|
2989
|
+
}, [id]);
|
|
2736
2990
|
const {
|
|
2737
2991
|
designSystems,
|
|
2738
2992
|
defaultSystem,
|
|
2739
2993
|
isLoading: designSystemsLoading,
|
|
2740
2994
|
} = useDesignSystems();
|
|
2741
2995
|
|
|
2996
|
+
useEffect(() => {
|
|
2997
|
+
if (!id || !design || !isSignedIn || !postAuthIntent) return;
|
|
2998
|
+
|
|
2999
|
+
const shouldDuplicate =
|
|
3000
|
+
postAuthIntent === "share" ? !canShareDesign : !canEditDesign;
|
|
3001
|
+
if (!shouldDuplicate) return;
|
|
3002
|
+
|
|
3003
|
+
const key = `${postAuthIntent}:${id}`;
|
|
3004
|
+
if (postAuthSaveRef.current === key) return;
|
|
3005
|
+
postAuthSaveRef.current = key;
|
|
3006
|
+
|
|
3007
|
+
duplicateDesignMutation
|
|
3008
|
+
.mutateAsync({ id, title: design.title } as any)
|
|
3009
|
+
.then((result: any) => {
|
|
3010
|
+
if (!result?.id) throw new Error("Missing copied design id");
|
|
3011
|
+
const nextSearch = postAuthIntent === "share" ? "?intent=share" : "";
|
|
3012
|
+
navigate(`/design/${result.id}${nextSearch}`, { replace: true });
|
|
3013
|
+
})
|
|
3014
|
+
.catch(() => {
|
|
3015
|
+
postAuthSaveRef.current = null;
|
|
3016
|
+
toast.error(t("designEditor.toasts.saveCopyError"));
|
|
3017
|
+
});
|
|
3018
|
+
}, [
|
|
3019
|
+
canEditDesign,
|
|
3020
|
+
canShareDesign,
|
|
3021
|
+
design,
|
|
3022
|
+
duplicateDesignMutation,
|
|
3023
|
+
id,
|
|
3024
|
+
isSignedIn,
|
|
3025
|
+
navigate,
|
|
3026
|
+
postAuthIntent,
|
|
3027
|
+
t,
|
|
3028
|
+
]);
|
|
3029
|
+
|
|
2742
3030
|
const resolvePromptDesignSystemId = useCallback(
|
|
2743
3031
|
() =>
|
|
2744
3032
|
design?.designSystemId ??
|
|
@@ -2841,6 +3129,12 @@ export default function DesignEditor() {
|
|
|
2841
3129
|
() => parseDesignDataJson(design?.data),
|
|
2842
3130
|
[design?.data],
|
|
2843
3131
|
);
|
|
3132
|
+
// Keep a ref in sync so debounced timer callbacks can read the freshest
|
|
3133
|
+
// designDataJson without closing over a stale snapshot from render time.
|
|
3134
|
+
const designDataJsonRef = useRef(designDataJson);
|
|
3135
|
+
useEffect(() => {
|
|
3136
|
+
designDataJsonRef.current = designDataJson;
|
|
3137
|
+
}, [designDataJson]);
|
|
2844
3138
|
const canvasFrameGeometryById = useMemo(
|
|
2845
3139
|
() => getCanvasFrameGeometry(designDataJson),
|
|
2846
3140
|
[designDataJson],
|
|
@@ -2853,8 +3147,11 @@ export default function DesignEditor() {
|
|
|
2853
3147
|
}
|
|
2854
3148
|
frameGeometrySaveTimerRef.current = window.setTimeout(() => {
|
|
2855
3149
|
frameGeometrySaveTimerRef.current = null;
|
|
3150
|
+
// Read the freshest designDataJson from the ref so any concurrent
|
|
3151
|
+
// server writes (e.g. apply-tweaks) that arrived during the 500 ms
|
|
3152
|
+
// debounce window are not overwritten with stale closure data.
|
|
2856
3153
|
const nextData = {
|
|
2857
|
-
...
|
|
3154
|
+
...designDataJsonRef.current,
|
|
2858
3155
|
canvasFrames: geometryById,
|
|
2859
3156
|
};
|
|
2860
3157
|
updateDesignMutation.mutate(
|
|
@@ -2872,7 +3169,7 @@ export default function DesignEditor() {
|
|
|
2872
3169
|
);
|
|
2873
3170
|
}, 500);
|
|
2874
3171
|
},
|
|
2875
|
-
[
|
|
3172
|
+
[id, queryClient, updateDesignMutation],
|
|
2876
3173
|
);
|
|
2877
3174
|
|
|
2878
3175
|
const writeFrameGeometrySnapshot = useCallback(
|
|
@@ -2884,7 +3181,7 @@ export default function DesignEditor() {
|
|
|
2884
3181
|
}
|
|
2885
3182
|
const snapshot = cloneCanvasFrameGeometry(geometryById);
|
|
2886
3183
|
const nextData = {
|
|
2887
|
-
...
|
|
3184
|
+
...designDataJsonRef.current,
|
|
2888
3185
|
canvasFrames: snapshot,
|
|
2889
3186
|
};
|
|
2890
3187
|
queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
|
|
@@ -2905,7 +3202,7 @@ export default function DesignEditor() {
|
|
|
2905
3202
|
},
|
|
2906
3203
|
);
|
|
2907
3204
|
},
|
|
2908
|
-
[
|
|
3205
|
+
[id, queryClient, updateDesignMutation],
|
|
2909
3206
|
);
|
|
2910
3207
|
|
|
2911
3208
|
const handleGeometryCommit = useCallback(
|
|
@@ -3026,10 +3323,6 @@ export default function DesignEditor() {
|
|
|
3026
3323
|
clearGenerationCompleteTimer,
|
|
3027
3324
|
]);
|
|
3028
3325
|
|
|
3029
|
-
useEffect(() => {
|
|
3030
|
-
return () => clearPendingGeneration(id);
|
|
3031
|
-
}, [id]);
|
|
3032
|
-
|
|
3033
3326
|
useEffect(() => {
|
|
3034
3327
|
return () => {
|
|
3035
3328
|
if (frameGeometrySaveTimerRef.current !== null) {
|
|
@@ -3081,6 +3374,27 @@ export default function DesignEditor() {
|
|
|
3081
3374
|
: undefined;
|
|
3082
3375
|
if (inspectorTab) setActiveInspectorTab(inspectorTab);
|
|
3083
3376
|
|
|
3377
|
+
const commandTool = normalizeDesignTool(command.tool);
|
|
3378
|
+
const effectiveCommandTool =
|
|
3379
|
+
editorView === "overview" &&
|
|
3380
|
+
commandTool &&
|
|
3381
|
+
isSingleScreenAnnotationTool(commandTool)
|
|
3382
|
+
? "move"
|
|
3383
|
+
: commandTool;
|
|
3384
|
+
const applyCommandTool = (fallback: DesignTool) => {
|
|
3385
|
+
const nextTool = effectiveCommandTool ?? fallback;
|
|
3386
|
+
setActiveTool(nextTool);
|
|
3387
|
+
if (isSingleScreenAnnotationTool(nextTool)) {
|
|
3388
|
+
setMode("annotate");
|
|
3389
|
+
setDrawMode(true);
|
|
3390
|
+
setPinMode(nextTool === "comment");
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
setMode("edit");
|
|
3394
|
+
setDrawMode(false);
|
|
3395
|
+
setPinMode(false);
|
|
3396
|
+
};
|
|
3397
|
+
|
|
3084
3398
|
if (targetFile) {
|
|
3085
3399
|
setActiveFileId(targetFile.id);
|
|
3086
3400
|
}
|
|
@@ -3091,19 +3405,13 @@ export default function DesignEditor() {
|
|
|
3091
3405
|
|
|
3092
3406
|
if (editorView === "overview") {
|
|
3093
3407
|
viewModeRef.current = "overview";
|
|
3094
|
-
setDrawMode(false);
|
|
3095
|
-
setPinMode(false);
|
|
3096
|
-
setMode("edit");
|
|
3097
3408
|
setSelectedElement(null);
|
|
3098
|
-
|
|
3409
|
+
applyCommandTool("move");
|
|
3099
3410
|
setViewMode("overview");
|
|
3100
3411
|
} else if (editorView === "single") {
|
|
3101
3412
|
viewModeRef.current = "single";
|
|
3102
|
-
setDrawMode(false);
|
|
3103
|
-
setPinMode(false);
|
|
3104
|
-
setMode("edit");
|
|
3105
3413
|
setSelectedElement(null);
|
|
3106
|
-
|
|
3414
|
+
applyCommandTool("move");
|
|
3107
3415
|
if (
|
|
3108
3416
|
typeof command.zoom !== "number" ||
|
|
3109
3417
|
!Number.isFinite(command.zoom)
|
|
@@ -3111,6 +3419,8 @@ export default function DesignEditor() {
|
|
|
3111
3419
|
setZoom((currentZoom) => Math.max(currentZoom, FOCUSED_SCREEN_ZOOM));
|
|
3112
3420
|
}
|
|
3113
3421
|
setViewMode("single");
|
|
3422
|
+
} else if (effectiveCommandTool) {
|
|
3423
|
+
applyCommandTool("move");
|
|
3114
3424
|
}
|
|
3115
3425
|
|
|
3116
3426
|
return true;
|
|
@@ -3308,7 +3618,7 @@ export default function DesignEditor() {
|
|
|
3308
3618
|
// Collaborative editing for the active file
|
|
3309
3619
|
const { ydoc, awareness, isSynced, activeUsers, agentActive } =
|
|
3310
3620
|
useCollaborativeDoc({
|
|
3311
|
-
docId: activeFileId,
|
|
3621
|
+
docId: isSignedIn ? activeFileId : null,
|
|
3312
3622
|
requestSource: TAB_ID,
|
|
3313
3623
|
user: currentUser,
|
|
3314
3624
|
});
|
|
@@ -3973,7 +4283,7 @@ export default function DesignEditor() {
|
|
|
3973
4283
|
});
|
|
3974
4284
|
}
|
|
3975
4285
|
|
|
3976
|
-
return true;
|
|
4286
|
+
return primitive.nodeId ?? true;
|
|
3977
4287
|
},
|
|
3978
4288
|
[
|
|
3979
4289
|
activeContent,
|
|
@@ -4284,15 +4594,7 @@ export default function DesignEditor() {
|
|
|
4284
4594
|
context: contextLines.join("\n"),
|
|
4285
4595
|
openSidebar: false,
|
|
4286
4596
|
});
|
|
4287
|
-
}, [
|
|
4288
|
-
activeFile,
|
|
4289
|
-
design?.title,
|
|
4290
|
-
id,
|
|
4291
|
-
removeAgentChatContextItem,
|
|
4292
|
-
selectedCodeLayerNode,
|
|
4293
|
-
selectedElement,
|
|
4294
|
-
setAgentChatContextItem,
|
|
4295
|
-
]);
|
|
4597
|
+
}, [activeFile, design?.title, id, selectedCodeLayerNode, selectedElement]);
|
|
4296
4598
|
|
|
4297
4599
|
const designExtensionContext = useMemo<DesignExtensionSlotContext>(
|
|
4298
4600
|
() => ({
|
|
@@ -5078,6 +5380,15 @@ export default function DesignEditor() {
|
|
|
5078
5380
|
if (!replacePreviewContent(next)) {
|
|
5079
5381
|
setContentRenderRevision((revision) => revision + 1);
|
|
5080
5382
|
}
|
|
5383
|
+
// Clear stale selection if the undo removed the selected element.
|
|
5384
|
+
setSelectedElement((prev) => {
|
|
5385
|
+
if (!prev) return prev;
|
|
5386
|
+
return elementInfoExistsInContent(next, prev) ? prev : null;
|
|
5387
|
+
});
|
|
5388
|
+
setHoveredElement((prev) => {
|
|
5389
|
+
if (!prev) return prev;
|
|
5390
|
+
return elementInfoExistsInContent(next, prev) ? prev : null;
|
|
5391
|
+
});
|
|
5081
5392
|
}
|
|
5082
5393
|
redoOrderRef.current = [
|
|
5083
5394
|
...redoOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
@@ -5134,6 +5445,15 @@ export default function DesignEditor() {
|
|
|
5134
5445
|
if (!replacePreviewContent(next)) {
|
|
5135
5446
|
setContentRenderRevision((revision) => revision + 1);
|
|
5136
5447
|
}
|
|
5448
|
+
// Clear stale selection if the redo removed the selected element.
|
|
5449
|
+
setSelectedElement((prev) => {
|
|
5450
|
+
if (!prev) return prev;
|
|
5451
|
+
return elementInfoExistsInContent(next, prev) ? prev : null;
|
|
5452
|
+
});
|
|
5453
|
+
setHoveredElement((prev) => {
|
|
5454
|
+
if (!prev) return prev;
|
|
5455
|
+
return elementInfoExistsInContent(next, prev) ? prev : null;
|
|
5456
|
+
});
|
|
5137
5457
|
}
|
|
5138
5458
|
historyOrderRef.current = [
|
|
5139
5459
|
...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
@@ -5268,6 +5588,7 @@ export default function DesignEditor() {
|
|
|
5268
5588
|
}
|
|
5269
5589
|
|
|
5270
5590
|
if (activeFile && viewMode === "overview") {
|
|
5591
|
+
viewModeRef.current = "single";
|
|
5271
5592
|
setViewMode("single");
|
|
5272
5593
|
}
|
|
5273
5594
|
setMode(next);
|
|
@@ -5493,6 +5814,20 @@ export default function DesignEditor() {
|
|
|
5493
5814
|
].join("\n");
|
|
5494
5815
|
clearGenerationCompleteTimer();
|
|
5495
5816
|
setGenerationIssue(null);
|
|
5817
|
+
const startedAt = Date.now();
|
|
5818
|
+
patchPendingGeneration(id, {
|
|
5819
|
+
prompt: promptState.prompt,
|
|
5820
|
+
files: promptState.files,
|
|
5821
|
+
title: design.title,
|
|
5822
|
+
designSystemId: promptState.designSystemId,
|
|
5823
|
+
model: promptState.model,
|
|
5824
|
+
engine: promptState.engine,
|
|
5825
|
+
effort: promptState.effort,
|
|
5826
|
+
attempt,
|
|
5827
|
+
startedAt,
|
|
5828
|
+
});
|
|
5829
|
+
setHasPendingGeneration(true);
|
|
5830
|
+
setRetryablePrompt(null);
|
|
5496
5831
|
const runTabId = agentSubmit(
|
|
5497
5832
|
`Generate design for "${design.title}": ${promptState.prompt}`,
|
|
5498
5833
|
context,
|
|
@@ -5514,10 +5849,8 @@ export default function DesignEditor() {
|
|
|
5514
5849
|
effort: promptState.effort,
|
|
5515
5850
|
attempt,
|
|
5516
5851
|
runTabId,
|
|
5517
|
-
startedAt
|
|
5852
|
+
startedAt,
|
|
5518
5853
|
});
|
|
5519
|
-
setHasPendingGeneration(true);
|
|
5520
|
-
setRetryablePrompt(null);
|
|
5521
5854
|
},
|
|
5522
5855
|
[
|
|
5523
5856
|
id,
|
|
@@ -5670,6 +6003,7 @@ export default function DesignEditor() {
|
|
|
5670
6003
|
|
|
5671
6004
|
const handleDownloadPng = useCallback(
|
|
5672
6005
|
async (settings?: Partial<ExportSettingsValue>) => {
|
|
6006
|
+
if (pngExportingRef.current) return;
|
|
5673
6007
|
const iframe = document.querySelector<HTMLIFrameElement>(
|
|
5674
6008
|
"iframe[data-design-preview-iframe]",
|
|
5675
6009
|
);
|
|
@@ -5678,6 +6012,8 @@ export default function DesignEditor() {
|
|
|
5678
6012
|
toast.error(t("designEditor.toasts.openScreenPng"));
|
|
5679
6013
|
return;
|
|
5680
6014
|
}
|
|
6015
|
+
pngExportingRef.current = true;
|
|
6016
|
+
setPngExporting(true);
|
|
5681
6017
|
try {
|
|
5682
6018
|
const html2canvas = (await import("html2canvas")).default;
|
|
5683
6019
|
const width = Math.max(
|
|
@@ -5703,32 +6039,42 @@ export default function DesignEditor() {
|
|
|
5703
6039
|
),
|
|
5704
6040
|
),
|
|
5705
6041
|
useCORS: true,
|
|
6042
|
+
foreignObjectRendering: true,
|
|
5706
6043
|
backgroundColor: null,
|
|
6044
|
+
onclone: (clonedDocument) =>
|
|
6045
|
+
sanitizeHtml2CanvasClone(doc, clonedDocument),
|
|
5707
6046
|
});
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
6047
|
+
await new Promise<void>((resolve) => {
|
|
6048
|
+
canvas.toBlob((blob) => {
|
|
6049
|
+
try {
|
|
6050
|
+
if (!blob) {
|
|
6051
|
+
toast.error(t("designEditor.toasts.pngCreateError"));
|
|
6052
|
+
return;
|
|
6053
|
+
}
|
|
6054
|
+
triggerBlobDownload(
|
|
6055
|
+
blob,
|
|
6056
|
+
fallbackExportName("png", settings?.suffix),
|
|
6057
|
+
);
|
|
6058
|
+
toast.success(t("designEditor.toasts.pngDownloaded"));
|
|
6059
|
+
} catch (callbackError) {
|
|
6060
|
+
// `triggerBlobDownload` does DOM mutation + `URL.createObjectURL`,
|
|
6061
|
+
// either of which can throw inside this async callback — outside
|
|
6062
|
+
// the outer try/catch. Surface the failure instead of silently
|
|
6063
|
+
// dropping it.
|
|
6064
|
+
console.error(
|
|
6065
|
+
"PNG export failed during download:",
|
|
6066
|
+
callbackError,
|
|
6067
|
+
);
|
|
6068
|
+
toast.error(
|
|
6069
|
+
callbackError instanceof Error
|
|
6070
|
+
? callbackError.message
|
|
6071
|
+
: t("designEditor.toasts.pngSaveError"),
|
|
6072
|
+
);
|
|
6073
|
+
} finally {
|
|
6074
|
+
resolve();
|
|
5713
6075
|
}
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
fallbackExportName("png", settings?.suffix),
|
|
5717
|
-
);
|
|
5718
|
-
toast.success(t("designEditor.toasts.pngDownloaded"));
|
|
5719
|
-
} catch (callbackError) {
|
|
5720
|
-
// `triggerBlobDownload` does DOM mutation + `URL.createObjectURL`,
|
|
5721
|
-
// either of which can throw inside this async callback — outside
|
|
5722
|
-
// the outer try/catch. Surface the failure instead of silently
|
|
5723
|
-
// dropping it.
|
|
5724
|
-
console.error("PNG export failed during download:", callbackError);
|
|
5725
|
-
toast.error(
|
|
5726
|
-
callbackError instanceof Error
|
|
5727
|
-
? callbackError.message
|
|
5728
|
-
: t("designEditor.toasts.pngSaveError"),
|
|
5729
|
-
);
|
|
5730
|
-
}
|
|
5731
|
-
}, "image/png");
|
|
6076
|
+
}, "image/png");
|
|
6077
|
+
});
|
|
5732
6078
|
} catch (error) {
|
|
5733
6079
|
console.error("PNG export failed:", error);
|
|
5734
6080
|
toast.error(
|
|
@@ -5736,6 +6082,9 @@ export default function DesignEditor() {
|
|
|
5736
6082
|
? error.message
|
|
5737
6083
|
: t("designEditor.toasts.pngExportError"),
|
|
5738
6084
|
);
|
|
6085
|
+
} finally {
|
|
6086
|
+
pngExportingRef.current = false;
|
|
6087
|
+
setPngExporting(false);
|
|
5739
6088
|
}
|
|
5740
6089
|
},
|
|
5741
6090
|
[fallbackExportName, t, triggerBlobDownload],
|
|
@@ -5846,12 +6195,14 @@ ${serializedHtml}
|
|
|
5846
6195
|
);
|
|
5847
6196
|
|
|
5848
6197
|
const handleInspectorExport = useCallback(
|
|
5849
|
-
(
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
6198
|
+
(settingsList: ExportSettingsValue[]) => {
|
|
6199
|
+
for (const settings of settingsList) {
|
|
6200
|
+
if (settings.format === "svg") {
|
|
6201
|
+
void handleDownloadSvg(settings);
|
|
6202
|
+
} else {
|
|
6203
|
+
void handleDownloadPng(settings);
|
|
6204
|
+
}
|
|
5853
6205
|
}
|
|
5854
|
-
void handleDownloadPng(settings);
|
|
5855
6206
|
},
|
|
5856
6207
|
[handleDownloadPng, handleDownloadSvg],
|
|
5857
6208
|
);
|
|
@@ -5926,6 +6277,27 @@ ${serializedHtml}
|
|
|
5926
6277
|
});
|
|
5927
6278
|
return owners;
|
|
5928
6279
|
}, [codeLayerModelsByFile]);
|
|
6280
|
+
const effectiveCodeLayerState = useMemo(() => {
|
|
6281
|
+
const state: EffectiveCodeLayerState = {
|
|
6282
|
+
lockedIds: new Set(),
|
|
6283
|
+
hiddenIds: new Set(),
|
|
6284
|
+
};
|
|
6285
|
+
codeLayerModelsByFile.forEach((model) => {
|
|
6286
|
+
const fileLocked = lockedLayerIds.has(model.fileId);
|
|
6287
|
+
const fileHidden = hiddenLayerIds.has(model.fileId);
|
|
6288
|
+
if (fileLocked) state.lockedIds.add(model.fileId);
|
|
6289
|
+
if (fileHidden) state.hiddenIds.add(model.fileId);
|
|
6290
|
+
collectEffectiveCodeLayerState(
|
|
6291
|
+
model.tree,
|
|
6292
|
+
lockedLayerIds,
|
|
6293
|
+
hiddenLayerIds,
|
|
6294
|
+
fileLocked,
|
|
6295
|
+
fileHidden,
|
|
6296
|
+
state,
|
|
6297
|
+
);
|
|
6298
|
+
});
|
|
6299
|
+
return state;
|
|
6300
|
+
}, [codeLayerModelsByFile, hiddenLayerIds, lockedLayerIds]);
|
|
5929
6301
|
useEffect(() => {
|
|
5930
6302
|
const fileIds = new Set(files.map((file) => file.id));
|
|
5931
6303
|
const allCodeLayerNodes = codeLayerModelsByFile.flatMap(
|
|
@@ -6106,11 +6478,14 @@ ${serializedHtml}
|
|
|
6106
6478
|
];
|
|
6107
6479
|
const activeFileBlocked =
|
|
6108
6480
|
activeFile?.id &&
|
|
6109
|
-
(
|
|
6481
|
+
(effectiveCodeLayerState.lockedIds.has(activeFile.id) ||
|
|
6482
|
+
effectiveCodeLayerState.hiddenIds.has(activeFile.id));
|
|
6110
6483
|
const selectionBlocked =
|
|
6111
6484
|
Boolean(activeFileBlocked) ||
|
|
6112
6485
|
selectedPathIds.some(
|
|
6113
|
-
(layerId) =>
|
|
6486
|
+
(layerId) =>
|
|
6487
|
+
effectiveCodeLayerState.lockedIds.has(layerId) ||
|
|
6488
|
+
effectiveCodeLayerState.hiddenIds.has(layerId),
|
|
6114
6489
|
);
|
|
6115
6490
|
if (!selectionBlocked) return;
|
|
6116
6491
|
setSelectedElement(null);
|
|
@@ -6121,8 +6496,7 @@ ${serializedHtml}
|
|
|
6121
6496
|
activeCodeLayerTree,
|
|
6122
6497
|
activeFile?.id,
|
|
6123
6498
|
codeLayerOwnerByNodeId,
|
|
6124
|
-
|
|
6125
|
-
lockedLayerIds,
|
|
6499
|
+
effectiveCodeLayerState,
|
|
6126
6500
|
selectedElementLayerId,
|
|
6127
6501
|
]);
|
|
6128
6502
|
|
|
@@ -6191,19 +6565,44 @@ ${serializedHtml}
|
|
|
6191
6565
|
activeFile?.id ??
|
|
6192
6566
|
"";
|
|
6193
6567
|
const activeLayerLocked = Boolean(
|
|
6194
|
-
activeLayerId &&
|
|
6568
|
+
activeLayerId && effectiveCodeLayerState.lockedIds.has(activeLayerId),
|
|
6195
6569
|
);
|
|
6196
6570
|
const activeLayerHidden = Boolean(
|
|
6197
|
-
activeLayerId &&
|
|
6571
|
+
activeLayerId && effectiveCodeLayerState.hiddenIds.has(activeLayerId),
|
|
6572
|
+
);
|
|
6573
|
+
|
|
6574
|
+
const canMoveLayer = useCallback(
|
|
6575
|
+
(intent: LayersPanelMoveIntent) => {
|
|
6576
|
+
const targetOwner = codeLayerOwnerByNodeId.get(intent.targetId);
|
|
6577
|
+
if (
|
|
6578
|
+
!targetOwner ||
|
|
6579
|
+
effectiveCodeLayerState.lockedIds.has(intent.targetId) ||
|
|
6580
|
+
effectiveCodeLayerState.hiddenIds.has(intent.targetId)
|
|
6581
|
+
) {
|
|
6582
|
+
return false;
|
|
6583
|
+
}
|
|
6584
|
+
return intent.draggedIds.some((draggedId) => {
|
|
6585
|
+
const draggedOwner = codeLayerOwnerByNodeId.get(draggedId);
|
|
6586
|
+
return (
|
|
6587
|
+
draggedId !== intent.targetId &&
|
|
6588
|
+
!!draggedOwner &&
|
|
6589
|
+
draggedOwner.fileId === targetOwner.fileId &&
|
|
6590
|
+
!effectiveCodeLayerState.lockedIds.has(draggedId) &&
|
|
6591
|
+
!effectiveCodeLayerState.hiddenIds.has(draggedId)
|
|
6592
|
+
);
|
|
6593
|
+
});
|
|
6594
|
+
},
|
|
6595
|
+
[codeLayerOwnerByNodeId, effectiveCodeLayerState],
|
|
6198
6596
|
);
|
|
6199
6597
|
|
|
6200
6598
|
const handleLayerMove = useCallback(
|
|
6201
6599
|
(intent: LayersPanelMoveIntent) => {
|
|
6600
|
+
if (!canMoveLayer(intent)) return;
|
|
6202
6601
|
const targetOwner = codeLayerOwnerByNodeId.get(intent.targetId);
|
|
6203
6602
|
if (!targetOwner) return;
|
|
6204
6603
|
if (
|
|
6205
|
-
|
|
6206
|
-
|
|
6604
|
+
effectiveCodeLayerState.lockedIds.has(intent.targetId) ||
|
|
6605
|
+
effectiveCodeLayerState.hiddenIds.has(intent.targetId)
|
|
6207
6606
|
) {
|
|
6208
6607
|
return;
|
|
6209
6608
|
}
|
|
@@ -6221,8 +6620,8 @@ ${serializedHtml}
|
|
|
6221
6620
|
draggedId === intent.targetId ||
|
|
6222
6621
|
!draggedOwner ||
|
|
6223
6622
|
draggedOwner.fileId !== targetOwner.fileId ||
|
|
6224
|
-
|
|
6225
|
-
|
|
6623
|
+
effectiveCodeLayerState.lockedIds.has(draggedId) ||
|
|
6624
|
+
effectiveCodeLayerState.hiddenIds.has(draggedId)
|
|
6226
6625
|
) {
|
|
6227
6626
|
continue;
|
|
6228
6627
|
}
|
|
@@ -6250,10 +6649,10 @@ ${serializedHtml}
|
|
|
6250
6649
|
activeContent,
|
|
6251
6650
|
activeFile?.id,
|
|
6252
6651
|
applyFileContentUpdate,
|
|
6652
|
+
canMoveLayer,
|
|
6253
6653
|
codeLayerOwnerByNodeId,
|
|
6254
6654
|
files,
|
|
6255
|
-
|
|
6256
|
-
lockedLayerIds,
|
|
6655
|
+
effectiveCodeLayerState,
|
|
6257
6656
|
t,
|
|
6258
6657
|
],
|
|
6259
6658
|
);
|
|
@@ -6366,12 +6765,18 @@ ${serializedHtml}
|
|
|
6366
6765
|
|
|
6367
6766
|
const handleToggleLayerLocked = useCallback(
|
|
6368
6767
|
(layerId: string, locked: boolean) => {
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6768
|
+
const applyLockedState = () => {
|
|
6769
|
+
setLockedLayerIds((current) => {
|
|
6770
|
+
const next = new Set(current);
|
|
6771
|
+
if (locked) next.add(layerId);
|
|
6772
|
+
else next.delete(layerId);
|
|
6773
|
+
return next;
|
|
6774
|
+
});
|
|
6775
|
+
};
|
|
6776
|
+
if (files.some((file) => file.id === layerId)) {
|
|
6777
|
+
applyLockedState();
|
|
6778
|
+
return;
|
|
6779
|
+
}
|
|
6375
6780
|
const owner = codeLayerOwnerByNodeId.get(layerId);
|
|
6376
6781
|
const node = owner?.node;
|
|
6377
6782
|
if (!owner || !node) return;
|
|
@@ -6387,11 +6792,11 @@ ${serializedHtml}
|
|
|
6387
6792
|
"data-agent-native-locked",
|
|
6388
6793
|
locked ? "true" : null,
|
|
6389
6794
|
);
|
|
6390
|
-
if (nextContent
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6795
|
+
if (!nextContent || nextContent === sourceContent) return;
|
|
6796
|
+
applyFileContentUpdate(owner.fileId, nextContent, {
|
|
6797
|
+
refreshPreview: false,
|
|
6798
|
+
});
|
|
6799
|
+
applyLockedState();
|
|
6395
6800
|
},
|
|
6396
6801
|
[
|
|
6397
6802
|
activeContent,
|
|
@@ -6404,12 +6809,18 @@ ${serializedHtml}
|
|
|
6404
6809
|
|
|
6405
6810
|
const handleToggleLayerHidden = useCallback(
|
|
6406
6811
|
(layerId: string, hidden: boolean) => {
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6812
|
+
const applyHiddenState = () => {
|
|
6813
|
+
setHiddenLayerIds((current) => {
|
|
6814
|
+
const next = new Set(current);
|
|
6815
|
+
if (hidden) next.add(layerId);
|
|
6816
|
+
else next.delete(layerId);
|
|
6817
|
+
return next;
|
|
6818
|
+
});
|
|
6819
|
+
};
|
|
6820
|
+
if (files.some((file) => file.id === layerId)) {
|
|
6821
|
+
applyHiddenState();
|
|
6822
|
+
return;
|
|
6823
|
+
}
|
|
6413
6824
|
const owner = codeLayerOwnerByNodeId.get(layerId);
|
|
6414
6825
|
const node = owner?.node;
|
|
6415
6826
|
if (!owner || !node) return;
|
|
@@ -6425,11 +6836,11 @@ ${serializedHtml}
|
|
|
6425
6836
|
"data-agent-native-hidden",
|
|
6426
6837
|
hidden ? "true" : null,
|
|
6427
6838
|
);
|
|
6428
|
-
if (nextContent
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6839
|
+
if (!nextContent || nextContent === sourceContent) return;
|
|
6840
|
+
applyFileContentUpdate(owner.fileId, nextContent, {
|
|
6841
|
+
refreshPreview: false,
|
|
6842
|
+
});
|
|
6843
|
+
applyHiddenState();
|
|
6433
6844
|
},
|
|
6434
6845
|
[
|
|
6435
6846
|
activeContent,
|
|
@@ -6454,10 +6865,14 @@ ${serializedHtml}
|
|
|
6454
6865
|
|
|
6455
6866
|
const zoomLabel = `${Math.round(zoom)}%`;
|
|
6456
6867
|
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6868
|
+
// Hooks must not be called conditionally; keep navigate as an effect so the
|
|
6869
|
+
// render phase stays pure. This branch is unreachable in practice because the
|
|
6870
|
+
// design.$id.tsx route always supplies an id param.
|
|
6871
|
+
useEffect(() => {
|
|
6872
|
+
if (!id) navigate("/");
|
|
6873
|
+
}, [id, navigate]);
|
|
6874
|
+
|
|
6875
|
+
if (!id) return null;
|
|
6461
6876
|
|
|
6462
6877
|
if (designLoading || (!design && pendingGenerationActive)) {
|
|
6463
6878
|
return <DesignEditorSkeleton embedded={embedded} />;
|
|
@@ -6573,7 +6988,7 @@ ${serializedHtml}
|
|
|
6573
6988
|
</DropdownMenuItem>
|
|
6574
6989
|
<DropdownMenuItem
|
|
6575
6990
|
onClick={() => void handleDownloadPng()}
|
|
6576
|
-
disabled={!activeFile}
|
|
6991
|
+
disabled={!activeFile || pngExporting}
|
|
6577
6992
|
>
|
|
6578
6993
|
<IconPhoto className="mr-2 h-4 w-4" />
|
|
6579
6994
|
{t("designEditor.downloadPng")}
|
|
@@ -6744,6 +7159,42 @@ ${serializedHtml}
|
|
|
6744
7159
|
</DropdownMenu>
|
|
6745
7160
|
);
|
|
6746
7161
|
|
|
7162
|
+
const signedOutPersistenceActions = (
|
|
7163
|
+
<>
|
|
7164
|
+
<Tooltip>
|
|
7165
|
+
<TooltipTrigger asChild>
|
|
7166
|
+
<Button
|
|
7167
|
+
variant="outline"
|
|
7168
|
+
size="sm"
|
|
7169
|
+
onClick={handleSignInToSave}
|
|
7170
|
+
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"
|
|
7171
|
+
aria-label={t("designEditor.signUpToSaveDescription")}
|
|
7172
|
+
>
|
|
7173
|
+
<IconDeviceFloppy className="size-4 shrink-0" />
|
|
7174
|
+
<span className="truncate">{t("designEditor.signUpToSave")}</span>
|
|
7175
|
+
</Button>
|
|
7176
|
+
</TooltipTrigger>
|
|
7177
|
+
<TooltipContent>
|
|
7178
|
+
{t("designEditor.signUpToSaveDescription")}
|
|
7179
|
+
</TooltipContent>
|
|
7180
|
+
</Tooltip>
|
|
7181
|
+
<Tooltip>
|
|
7182
|
+
<TooltipTrigger asChild>
|
|
7183
|
+
<Button
|
|
7184
|
+
variant="default"
|
|
7185
|
+
size="sm"
|
|
7186
|
+
onClick={handleSignInToShare}
|
|
7187
|
+
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)]"
|
|
7188
|
+
>
|
|
7189
|
+
<span>{t("designEditor.share")}</span>
|
|
7190
|
+
<IconArrowUpRight className="size-4 shrink-0" />
|
|
7191
|
+
</Button>
|
|
7192
|
+
</TooltipTrigger>
|
|
7193
|
+
<TooltipContent>{t("designEditor.signUpToShare")}</TooltipContent>
|
|
7194
|
+
</Tooltip>
|
|
7195
|
+
</>
|
|
7196
|
+
);
|
|
7197
|
+
|
|
6747
7198
|
const rightSidebarActions = (
|
|
6748
7199
|
<div className="shrink-0 border-b border-border bg-[var(--design-editor-panel-bg)] px-2 py-1.5">
|
|
6749
7200
|
<div className="flex min-h-8 items-center justify-between gap-2">
|
|
@@ -6771,15 +7222,23 @@ ${serializedHtml}
|
|
|
6771
7222
|
<TooltipContent>{t("designEditor.designPreview")}</TooltipContent>
|
|
6772
7223
|
</Tooltip>
|
|
6773
7224
|
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
7225
|
+
{isSignedIn ? (
|
|
7226
|
+
<ShareButton
|
|
7227
|
+
resourceType="design"
|
|
7228
|
+
resourceId={id}
|
|
7229
|
+
resourceTitle={design.title}
|
|
7230
|
+
hideTriggerIcon
|
|
7231
|
+
defaultOpen={shouldOpenShare}
|
|
7232
|
+
shareUrl={editorShareUrl}
|
|
7233
|
+
shareUrlLabel={t("designEditor.shareEditorLink")}
|
|
7234
|
+
shareUrlDescription={t("designEditor.shareEditorLinkDescription")}
|
|
7235
|
+
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)]"
|
|
7236
|
+
/>
|
|
7237
|
+
) : (
|
|
7238
|
+
signedOutPersistenceActions
|
|
7239
|
+
)}
|
|
6781
7240
|
|
|
6782
|
-
<AgentToggleButton />
|
|
7241
|
+
{isSignedIn && <AgentToggleButton />}
|
|
6783
7242
|
</div>
|
|
6784
7243
|
</div>
|
|
6785
7244
|
</div>
|
|
@@ -6802,7 +7261,10 @@ ${serializedHtml}
|
|
|
6802
7261
|
size="icon"
|
|
6803
7262
|
className="size-8 cursor-pointer"
|
|
6804
7263
|
onClick={() => {
|
|
6805
|
-
window.parent.postMessage(
|
|
7264
|
+
window.parent.postMessage(
|
|
7265
|
+
{ type: "design:close" },
|
|
7266
|
+
parentOriginRef.current ?? window.location.origin,
|
|
7267
|
+
);
|
|
6806
7268
|
}}
|
|
6807
7269
|
>
|
|
6808
7270
|
<IconX className="size-4" />
|
|
@@ -6856,7 +7318,7 @@ ${serializedHtml}
|
|
|
6856
7318
|
</DropdownMenuItem>
|
|
6857
7319
|
<DropdownMenuItem
|
|
6858
7320
|
onClick={() => void handleDownloadPng()}
|
|
6859
|
-
disabled={!activeFile}
|
|
7321
|
+
disabled={!activeFile || pngExporting}
|
|
6860
7322
|
>
|
|
6861
7323
|
<IconPhoto className="mr-2 h-4 w-4" />
|
|
6862
7324
|
{t("designEditor.downloadPng")}
|
|
@@ -7125,7 +7587,7 @@ ${serializedHtml}
|
|
|
7125
7587
|
</>
|
|
7126
7588
|
)}
|
|
7127
7589
|
|
|
7128
|
-
{!embedded && (
|
|
7590
|
+
{!embedded && isSignedIn && (
|
|
7129
7591
|
<PresenceBar
|
|
7130
7592
|
activeUsers={activeUsers}
|
|
7131
7593
|
agentActive={agentActive}
|
|
@@ -7135,17 +7597,25 @@ ${serializedHtml}
|
|
|
7135
7597
|
/>
|
|
7136
7598
|
)}
|
|
7137
7599
|
|
|
7138
|
-
{!embedded && (
|
|
7600
|
+
{!embedded && isSignedIn ? (
|
|
7139
7601
|
<ShareButton
|
|
7140
7602
|
resourceType="design"
|
|
7141
7603
|
resourceId={id}
|
|
7142
7604
|
resourceTitle={design.title}
|
|
7143
7605
|
hideTriggerIcon
|
|
7606
|
+
defaultOpen={shouldOpenShare}
|
|
7607
|
+
shareUrl={editorShareUrl}
|
|
7608
|
+
shareUrlLabel={t("designEditor.shareEditorLink")}
|
|
7609
|
+
shareUrlDescription={t(
|
|
7610
|
+
"designEditor.shareEditorLinkDescription",
|
|
7611
|
+
)}
|
|
7144
7612
|
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)]"
|
|
7145
7613
|
/>
|
|
7146
|
-
)
|
|
7614
|
+
) : !embedded ? (
|
|
7615
|
+
signedOutPersistenceActions
|
|
7616
|
+
) : null}
|
|
7147
7617
|
|
|
7148
|
-
{!embedded && <AgentToggleButton />}
|
|
7618
|
+
{!embedded && isSignedIn && <AgentToggleButton />}
|
|
7149
7619
|
</div>
|
|
7150
7620
|
</div>
|
|
7151
7621
|
</header>
|
|
@@ -7198,6 +7668,7 @@ ${serializedHtml}
|
|
|
7198
7668
|
onHoverLayer={handleLayerHover}
|
|
7199
7669
|
onLeaveLayer={handleLayerLeave}
|
|
7200
7670
|
onMoveLayer={handleLayerMove}
|
|
7671
|
+
canMoveLayer={canMoveLayer}
|
|
7201
7672
|
/>
|
|
7202
7673
|
</div>
|
|
7203
7674
|
<div
|
|
@@ -7282,11 +7753,13 @@ ${serializedHtml}
|
|
|
7282
7753
|
)}
|
|
7283
7754
|
|
|
7284
7755
|
{!embedded && activeFile && !pendingVariants && !questionFlowActive && (
|
|
7285
|
-
<
|
|
7756
|
+
<DesignBottomToolbar
|
|
7286
7757
|
mode={mode}
|
|
7287
7758
|
pinMode={pinMode}
|
|
7288
7759
|
drawMode={drawMode}
|
|
7289
7760
|
activeTool={activeTool}
|
|
7761
|
+
isOverview={viewMode === "overview"}
|
|
7762
|
+
hasActiveFile={Boolean(activeFile)}
|
|
7290
7763
|
onMove={handleMoveTool}
|
|
7291
7764
|
onFrame={handleFrameTool}
|
|
7292
7765
|
onShape={handleShapeTool}
|
|
@@ -7622,7 +8095,7 @@ ${serializedHtml}
|
|
|
7622
8095
|
onStyleChange={handleStyleChange}
|
|
7623
8096
|
onStylesChange={handleStylesChange}
|
|
7624
8097
|
onExport={handleInspectorExport}
|
|
7625
|
-
exporting={svgExporting}
|
|
8098
|
+
exporting={pngExporting || svgExporting}
|
|
7626
8099
|
/>
|
|
7627
8100
|
</div>
|
|
7628
8101
|
) : (
|
|
@@ -7648,7 +8121,7 @@ ${serializedHtml}
|
|
|
7648
8121
|
type: "agentNative.submitChat",
|
|
7649
8122
|
data: { message: prompt, submit: true },
|
|
7650
8123
|
},
|
|
7651
|
-
|
|
8124
|
+
parentOriginRef.current ?? window.location.origin,
|
|
7652
8125
|
);
|
|
7653
8126
|
handlePromptOpenChange(false);
|
|
7654
8127
|
return;
|
|
@@ -7667,6 +8140,17 @@ ${serializedHtml}
|
|
|
7667
8140
|
].join("\n");
|
|
7668
8141
|
clearGenerationCompleteTimer();
|
|
7669
8142
|
setGenerationIssue(null);
|
|
8143
|
+
const startedAt = Date.now();
|
|
8144
|
+
patchPendingGeneration(id, {
|
|
8145
|
+
prompt,
|
|
8146
|
+
files,
|
|
8147
|
+
title: design.title,
|
|
8148
|
+
designSystemId,
|
|
8149
|
+
...options,
|
|
8150
|
+
attempt: 1,
|
|
8151
|
+
startedAt,
|
|
8152
|
+
});
|
|
8153
|
+
setHasPendingGeneration(true);
|
|
7670
8154
|
const runTabId = agentSubmit(
|
|
7671
8155
|
`Prepare design questions for "${design.title}": ${prompt}`,
|
|
7672
8156
|
context,
|
|
@@ -7681,9 +8165,8 @@ ${serializedHtml}
|
|
|
7681
8165
|
...options,
|
|
7682
8166
|
runTabId,
|
|
7683
8167
|
attempt: 1,
|
|
7684
|
-
startedAt
|
|
8168
|
+
startedAt,
|
|
7685
8169
|
});
|
|
7686
|
-
setHasPendingGeneration(true);
|
|
7687
8170
|
handlePromptOpenChange(false);
|
|
7688
8171
|
}}
|
|
7689
8172
|
loading={generating}
|