nudge-ui 0.2.2 → 0.2.4
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 +164 -46
- package/dist/client.mjs +260 -61
- package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
- package/dist/css/value-semantics/colorSemantics.js +17 -0
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
- package/dist/hosts/astro/clientTransport.js +38 -32
- package/dist/hosts/astro/integration.js +5 -1
- package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
- package/dist/hosts/astro/markdownIdentity.js +81 -0
- package/dist/hosts/next/manifest.js +1 -1
- package/dist/hosts/next/sidecar.js +5 -1
- package/dist/hosts/static/manifest.js +1 -1
- package/dist/hosts/static/server.js +3 -0
- package/dist/hosts/vite/vite.d.ts +3 -0
- package/dist/hosts/vite/vite.js +24 -4
- package/dist/inspector/agent/McpConnectionDialog.js +13 -19
- package/dist/inspector/agent/activity.d.ts +18 -0
- package/dist/inspector/agent/activity.js +70 -0
- package/dist/inspector/agent/client.d.ts +2 -0
- package/dist/inspector/agent/client.js +29 -1
- package/dist/inspector/agent/connectionStatus.js +1 -1
- package/dist/inspector/agent/httpTransport.js +9 -2
- package/dist/inspector/agent/protocol.d.ts +6 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +15 -7
- package/dist/inspector/agent/verification.js +113 -41
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
- package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
- package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
- package/dist/inspector/canvas/CanvasCard.js +135 -38
- package/dist/inspector/canvas/CanvasElementOverlay.js +166 -16
- package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
- package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
- package/dist/inspector/canvas/CanvasToolbar.js +8 -4
- package/dist/inspector/canvas/CanvasWorkspace.js +270 -122
- package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
- package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
- package/dist/inspector/canvas/agentPresentation.js +64 -52
- package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
- package/dist/inspector/canvas/canvasGestures.js +1 -1
- package/dist/inspector/canvas/canvasStore.d.ts +46 -82
- package/dist/inspector/canvas/canvasStore.js +251 -284
- package/dist/inspector/canvas/frameContent.d.ts +21 -0
- package/dist/inspector/canvas/frameContent.js +44 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +21 -4
- package/dist/inspector/canvas/frameProtocol.js +3 -3
- package/dist/inspector/canvas/frameScrollbarStyles.d.ts +3 -0
- package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
- package/dist/inspector/canvas/layout.d.ts +52 -0
- package/dist/inspector/canvas/layout.js +106 -0
- package/dist/inspector/canvas/projection.d.ts +15 -5
- package/dist/inspector/canvas/projection.js +69 -13
- package/dist/inspector/canvas/rendererBootstrap.js +32 -4
- package/dist/inspector/canvas/rendererElementSelector.js +47 -6
- package/dist/inspector/canvas/rendererStylesheet.js +2 -2
- package/dist/inspector/canvas/resizeHandleScale.js +1 -1
- package/dist/inspector/canvas/sessionStore.d.ts +11 -26
- package/dist/inspector/canvas/sessionStore.js +43 -183
- package/dist/inspector/canvas/staleChangeDetector.js +9 -3
- package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
- package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
- package/dist/inspector/canvas/viewStore.d.ts +38 -0
- package/dist/inspector/canvas/viewStore.js +104 -0
- package/dist/inspector/canvas/wheelPan.d.ts +8 -0
- package/dist/inspector/canvas/wheelPan.js +8 -0
- package/dist/inspector/changes/changesLog.d.ts +9 -6
- package/dist/inspector/changes/changesLog.js +48 -32
- package/dist/inspector/changes/codecs.d.ts +1 -0
- package/dist/inspector/changes/codecs.js +10 -1
- package/dist/inspector/changes/draftChanges.d.ts +58 -0
- package/dist/inspector/changes/draftChanges.js +304 -0
- package/dist/inspector/changes/editModel.d.ts +10 -0
- package/dist/inspector/changes/editModel.js +9 -2
- package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
- package/dist/inspector/changes/previewDiagnostics.js +11 -25
- package/dist/inspector/changes/types.d.ts +7 -0
- package/dist/inspector/clientManifest.d.ts +1 -1
- package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
- package/dist/inspector/comments/CommentsOverlay.js +250 -0
- package/dist/inspector/comments/element.d.ts +6 -0
- package/dist/inspector/comments/element.js +54 -0
- package/dist/inspector/comments/store.d.ts +29 -0
- package/dist/inspector/comments/store.js +121 -0
- package/dist/inspector/comments/verification.d.ts +5 -0
- package/dist/inspector/comments/verification.js +52 -0
- package/dist/inspector/componentSemantics/adapterRegistry.d.ts +6 -1
- package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
- package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
- package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
- package/dist/inspector/componentSemantics/types.d.ts +11 -0
- package/dist/inspector/drafts/model.d.ts +35 -0
- package/dist/inspector/drafts/model.js +45 -0
- package/dist/inspector/drafts/store.d.ts +28 -0
- package/dist/inspector/drafts/store.js +231 -0
- package/dist/inspector/index.js +20 -27
- package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
- package/dist/inspector/iterations/capture.d.ts +2 -0
- package/dist/inspector/iterations/capture.js +174 -0
- package/dist/inspector/iterations/client.d.ts +7 -0
- package/dist/inspector/iterations/client.js +52 -0
- package/dist/inspector/iterations/coordinator.d.ts +15 -0
- package/dist/inspector/iterations/coordinator.js +130 -0
- package/dist/inspector/iterations/prompt.d.ts +3 -0
- package/dist/inspector/iterations/prompt.js +26 -0
- package/dist/inspector/overlay/DomNavigator.js +21 -15
- package/dist/inspector/overlay/interactionStyles.js +3 -1
- package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
- package/dist/inspector/overlay/spacingGestures.js +82 -5
- package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
- package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
- package/dist/inspector/projection/renderedInstance.d.ts +17 -3
- package/dist/inspector/projection/renderedInstance.js +62 -11
- package/dist/inspector/projection/structuralProjection.d.ts +2 -1
- package/dist/inspector/projection/structuralProjection.js +21 -17
- package/dist/inspector/projection/textProjection.d.ts +2 -1
- package/dist/inspector/projection/textProjection.js +64 -15
- package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
- package/dist/inspector/prompt/clipboardHandoff.d.ts +15 -18
- package/dist/inspector/prompt/clipboardHandoff.js +97 -143
- package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
- package/dist/inspector/prompt/generatePrompt.js +22 -17
- package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
- package/dist/inspector/runtime/runtimeConfig.js +24 -0
- package/dist/inspector/selection/sourceSite.d.ts +12 -0
- package/dist/inspector/selection/sourceSite.js +28 -0
- package/dist/inspector/settings/SettingsDialog.js +21 -8
- package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
- package/dist/inspector/shell/AgentStatusButton.js +36 -0
- package/dist/inspector/shell/AppShell.js +4 -2
- package/dist/inspector/shell/ChangesLog.js +46 -26
- package/dist/inspector/shell/CopyPromptButton.js +184 -93
- package/dist/inspector/shell/EmptyState.js +9 -8
- package/dist/inspector/shell/InspectorShell.js +13 -21
- package/dist/inspector/shell/PeekOriginalButton.js +24 -23
- package/dist/inspector/shell/originalPreview.js +5 -5
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +11 -0
- package/dist/inspector/sketch/SketchEditor.js +14 -14
- package/dist/inspector/sketch/SketchEntryButton.js +1 -1
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
- package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
- package/dist/inspector/sketch/SketchOverlay.js +7 -7
- package/dist/inspector/sketch/SketchWorkspace.js +18 -12
- package/dist/inspector/sketch/interaction.d.ts +1 -0
- package/dist/inspector/sketch/interaction.js +8 -4
- package/dist/inspector/sketch/model.d.ts +5 -0
- package/dist/inspector/sketch/model.js +6 -2
- package/dist/inspector/sketch/raster.d.ts +1 -2
- package/dist/inspector/sketch/raster.js +68 -4
- package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
- package/dist/inspector/styleEditors/BorderEditor.js +49 -78
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
- package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
- package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
- package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
- package/dist/inspector/styleEditors/ColorPicker.js +10 -4
- package/dist/inspector/styleEditors/GapField.js +3 -5
- package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
- package/dist/inspector/styleEditors/GridChildSection.js +18 -9
- package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
- package/dist/inspector/styleEditors/GridPicker.js +8 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/GridValueField.js +19 -14
- package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
- package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
- package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
- package/dist/inspector/styleEditors/LayoutSection.js +32 -11
- package/dist/inspector/styleEditors/SpacingBox.js +7 -5
- package/dist/inspector/styleEditors/Typography.js +11 -3
- package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
- package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
- package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
- package/dist/inspector/styleEditors/gradientValue.js +114 -0
- package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
- package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
- package/dist/inspector/styleEditors/layoutValue.js +0 -42
- package/dist/inspector/styleEditors/nudgeValue.d.ts +2 -2
- package/dist/inspector/styleEditors/nudgeValue.js +36 -8
- package/dist/inspector/tokens/TokenField.d.ts +7 -0
- package/dist/inspector/tokens/TokenField.js +35 -25
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- package/dist/inspector/tokens/editActions.d.ts +1 -1
- package/dist/inspector/tokens/editActions.js +7 -1
- package/dist/inspector/ui/AtRuleContext.js +4 -6
- package/dist/inspector/ui/Button.d.ts +6 -2
- package/dist/inspector/ui/Button.js +4 -3
- package/dist/inspector/ui/ControlSurface.d.ts +2 -2
- package/dist/inspector/ui/ControlSurface.js +4 -3
- package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
- package/dist/inspector/ui/InlineStyleWarning.js +13 -0
- package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
- package/dist/inspector/ui/InspectorPopover.js +4 -5
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +8 -7
- package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
- package/dist/inspector/ui/SegmentedControl.js +2 -2
- package/dist/inspector/ui/Select.d.ts +6 -1
- package/dist/inspector/ui/Select.js +7 -11
- package/dist/inspector/ui/SideValuesField.js +1 -1
- package/dist/inspector/ui/Tooltip.d.ts +26 -0
- package/dist/inspector/ui/Tooltip.js +32 -0
- package/dist/inspector/ui/classNames.d.ts +2 -0
- package/dist/inspector/ui/classNames.js +4 -0
- package/dist/inspector/ui/customCursors.d.ts +23 -0
- package/dist/inspector/ui/customCursors.js +122 -0
- package/dist/inspector/ui/dragCursor.d.ts +0 -2
- package/dist/inspector/ui/dragCursor.js +2 -10
- package/dist/inspector/ui/portalContainer.d.ts +2 -0
- package/dist/inspector/ui/portalContainer.js +6 -0
- package/dist/inspector/ui/styles.js +29 -21
- package/dist/inspector/workspace/commands.d.ts +9 -0
- package/dist/inspector/workspace/commands.js +82 -0
- package/dist/inspector/workspace/controller.d.ts +4 -0
- package/dist/inspector/workspace/controller.js +66 -0
- package/dist/inspector/workspace/handoff.d.ts +42 -0
- package/dist/inspector/workspace/handoff.js +115 -0
- package/dist/inspector/workspace/sentItems.d.ts +19 -0
- package/dist/inspector/workspace/sentItems.js +51 -0
- package/dist/inspector/workspace/timeline.d.ts +30 -0
- package/dist/inspector/workspace/timeline.js +108 -0
- package/dist/internal/agent-protocol.d.ts +19 -2
- package/dist/internal/agent-protocol.js +13 -0
- package/dist/nudge-ui.mjs +558 -311
- package/dist/project/artifacts.d.ts +13 -0
- package/dist/project/artifacts.js +253 -0
- package/dist/transport/artifacts.d.ts +8 -0
- package/dist/transport/artifacts.js +9 -0
- package/dist/transport/editor.js +3 -1
- package/dist/transport/routes.d.ts +2 -0
- package/dist/transport/routes.js +2 -0
- package/package.json +7 -3
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
- package/dist/inspector/changes/workspaceChanges.js +0 -265
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { getCanonicalFrameProjection } from "./projection.js";
|
|
3
|
+
import { getDraftForCard } from "../drafts/store.js";
|
|
4
|
+
import { iterationId, contentSourceUrl, getLinkedFrameIds } from "./frameContent.js";
|
|
2
5
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
6
|
import { IconCornerLeftUp } from "@tabler/icons-react";
|
|
4
|
-
import { useCanvasCards, activateIframeWorkspace,
|
|
7
|
+
import { useCanvasCards, activateIframeWorkspace, getSelectedCardId, getFocusedCardId, getCanvasCards, batchCanvasChanges, fitAllCards, focusCanvasCards, focusCard, useSelectedCardId, useFocusedCardId, resizeCard, setCardPosition, updateCardTitle, addCanvasCard, selectCard, deselectCard, CARD_GAP, updateCardUrl, } from "./canvasStore.js";
|
|
8
|
+
import { setBoardCamera, getBoardCamera, setCanvasPresentation, hasFitAllRan, useBoardCamera, useCanvasPresentation, useCanvasPresentationTransitioning, useCanvasLayoutTransitioning, useTemporaryAppInteraction, setTemporaryAppInteraction, } from "./viewStore.js";
|
|
5
9
|
import { CanvasCard } from "./CanvasCard.js";
|
|
10
|
+
import { useCanvasLayoutAnimation } from "./useCanvasLayoutAnimation.js";
|
|
6
11
|
import { Button } from "../ui/Button.js";
|
|
7
|
-
import { useCanvasMode } from "./
|
|
12
|
+
import { useCanvasMode } from "./viewStore.js";
|
|
8
13
|
import { subscribeChanges } from "../changes/changesLog.js";
|
|
9
14
|
import { subscribeOriginalPreview, isOriginalPreviewActive } from "../shell/originalPreview.js";
|
|
10
15
|
import { recordCanvasStructuralProjectionReports } from "../projection/structuralProjection.js";
|
|
@@ -12,31 +17,37 @@ import { recordCanvasRenderedInstanceProjectionReports } from "../projection/ren
|
|
|
12
17
|
import { recordCanvasTextProjectionReports } from "../projection/textProjection.js";
|
|
13
18
|
import { getChangesList, isPreviewableChange } from "../changes/changesLog.js";
|
|
14
19
|
import { changeKey } from "../changes/model.js";
|
|
15
|
-
import {
|
|
20
|
+
import { getActiveDraftChanges } from "../changes/draftChanges.js";
|
|
16
21
|
import { beginPreviewAttempt, publishPreviewDiagnostic } from "../changes/previewDiagnostics.js";
|
|
17
22
|
import { verifyManagedStyleProjection } from "../changes/managedStyleProjection.js";
|
|
18
23
|
import { getCanvasPreviewDocument, getRegisteredFrames, isCanvasCanonicalProjectionRevisionCurrent, PROJECT_ID, projectToAllReadyCards, recordCanvasProjectionApplied, WORKSPACE_ID, } from "./projection.js";
|
|
19
|
-
import { normalizeUrl } from "./normalizeUrl.js";
|
|
24
|
+
import { normalizeUrl, normalizedUrlKey } from "./normalizeUrl.js";
|
|
20
25
|
import { createNudgeUiEditorUrl } from "../../transport/editor.js";
|
|
21
26
|
import { disposeInlineTextEdit } from "../inline-text/inlineTextEditor.js";
|
|
22
27
|
import { PROTOCOL_VERSION, isProjectionAppliedMessage, isKeyboardShortcutMessage, isRenderedInstanceProjectionReportMessage, isTextProjectionReportMessage, isStructuralProjectionReportMessage, } from "./frameProtocol.js";
|
|
28
|
+
import { wheelPanDelta } from "./wheelPan.js";
|
|
23
29
|
import { iframePointToClientPoint, zoomCameraAtPointer } from "./canvasGestures.js";
|
|
24
|
-
const canvasWorkspaceStyles = ".canvas-workspace {\n position: fixed;\n inset: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface-panel);\n pointer-events: auto;\n}\n\n.canvas-workspace__board {\n flex: 1;\n overflow: hidden;\n position: relative;\n background: var(--surface-panel);\n}\n\n.canvas-workspace__focus-action {\n position: absolute;\n top: max(12px, env(safe-area-inset-top));\n left: max(12px, env(safe-area-inset-left));\n z-index: calc(var(--z-index) - 1);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n pointer-events: auto;\n}\n\n.canvas-workspace__board.is-grabbable {\n cursor: grab;\n}\n\n.canvas-workspace__board.is-grabbing {\n cursor: grabbing;\n}\n\n/* Canvas gestures take precedence over every card interaction while Space is\n held. This also closes the short postMessage gap before an iframe receives\n its mirrored modifier state. */\n.canvas-workspace__board.is-grabbable .canvas-card,\n.canvas-workspace__board.is-grabbing .canvas-card {\n pointer-events: none;\n}\n\n.canvas-workspace__board-content {\n position: absolute;\n width: 100%;\n height: 100%;\n /*\n * Leave `will-change` unset so the browser can re-rasterize this subtree at\n * the current camera scale. A permanent `will-change: transform` promotes\n * it to a fixed bitmap, which makes iframe content look soft when zoomed.\n */\n}\n\n.canvas-workspace__board.is-grabbing .canvas-workspace__board-content {\n /* Panning benefits from a cached composited layer; zoom restores the crisp\n * default path as soon as the gesture ends. */\n will-change: transform;\n}\n\n.canvas-workspace
|
|
25
|
-
const canvasCardStyles = ".canvas-card {\n position: relative;\n}\n\n/* Extend the card's hover region to the toolbar above the iframe. */\n.canvas-card::before {\n content: \"\";\n position: absolute;\n right: 0;\n bottom: 100%;\n left: 0;\n height: calc(var(--control-height-large) + var(--space-
|
|
26
|
-
const foundationStyles = ":host {\n all: initial;\n --surface-panel: #F5F5F4;\n --surface-control: var(--surface-well);\n --surface-control-hover: var(--surface-well-2x);\n --surface-raised: #FAFAFA;\n --surface-raised-2x: #fff ;\n --surface-raised-hover: #F5F5F4;\n --surface-well: hsla(0, 0%, 0%, 0.05);\n --surface-well-2x: hsla(0, 0%, 0%, 0.10);\n\n --border-subtle: hsla(0, 0%, 0%, 0.1);\n --border-medium: hsla(0, 0%, 0%, 0.2);\n --border-strong: #d8d8d8;\n\n --text-primary: #242424;\n --text-secondary: #6f6f6f;\n --text-muted: #777777;\n --text-muted-subtle: #a1a1a1;\n\n --accent: #0096ff;\n --accent-surface: rgba(0, 176, 255, 0.15);\n --warning: #d97706;\n --warning-text: #9a5b00;\n --warning-surface: #fff6e5;\n --danger: #df4b4b;\n --danger-text: #bd3636;\n --danger-surface: rgba(223, 75, 75, 0.2);\n\n --space-0: 0;\n --space-4: 4px;\n --space-8: 8px;\n --space-12: 12px;\n --space-16: 16px;\n --space-20: 20px;\n --space-24: 24px;\n --space-32: 32px;\n --space-40: 40px;\n --space-48: 48px;\n\n /* Radius */\n --radius-0: 0;\n --radius-4: 4px;\n --radius-8: 8px;\n --radius-12: 12px;\n --radius-16: 16px;\n --radius-20: 20px;\n --radius-24: 24px;\n --radius-pill: 999px;\n\n --radius-control: var(--radius-8);\n --shadow-outline: 0 0 0 1px rgba(0, 0, 0, 0.05);\n --shadow-popup: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 16px rgba(0, 0, 0, 0.10);\n --shadow-control: inset 0 -2px 0 rgba(0, 0, 0, 0.03), 0 0 0 0.5px rgba(0, 0, 0, 0.25);\n --z-index: 2147483647;\n\n --font-family: \"Inter Variable\", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n --font-size-base: 12px;\n --font-size-small: 10px;\n --font-weight-base: 400;\n --font-weight-label: 600;\n\n /* Control semantic sizes */\n --control-height-small: 24px;\n --control-height-large: 32px;\n --control-height-xlarge: 40px;\n --label-column: 104px;\n\n --icon-size-small: 16px;\n --icon-size-large: 20px;\n --icon-stroke-width: 1.5px;\n\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n font-family: var(--font-family);\n background: transparent;\n box-sizing: border-box;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nbutton,\ninput,\nselect {\n font-family: inherit;\n}\n";
|
|
30
|
+
const canvasWorkspaceStyles = ".canvas-workspace {\n position: fixed;\n inset: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface-panel);\n pointer-events: auto;\n}\n\n.canvas-workspace__board {\n flex: 1;\n overflow: hidden;\n position: relative;\n background: var(--surface-panel);\n}\n\n.canvas-workspace__focus-action {\n position: absolute;\n top: max(12px, env(safe-area-inset-top));\n left: max(12px, env(safe-area-inset-left));\n z-index: calc(var(--z-index) - 1);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n pointer-events: auto;\n}\n\n.canvas-workspace__board.is-grabbable {\n cursor: grab;\n}\n\n.canvas-workspace__board.is-grabbing {\n cursor: grabbing;\n}\n\n/* Canvas gestures take precedence over every card interaction while Space is\n held. This also closes the short postMessage gap before an iframe receives\n its mirrored modifier state. */\n.canvas-workspace__board.is-grabbable .canvas-card,\n.canvas-workspace__board.is-grabbing .canvas-card {\n pointer-events: none;\n}\n\n.canvas-workspace__board-content {\n position: absolute;\n width: 100%;\n height: 100%;\n /*\n * Leave `will-change` unset so the browser can re-rasterize this subtree at\n * the current camera scale. A permanent `will-change: transform` promotes\n * it to a fixed bitmap, which makes iframe content look soft when zoomed.\n */\n}\n\n.canvas-workspace__board.is-grabbing .canvas-workspace__board-content {\n /* Panning benefits from a cached composited layer; zoom restores the crisp\n * default path as soon as the gesture ends. */\n will-change: transform;\n}\n\n.canvas-workspace[data-presentation=\"focus\"] .canvas-workspace__board-content {\n inset: 0;\n transform: none !important;\n}\n\n.canvas-workspace.is-presentation-transitioning .canvas-card:not(.is-presentation-card) {\n visibility: hidden !important;\n pointer-events: none !important;\n}\n\n.canvas-workspace__target-error {\n position: absolute;\n inset: 0;\n display: grid;\n place-items: center;\n color: #6b7280;\n font: 500 14px/1.4 system-ui, sans-serif;\n}\n\n/* Moving iframes must not acquire hover state beneath a stationary pointer. */\n.canvas-workspace.is-presentation-transitioning .canvas-card__iframe,\n.canvas-workspace__board.is-layout-transitioning .canvas-card__iframe {\n pointer-events: none;\n}\n";
|
|
31
|
+
const canvasCardStyles = ".canvas-card {\n position: relative;\n}\n\n/* Extend the card's hover region to the toolbar above the iframe. */\n.canvas-card::before {\n content: \"\";\n position: absolute;\n right: 0;\n bottom: 100%;\n left: 0;\n height: calc((var(--control-height-large) + var(--space-16)) * var(--canvas-toolbar-scale, 1));\n pointer-events: auto;\n}\n\n.canvas-card__frame {\n position: relative;\n width: 100%;\n height: 100%;\n background: var(--surface-panel);\n border-radius: 0;\n overflow: hidden;\n}\n\n.canvas-card.is-selected .canvas-card__frame {\n outline: 2px solid var(--frame-accent, var(--accent));\n outline-offset: -2px;\n}\n\n.canvas-card.is-focus-presentation .canvas-card__frame {\n outline: none;\n}\n\n.canvas-card.is-focused.is-selected:not(.is-linked) .canvas-card__frame {\n animation: canvas-card-focus 900ms ease-out;\n}\n\n@keyframes canvas-card-focus {\n 0%, 35% { outline: 3px solid var(--frame-accent, var(--accent)); outline-offset: -3px; }\n 100% { outline: 0 solid transparent; outline-offset: -2px; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .canvas-card.is-focused .canvas-card__frame {\n animation: none;\n }\n}\n\n.canvas-card__header {\n display: flex;\n align-items: center;\n position: absolute;\n left: 0;\n right: 0;\n bottom: calc(100% + var(--space-8));\n justify-content: flex-end;\n z-index: 3;\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n}\n\n.canvas-card__drag-surface {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--space-8);\n flex: 0 1 auto;\n margin-right: auto;\n min-width: 0;\n min-height: var(--control-height-large);\n padding: 0;\n cursor: grab;\n user-select: none;\n touch-action: none;\n}\n\n.canvas-card.is-dragging .canvas-card__drag-surface {\n cursor: grabbing;\n}\n\n.canvas-card__identity {\n display: flex;\n align-items: center;\n gap: var(--space-8);\n min-width: 0;\n}\n\n.canvas-card__dimensions {\n display: flex;\n align-items: center;\n gap: var(--space-8);\n position: relative;\n flex: 0 1 auto;\n min-width: 0;\n max-width: 260px;\n color: var(--text-secondary);\n font-size: var(--font-size-base);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n pointer-events: none;\n}\n\n.canvas-card__title {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.canvas-card__live-badge,\n.canvas-card__iteration-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 20px;\n height: 20px;\n background: var(--accent-surface);\n color: var(--accent);\n border-radius: var(--radius-4);\n pointer-events: auto;\n}\n\n.canvas-card__floating-toolbar {\n position: absolute;\n left: 50%;\n flex-shrink: 0;\n width: max-content;\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transform: translate(-50%, calc(-24px + var(--space-4)));\n transition: opacity 140ms ease-out, transform 140ms ease-out, visibility 140ms;\n}\n\n.canvas-card:hover .canvas-card__floating-toolbar,\n.canvas-card:focus-within .canvas-card__floating-toolbar,\n.canvas-card__floating-toolbar[data-busy=\"true\"] {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n transform: translate(-50%, -24px);\n}\n\n.canvas-card__actions.canvas-toolbar {\n position: static;\n width: max-content;\n transform: none;\n white-space: nowrap;\n}\n\n.canvas-card__actions .canvas-toolbar__divider {\n margin-inline: 0;\n}\n\n@media (hover: none) {\n .canvas-card.is-selected .canvas-card__floating-toolbar {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n transform: translate(-50%, -24px);\n }\n}\n\n.canvas-card.is-dragging .canvas-card__floating-toolbar,\n.canvas-workspace__board.is-grabbable .canvas-card__floating-toolbar,\n.canvas-workspace__board.is-grabbing .canvas-card__floating-toolbar,\n.canvas-workspace.is-presentation-transitioning .canvas-card__floating-toolbar,\n.canvas-workspace__board.is-layout-transitioning .canvas-card__floating-toolbar {\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transition: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .canvas-card__floating-toolbar { transition: none; }\n}\n\n.canvas-card__iteration-error {\n position: absolute;\n right: 0;\n bottom: calc(100% + var(--space-8));\n color: var(--text-danger, #b42318);\n font-size: 12px;\n white-space: nowrap;\n}\n\n.canvas-card__iframe {\n width: 100%;\n height: 100%;\n border: none;\n display: block;\n}\n\n.canvas-card__sketch-overlay {\n position: absolute;\n inset: 0;\n z-index: 2;\n display: block;\n pointer-events: none;\n}\n\n.canvas-card__loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--text-muted, #777777);\n font-size: 12px;\n font-weight: 500;\n}\n\n.canvas-card__error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n height: 100%;\n padding: 20px;\n text-align: center;\n}\n\n.canvas-card__error p {\n margin: 0;\n color: var(--text-muted, #777777);\n font-size: 12px;\n font-weight: 500;\n}\n\n.canvas-card__error-actions {\n display: flex;\n gap: 8px;\n}\n\n.canvas-card__resize-handle {\n position: absolute;\n z-index: 3;\n display: flex;\n align-items: flex-end;\n justify-content: flex-end;\n padding: 4px;\n color: var(--text-muted, #777777);\n opacity: 0;\n transition: opacity 150ms ease;\n touch-action: none;\n user-select: none;\n}\n\n.canvas-card__resize-handle--top,\n.canvas-card__resize-handle--bottom {\n right: 16px;\n left: 16px;\n height: 16px;\n cursor: ns-resize;\n}\n\n.canvas-card__resize-handle--top {\n top: 0;\n}\n\n.canvas-card__resize-handle--bottom {\n bottom: 0;\n}\n\n.canvas-card__resize-handle--left,\n.canvas-card__resize-handle--right {\n top: 16px;\n bottom: 16px;\n width: 16px;\n cursor: ew-resize;\n}\n\n.canvas-card__resize-handle--left {\n left: 0;\n}\n\n.canvas-card__resize-handle--right {\n right: 0;\n}\n\n.canvas-card__resize-handle--top-left,\n.canvas-card__resize-handle--top-right,\n.canvas-card__resize-handle--bottom-right,\n.canvas-card__resize-handle--bottom-left {\n z-index: 4;\n width: 24px;\n height: 24px;\n cursor: nwse-resize;\n}\n\n.canvas-card__resize-handle--top-left {\n top: 0;\n left: 0;\n}\n\n.canvas-card__resize-handle--top-right {\n top: 0;\n right: 0;\n cursor: nesw-resize;\n}\n\n.canvas-card__resize-handle--bottom-right {\n right: 0;\n bottom: 0;\n}\n\n.canvas-card__resize-handle--bottom-left {\n bottom: 0;\n left: 0;\n cursor: nesw-resize;\n}\n\n.canvas-card:hover .canvas-card__resize-handle,\n.canvas-card:focus-within .canvas-card__resize-handle {\n opacity: 1;\n}\n\n.canvas-card__resize-handle:hover {\n color: var(--accent);\n}\n\n.canvas-card__resize-handle:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n opacity: 1;\n}\n\n.canvas-card.is-linked {\n --frame-accent: #a855f7;\n}\n\n.canvas-card.is-linked:not(.is-focus-presentation) .canvas-card__frame {\n outline: 2px solid var(--frame-accent);\n outline-offset: -2px;\n}\n\n.canvas-card__iteration-badge {\n color: var(--text-secondary);\n background: var(--surface-raised-2x);\n}\n";
|
|
32
|
+
const foundationStyles = ":host {\n all: initial;\n --surface-panel: #F5F5F4;\n --surface-control: var(--surface-well);\n --surface-control-hover: var(--surface-well-2x);\n --surface-raised: #FAFAFA;\n --surface-raised-2x: #fff ;\n --surface-raised-hover: #F5F5F4;\n --surface-well: hsla(0, 0%, 0%, 0.05);\n --surface-well-2x: hsla(0, 0%, 0%, 0.10);\n\n --border-subtle: hsla(0, 0%, 0%, 0.1);\n --border-medium: hsla(0, 0%, 0%, 0.2);\n --border-strong: #d8d8d8;\n\n --text-primary: #242424;\n --text-secondary: #6f6f6f;\n --text-muted: #777777;\n --text-muted-subtle: #a1a1a1;\n\n --accent: #0096ff;\n --accent-surface: rgba(0, 176, 255, 0.15);\n --warning: #d97706;\n --warning-text: #9a5b00;\n --warning-surface: #fff6e5;\n --danger: #df4b4b;\n --danger-text: #bd3636;\n --danger-surface: rgba(223, 75, 75, 0.2);\n --success: #2e7d52;\n\n --space-0: 0;\n --space-4: 4px;\n --space-8: 8px;\n --space-12: 12px;\n --space-16: 16px;\n --space-20: 20px;\n --space-24: 24px;\n --space-32: 32px;\n --space-40: 40px;\n --space-48: 48px;\n\n /* Radius */\n --radius-0: 0;\n --radius-4: 4px;\n --radius-8: 8px;\n --radius-12: 12px;\n --radius-16: 16px;\n --radius-20: 20px;\n --radius-24: 24px;\n --radius-pill: 999px;\n\n --radius-control: var(--radius-8);\n --shadow-outline: 0 0 0 1px rgba(0, 0, 0, 0.05);\n --shadow-popup: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 16px rgba(0, 0, 0, 0.10);\n --shadow-control: inset 0 -2px 0 rgba(0, 0, 0, 0.03), 0 0 0 0.5px rgba(0, 0, 0, 0.25);\n --z-index: 2147483647;\n\n --font-family: \"Inter Variable\", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n --font-size-base: 12px;\n --font-size-small: 10px;\n --font-weight-base: 400;\n --font-weight-label: 600;\n\n /* Control semantic sizes */\n --control-height-small: 24px;\n --control-height-large: 32px;\n --control-height-xlarge: 40px;\n --label-column: 104px;\n\n --icon-size-small: 16px;\n --icon-size-large: 20px;\n --icon-stroke-width: 1.5px;\n\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n font-family: var(--font-family);\n background: transparent;\n box-sizing: border-box;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nbutton,\ninput,\nselect {\n font-family: inherit;\n}\n";
|
|
27
33
|
import { useInspectorOpen } from "../shell/openStore.js";
|
|
28
34
|
import { isEditableEvent } from "../shell/shortcuts.js";
|
|
29
35
|
import { acknowledgeAgentRendererReady } from "./agentPresentation.js";
|
|
30
36
|
import { useInspectorSession } from "../session/sessionContext.js";
|
|
37
|
+
import { isNudgeUiFeatureEnabled } from "../runtime/runtimeConfig.js";
|
|
31
38
|
import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
|
|
32
39
|
import { subscribeCanvasRendererMessages } from "./rendererMessageRouter.js";
|
|
33
40
|
import { adjacentCanvasZoomLevel, CanvasToolbar, } from "./CanvasToolbar.js";
|
|
34
|
-
import { canvasToolForEvent, canvasToolForCode } from "./keyboardShortcuts.js";
|
|
35
|
-
const canvasToolbarStyles = ".canvas-toolbar {\n position: absolute;\n left: 50%;\n bottom: var(--space-20);\n /* Keep the persistent controls above the live sketch surface. The sketch\n toolbar gets the next layer so both toolbars remain available together. */\n z-index: calc(var(--z-index) - 2);\n display: flex;\n align-items: center;\n gap: var(--space-4);\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n transform: translateX(-50%);\n pointer-events: auto;\n}\n\n/* The live sketch surface is a separate stacking context. Keep a copy of the\n toolbar at the root layer while sketching so pointer events reach the\n controls instead of the full-screen SVG drawing surface. */\n.canvas-toolbar__portal {\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n}\n\n.canvas-toolbar__portal > .canvas-toolbar {\n pointer-events: auto;\n}\n\n.canvas-toolbar__tools {\n display: flex;\n align-items: center;\n gap: var(--space-4);\n}\n\n.canvas-toolbar__divider {\n flex-shrink: 0;\n width: 1px;\n height: var(--control-height-small);\n margin-inline:
|
|
41
|
+
import { APP_INTERACTION_KEY, canvasToolForEvent, canvasToolForCode } from "./keyboardShortcuts.js";
|
|
42
|
+
const canvasToolbarStyles = ".canvas-toolbar {\n position: absolute;\n left: 50%;\n bottom: var(--space-20);\n /* Keep the persistent controls above the live sketch surface. The sketch\n toolbar gets the next layer so both toolbars remain available together. */\n z-index: calc(var(--z-index) - 2);\n display: flex;\n align-items: center;\n gap: var(--space-4);\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n transform: translateX(-50%);\n pointer-events: auto;\n}\n\n/* The live sketch surface is a separate stacking context. Keep a copy of the\n toolbar at the root layer while sketching so pointer events reach the\n controls instead of the full-screen SVG drawing surface. */\n.canvas-toolbar__portal {\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n}\n\n.canvas-toolbar__portal > .canvas-toolbar {\n pointer-events: auto;\n}\n\n.canvas-toolbar__tools {\n display: flex;\n align-items: center;\n gap: var(--space-4);\n}\n\n.canvas-toolbar__divider {\n flex-shrink: 0;\n width: 1px;\n height: var(--control-height-small);\n margin-inline: 0;\n background: var(--border-subtle);\n}\n\n.canvas-toolbar__tool[data-active=\"true\"] {\n background: var(--accent-surface);\n color: var(--accent);\n}\n";
|
|
36
43
|
import { getActiveCanvasFrame } from "./activeCanvasDocument.js";
|
|
37
44
|
import { startSketchCapture } from "../sketch/SketchWorkspace.js";
|
|
38
45
|
import { cancelSketchInteraction, useSketchInteractionActive } from "../sketch/interaction.js";
|
|
39
46
|
import { isNudgeUiDev } from "../runtime/devFlag.js";
|
|
47
|
+
import { createCommentCursor, DESIGN_SELECT_CURSOR, PAN_CURSOR } from "../ui/customCursors.js";
|
|
48
|
+
import { addLinkedFrame, createIteration as createWorkspaceIteration, removeFrame } from "../workspace/commands.js";
|
|
49
|
+
import { getSelectedElements, setSelectedElement } from "../selection/selectionStore.js";
|
|
50
|
+
import { setCommentToolActive } from "../comments/store.js";
|
|
40
51
|
const WORKSPACE_STYLES = [foundationStyles, canvasWorkspaceStyles, canvasCardStyles, canvasToolbarStyles].join("\n");
|
|
41
52
|
const PRESENTATION_TRANSITION_MS = 200;
|
|
42
53
|
function snapshotRect(rect) {
|
|
@@ -60,8 +71,10 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
60
71
|
const focusedCardId = useFocusedCardId();
|
|
61
72
|
const sketchActive = useSketchInteractionActive();
|
|
62
73
|
const sketchEnabled = isNudgeUiDev() && runtimeConfig.demo !== true;
|
|
74
|
+
const canvasIterationsEnabled = isNudgeUiFeatureEnabled(runtimeConfig.features, "canvasIterations");
|
|
63
75
|
const sketchAvailable = sketchEnabled && getActiveCanvasFrame() !== null;
|
|
64
76
|
const boardRef = useRef(null);
|
|
77
|
+
useCanvasLayoutAnimation(boardRef, cards, camera, presentation);
|
|
65
78
|
const panningRef = useRef(false);
|
|
66
79
|
const panOriginRef = useRef({ x: 0, y: 0 });
|
|
67
80
|
const cameraAtPanStartRef = useRef({ x: 0, y: 0 });
|
|
@@ -71,81 +84,75 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
71
84
|
const activatedTargetRef = useRef(null);
|
|
72
85
|
const demoSeededRef = useRef(false);
|
|
73
86
|
const previousPresentationRef = useRef(presentation);
|
|
74
|
-
const
|
|
75
|
-
const canvasEntryAnimationRef = useRef(null);
|
|
87
|
+
const presentationAnimationRef = useRef(null);
|
|
76
88
|
const [boardCursorClass, setBoardCursorClass] = useState("");
|
|
77
|
-
const [
|
|
78
|
-
const
|
|
89
|
+
const [selectedTool, setInteractionTool] = useState("design");
|
|
90
|
+
const temporaryAppInteraction = useTemporaryAppInteraction();
|
|
91
|
+
const interactionTool = temporaryAppInteraction ? "select" : selectedTool;
|
|
92
|
+
const layoutTransitioning = useCanvasLayoutTransitioning();
|
|
93
|
+
const rendererInteractionsEnabled = interactionTool !== "select" && !layoutTransitioning;
|
|
94
|
+
const rendererCursor = interactionTool === "comment" ? "comment" : "design";
|
|
95
|
+
const boardCursor = boardCursorClass === "is-grabbing"
|
|
96
|
+
? "grabbing"
|
|
97
|
+
: boardCursorClass !== ""
|
|
98
|
+
? PAN_CURSOR
|
|
99
|
+
: interactionTool === "comment"
|
|
100
|
+
? createCommentCursor(boardRef.current)
|
|
101
|
+
: interactionTool === "design"
|
|
102
|
+
? DESIGN_SELECT_CURSOR
|
|
103
|
+
: undefined;
|
|
79
104
|
const linksOpenInCards = interactionTool === "select" && presentation === "canvas";
|
|
80
105
|
const presentationCardId = selectedCardId ?? focusedCardId ?? cards[0]?.id ?? null;
|
|
106
|
+
const linkedFrameIds = getLinkedFrameIds(cards, selectedCardId);
|
|
81
107
|
useLayoutEffect(() => {
|
|
108
|
+
const previousPresentation = previousPresentationRef.current;
|
|
109
|
+
previousPresentationRef.current = presentation;
|
|
110
|
+
if (previousPresentation === presentation)
|
|
111
|
+
return;
|
|
112
|
+
presentationAnimationRef.current?.cancel();
|
|
113
|
+
presentationAnimationRef.current = null;
|
|
114
|
+
if (!presentationTransitioning)
|
|
115
|
+
return;
|
|
82
116
|
const board = boardRef.current;
|
|
117
|
+
const card = cards.find((candidate) => candidate.id === presentationCardId);
|
|
83
118
|
const cardElement = presentationCardId
|
|
84
119
|
? board?.querySelector(`[data-card-id="${presentationCardId}"]`)
|
|
85
120
|
: null;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
transformOrigin: "center center",
|
|
122
|
-
transform: `translate(${localTranslateX}px, ${localTranslateY}px) scale(${scaleX}, ${scaleY})`,
|
|
123
|
-
},
|
|
124
|
-
{ transformOrigin: "center center", transform: "none" },
|
|
125
|
-
], { duration: PRESENTATION_TRANSITION_MS, easing: "ease", fill: "both" });
|
|
126
|
-
canvasEntryAnimationRef.current = animation;
|
|
127
|
-
void animation.finished.then(() => {
|
|
128
|
-
if (canvasEntryAnimationRef.current !== animation)
|
|
129
|
-
return;
|
|
130
|
-
canvasEntryAnimationRef.current = null;
|
|
131
|
-
animation.cancel();
|
|
132
|
-
boardContent.style.transition = "";
|
|
133
|
-
cardElement.style.transition = "";
|
|
134
|
-
}).catch(() => {
|
|
135
|
-
if (canvasEntryAnimationRef.current !== animation)
|
|
136
|
-
return;
|
|
137
|
-
canvasEntryAnimationRef.current = null;
|
|
138
|
-
boardContent.style.transition = "";
|
|
139
|
-
cardElement.style.transition = "";
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
boardContent.style.transition = "";
|
|
144
|
-
cardElement.style.transition = "";
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
previousPresentationRef.current = presentation;
|
|
148
|
-
}, [camera.zoom, presentation, presentationCardId, presentationTransitioning]);
|
|
121
|
+
if (!board || !card || !cardElement || typeof cardElement.animate !== "function" || camera.zoom <= 0)
|
|
122
|
+
return;
|
|
123
|
+
// The previous layout is already gone, so derive it: focus fills the
|
|
124
|
+
// board, and canvas places the card through the camera.
|
|
125
|
+
const boardRect = snapshotRect(board.getBoundingClientRect());
|
|
126
|
+
const canvasRect = {
|
|
127
|
+
left: boardRect.left + camera.x + card.x * camera.zoom,
|
|
128
|
+
top: boardRect.top + camera.y + card.y * camera.zoom,
|
|
129
|
+
width: card.width * camera.zoom,
|
|
130
|
+
height: card.height * camera.zoom,
|
|
131
|
+
};
|
|
132
|
+
const oldRect = presentation === "focus" ? canvasRect : boardRect;
|
|
133
|
+
const localScale = presentation === "focus" ? 1 : camera.zoom;
|
|
134
|
+
const newRect = snapshotRect(cardElement.getBoundingClientRect());
|
|
135
|
+
if (newRect.width <= 0 || newRect.height <= 0)
|
|
136
|
+
return;
|
|
137
|
+
const localTranslateX = (oldRect.left + oldRect.width / 2 - (newRect.left + newRect.width / 2)) / localScale;
|
|
138
|
+
const localTranslateY = (oldRect.top + oldRect.height / 2 - (newRect.top + newRect.height / 2)) / localScale;
|
|
139
|
+
const scaleX = oldRect.width / newRect.width;
|
|
140
|
+
const scaleY = oldRect.height / newRect.height;
|
|
141
|
+
const animation = cardElement.animate([
|
|
142
|
+
{
|
|
143
|
+
transformOrigin: "center center",
|
|
144
|
+
transform: `translate(${localTranslateX}px, ${localTranslateY}px) scale(${scaleX}, ${scaleY})`,
|
|
145
|
+
},
|
|
146
|
+
{ transformOrigin: "center center", transform: "none" },
|
|
147
|
+
], { duration: PRESENTATION_TRANSITION_MS, easing: "ease", fill: "both" });
|
|
148
|
+
presentationAnimationRef.current = animation;
|
|
149
|
+
void animation.finished.then(() => {
|
|
150
|
+
if (presentationAnimationRef.current !== animation)
|
|
151
|
+
return;
|
|
152
|
+
presentationAnimationRef.current = null;
|
|
153
|
+
animation.cancel();
|
|
154
|
+
}).catch(() => undefined);
|
|
155
|
+
}, [camera, cards, presentation, presentationCardId, presentationTransitioning]);
|
|
149
156
|
useLayoutEffect(() => {
|
|
150
157
|
const board = boardRef.current;
|
|
151
158
|
if (!primaryUrl || !board)
|
|
@@ -157,7 +164,16 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
157
164
|
width: board.clientWidth,
|
|
158
165
|
height: board.clientHeight,
|
|
159
166
|
};
|
|
160
|
-
const primaryCard = activateIframeWorkspace(primaryUrl, viewport
|
|
167
|
+
const primaryCard = activateIframeWorkspace(primaryUrl, viewport, {
|
|
168
|
+
replaceActiveCard: presentation === "focus" && runtimeConfig.demo !== true,
|
|
169
|
+
preserveIterationFocus: true,
|
|
170
|
+
});
|
|
171
|
+
if (primaryCard && cards.length === 0 && runtimeConfig.demo !== true) {
|
|
172
|
+
setBoardCamera({
|
|
173
|
+
x: 40, y: 120,
|
|
174
|
+
zoom: Math.max(0.25, Math.min(0.9, (viewport.width - 80) / primaryCard.width, (viewport.height - 160) / primaryCard.height)),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
161
177
|
if (runtimeConfig.demo === true && primaryCard && !demoSeededRef.current) {
|
|
162
178
|
demoSeededRef.current = true;
|
|
163
179
|
const demoPages = runtimeConfig.demoPages ?? [];
|
|
@@ -201,7 +217,7 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
201
217
|
}
|
|
202
218
|
focusCard(primaryCard.id);
|
|
203
219
|
}
|
|
204
|
-
}, [primaryUrl, cards.length, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
|
|
220
|
+
}, [primaryUrl, cards.length, presentation, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
|
|
205
221
|
useEffect(() => {
|
|
206
222
|
if (primaryUrl && activatedTargetRef.current !== primaryUrl)
|
|
207
223
|
return;
|
|
@@ -209,7 +225,7 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
209
225
|
const activeCard = cards.find((card) => card.id === activeCardId);
|
|
210
226
|
if (!activeCard)
|
|
211
227
|
return;
|
|
212
|
-
const editorUrl = createNudgeUiEditorUrl(activeCard.
|
|
228
|
+
const editorUrl = createNudgeUiEditorUrl(contentSourceUrl(activeCard.content));
|
|
213
229
|
if (window.location.href !== editorUrl) {
|
|
214
230
|
window.history.replaceState(window.history.state, "", editorUrl);
|
|
215
231
|
}
|
|
@@ -222,9 +238,10 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
222
238
|
workspaceId: WORKSPACE_ID,
|
|
223
239
|
cardId,
|
|
224
240
|
spaceHeld,
|
|
241
|
+
cursor: spaceHeld ? "drag" : rendererCursor,
|
|
225
242
|
}, window.location.origin);
|
|
226
|
-
}, []);
|
|
227
|
-
const sendInspectorInteractionState = useCallback((iframe, cardId, open, interactionsEnabled) => {
|
|
243
|
+
}, [rendererCursor]);
|
|
244
|
+
const sendInspectorInteractionState = useCallback((iframe, cardId, open, interactionsEnabled, cursor) => {
|
|
228
245
|
iframe.contentWindow?.postMessage({
|
|
229
246
|
type: "inspector-interaction-state",
|
|
230
247
|
protocolVersion: PROTOCOL_VERSION,
|
|
@@ -233,6 +250,8 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
233
250
|
cardId,
|
|
234
251
|
open,
|
|
235
252
|
interactionsEnabled,
|
|
253
|
+
cursor,
|
|
254
|
+
commentCursor: cursor === "comment" ? createCommentCursor(boardRef.current) : undefined,
|
|
236
255
|
}, window.location.origin);
|
|
237
256
|
}, []);
|
|
238
257
|
const sendLinkTargetState = useCallback((iframe, cardId, openInCard) => {
|
|
@@ -253,15 +272,16 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
253
272
|
workspaceId: WORKSPACE_ID,
|
|
254
273
|
cardId,
|
|
255
274
|
enabled,
|
|
275
|
+
panScroll: enabled && interactionTool !== "select",
|
|
256
276
|
}, window.location.origin);
|
|
257
|
-
}, []);
|
|
277
|
+
}, [interactionTool]);
|
|
258
278
|
useEffect(() => {
|
|
259
279
|
for (const [cardId, iframe] of getRegisteredFrames()) {
|
|
260
|
-
sendInspectorInteractionState(iframe, cardId, inspectorOpen, rendererInteractionsEnabled);
|
|
280
|
+
sendInspectorInteractionState(iframe, cardId, inspectorOpen, rendererInteractionsEnabled, rendererCursor);
|
|
261
281
|
sendBoardGestureState(iframe, cardId, presentation === "canvas");
|
|
262
282
|
sendLinkTargetState(iframe, cardId, linksOpenInCards);
|
|
263
283
|
}
|
|
264
|
-
}, [inspectorOpen, linksOpenInCards, presentation, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState]);
|
|
284
|
+
}, [inspectorOpen, linksOpenInCards, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState]);
|
|
265
285
|
const broadcastPanModifier = useCallback((spaceHeld) => {
|
|
266
286
|
for (const [cardId, iframe] of getRegisteredFrames()) {
|
|
267
287
|
sendPanModifier(iframe, cardId, spaceHeld);
|
|
@@ -273,6 +293,12 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
273
293
|
return;
|
|
274
294
|
setBoardCamera(zoomCameraAtPointer(getBoardCamera(), point, board.getBoundingClientRect(), deltaY));
|
|
275
295
|
}, []);
|
|
296
|
+
const panCanvasByWheel = useCallback((deltaX, deltaY) => {
|
|
297
|
+
if (!Number.isFinite(deltaX) || !Number.isFinite(deltaY))
|
|
298
|
+
return;
|
|
299
|
+
const camera = getBoardCamera();
|
|
300
|
+
setBoardCamera({ ...camera, x: camera.x - deltaX, y: camera.y - deltaY });
|
|
301
|
+
}, []);
|
|
276
302
|
const fitCanvasToBoard = useCallback(() => {
|
|
277
303
|
const board = boardRef.current;
|
|
278
304
|
fitAllCards(board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
|
|
@@ -310,6 +336,14 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
310
336
|
setBoardCursorClass("");
|
|
311
337
|
}, [broadcastPanModifier, endPanning]);
|
|
312
338
|
const handleToolChange = useCallback((nextTool) => {
|
|
339
|
+
if (nextTool === "select" && selectedTool === "select") {
|
|
340
|
+
setInteractionTool("design");
|
|
341
|
+
stopPanMode();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (nextTool === "select") {
|
|
345
|
+
setSelectedElement(null);
|
|
346
|
+
}
|
|
313
347
|
if (nextTool === "sketch") {
|
|
314
348
|
if (!sketchEnabled)
|
|
315
349
|
return;
|
|
@@ -317,27 +351,52 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
317
351
|
if (!hostElement)
|
|
318
352
|
return;
|
|
319
353
|
stopPanMode();
|
|
354
|
+
setCommentToolActive(false);
|
|
320
355
|
setInteractionTool(nextTool);
|
|
321
356
|
startSketchCapture(hostElement);
|
|
322
357
|
return;
|
|
323
358
|
}
|
|
324
359
|
if (sketchActive)
|
|
325
360
|
cancelSketchInteraction();
|
|
326
|
-
|
|
327
|
-
if (presentation === "focus") {
|
|
328
|
-
const board = boardRef.current;
|
|
329
|
-
setCanvasPresentation("canvas", board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
|
|
330
|
-
}
|
|
331
|
-
setInteractionTool(nextTool);
|
|
332
|
-
spaceHeldRef.current = true;
|
|
333
|
-
broadcastPanModifier(true);
|
|
334
|
-
if (!panningRef.current)
|
|
335
|
-
setBoardCursorClass("is-grabbable");
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
361
|
+
setCommentToolActive(nextTool === "comment");
|
|
338
362
|
setInteractionTool(nextTool);
|
|
339
363
|
stopPanMode();
|
|
340
|
-
}, [
|
|
364
|
+
}, [selectedTool, sketchActive, sketchEnabled, stopPanMode]);
|
|
365
|
+
useEffect(() => () => setCommentToolActive(false), []);
|
|
366
|
+
useEffect(() => {
|
|
367
|
+
setCommentToolActive(interactionTool === "comment");
|
|
368
|
+
if (temporaryAppInteraction)
|
|
369
|
+
stopPanMode();
|
|
370
|
+
}, [interactionTool, temporaryAppInteraction, stopPanMode]);
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
const onKeyDown = (event) => {
|
|
373
|
+
if (mode === "canvas" && event.code === APP_INTERACTION_KEY && !event.repeat && !isEditableEvent(event))
|
|
374
|
+
setTemporaryAppInteraction(true);
|
|
375
|
+
};
|
|
376
|
+
const onKeyUp = (event) => {
|
|
377
|
+
if (event.code === APP_INTERACTION_KEY)
|
|
378
|
+
setTemporaryAppInteraction(false);
|
|
379
|
+
};
|
|
380
|
+
const onBlur = () => {
|
|
381
|
+
if (!document.hasFocus())
|
|
382
|
+
setTemporaryAppInteraction(false);
|
|
383
|
+
};
|
|
384
|
+
const onVisibility = () => {
|
|
385
|
+
if (document.visibilityState === "hidden")
|
|
386
|
+
setTemporaryAppInteraction(false);
|
|
387
|
+
};
|
|
388
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
389
|
+
window.addEventListener("keyup", onKeyUp, true);
|
|
390
|
+
window.addEventListener("blur", onBlur);
|
|
391
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
392
|
+
return () => {
|
|
393
|
+
window.removeEventListener("keydown", onKeyDown, true);
|
|
394
|
+
window.removeEventListener("keyup", onKeyUp, true);
|
|
395
|
+
window.removeEventListener("blur", onBlur);
|
|
396
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
397
|
+
setTemporaryAppInteraction(false);
|
|
398
|
+
};
|
|
399
|
+
}, [mode]);
|
|
341
400
|
useEffect(() => {
|
|
342
401
|
if (mode !== "canvas")
|
|
343
402
|
return;
|
|
@@ -356,8 +415,21 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
356
415
|
const handleZoomStep = useCallback((direction) => {
|
|
357
416
|
if (presentation === "focus") {
|
|
358
417
|
if (direction < 0) {
|
|
359
|
-
|
|
360
|
-
|
|
418
|
+
batchCanvasChanges(() => {
|
|
419
|
+
const board = boardRef.current;
|
|
420
|
+
const cards = getCanvasCards();
|
|
421
|
+
const activeId = getSelectedCardId() ?? getFocusedCardId();
|
|
422
|
+
const card = cards.find((candidate) => candidate.id === activeId) ?? cards[0];
|
|
423
|
+
if (board && card) {
|
|
424
|
+
const zoom = 0.9;
|
|
425
|
+
setBoardCamera({
|
|
426
|
+
x: board.clientWidth / 2 - (card.x + card.width / 2) * zoom,
|
|
427
|
+
y: board.clientHeight / 2 - (card.y + card.height / 2) * zoom,
|
|
428
|
+
zoom,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
setCanvasPresentation("canvas");
|
|
432
|
+
});
|
|
361
433
|
}
|
|
362
434
|
return;
|
|
363
435
|
}
|
|
@@ -373,6 +445,23 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
373
445
|
focusCard(cardId);
|
|
374
446
|
setCanvasPresentation("focus");
|
|
375
447
|
}, []);
|
|
448
|
+
const duplicateDesign = useCallback((cardId) => {
|
|
449
|
+
const duplicate = addLinkedFrame(cardId);
|
|
450
|
+
if (!duplicate)
|
|
451
|
+
return;
|
|
452
|
+
setSelectedElement(null);
|
|
453
|
+
setCanvasPresentation("canvas");
|
|
454
|
+
const board = boardRef.current;
|
|
455
|
+
focusCanvasCards([duplicate.id], board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
|
|
456
|
+
}, []);
|
|
457
|
+
const createIteration = useCallback(async (cardId, position) => {
|
|
458
|
+
const iteration = await createWorkspaceIteration(cardId, position);
|
|
459
|
+
setSelectedElement(null);
|
|
460
|
+
const board = boardRef.current;
|
|
461
|
+
if (board && !position)
|
|
462
|
+
focusCanvasCards([iteration.id], { width: board.clientWidth, height: board.clientHeight });
|
|
463
|
+
return iteration.id;
|
|
464
|
+
}, []);
|
|
376
465
|
useEffect(() => {
|
|
377
466
|
if (!sketchActive && interactionTool === "sketch") {
|
|
378
467
|
setInteractionTool("design");
|
|
@@ -402,6 +491,11 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
402
491
|
return subscribeCanvasRendererMessages(({ cardId, iframe, identity, message: msg }) => {
|
|
403
492
|
if (msg.type === "renderer-hello")
|
|
404
493
|
return;
|
|
494
|
+
if (msg.type === "app-interaction-modifier") {
|
|
495
|
+
if (mode === "canvas")
|
|
496
|
+
setTemporaryAppInteraction(msg.held === true);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
405
499
|
if (isKeyboardShortcutMessage(msg, identity)) {
|
|
406
500
|
if (msg.phase === "keydown") {
|
|
407
501
|
const nextTool = canvasToolForCode(msg.code);
|
|
@@ -412,11 +506,14 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
412
506
|
}
|
|
413
507
|
const frame = { cardId, iframe };
|
|
414
508
|
const syncActiveFrameUrl = (url) => {
|
|
415
|
-
|
|
416
|
-
const activeCardId = getSelectedCardId() ?? getFocusedCardId() ?? getCanvasCards()[0]?.id;
|
|
417
|
-
if (activeCardId !== frame.cardId && getCanvasCards().length !== 1)
|
|
509
|
+
if (getCanvasCards().some((card) => card.id === frame.cardId && iterationId(card.content)))
|
|
418
510
|
return;
|
|
419
|
-
|
|
511
|
+
const activeCardId = getSelectedCardId() ?? getFocusedCardId() ?? getCanvasCards()[0]?.id;
|
|
512
|
+
if (activeCardId === frame.cardId || getCanvasCards().length === 1) {
|
|
513
|
+
activatedTargetRef.current = url;
|
|
514
|
+
window.history.replaceState(window.history.state, "", createNudgeUiEditorUrl(url));
|
|
515
|
+
}
|
|
516
|
+
updateCardUrl(frame.cardId, url);
|
|
420
517
|
};
|
|
421
518
|
if (isProjectionAppliedMessage(msg, identity)) {
|
|
422
519
|
recordCanvasProjectionApplied(frame.cardId, msg.revision);
|
|
@@ -425,10 +522,12 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
425
522
|
if (isOriginalPreviewActive())
|
|
426
523
|
return;
|
|
427
524
|
const frameDocument = frame.iframe.contentDocument;
|
|
428
|
-
|
|
429
|
-
|
|
525
|
+
const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
|
|
526
|
+
const draft = getDraftForCard(frame.cardId);
|
|
527
|
+
if (frameDocument && plan && draft && isCanvasCanonicalProjectionRevisionCurrent(frameDocument, msg.revision)) {
|
|
528
|
+
const attempt = beginPreviewAttempt(getCanvasPreviewDocument(frame.cardId), plan.draftRevision);
|
|
430
529
|
if (attempt) {
|
|
431
|
-
for (const change of
|
|
530
|
+
for (const change of draft.contents.changes) {
|
|
432
531
|
if (!isPreviewableChange(change))
|
|
433
532
|
continue;
|
|
434
533
|
const result = verifyManagedStyleProjection(change, null, frameDocument);
|
|
@@ -450,15 +549,21 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
450
549
|
return;
|
|
451
550
|
}
|
|
452
551
|
if (isStructuralProjectionReportMessage(msg, identity)) {
|
|
453
|
-
|
|
552
|
+
const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
|
|
553
|
+
if (plan)
|
|
554
|
+
recordCanvasStructuralProjectionReports(frame.cardId, msg.revision, msg.reports, plan);
|
|
454
555
|
return;
|
|
455
556
|
}
|
|
456
557
|
if (isRenderedInstanceProjectionReportMessage(msg, identity)) {
|
|
457
|
-
|
|
558
|
+
const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
|
|
559
|
+
if (plan)
|
|
560
|
+
recordCanvasRenderedInstanceProjectionReports(frame.cardId, msg.revision, msg.reports, plan);
|
|
458
561
|
return;
|
|
459
562
|
}
|
|
460
563
|
if (isTextProjectionReportMessage(msg, identity)) {
|
|
461
|
-
|
|
564
|
+
const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
|
|
565
|
+
if (plan)
|
|
566
|
+
recordCanvasTextProjectionReports(frame.cardId, msg.revision, msg.reports, plan);
|
|
462
567
|
return;
|
|
463
568
|
}
|
|
464
569
|
if (msg.type === "frame-ready") {
|
|
@@ -467,7 +572,7 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
467
572
|
// A card can finish loading after Space was pressed on the controller.
|
|
468
573
|
// Seed it with the current modifier state before its first pointer event.
|
|
469
574
|
sendPanModifier(frame.iframe, frame.cardId, spaceHeldRef.current);
|
|
470
|
-
sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen, rendererInteractionsEnabled);
|
|
575
|
+
sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen, rendererInteractionsEnabled, spaceHeldRef.current ? "drag" : rendererCursor);
|
|
471
576
|
sendBoardGestureState(frame.iframe, frame.cardId, presentation === "canvas");
|
|
472
577
|
sendLinkTargetState(frame.iframe, frame.cardId, linksOpenInCards);
|
|
473
578
|
}
|
|
@@ -481,6 +586,11 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
481
586
|
broadcastPanModifier(msg.spaceHeld);
|
|
482
587
|
return;
|
|
483
588
|
}
|
|
589
|
+
if (msg.type === "wheel-pan") {
|
|
590
|
+
if (presentation === "canvas" && interactionTool !== "select")
|
|
591
|
+
panCanvasByWheel(msg.deltaX, msg.deltaY);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
484
594
|
if (msg.type === "zoom") {
|
|
485
595
|
if (presentation !== "canvas")
|
|
486
596
|
return;
|
|
@@ -531,19 +641,33 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
531
641
|
}
|
|
532
642
|
if (msg.type !== "navigation-intent")
|
|
533
643
|
return;
|
|
644
|
+
if (getCanvasCards().some((card) => card.id === frame.cardId && iterationId(card.content)))
|
|
645
|
+
return;
|
|
534
646
|
const normalized = normalizeUrl(msg.url);
|
|
535
647
|
if (!normalized || normalized.origin !== window.location.origin)
|
|
536
648
|
return;
|
|
537
|
-
if (msg.openInCard === true) {
|
|
538
|
-
const card =
|
|
649
|
+
if (msg.openInCard === true && presentation === "canvas") {
|
|
650
|
+
const card = getCanvasCards().find((candidate) => {
|
|
651
|
+
const route = !iterationId(candidate.content) && normalizeUrl(contentSourceUrl(candidate.content));
|
|
652
|
+
return route && normalizedUrlKey(route) === normalizedUrlKey(normalized);
|
|
653
|
+
}) ?? addCanvasCard(msg.url);
|
|
539
654
|
selectCard(card.id);
|
|
655
|
+
const board = boardRef.current;
|
|
656
|
+
if (board)
|
|
657
|
+
focusCanvasCards([card.id], { width: board.clientWidth, height: board.clientHeight });
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if (msg.openInCard === true) {
|
|
661
|
+
// A delayed renderer message must not create a new frame after entering focus mode.
|
|
662
|
+
selectCard(frame.cardId);
|
|
663
|
+
activateIframeWorkspace(msg.url, { width: boardRef.current?.clientWidth ?? window.innerWidth, height: boardRef.current?.clientHeight ?? window.innerHeight }, { replaceActiveCard: true });
|
|
540
664
|
return;
|
|
541
665
|
}
|
|
542
666
|
const frameDocument = frame.iframe.contentDocument;
|
|
543
667
|
if (frameDocument)
|
|
544
668
|
disposeInlineTextEdit("route-disposed", frameDocument);
|
|
545
669
|
});
|
|
546
|
-
}, [broadcastPanModifier, endPanning, handleToolChange, inspectorOpen, linksOpenInCards, movePanning, presentation, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState, sendPanModifier, sketchAvailable, startPanning, zoomAtPointer]);
|
|
670
|
+
}, [broadcastPanModifier, endPanning, handleToolChange, interactionTool, mode, panCanvasByWheel, inspectorOpen, linksOpenInCards, movePanning, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState, sendPanModifier, sketchAvailable, startPanning, zoomAtPointer]);
|
|
547
671
|
useEffect(() => {
|
|
548
672
|
if (mode === "canvas" && !hasFitAllRan()) {
|
|
549
673
|
if (!fitAllScheduledRef.current) {
|
|
@@ -557,14 +681,16 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
557
681
|
}, [mode, cards.length, inspectorOpen, fitCanvasToBoard]);
|
|
558
682
|
useEffect(() => {
|
|
559
683
|
function onKeyDown(e) {
|
|
560
|
-
if (mode !== "canvas" || presentation !== "canvas" || sketchActive)
|
|
684
|
+
if (mode !== "canvas" || presentation !== "canvas" || sketchActive || interactionTool === "select")
|
|
561
685
|
return;
|
|
562
686
|
if ((e.key === "Delete" || e.key === "Backspace") && !isEditableEvent(e)) {
|
|
687
|
+
if (e.defaultPrevented || getSelectedElements().length > 0 || getCanvasCards().length < 2)
|
|
688
|
+
return;
|
|
563
689
|
const selectedCardId = getSelectedCardId();
|
|
564
690
|
if (!selectedCardId)
|
|
565
691
|
return;
|
|
566
692
|
e.preventDefault();
|
|
567
|
-
|
|
693
|
+
removeFrame(selectedCardId);
|
|
568
694
|
return;
|
|
569
695
|
}
|
|
570
696
|
if (e.code === "Space" && !e.repeat) {
|
|
@@ -580,7 +706,7 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
580
706
|
}
|
|
581
707
|
function onKeyUp(e) {
|
|
582
708
|
if (e.code === "Space") {
|
|
583
|
-
spaceHeldRef.current =
|
|
709
|
+
spaceHeldRef.current = false;
|
|
584
710
|
broadcastPanModifier(spaceHeldRef.current);
|
|
585
711
|
if (!panningRef.current) {
|
|
586
712
|
setBoardCursorClass(spaceHeldRef.current ? "is-grabbable" : "");
|
|
@@ -594,6 +720,15 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
594
720
|
window.removeEventListener("keyup", onKeyUp, true);
|
|
595
721
|
};
|
|
596
722
|
}, [broadcastPanModifier, interactionTool, mode, presentation, sketchActive]);
|
|
723
|
+
const handleBoardClick = useCallback((event) => {
|
|
724
|
+
if (presentation !== "canvas" || sketchActive || spaceHeldRef.current || panningRef.current)
|
|
725
|
+
return;
|
|
726
|
+
if (event.target === event.currentTarget
|
|
727
|
+
|| (event.target instanceof HTMLElement && event.target.dataset.test === "canvas-board-content")) {
|
|
728
|
+
setSelectedElement(null);
|
|
729
|
+
deselectCard();
|
|
730
|
+
}
|
|
731
|
+
}, [presentation, sketchActive]);
|
|
597
732
|
const handleBoardPointerDown = useCallback((e) => {
|
|
598
733
|
if (presentation !== "canvas" || sketchActive)
|
|
599
734
|
return;
|
|
@@ -618,22 +753,35 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
618
753
|
window.addEventListener("pointerup", onUp);
|
|
619
754
|
}, [endPanning, movePanning, presentation, sketchActive, startPanning]);
|
|
620
755
|
useEffect(() => {
|
|
621
|
-
if (mode !== "canvas" || presentation !== "canvas"
|
|
756
|
+
if (mode !== "canvas" || presentation !== "canvas")
|
|
622
757
|
return;
|
|
623
758
|
function handleGlobalWheel(e) {
|
|
624
|
-
|
|
759
|
+
const zoom = e.ctrlKey || e.metaKey;
|
|
760
|
+
const board = boardRef.current;
|
|
761
|
+
if (zoom && sketchActive)
|
|
762
|
+
return;
|
|
763
|
+
const path = e.composedPath();
|
|
764
|
+
// Comment pins overlay the board without being its DOM descendants.
|
|
765
|
+
const overCommentPin = path.some((node) => node instanceof Element && node.matches(".comment-pin"));
|
|
766
|
+
if (!zoom && (interactionTool === "select" || !board || (!path.includes(board) && !overCommentPin)))
|
|
625
767
|
return;
|
|
626
768
|
e.preventDefault();
|
|
627
769
|
e.stopPropagation();
|
|
628
|
-
|
|
770
|
+
if (zoom)
|
|
771
|
+
zoomAtPointer({ x: e.clientX, y: e.clientY }, e.deltaY);
|
|
772
|
+
else if (board) {
|
|
773
|
+
const delta = wheelPanDelta(e, { width: board.clientWidth, height: board.clientHeight });
|
|
774
|
+
panCanvasByWheel(delta.x, delta.y);
|
|
775
|
+
}
|
|
629
776
|
}
|
|
630
777
|
window.addEventListener("wheel", handleGlobalWheel, { capture: true, passive: false });
|
|
631
778
|
return () => window.removeEventListener("wheel", handleGlobalWheel, { capture: true });
|
|
632
|
-
}, [mode, presentation, sketchActive, zoomAtPointer]);
|
|
779
|
+
}, [mode, presentation, sketchActive, interactionTool, panCanvasByWheel, zoomAtPointer]);
|
|
780
|
+
const renderCard = (card) => (_jsx(CanvasCard, { card: card, presentation: presentation, presentationCard: card.id === presentationCardId, linkedToActive: linkedFrameIds.includes(card.id), onShowFocus: showFocus, onDuplicate: duplicateDesign, onCreateIteration: canvasIterationsEnabled ? createIteration : undefined, documentOwner: inspectorSession }, card.id));
|
|
633
781
|
if (mode !== "canvas")
|
|
634
782
|
return null;
|
|
635
|
-
return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "canvas-styles", children: WORKSPACE_STYLES }), _jsx("div", { className: `canvas-workspace${presentationTransitioning ? " is-presentation-transitioning" : ""}`, "data-test": "canvas-workspace", "data-presentation": presentation, style: { right: inspectorOpen ? "min(320px, 100vw)" : 0 }, children: _jsxs("div", { className: `canvas-workspace__board ${boardCursorClass}`.trim(), "data-test": "canvas-board", ref: boardRef, onPointerDown: handleBoardPointerDown, children: [!primaryUrl ? (_jsx("div", { className: "canvas-workspace__target-error", "data-test": "canvas-target-error", children: "This editor URL does not identify an application page." })) : null, inspectorOpen && presentation === "focus" ? (_jsx("div", { className: "canvas-workspace__focus-action", "data-test": "canvas-show-canvas", children: _jsxs(Button, { variant: "quiet", size: "default", type: "button", onClick: showCanvas, children: [_jsx(IconCornerLeftUp, { size: "var(--icon-size-small)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), "Canvas"] }) })) : null, _jsx("div", { className: "canvas-workspace__board-content", style: {
|
|
783
|
+
return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "canvas-styles", children: WORKSPACE_STYLES }), _jsx("div", { className: `canvas-workspace${presentationTransitioning ? " is-presentation-transitioning" : ""}`, "data-test": "canvas-workspace", "data-presentation": presentation, style: { right: inspectorOpen ? "min(320px, 100vw)" : 0 }, children: _jsxs("div", { className: `canvas-workspace__board ${boardCursorClass}${layoutTransitioning ? " is-layout-transitioning" : ""}`.trim(), "data-test": "canvas-board", ref: boardRef, style: { cursor: boardCursor }, onPointerDown: handleBoardPointerDown, onClick: handleBoardClick, children: [!primaryUrl ? (_jsx("div", { className: "canvas-workspace__target-error", "data-test": "canvas-target-error", children: "This editor URL does not identify an application page." })) : null, inspectorOpen && presentation === "focus" ? (_jsx("div", { className: "canvas-workspace__focus-action", "data-test": "canvas-show-canvas", children: _jsxs(Button, { variant: "quiet", size: "default", type: "button", onClick: showCanvas, children: [_jsx(IconCornerLeftUp, { size: "var(--icon-size-small)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), "Canvas"] }) })) : null, _jsx("div", { className: "canvas-workspace__board-content", style: {
|
|
636
784
|
transform: `translate(${camera.x}px, ${camera.y}px) scale(${camera.zoom})`,
|
|
637
785
|
transformOrigin: "0 0",
|
|
638
|
-
}, "data-test": "canvas-board-content", children: cards.map(
|
|
786
|
+
}, "data-test": "canvas-board-content", children: cards.map(renderCard) }), inspectorOpen && !sketchActive ? (_jsx(CanvasToolbar, { tool: interactionTool, sketchEnabled: sketchAvailable, onToolChange: handleToolChange })) : null] }) }), inspectorOpen && sketchActive ? (_jsx("div", { className: "canvas-toolbar__portal", "data-test": "canvas-toolbar-portal", style: { right: inspectorOpen ? "min(320px, 100vw)" : 0 }, children: _jsx(CanvasToolbar, { tool: interactionTool, sketchEnabled: sketchAvailable, onToolChange: handleToolChange }) })) : null] }));
|
|
639
787
|
}
|