nudge-ui 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -46
- package/dist/client.mjs +252 -54
- 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 +41 -32
- package/dist/hosts/astro/integration.d.ts +2 -2
- package/dist/hosts/astro/integration.js +9 -4
- package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
- package/dist/hosts/astro/markdownIdentity.js +81 -0
- package/dist/hosts/environment.d.ts +15 -0
- package/dist/hosts/environment.js +16 -0
- package/dist/hosts/next/manifest.js +1 -1
- package/dist/hosts/next/sidecar.js +12 -2
- package/dist/hosts/next/wrapper.d.ts +2 -0
- package/dist/hosts/next/wrapper.js +2 -1
- package/dist/hosts/projectBridge.d.ts +1 -0
- package/dist/hosts/projectBridge.js +1 -0
- package/dist/hosts/static/manifest.js +3 -1
- package/dist/hosts/static/server.js +4 -0
- package/dist/hosts/vite/vite.d.ts +4 -0
- package/dist/hosts/vite/vite.js +28 -5
- package/dist/inspector/agent/McpConnectionDialog.d.ts +3 -2
- package/dist/inspector/agent/McpConnectionDialog.js +23 -22
- package/dist/inspector/agent/activity.d.ts +18 -0
- package/dist/inspector/agent/activity.js +70 -0
- package/dist/inspector/agent/client.d.ts +8 -0
- package/dist/inspector/agent/client.js +65 -5
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +9 -2
- package/dist/inspector/agent/httpTransport.d.ts +6 -0
- package/dist/inspector/agent/httpTransport.js +50 -3
- package/dist/inspector/agent/protocol.d.ts +9 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +11 -1
- package/dist/inspector/agent/verification.js +53 -25
- 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 +133 -38
- package/dist/inspector/canvas/CanvasElementOverlay.js +200 -22
- 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 +243 -56
- 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 +34 -4
- package/dist/inspector/canvas/frameProtocol.js +9 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +5 -3
- package/dist/inspector/canvas/layout.d.ts +52 -0
- package/dist/inspector/canvas/layout.js +106 -0
- package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
- package/dist/inspector/canvas/linkEligibility.js +0 -29
- package/dist/inspector/canvas/projection.d.ts +15 -5
- package/dist/inspector/canvas/projection.js +69 -13
- package/dist/inspector/canvas/rendererBootstrap.js +45 -4
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +102 -45
- 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 +6 -5
- package/dist/inspector/changes/changesLog.js +40 -32
- package/dist/inspector/changes/draftChanges.d.ts +56 -0
- package/dist/inspector/changes/draftChanges.js +300 -0
- package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
- package/dist/inspector/changes/previewDiagnostics.js +11 -25
- package/dist/inspector/clientManifest.d.ts +3 -1
- package/dist/inspector/clientManifest.js +1 -0
- 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 +47 -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/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 +26 -35
- 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 +173 -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.d.ts +10 -0
- package/dist/inspector/overlay/DomNavigator.js +46 -0
- package/dist/inspector/overlay/InspectorOverlay.js +6 -2
- package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
- package/dist/inspector/overlay/clickPolicy.js +34 -3
- package/dist/inspector/overlay/elementSelector.d.ts +1 -1
- package/dist/inspector/overlay/elementSelector.js +18 -7
- package/dist/inspector/overlay/interactionStyles.js +3 -1
- package/dist/inspector/overlay/spacingGestures.d.ts +15 -3
- package/dist/inspector/overlay/spacingGestures.js +88 -13
- 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 +2 -1
- package/dist/inspector/projection/renderedInstance.js +9 -5
- package/dist/inspector/projection/structuralProjection.d.ts +2 -1
- package/dist/inspector/projection/structuralProjection.js +19 -15
- package/dist/inspector/projection/textProjection.d.ts +2 -1
- package/dist/inspector/projection/textProjection.js +8 -4
- package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
- package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
- package/dist/inspector/prompt/clipboardHandoff.js +92 -143
- package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
- package/dist/inspector/prompt/generatePrompt.js +17 -16
- package/dist/inspector/runtime/runtimeConfig.d.ts +10 -1
- package/dist/inspector/runtime/runtimeConfig.js +24 -0
- package/dist/inspector/selection/hierarchy.d.ts +6 -0
- package/dist/inspector/selection/hierarchy.js +8 -0
- package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
- package/dist/inspector/settings/SettingsDialog.js +22 -9
- 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 +47 -27
- package/dist/inspector/shell/CopyPromptButton.js +146 -90
- package/dist/inspector/shell/EmptyState.js +9 -8
- package/dist/inspector/shell/InspectorShell.js +14 -25
- 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/sketch/store.js +48 -23
- 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 +16 -9
- 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 +4 -4
- package/dist/inspector/styleEditors/nudgeValue.js +41 -11
- package/dist/inspector/tokens/TokenField.d.ts +7 -0
- package/dist/inspector/tokens/TokenField.js +49 -22
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- package/dist/inspector/tokens/TokensPanel.js +2 -1
- package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
- package/dist/inspector/tokens/dragPointerLock.js +70 -0
- package/dist/inspector/tokens/editActions.d.ts +1 -1
- package/dist/inspector/tokens/editActions.js +1 -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/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- 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 +6 -0
- package/dist/inspector/ui/dragCursor.js +13 -0
- package/dist/inspector/ui/portalContainer.d.ts +2 -0
- package/dist/inspector/ui/portalContainer.js +6 -0
- package/dist/inspector/ui/styles.js +34 -24
- 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/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 +560 -299
- 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.d.ts +20 -1
- package/dist/transport/editor.js +44 -11
- package/dist/transport/routes.d.ts +6 -1
- package/dist/transport/routes.js +6 -1
- package/package.json +6 -2
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -43
- package/dist/inspector/changes/workspaceChanges.js +0 -239
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { isFrameContent } from "./frameContent.js";
|
|
2
|
+
import { clearComments } from "../comments/store.js";
|
|
3
|
+
import { documentTarget, targetFromKey } from "../drafts/model.js";
|
|
4
|
+
import { canEditDraft, getActiveDraftChanges, isDraftLocked } from "../changes/draftChanges.js";
|
|
5
|
+
import { getFrameGroups, isFrameGroup, getCanvasCards, getFocusedCardId, getSelectedCardId, hydrateCanvasStore, removeCanvasCard, } from "./canvasStore.js";
|
|
6
|
+
import { getCanvasMode, getCanvasPresentation, getBoardCamera, setBoardCamera, } from "./viewStore.js";
|
|
5
7
|
import { applyRules } from "../projection/managedStylesheet.js";
|
|
6
|
-
import {
|
|
8
|
+
import { clearActiveDraft } from "../changes/changesLog.js";
|
|
7
9
|
import { removeManagedSheet } from "../projection/managedStylesheet.js";
|
|
8
10
|
import { setSelectedElement } from "../selection/selectionStore.js";
|
|
9
11
|
import { canWriteWorkspace } from "./workspaceLease.js";
|
|
10
|
-
import {
|
|
12
|
+
import { resetStructuralDeleteProjection, } from "../projection/structuralProjection.js";
|
|
11
13
|
import { projectToAllReadyCards } from "./projection.js";
|
|
12
14
|
import { getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
|
|
13
15
|
import { clearClipboardHandoff, getClipboardHandoffSnapshot, hydrateClipboardHandoff, isClipboardHandoffSnapshot, } from "../prompt/clipboardHandoff.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const SCHEMA_VERSION = 12;
|
|
16
|
+
import { activateDraftForCard, clearDrafts, persistDrafts } from "../drafts/store.js";
|
|
17
|
+
const SCHEMA_VERSION = 17;
|
|
17
18
|
const STORAGE_PREFIX = "nudge-ui";
|
|
18
19
|
function projectId() {
|
|
19
20
|
return getNudgeUiRuntimeConfig().projectId;
|
|
@@ -21,9 +22,6 @@ function projectId() {
|
|
|
21
22
|
function isFiniteNumber(value) {
|
|
22
23
|
return typeof value === "number" && Number.isFinite(value);
|
|
23
24
|
}
|
|
24
|
-
function isRecord(value) {
|
|
25
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26
|
-
}
|
|
27
25
|
function isSameOriginUrl(value) {
|
|
28
26
|
if (typeof value !== "string")
|
|
29
27
|
return false;
|
|
@@ -34,95 +32,32 @@ function isSameOriginUrl(value) {
|
|
|
34
32
|
return false;
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
|
-
function hasOnlyKeys(value, allowed) {
|
|
38
|
-
return Object.keys(value).every((key) => allowed.includes(key));
|
|
39
|
-
}
|
|
40
|
-
function isSerializableComparisonGroup(value) {
|
|
41
|
-
if (!isRecord(value)
|
|
42
|
-
|| !hasOnlyKeys(value, ["id", "label", "owner", "agentId", "cardIds", "routes"])
|
|
43
|
-
|| typeof value.id !== "string"
|
|
44
|
-
|| value.id.length === 0
|
|
45
|
-
|| value.id.length > 256
|
|
46
|
-
|| typeof value.label !== "string"
|
|
47
|
-
|| value.label.length === 0
|
|
48
|
-
|| value.label.length > 160
|
|
49
|
-
|| value.owner !== "agent"
|
|
50
|
-
|| typeof value.agentId !== "string"
|
|
51
|
-
|| value.agentId.length === 0
|
|
52
|
-
|| value.agentId.length > 256
|
|
53
|
-
|| !Array.isArray(value.cardIds)
|
|
54
|
-
|| value.cardIds.length === 0
|
|
55
|
-
|| value.cardIds.length > 64
|
|
56
|
-
|| !Array.isArray(value.routes)
|
|
57
|
-
|| value.routes.length === 0
|
|
58
|
-
|| value.routes.length > 64) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
if (!value.cardIds.every((cardId) => typeof cardId === "string" && cardId.length > 0 && cardId.length <= 256)) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
return value.routes.every((route) => {
|
|
65
|
-
if (!isRecord(route)
|
|
66
|
-
|| !hasOnlyKeys(route, ["url", "title", "label"])
|
|
67
|
-
|| !isSameOriginUrl(route.url)
|
|
68
|
-
|| (route.title !== undefined && route.title !== null
|
|
69
|
-
&& (typeof route.title !== "string" || route.title.length > 512))
|
|
70
|
-
|| (route.label !== undefined
|
|
71
|
-
&& (typeof route.label !== "string" || route.label.length > 160))) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
return true;
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
35
|
function storageKey(projectId) {
|
|
78
36
|
return `${STORAGE_PREFIX}:${projectId}:v${SCHEMA_VERSION}`;
|
|
79
37
|
}
|
|
80
38
|
function buildSession() {
|
|
81
|
-
const workspace = getWorkspaceChanges();
|
|
82
|
-
const changes = workspace.changes;
|
|
83
|
-
const serializableChanges = [];
|
|
84
|
-
for (const change of changes) {
|
|
85
|
-
const serialized = serializeChange(change);
|
|
86
|
-
// serializeChange already fails closed, but re-validate here so a future
|
|
87
|
-
// serializer skew can never poison the whole durable session.
|
|
88
|
-
if (serialized && isSerializableChange(serialized))
|
|
89
|
-
serializableChanges.push(serialized);
|
|
90
|
-
}
|
|
91
39
|
const cards = getCanvasCards();
|
|
92
|
-
const comparisonGroups = getCanvasComparisonGroups();
|
|
93
40
|
const camera = getBoardCamera();
|
|
94
41
|
const mode = getCanvasMode();
|
|
95
42
|
return {
|
|
96
43
|
schemaVersion: SCHEMA_VERSION,
|
|
97
44
|
projectId: projectId(),
|
|
98
45
|
mode,
|
|
46
|
+
presentation: getCanvasPresentation(),
|
|
47
|
+
groups: getFrameGroups(),
|
|
99
48
|
inspectUrl: window.location.href,
|
|
100
49
|
cards: cards.map((c) => ({
|
|
101
50
|
id: c.id,
|
|
102
|
-
url: c.url,
|
|
51
|
+
content: c.content.kind === "route" ? { kind: "route", url: c.content.url } : { ...c.content },
|
|
103
52
|
title: c.title,
|
|
53
|
+
...(c.groupId ? { groupId: c.groupId } : {}),
|
|
104
54
|
x: c.x,
|
|
105
55
|
y: c.y,
|
|
106
56
|
width: c.width,
|
|
107
57
|
height: c.height,
|
|
108
|
-
...(c.comparisonGroupId ? { comparisonGroupId: c.comparisonGroupId } : {}),
|
|
109
|
-
})),
|
|
110
|
-
comparisonGroups: comparisonGroups.map((group) => ({
|
|
111
|
-
id: group.id,
|
|
112
|
-
label: group.label,
|
|
113
|
-
owner: "agent",
|
|
114
|
-
agentId: group.agentId,
|
|
115
|
-
cardIds: [...group.cardIds],
|
|
116
|
-
routes: group.routes.map((route) => ({
|
|
117
|
-
url: route.url,
|
|
118
|
-
...(route.title === undefined ? {} : { title: route.title }),
|
|
119
|
-
...(route.label === undefined ? {} : { label: route.label }),
|
|
120
|
-
})),
|
|
121
58
|
})),
|
|
122
59
|
camera: { x: camera.x, y: camera.y, zoom: camera.zoom },
|
|
123
60
|
focusedCardId: getFocusedCardId(),
|
|
124
|
-
changes: serializableChanges,
|
|
125
|
-
structuralChanges: workspace.structuralChanges.map((change) => ({ ...change })),
|
|
126
61
|
clipboardHandoff: getClipboardHandoffSnapshot(),
|
|
127
62
|
};
|
|
128
63
|
}
|
|
@@ -142,6 +77,7 @@ function persistSessionUnchecked() {
|
|
|
142
77
|
if (!projectId())
|
|
143
78
|
return false;
|
|
144
79
|
try {
|
|
80
|
+
persistDrafts();
|
|
145
81
|
const session = buildSession();
|
|
146
82
|
localStorage.setItem(storageKey(projectId()), JSON.stringify(session));
|
|
147
83
|
return true;
|
|
@@ -203,7 +139,7 @@ export function hydrateSession() {
|
|
|
203
139
|
const candidate = card;
|
|
204
140
|
if (!card || typeof card !== "object" ||
|
|
205
141
|
typeof candidate.id !== "string" ||
|
|
206
|
-
!
|
|
142
|
+
!isFrameContent(candidate.content, window.location.origin) ||
|
|
207
143
|
!isFiniteNumber(candidate.x) ||
|
|
208
144
|
!isFiniteNumber(candidate.y) ||
|
|
209
145
|
!isFiniteNumber(candidate.width) ||
|
|
@@ -216,70 +152,20 @@ export function hydrateSession() {
|
|
|
216
152
|
const c = card;
|
|
217
153
|
serializableCards.push({
|
|
218
154
|
id: c.id,
|
|
219
|
-
|
|
155
|
+
content: c.content,
|
|
220
156
|
title: typeof c.title === "string" ? c.title : null,
|
|
157
|
+
...(typeof c.groupId === "string" && c.groupId.length <= 256 ? { groupId: c.groupId } : {}),
|
|
221
158
|
x: c.x,
|
|
222
159
|
y: c.y,
|
|
223
160
|
width: c.width,
|
|
224
161
|
height: c.height,
|
|
225
|
-
...(typeof c.comparisonGroupId === "string"
|
|
226
|
-
? { comparisonGroupId: c.comparisonGroupId }
|
|
227
|
-
: {}),
|
|
228
162
|
});
|
|
229
163
|
}
|
|
230
164
|
if (new Set(serializableCards.map((card) => card.id)).size !== serializableCards.length) {
|
|
231
165
|
safeDiscard();
|
|
232
166
|
return { restored: false, changeCount: 0 };
|
|
233
167
|
}
|
|
234
|
-
const comparisonGroupsRaw = s.comparisonGroups;
|
|
235
|
-
if (!Array.isArray(comparisonGroupsRaw) || comparisonGroupsRaw.length > 128) {
|
|
236
|
-
safeDiscard();
|
|
237
|
-
return { restored: false, changeCount: 0 };
|
|
238
|
-
}
|
|
239
|
-
const serializableComparisonGroups = [];
|
|
240
|
-
for (const group of comparisonGroupsRaw) {
|
|
241
|
-
if (!isSerializableComparisonGroup(group)) {
|
|
242
|
-
safeDiscard();
|
|
243
|
-
return { restored: false, changeCount: 0 };
|
|
244
|
-
}
|
|
245
|
-
serializableComparisonGroups.push({
|
|
246
|
-
id: group.id,
|
|
247
|
-
label: group.label,
|
|
248
|
-
owner: "agent",
|
|
249
|
-
agentId: group.agentId,
|
|
250
|
-
cardIds: [...group.cardIds],
|
|
251
|
-
routes: group.routes.map((route) => ({ ...route })),
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
if (new Set(serializableComparisonGroups.map((group) => group.id)).size
|
|
255
|
-
!== serializableComparisonGroups.length) {
|
|
256
|
-
safeDiscard();
|
|
257
|
-
return { restored: false, changeCount: 0 };
|
|
258
|
-
}
|
|
259
168
|
const cardsById = new Map(serializableCards.map((card) => [card.id, card]));
|
|
260
|
-
const groupedCardIds = new Set();
|
|
261
|
-
for (const group of serializableComparisonGroups) {
|
|
262
|
-
if (group.cardIds.length !== group.routes.length) {
|
|
263
|
-
safeDiscard();
|
|
264
|
-
return { restored: false, changeCount: 0 };
|
|
265
|
-
}
|
|
266
|
-
for (const [routeIndex, cardId] of group.cardIds.entries()) {
|
|
267
|
-
const card = cardsById.get(cardId);
|
|
268
|
-
const route = group.routes[routeIndex];
|
|
269
|
-
if (!card || !route || card.comparisonGroupId !== group.id || groupedCardIds.has(cardId)
|
|
270
|
-
|| card.url !== route.url) {
|
|
271
|
-
safeDiscard();
|
|
272
|
-
return { restored: false, changeCount: 0 };
|
|
273
|
-
}
|
|
274
|
-
groupedCardIds.add(cardId);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
for (const card of serializableCards) {
|
|
278
|
-
if (card.comparisonGroupId && !serializableComparisonGroups.some((group) => group.id === card.comparisonGroupId)) {
|
|
279
|
-
safeDiscard();
|
|
280
|
-
return { restored: false, changeCount: 0 };
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
169
|
const cameraRaw = s.camera;
|
|
284
170
|
if (!cameraRaw || typeof cameraRaw !== "object" ||
|
|
285
171
|
!isFiniteNumber(cameraRaw.x) ||
|
|
@@ -288,35 +174,9 @@ export function hydrateSession() {
|
|
|
288
174
|
safeDiscard();
|
|
289
175
|
return { restored: false, changeCount: 0 };
|
|
290
176
|
}
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
return { restored: false, changeCount: 0 };
|
|
295
|
-
}
|
|
296
|
-
const deserializedChanges = [];
|
|
297
|
-
const textChangeIds = new Set();
|
|
298
|
-
for (const c of changesRaw) {
|
|
299
|
-
if (!isSerializableChange(c)) {
|
|
300
|
-
safeDiscard();
|
|
301
|
-
return { restored: false, changeCount: 0 };
|
|
302
|
-
}
|
|
303
|
-
if (c.kind === "text-content") {
|
|
304
|
-
if (textChangeIds.has(c.id)) {
|
|
305
|
-
safeDiscard();
|
|
306
|
-
return { restored: false, changeCount: 0 };
|
|
307
|
-
}
|
|
308
|
-
textChangeIds.add(c.id);
|
|
309
|
-
}
|
|
310
|
-
try {
|
|
311
|
-
deserializedChanges.push(deserializeChange(c));
|
|
312
|
-
}
|
|
313
|
-
catch {
|
|
314
|
-
safeDiscard();
|
|
315
|
-
return { restored: false, changeCount: 0 };
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
const structuralChanges = s.structuralChanges;
|
|
319
|
-
if (!Array.isArray(structuralChanges) || !structuralChanges.every(isStructuralChange)) {
|
|
177
|
+
const presentation = s.presentation ?? "focus";
|
|
178
|
+
const groups = Array.isArray(s.groups) ? s.groups.filter((group) => group?.kind !== "linked") : s.groups ?? [];
|
|
179
|
+
if ((presentation !== "focus" && presentation !== "canvas") || !Array.isArray(groups) || !groups.every(isFrameGroup)) {
|
|
320
180
|
safeDiscard();
|
|
321
181
|
return { restored: false, changeCount: 0 };
|
|
322
182
|
}
|
|
@@ -339,10 +199,7 @@ export function hydrateSession() {
|
|
|
339
199
|
safeDiscard();
|
|
340
200
|
return { restored: false, changeCount: 0 };
|
|
341
201
|
}
|
|
342
|
-
hydrateCanvasStore(s.mode, serializableCards, camera,
|
|
343
|
-
// Structural intent and instance evidence become visible atomically. The
|
|
344
|
-
// projection layer preserves structural-first document application order.
|
|
345
|
-
loadWorkspaceChanges(deserializedChanges, structuralChanges);
|
|
202
|
+
hydrateCanvasStore(s.mode, serializableCards, camera, focusedCardId, presentation, groups);
|
|
346
203
|
hydrateClipboardHandoff(clipboardHandoff);
|
|
347
204
|
// A different URL means the user intentionally navigated while Inspect was
|
|
348
205
|
// active. Keep the durable edits, but adopt the new route instead of
|
|
@@ -350,8 +207,7 @@ export function hydrateSession() {
|
|
|
350
207
|
// match and restoration remains unchanged.
|
|
351
208
|
if (inspectRouteChanged)
|
|
352
209
|
persistSessionUnchecked();
|
|
353
|
-
|
|
354
|
-
return { restored: true, changeCount: deserializedChanges.length + structuralChanges.length };
|
|
210
|
+
return { restored: true, changeCount: 0 };
|
|
355
211
|
}
|
|
356
212
|
function safeDiscard() {
|
|
357
213
|
try {
|
|
@@ -361,7 +217,22 @@ function safeDiscard() {
|
|
|
361
217
|
// ignore
|
|
362
218
|
}
|
|
363
219
|
}
|
|
220
|
+
/** Clears the active frame's edit draft while preserving canvas geometry and other drafts. */
|
|
221
|
+
export function clearSelectedFrameChanges() {
|
|
222
|
+
if (!canEditDraft())
|
|
223
|
+
return;
|
|
224
|
+
const cardId = getSelectedCardId() ?? getFocusedCardId();
|
|
225
|
+
if (cardId)
|
|
226
|
+
activateDraftForCard(cardId);
|
|
227
|
+
clearComments(targetFromKey(getActiveDraftChanges().draftId) ?? documentTarget(window.location.href));
|
|
228
|
+
clearActiveDraft();
|
|
229
|
+
setSelectedElement(null);
|
|
230
|
+
projectToAllReadyCards();
|
|
231
|
+
scheduleAutoSave();
|
|
232
|
+
}
|
|
364
233
|
export function clearSession() {
|
|
234
|
+
if (isDraftLocked())
|
|
235
|
+
return;
|
|
365
236
|
if (!canWriteWorkspace())
|
|
366
237
|
return;
|
|
367
238
|
try {
|
|
@@ -370,8 +241,10 @@ export function clearSession() {
|
|
|
370
241
|
catch {
|
|
371
242
|
// ignore
|
|
372
243
|
}
|
|
373
|
-
|
|
244
|
+
clearComments();
|
|
245
|
+
clearActiveDraft();
|
|
374
246
|
clearClipboardHandoff();
|
|
247
|
+
clearDrafts();
|
|
375
248
|
resetStructuralDeleteProjection();
|
|
376
249
|
removeManagedSheet();
|
|
377
250
|
setSelectedElement(null);
|
|
@@ -415,27 +288,14 @@ export function scheduleAutoSave() {
|
|
|
415
288
|
autoSaveDirty = false;
|
|
416
289
|
}, AUTOSAVE_DEBOUNCE_MS);
|
|
417
290
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
* refresh always restores the workspace, even inside the edit-autosave
|
|
421
|
-
* debounce window. Edit autosave stays coalesced behind the trailing timer.
|
|
422
|
-
*/
|
|
423
|
-
export function scheduleCanvasSave() {
|
|
424
|
-
if (!autoSaveEnabled)
|
|
425
|
-
return;
|
|
426
|
-
if (persistSession())
|
|
427
|
-
autoSaveDirty = false;
|
|
428
|
-
}
|
|
429
|
-
function flushAutoSave() {
|
|
291
|
+
export function scheduleCanvasSave() { scheduleAutoSave(); }
|
|
292
|
+
export function flushAutoSave() {
|
|
430
293
|
if (autosaveTimer !== null) {
|
|
431
294
|
clearTimeout(autosaveTimer);
|
|
432
295
|
autosaveTimer = null;
|
|
433
296
|
}
|
|
434
297
|
if (!autoSaveDirty)
|
|
435
298
|
return;
|
|
436
|
-
// Keep the normal lease gate on the unload path. The controller registers
|
|
437
|
-
// this listener before releaseLease, so a current owner can flush a first
|
|
438
|
-
// write without allowing a stale tab to overwrite the new owner's session.
|
|
439
299
|
if (persistSession())
|
|
440
300
|
autoSaveDirty = false;
|
|
441
301
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { draftIdForCard } from "../drafts/store.js";
|
|
1
2
|
import { isPreviewableChange, isTokenChange, } from "../changes/changesLog.js";
|
|
2
3
|
import { changeKey } from "../changes/model.js";
|
|
3
4
|
import { beginPreviewAttempt, clearPreviewDiagnostics, getHostPreviewDocument, notifyPreviewDiagnostics, publishPreviewDiagnostic, } from "../changes/previewDiagnostics.js";
|
|
4
|
-
import {
|
|
5
|
+
import { getActiveDraftChanges } from "../changes/draftChanges.js";
|
|
5
6
|
import { getCanvasPreviewDocument, getRegisteredFrames } from "./projection.js";
|
|
6
7
|
import { findCanvasFrameBySource, PROJECT_ID, WORKSPACE_ID } from "./projection.js";
|
|
7
|
-
import { getCanvasCards
|
|
8
|
+
import { getCanvasCards } from "./canvasStore.js";
|
|
9
|
+
import { getCanvasMode } from "./viewStore.js";
|
|
8
10
|
import { isRendererMessageFor } from "./frameProtocol.js";
|
|
9
11
|
import { getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
|
|
10
12
|
const VERIFICATION_TIMEOUT_MS = 5000;
|
|
@@ -82,6 +84,8 @@ function getPreviewDocuments() {
|
|
|
82
84
|
const documents = getCanvasMode() === "canvas" ? [] : [getHostPreviewDocument()];
|
|
83
85
|
const frames = getRegisteredFrames();
|
|
84
86
|
for (const card of getCanvasCards()) {
|
|
87
|
+
if (draftIdForCard(card.id) && draftIdForCard(card.id) !== getActiveDraftChanges().draftId)
|
|
88
|
+
continue;
|
|
85
89
|
if (frames.has(card.id))
|
|
86
90
|
documents.push(getCanvasPreviewDocument(card.id));
|
|
87
91
|
}
|
|
@@ -89,7 +93,7 @@ function getPreviewDocuments() {
|
|
|
89
93
|
}
|
|
90
94
|
function beginPreviewAttempts() {
|
|
91
95
|
const attempts = new Map();
|
|
92
|
-
const revision =
|
|
96
|
+
const revision = getActiveDraftChanges().revision;
|
|
93
97
|
for (const document of getPreviewDocuments()) {
|
|
94
98
|
const attempt = beginPreviewAttempt(document, revision);
|
|
95
99
|
if (attempt)
|
|
@@ -115,6 +119,8 @@ function gatherMatchEvidence(changes) {
|
|
|
115
119
|
const frames = getRegisteredFrames();
|
|
116
120
|
const cards = getCanvasCards();
|
|
117
121
|
for (const card of cards) {
|
|
122
|
+
if (draftIdForCard(card.id) && draftIdForCard(card.id) !== getActiveDraftChanges().draftId)
|
|
123
|
+
continue;
|
|
118
124
|
const frame = frames.get(card.id);
|
|
119
125
|
if (!frame || !frame.contentWindow)
|
|
120
126
|
continue;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
import { type CanvasCard } from "./canvasStore.js";
|
|
3
|
+
import { type CanvasCamera, type CanvasPresentation } from "./viewStore.js";
|
|
4
|
+
/** Animates frame creation and layout changes without delaying direct manipulation. */
|
|
5
|
+
export declare function useCanvasLayoutAnimation(boardRef: RefObject<HTMLDivElement | null>, cards: readonly CanvasCard[], camera: CanvasCamera, presentation: CanvasPresentation): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { iterationId } from "./frameContent.js";
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef } from "react";
|
|
3
|
+
import {} from "./canvasStore.js";
|
|
4
|
+
import { setCanvasLayoutTransitioning } from "./viewStore.js";
|
|
5
|
+
const SLIDE_OPTIONS = { duration: 300, easing: "ease-out" };
|
|
6
|
+
const CAMERA_OPTIONS = { duration: 150, easing: "ease-out" };
|
|
7
|
+
/** Animates frame creation and layout changes without delaying direct manipulation. */
|
|
8
|
+
export function useCanvasLayoutAnimation(boardRef, cards, camera, presentation) {
|
|
9
|
+
const previous = useRef({ cards, camera, presentation });
|
|
10
|
+
const animations = useRef([]);
|
|
11
|
+
const generation = useRef(0);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const board = boardRef.current;
|
|
14
|
+
const cancel = () => {
|
|
15
|
+
generation.current += 1;
|
|
16
|
+
setCanvasLayoutTransitioning(false);
|
|
17
|
+
for (const animation of animations.current)
|
|
18
|
+
animation.cancel();
|
|
19
|
+
animations.current = [];
|
|
20
|
+
};
|
|
21
|
+
const cancelWheel = (event) => {
|
|
22
|
+
if (board && event.composedPath().includes(board))
|
|
23
|
+
cancel();
|
|
24
|
+
};
|
|
25
|
+
board?.addEventListener("pointerdown", cancel, true);
|
|
26
|
+
window.addEventListener("wheel", cancelWheel, true);
|
|
27
|
+
return () => {
|
|
28
|
+
cancel();
|
|
29
|
+
board?.removeEventListener("pointerdown", cancel, true);
|
|
30
|
+
window.removeEventListener("wheel", cancelWheel, true);
|
|
31
|
+
};
|
|
32
|
+
}, [boardRef]);
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
const before = previous.current;
|
|
35
|
+
previous.current = { cards, camera, presentation };
|
|
36
|
+
const oldCards = new Map(before.cards.map((card) => [card.id, card]));
|
|
37
|
+
const layoutChanged = cards.some((card) => {
|
|
38
|
+
const old = oldCards.get(card.id);
|
|
39
|
+
return (!old && card.animateEntrance !== false && (card.entrance === "linked" || iterationId(card.content))) || (old && iterationId(card.content) && iterationId(old.content) !== iterationId(card.content));
|
|
40
|
+
});
|
|
41
|
+
const cameraChanged = camera.x !== before.camera.x || camera.y !== before.camera.y || camera.zoom !== before.camera.zoom;
|
|
42
|
+
const shouldAnimate = (layoutChanged || (cameraChanged && animations.current.length > 0))
|
|
43
|
+
&& presentation === "canvas" && before.presentation === "canvas"
|
|
44
|
+
&& !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
45
|
+
const board = boardRef.current;
|
|
46
|
+
const animate = (element, frames, options = SLIDE_OPTIONS) => {
|
|
47
|
+
if (shouldAnimate && typeof element.animate === "function") {
|
|
48
|
+
animations.current.push(element.animate(frames, options));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
if (shouldAnimate) {
|
|
52
|
+
generation.current += 1;
|
|
53
|
+
setCanvasLayoutTransitioning(true);
|
|
54
|
+
if (layoutChanged) {
|
|
55
|
+
for (const animation of animations.current)
|
|
56
|
+
animation.cancel();
|
|
57
|
+
animations.current = [];
|
|
58
|
+
}
|
|
59
|
+
for (const element of board?.querySelectorAll(".canvas-card") ?? []) {
|
|
60
|
+
const card = cards.find((item) => item.id === element.dataset.cardId);
|
|
61
|
+
if (!card)
|
|
62
|
+
continue;
|
|
63
|
+
const old = oldCards.get(card.id);
|
|
64
|
+
if (!old) {
|
|
65
|
+
if (card.animateEntrance === false)
|
|
66
|
+
continue;
|
|
67
|
+
const slideRight = Boolean(card.entrance === "linked");
|
|
68
|
+
animate(element, [
|
|
69
|
+
{ left: `${card.x - (slideRight ? 200 : 0)}px`, top: `${card.y - (slideRight ? 0 : 200)}px`, opacity: 0 },
|
|
70
|
+
{ left: `${card.x}px`, top: `${card.y}px`, opacity: 1 },
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
else if (old.x !== card.x || old.y !== card.y) {
|
|
74
|
+
animate(element, [{ left: `${old.x}px`, top: `${old.y}px` }, { left: `${card.x}px`, top: `${card.y}px` }]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const content = board?.querySelector(".canvas-workspace__board-content");
|
|
78
|
+
if (content && cameraChanged) {
|
|
79
|
+
const current = animations.current.find((animation) => animation.playState === "running"
|
|
80
|
+
&& animation.effect?.target === content);
|
|
81
|
+
const from = current ? getComputedStyle(content).transform
|
|
82
|
+
: `translate(${before.camera.x}px, ${before.camera.y}px) scale(${before.camera.zoom})`;
|
|
83
|
+
current?.cancel();
|
|
84
|
+
animate(content, [
|
|
85
|
+
{ transform: from },
|
|
86
|
+
{ transform: `translate(${camera.x}px, ${camera.y}px) scale(${camera.zoom})` },
|
|
87
|
+
], CAMERA_OPTIONS);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (shouldAnimate) {
|
|
91
|
+
const current = generation.current;
|
|
92
|
+
void Promise.all(animations.current.map((animation) => animation.finished.catch(() => undefined))).then(() => {
|
|
93
|
+
if (generation.current !== current)
|
|
94
|
+
return;
|
|
95
|
+
animations.current = [];
|
|
96
|
+
setCanvasLayoutTransitioning(false);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else if (presentation !== before.presentation) {
|
|
100
|
+
generation.current += 1;
|
|
101
|
+
for (const animation of animations.current)
|
|
102
|
+
animation.cancel();
|
|
103
|
+
animations.current = [];
|
|
104
|
+
setCanvasLayoutTransitioning(false);
|
|
105
|
+
}
|
|
106
|
+
}, [boardRef, cards, camera, presentation]);
|
|
107
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type CanvasMode = "inspect" | "canvas";
|
|
2
|
+
export type CanvasPresentation = "focus" | "canvas";
|
|
3
|
+
export interface CanvasCamera {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
zoom: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const MIN_CAMERA_ZOOM = 0.25;
|
|
9
|
+
export declare const MAX_CAMERA_ZOOM = 3;
|
|
10
|
+
export declare const DEFAULT_CAMERA: CanvasCamera;
|
|
11
|
+
export declare function subscribeCanvasView(listener: () => void): () => void;
|
|
12
|
+
export declare function getCanvasMode(): CanvasMode;
|
|
13
|
+
export declare function setCanvasMode(next: CanvasMode): void;
|
|
14
|
+
export declare function getCanvasPresentation(): CanvasPresentation;
|
|
15
|
+
export declare function setCanvasPresentation(next: CanvasPresentation): void;
|
|
16
|
+
/** Holding A temporarily enables app interaction without changing the selected tool. */
|
|
17
|
+
export declare function setTemporaryAppInteraction(value: boolean): void;
|
|
18
|
+
/** Element interactions pause while frame and camera geometry are animated. */
|
|
19
|
+
export declare function setCanvasLayoutTransitioning(value: boolean): void;
|
|
20
|
+
export declare function getCanvasLayoutTransitioning(): boolean;
|
|
21
|
+
export declare function getBoardCamera(): CanvasCamera;
|
|
22
|
+
export declare function setBoardCamera(next: CanvasCamera): void;
|
|
23
|
+
export declare function updateBoardCamera(partial: Partial<CanvasCamera>): void;
|
|
24
|
+
/** Moves the camera to frame content; the initial fit-all then no longer runs. */
|
|
25
|
+
export declare function placeBoardCamera(next: CanvasCamera): void;
|
|
26
|
+
export declare function hasFitAllRan(): boolean;
|
|
27
|
+
export declare function resetFitAllFlag(): void;
|
|
28
|
+
export declare function restoreCanvasView(next: {
|
|
29
|
+
mode: CanvasMode;
|
|
30
|
+
presentation: CanvasPresentation;
|
|
31
|
+
camera: CanvasCamera;
|
|
32
|
+
}): void;
|
|
33
|
+
export declare function useCanvasMode(): CanvasMode;
|
|
34
|
+
export declare function useCanvasPresentation(): CanvasPresentation;
|
|
35
|
+
export declare function useCanvasPresentationTransitioning(): boolean;
|
|
36
|
+
export declare function useTemporaryAppInteraction(): boolean;
|
|
37
|
+
export declare function useCanvasLayoutTransitioning(): boolean;
|
|
38
|
+
export declare function useBoardCamera(): CanvasCamera;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
export const MIN_CAMERA_ZOOM = 0.25;
|
|
3
|
+
export const MAX_CAMERA_ZOOM = 3;
|
|
4
|
+
export const DEFAULT_CAMERA = { x: 0, y: 0, zoom: 1 };
|
|
5
|
+
/** How the canvas is viewed. Frames, groups, and selection live in the canvas store. */
|
|
6
|
+
let mode = "inspect";
|
|
7
|
+
let presentation = "focus";
|
|
8
|
+
let presentationTransitioning = false;
|
|
9
|
+
let presentationTransitionTimer = null;
|
|
10
|
+
let layoutTransitioning = false;
|
|
11
|
+
let temporaryAppInteraction = false;
|
|
12
|
+
let camera = { ...DEFAULT_CAMERA };
|
|
13
|
+
let cameraPlaced = false;
|
|
14
|
+
const listeners = new Set();
|
|
15
|
+
function notify() {
|
|
16
|
+
for (const listener of listeners)
|
|
17
|
+
listener();
|
|
18
|
+
}
|
|
19
|
+
export function subscribeCanvasView(listener) {
|
|
20
|
+
listeners.add(listener);
|
|
21
|
+
return () => listeners.delete(listener);
|
|
22
|
+
}
|
|
23
|
+
function clampZoom(zoom) {
|
|
24
|
+
return Math.max(MIN_CAMERA_ZOOM, Math.min(MAX_CAMERA_ZOOM, zoom));
|
|
25
|
+
}
|
|
26
|
+
export function getCanvasMode() { return mode; }
|
|
27
|
+
export function setCanvasMode(next) {
|
|
28
|
+
if (mode === next)
|
|
29
|
+
return;
|
|
30
|
+
mode = next;
|
|
31
|
+
notify();
|
|
32
|
+
}
|
|
33
|
+
export function getCanvasPresentation() { return presentation; }
|
|
34
|
+
export function setCanvasPresentation(next) {
|
|
35
|
+
if (presentation === next)
|
|
36
|
+
return;
|
|
37
|
+
presentation = next;
|
|
38
|
+
presentationTransitioning = !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
39
|
+
if (presentationTransitionTimer !== null)
|
|
40
|
+
window.clearTimeout(presentationTransitionTimer);
|
|
41
|
+
presentationTransitionTimer = presentationTransitioning
|
|
42
|
+
? window.setTimeout(() => {
|
|
43
|
+
presentationTransitionTimer = null;
|
|
44
|
+
presentationTransitioning = false;
|
|
45
|
+
notify();
|
|
46
|
+
}, 220)
|
|
47
|
+
: null;
|
|
48
|
+
notify();
|
|
49
|
+
}
|
|
50
|
+
/** Holding A temporarily enables app interaction without changing the selected tool. */
|
|
51
|
+
export function setTemporaryAppInteraction(value) {
|
|
52
|
+
if (temporaryAppInteraction === value)
|
|
53
|
+
return;
|
|
54
|
+
temporaryAppInteraction = value;
|
|
55
|
+
notify();
|
|
56
|
+
}
|
|
57
|
+
/** Element interactions pause while frame and camera geometry are animated. */
|
|
58
|
+
export function setCanvasLayoutTransitioning(value) {
|
|
59
|
+
if (layoutTransitioning === value)
|
|
60
|
+
return;
|
|
61
|
+
layoutTransitioning = value;
|
|
62
|
+
notify();
|
|
63
|
+
}
|
|
64
|
+
export function getCanvasLayoutTransitioning() { return layoutTransitioning; }
|
|
65
|
+
export function getBoardCamera() { return camera; }
|
|
66
|
+
export function setBoardCamera(next) {
|
|
67
|
+
camera = { x: next.x, y: next.y, zoom: clampZoom(next.zoom) };
|
|
68
|
+
notify();
|
|
69
|
+
}
|
|
70
|
+
export function updateBoardCamera(partial) {
|
|
71
|
+
setBoardCamera({ ...camera, ...partial });
|
|
72
|
+
}
|
|
73
|
+
/** Moves the camera to frame content; the initial fit-all then no longer runs. */
|
|
74
|
+
export function placeBoardCamera(next) {
|
|
75
|
+
cameraPlaced = true;
|
|
76
|
+
setBoardCamera(next);
|
|
77
|
+
}
|
|
78
|
+
export function hasFitAllRan() { return cameraPlaced; }
|
|
79
|
+
export function resetFitAllFlag() { cameraPlaced = false; }
|
|
80
|
+
export function restoreCanvasView(next) {
|
|
81
|
+
mode = next.mode;
|
|
82
|
+
presentation = next.presentation;
|
|
83
|
+
camera = { ...next.camera };
|
|
84
|
+
cameraPlaced = true;
|
|
85
|
+
notify();
|
|
86
|
+
}
|
|
87
|
+
export function useCanvasMode() {
|
|
88
|
+
return useSyncExternalStore(subscribeCanvasView, getCanvasMode, getCanvasMode);
|
|
89
|
+
}
|
|
90
|
+
export function useCanvasPresentation() {
|
|
91
|
+
return useSyncExternalStore(subscribeCanvasView, getCanvasPresentation, getCanvasPresentation);
|
|
92
|
+
}
|
|
93
|
+
export function useCanvasPresentationTransitioning() {
|
|
94
|
+
return useSyncExternalStore(subscribeCanvasView, () => presentationTransitioning, () => presentationTransitioning);
|
|
95
|
+
}
|
|
96
|
+
export function useTemporaryAppInteraction() {
|
|
97
|
+
return useSyncExternalStore(subscribeCanvasView, () => temporaryAppInteraction, () => temporaryAppInteraction);
|
|
98
|
+
}
|
|
99
|
+
export function useCanvasLayoutTransitioning() {
|
|
100
|
+
return useSyncExternalStore(subscribeCanvasView, getCanvasLayoutTransitioning, getCanvasLayoutTransitioning);
|
|
101
|
+
}
|
|
102
|
+
export function useBoardCamera() {
|
|
103
|
+
return useSyncExternalStore(subscribeCanvasView, getBoardCamera, getBoardCamera);
|
|
104
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Converts wheel units to pixel distances for consistent canvas panning. */
|
|
2
|
+
export declare function wheelPanDelta(event: Pick<WheelEvent, "deltaX" | "deltaY" | "deltaMode" | "shiftKey">, viewport: {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
}): {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Converts wheel units to pixel distances for consistent canvas panning. */
|
|
2
|
+
export function wheelPanDelta(event, viewport) {
|
|
3
|
+
const xUnit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? viewport.width : 1;
|
|
4
|
+
const yUnit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? viewport.height : 1;
|
|
5
|
+
const x = event.deltaX * xUnit;
|
|
6
|
+
const y = event.deltaY * yUnit;
|
|
7
|
+
return event.shiftKey && x === 0 ? { x: y, y: 0 } : { x, y };
|
|
8
|
+
}
|