nudge-ui 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/agentCommand.js +4 -28
- package/dist/client.mjs +863 -213
- package/dist/hosts/next/wrapper.js +1 -7
- package/dist/hosts/projectBridge.js +3 -17
- package/dist/inspector/agent/McpConnectionDialog.js +7 -3
- package/dist/inspector/agent/activity.d.ts +1 -1
- package/dist/inspector/agent/activity.js +3 -2
- package/dist/inspector/agent/client.d.ts +2 -2
- package/dist/inspector/agent/client.js +28 -29
- package/dist/inspector/agent/connectionStatus.d.ts +3 -1
- package/dist/inspector/agent/connectionStatus.js +25 -0
- package/dist/inspector/agent/httpTransport.js +31 -147
- package/dist/inspector/agent/protocol.d.ts +1 -1
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +1 -1
- package/dist/inspector/agent/verification.js +7 -4
- package/dist/inspector/boot.d.ts +3 -0
- package/dist/inspector/boot.js +297 -0
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +2 -1
- package/dist/inspector/canvas/AgentActivityOverlay.js +6 -3
- package/dist/inspector/canvas/CanvasCard.js +21 -12
- package/dist/inspector/canvas/CanvasElementOverlay.js +82 -127
- package/dist/inspector/canvas/CanvasToolbar.js +2 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +74 -97
- package/dist/inspector/canvas/LockedWorkspaceNotice.d.ts +3 -2
- package/dist/inspector/canvas/LockedWorkspaceNotice.js +10 -6
- package/dist/inspector/canvas/PageCard.d.ts +10 -0
- package/dist/inspector/canvas/PageCard.js +51 -0
- package/dist/inspector/canvas/SketchToolOptions.d.ts +5 -0
- package/dist/inspector/canvas/SketchToolOptions.js +86 -0
- package/dist/inspector/canvas/StaleChangeIndicator.d.ts +1 -1
- package/dist/inspector/canvas/StaleChangeIndicator.js +1 -1
- package/dist/inspector/canvas/ToolOptionsDrawer.d.ts +21 -0
- package/dist/inspector/canvas/ToolOptionsDrawer.js +77 -0
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +3 -2
- package/dist/inspector/canvas/assets/eraser.svg +28 -0
- package/dist/inspector/canvas/assets/pen.svg +25 -0
- package/dist/inspector/canvas/canvasFrame.d.ts +16 -0
- package/dist/inspector/canvas/canvasFrame.js +10 -0
- package/dist/inspector/canvas/canvasGestures.d.ts +0 -1
- package/dist/inspector/canvas/canvasGestures.js +0 -6
- package/dist/inspector/canvas/canvasStore.d.ts +6 -0
- package/dist/inspector/canvas/canvasStore.js +36 -1
- package/dist/inspector/canvas/frameConnection.d.ts +48 -0
- package/dist/inspector/canvas/frameConnection.js +73 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +18 -1
- package/dist/inspector/canvas/frameProtocol.js +36 -0
- package/dist/inspector/canvas/pageSurface.d.ts +11 -0
- package/dist/inspector/canvas/pageSurface.js +34 -0
- package/dist/inspector/canvas/projection.d.ts +19 -9
- package/dist/inspector/canvas/projection.js +58 -31
- package/dist/inspector/canvas/rendererBootstrap.js +4 -1
- package/dist/inspector/canvas/rendererElementSelector.js +9 -1
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +4 -5
- package/dist/inspector/canvas/rendererMessageRouter.js +12 -16
- package/dist/inspector/canvas/rendererSelectionProxy.d.ts +7 -1
- package/dist/inspector/canvas/rendererSelectionProxy.js +8 -36
- package/dist/inspector/canvas/rendererStylesheet.js +15 -0
- package/dist/inspector/canvas/sessionStore.d.ts +2 -2
- package/dist/inspector/canvas/sessionStore.js +12 -9
- package/dist/inspector/canvas/staleChangeDetector.d.ts +1 -1
- package/dist/inspector/canvas/staleChangeDetector.js +10 -22
- package/dist/inspector/changes/codecs.d.ts +1 -0
- package/dist/inspector/changes/codecs.js +20 -5
- package/dist/inspector/changes/draftPreview.d.ts +12 -0
- package/dist/inspector/changes/draftPreview.js +133 -0
- package/dist/inspector/changes/editModel.d.ts +8 -0
- package/dist/inspector/changes/editModel.js +8 -2
- package/dist/inspector/changes/managedStyleProjection.js +2 -1
- package/dist/inspector/changes/previewDiagnostics.js +1 -1
- package/dist/inspector/changes/types.d.ts +11 -1
- package/dist/inspector/comments/CommentsOverlay.js +18 -16
- package/dist/inspector/comments/store.js +3 -2
- package/dist/inspector/comments/verification.js +1 -1
- package/dist/inspector/componentSemantics/ComponentPropsSection.js +2 -1
- package/dist/inspector/componentSemantics/changeAction.js +1 -1
- package/dist/inspector/componentSemantics/textBinding.d.ts +1 -2
- package/dist/inspector/componentSemantics/textBinding.js +4 -45
- package/dist/inspector/drafts/draftStore.d.ts +109 -0
- package/dist/inspector/drafts/draftStore.js +451 -0
- package/dist/inspector/drafts/model.d.ts +9 -1
- package/dist/inspector/drafts/model.js +5 -1
- package/dist/inspector/drafts/store.d.ts +53 -15
- package/dist/inspector/drafts/store.js +143 -169
- package/dist/inspector/index.d.ts +2 -4
- package/dist/inspector/index.js +2 -264
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +1 -1
- package/dist/inspector/inline-text/inlineTextEditor.js +1 -2
- package/dist/inspector/inline-text/textEditChange.d.ts +1 -1
- package/dist/inspector/inspection/bridge.d.ts +1 -1
- package/dist/inspector/inspection/bridge.js +2 -1
- package/dist/inspector/inspection/browserCssInspection.d.ts +8 -0
- package/dist/inspector/inspection/browserCssInspection.js +28 -6
- package/dist/inspector/inspection/inspectionSnapshot.d.ts +6 -0
- package/dist/inspector/inspection/inspectionSnapshot.js +10 -0
- package/dist/inspector/inspection/performanceDiagnostics.d.ts +18 -0
- package/dist/inspector/inspection/performanceDiagnostics.js +31 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +17 -38
- package/dist/inspector/iterations/capture.d.ts +2 -1
- package/dist/inspector/iterations/client.d.ts +2 -1
- package/dist/inspector/iterations/coordinator.d.ts +4 -3
- package/dist/inspector/iterations/coordinator.js +5 -3
- package/dist/inspector/iterations/prompt.js +9 -8
- package/dist/inspector/overlay/interactionStyles.js +17 -0
- package/dist/inspector/pageInspector.d.ts +42 -0
- package/dist/inspector/pageInspector.js +342 -0
- package/dist/inspector/projection/draftProjection.d.ts +10 -2
- package/dist/inspector/projection/draftProjection.js +9 -1
- package/dist/inspector/projection/managedStylesheet.js +24 -4
- package/dist/inspector/projection/renderedInstance.js +7 -1
- package/dist/inspector/projection/structuralProjection.d.ts +0 -9
- package/dist/inspector/projection/structuralProjection.js +14 -44
- package/dist/inspector/projection/textProjection.d.ts +8 -2
- package/dist/inspector/projection/textProjection.js +21 -13
- package/dist/inspector/prompt/clipboardHandoff.d.ts +9 -0
- package/dist/inspector/prompt/clipboardHandoff.js +42 -16
- package/dist/inspector/prompt/generatePrompt.d.ts +1 -1
- package/dist/inspector/prompt/generatePrompt.js +243 -147
- package/dist/inspector/prompt/promptDocument.d.ts +3 -0
- package/dist/inspector/prompt/promptDocument.js +8 -0
- package/dist/inspector/prompt/promptSettings.js +7 -5
- package/dist/inspector/runtime/inspectorStorage.d.ts +17 -0
- package/dist/inspector/runtime/inspectorStorage.js +42 -0
- package/dist/inspector/runtime/ownedStylesheets.d.ts +20 -0
- package/dist/inspector/runtime/ownedStylesheets.js +72 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
- package/dist/inspector/runtime/runtimeConfig.js +10 -1
- package/dist/inspector/runtime/runtimeIdentityClaims.d.ts +29 -0
- package/dist/inspector/runtime/runtimeIdentityClaims.js +188 -0
- package/dist/inspector/runtime/staticHtmlRuntimeIdentity.d.ts +15 -0
- package/dist/inspector/runtime/staticHtmlRuntimeIdentity.js +113 -29
- package/dist/inspector/selection/editScope.d.ts +1 -0
- package/dist/inspector/selection/editScope.js +13 -2
- package/dist/inspector/shell/AgentStatusButton.js +2 -2
- package/dist/inspector/shell/AppShell.js +6 -2
- package/dist/inspector/shell/ChangesLog.js +27 -9
- package/dist/inspector/shell/CopyPromptButton.js +60 -54
- package/dist/inspector/shell/EmptyState.js +3 -3
- package/dist/inspector/shell/InspectorShell.d.ts +0 -3
- package/dist/inspector/shell/InspectorShell.js +21 -13
- package/dist/inspector/shell/PeekOriginalButton.js +3 -3
- package/dist/inspector/shell/interactionStatePreview.d.ts +9 -0
- package/dist/inspector/shell/interactionStatePreview.js +112 -0
- package/dist/inspector/shell/originalPreview.d.ts +3 -2
- package/dist/inspector/shell/originalPreview.js +10 -37
- package/dist/inspector/shell/stateValue.js +2 -3
- package/dist/inspector/sketch/SketchEditor.js +5 -4
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +2 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +5 -4
- package/dist/inspector/sketch/SketchOverlay.d.ts +3 -5
- package/dist/inspector/sketch/SketchOverlay.js +139 -263
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +1 -1
- package/dist/inspector/sketch/SketchPromptPanel.js +8 -1
- package/dist/inspector/sketch/SketchThumbnail.d.ts +5 -0
- package/dist/inspector/sketch/SketchThumbnail.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +1 -2
- package/dist/inspector/sketch/SketchWorkspace.js +69 -34
- package/dist/inspector/sketch/capture.js +19 -3
- package/dist/inspector/sketch/copySketch.d.ts +10 -0
- package/dist/inspector/sketch/copySketch.js +52 -0
- package/dist/inspector/sketch/drawingTools.d.ts +17 -0
- package/dist/inspector/sketch/drawingTools.js +47 -0
- package/dist/inspector/sketch/frameHeading.d.ts +9 -0
- package/dist/inspector/sketch/frameHeading.js +30 -0
- package/dist/inspector/sketch/freehand.js +3 -3
- package/dist/inspector/sketch/geometry.d.ts +6 -0
- package/dist/inspector/sketch/geometry.js +19 -0
- package/dist/inspector/sketch/history.d.ts +30 -0
- package/dist/inspector/sketch/history.js +58 -0
- package/dist/inspector/sketch/interaction.d.ts +1 -3
- package/dist/inspector/sketch/interaction.js +6 -9
- package/dist/inspector/sketch/model.d.ts +35 -1
- package/dist/inspector/sketch/model.js +116 -61
- package/dist/inspector/sketch/persistence.d.ts +14 -0
- package/dist/inspector/sketch/persistence.js +63 -7
- package/dist/inspector/sketch/raster.d.ts +6 -2
- package/dist/inspector/sketch/raster.js +43 -17
- package/dist/inspector/sketch/store.d.ts +1 -1
- package/dist/inspector/sketch/store.js +1 -1
- package/dist/inspector/sketch/videoFrames.d.ts +6 -0
- package/dist/inspector/sketch/videoFrames.js +81 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +2 -1
- package/dist/inspector/styleEditors/BorderEditor.js +30 -130
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +8 -3
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +2 -2
- package/dist/inspector/styleEditors/borderModel.d.ts +42 -0
- package/dist/inspector/styleEditors/borderModel.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.js +2 -3
- package/dist/inspector/tokens/TokenField.js +34 -3
- package/dist/inspector/tokens/TokensPanel.js +2 -1
- package/dist/inspector/tokens/editActions.d.ts +3 -3
- package/dist/inspector/tokens/editActions.js +9 -3
- package/dist/inspector/tokens/resolution/containerQueryEvaluator.d.ts +15 -0
- package/dist/inspector/tokens/resolution/containerQueryEvaluator.js +86 -0
- package/dist/inspector/tokens/resolution/cssomCollector.js +44 -9
- package/dist/inspector/tokens/resolution/nestedSelectors.d.ts +6 -0
- package/dist/inspector/tokens/resolution/nestedSelectors.js +67 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +23 -4
- package/dist/inspector/tokens/resolution.d.ts +6 -3
- package/dist/inspector/tokens/resolution.js +91 -112
- package/dist/inspector/tokens/tokenEdits.d.ts +1 -1
- package/dist/inspector/tokens/tokenEdits.js +1 -1
- package/dist/inspector/ui/InlineStyleWarning.js +2 -2
- package/dist/inspector/ui/InspectorStyle.d.ts +8 -0
- package/dist/inspector/ui/InspectorStyle.js +26 -0
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +3 -3
- package/dist/inspector/ui/Toast.d.ts +14 -0
- package/dist/inspector/ui/Toast.js +30 -0
- package/dist/inspector/ui/assets/canvasIllustration.d.ts +2 -0
- package/dist/inspector/ui/assets/canvasIllustration.js +21 -0
- package/dist/inspector/ui/assets/gridIllustration.d.ts +2 -0
- package/dist/inspector/ui/assets/{grid.svg → gridIllustration.js} +3 -2
- package/dist/inspector/ui/customCursors.d.ts +5 -0
- package/dist/inspector/ui/customCursors.js +32 -6
- package/dist/inspector/ui/styles.js +12 -10
- package/dist/inspector/workspace/commands.js +1 -2
- package/dist/inspector/workspace/controller.js +1 -2
- package/dist/inspector/workspace/handoff.js +3 -2
- package/dist/inspector/workspace/sentItems.d.ts +3 -6
- package/dist/inspector/workspace/sentItems.js +6 -24
- package/dist/inspector/workspace/timeline.d.ts +24 -10
- package/dist/inspector/workspace/timeline.js +118 -96
- package/dist/internal/agent-protocol.d.ts +50 -13
- package/dist/internal/agent-protocol.js +145 -19
- package/dist/nudge-ui.mjs +81 -65
- package/dist/project/mcpPackage.d.ts +8 -0
- package/dist/project/mcpPackage.js +60 -0
- package/package.json +12 -2
- package/dist/inspector/canvas/measurementProjection.d.ts +0 -8
- package/dist/inspector/canvas/measurementProjection.js +0 -16
- package/dist/inspector/changes/changesLog.d.ts +0 -49
- package/dist/inspector/changes/changesLog.js +0 -255
- package/dist/inspector/changes/draftChanges.d.ts +0 -58
- package/dist/inspector/changes/draftChanges.js +0 -304
- package/dist/inspector/conformance/projection.d.ts +0 -2
- package/dist/inspector/conformance/projection.js +0 -3
- package/dist/inspector/inline-text/textChangeBoundary.d.ts +0 -6
- package/dist/inspector/inline-text/textChangeBoundary.js +0 -1
- package/dist/inspector/overlay/InspectorOverlay.d.ts +0 -5
- package/dist/inspector/overlay/InspectorOverlay.js +0 -332
- package/dist/inspector/overlay/elementSelector.d.ts +0 -1
- package/dist/inspector/overlay/elementSelector.js +0 -111
- package/dist/inspector/sketch/SketchChanges.d.ts +0 -2
- package/dist/inspector/sketch/SketchChanges.js +0 -72
- package/dist/inspector/sketch/SketchEntryButton.d.ts +0 -4
- package/dist/inspector/sketch/SketchEntryButton.js +0 -11
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +0 -2
- package/dist/inspector/sketch/SketchLayersPanel.js +0 -71
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { canvasFrameHost } from "../canvas/canvasFrame.js";
|
|
3
|
+
import { getRegisteredFrames } from "../canvas/projection.js";
|
|
4
|
+
import { setSelectedElement } from "../selection/selectionStore.js";
|
|
5
|
+
import { selectCard } from "../canvas/canvasStore.js";
|
|
6
|
+
import { activateDraftForCard, assignSketchToDraft, getActiveDraftChanges } from "../drafts/store.js";
|
|
4
7
|
import { useEffect, useState } from "react";
|
|
5
8
|
import { renderAnnotatedPng } from "./raster.js";
|
|
9
|
+
import { sketchFrameHeading } from "./frameHeading.js";
|
|
6
10
|
import { closeSketchEditor, completeSketchCapture, beginSketchCapture, cancelSketchInteraction, openSketchEditor, useSketchInteraction } from "./interaction.js";
|
|
7
|
-
import {
|
|
11
|
+
import { recordCanvasChange } from "../workspace/timeline.js";
|
|
12
|
+
import { getSketch, initializeSketchStore, removeSketch, saveSketch, useSketchStore } from "./store.js";
|
|
8
13
|
import { clearSketchClipboardHandoff } from "./handoff.js";
|
|
14
|
+
import { copySketchImage, saveAndCopySketch } from "./copySketch.js";
|
|
9
15
|
import { SketchEditor } from "./SketchEditor.js";
|
|
10
16
|
import { SketchOverlay } from "./SketchOverlay.js";
|
|
11
17
|
import { closeSketchNote, useSketchNote } from "./sketchNote.js";
|
|
12
18
|
import { SketchPromptPanel } from "./SketchPromptPanel.js";
|
|
19
|
+
/** Makes saving a new sketch one step in the session undo history. */
|
|
20
|
+
function recordSketchCreation(document, owner) {
|
|
21
|
+
let snapshot = document;
|
|
22
|
+
recordCanvasChange({
|
|
23
|
+
cardIds: [],
|
|
24
|
+
undo: () => {
|
|
25
|
+
snapshot = getSketch(document.id)?.document ?? snapshot;
|
|
26
|
+
void removeSketch(document.id);
|
|
27
|
+
},
|
|
28
|
+
redo: () => {
|
|
29
|
+
void saveSketch({ ...snapshot, annotations: snapshot.annotations ?? [] })
|
|
30
|
+
.then((saved) => assignSketchToDraft(owner, saved.id));
|
|
31
|
+
},
|
|
32
|
+
dispose: () => undefined,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
13
35
|
function clamp(value, maximum) {
|
|
14
36
|
return Math.max(0, Math.min(maximum, value));
|
|
15
37
|
}
|
|
@@ -37,26 +59,6 @@ export function projectLiveStrokes(draft, captured) {
|
|
|
37
59
|
}];
|
|
38
60
|
});
|
|
39
61
|
}
|
|
40
|
-
function projectLiveAnnotations(draft, captured) {
|
|
41
|
-
const scaleX = captured.imageWidth / Math.max(1, draft.viewport.width);
|
|
42
|
-
const scaleY = captured.imageHeight / Math.max(1, draft.viewport.height);
|
|
43
|
-
const captureScrollX = captured.capture.scrollX;
|
|
44
|
-
const captureScrollY = captured.capture.scrollY;
|
|
45
|
-
const visibleRight = captureScrollX + draft.viewport.width;
|
|
46
|
-
const visibleBottom = captureScrollY + draft.viewport.height;
|
|
47
|
-
return draft.annotations
|
|
48
|
-
.filter((annotation) => annotation.point.x >= captureScrollX
|
|
49
|
-
&& annotation.point.x <= visibleRight
|
|
50
|
-
&& annotation.point.y >= captureScrollY
|
|
51
|
-
&& annotation.point.y <= visibleBottom)
|
|
52
|
-
.map((annotation) => ({
|
|
53
|
-
...annotation,
|
|
54
|
-
point: {
|
|
55
|
-
x: clamp((annotation.point.x - captureScrollX) * scaleX, captured.imageWidth),
|
|
56
|
-
y: clamp((annotation.point.y - captureScrollY) * scaleY, captured.imageHeight),
|
|
57
|
-
},
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
62
|
export function SketchWorkspace({ hostElement, projectId }) {
|
|
61
63
|
const interaction = useSketchInteraction();
|
|
62
64
|
const store = useSketchStore();
|
|
@@ -77,39 +79,71 @@ export function SketchWorkspace({ hostElement, projectId }) {
|
|
|
77
79
|
const noteDocument = note.sketchId
|
|
78
80
|
? store.items.find((item) => item.document.id === note.sketchId)?.document ?? null
|
|
79
81
|
: null;
|
|
82
|
+
function selectTarget(point, lockedTarget) {
|
|
83
|
+
for (const [cardId, frame] of getRegisteredFrames()) {
|
|
84
|
+
const target = canvasFrameHost(frame);
|
|
85
|
+
if (!target || !frame.isConnected || getComputedStyle(target).visibility === "hidden")
|
|
86
|
+
continue;
|
|
87
|
+
const bounds = frame.getBoundingClientRect();
|
|
88
|
+
if (bounds.width <= 0 || bounds.height <= 0)
|
|
89
|
+
continue;
|
|
90
|
+
if (point.x < bounds.left || point.x > bounds.right || point.y < bounds.top || point.y > bounds.bottom)
|
|
91
|
+
continue;
|
|
92
|
+
if (lockedTarget && target !== lockedTarget)
|
|
93
|
+
return null;
|
|
94
|
+
if (target !== hostElement) {
|
|
95
|
+
setSelectedElement(null);
|
|
96
|
+
selectCard(cardId);
|
|
97
|
+
activateDraftForCard(cardId);
|
|
98
|
+
beginSketchCapture();
|
|
99
|
+
}
|
|
100
|
+
return target;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
80
104
|
async function save(input) {
|
|
81
105
|
const owner = interaction.draftId ?? getActiveDraftChanges().draftId;
|
|
82
106
|
const saved = await saveSketch(input);
|
|
83
|
-
if (!input.id)
|
|
107
|
+
if (!input.id) {
|
|
84
108
|
assignSketchToDraft(owner, saved.id);
|
|
109
|
+
recordSketchCreation(saved, owner);
|
|
110
|
+
}
|
|
85
111
|
closeSketchEditor();
|
|
112
|
+
void copySketchImage(input.annotatedImage);
|
|
113
|
+
}
|
|
114
|
+
function saveLive(draft) {
|
|
115
|
+
return saveAndCopySketch(() => saveLiveSketch(draft));
|
|
86
116
|
}
|
|
87
|
-
async function
|
|
117
|
+
async function saveLiveSketch(draft) {
|
|
88
118
|
if (!hostElement)
|
|
89
119
|
throw new Error("The inspector is no longer available.");
|
|
90
120
|
const owner = interaction.draftId ?? getActiveDraftChanges().draftId;
|
|
91
121
|
const captured = await completeSketchCapture(hostElement);
|
|
92
122
|
const strokes = projectLiveStrokes(draft, captured);
|
|
93
|
-
const
|
|
94
|
-
const rendered = await renderAnnotatedPng(captured.originalImage, captured.imageWidth, captured.imageHeight, strokes, annotations, draft.description);
|
|
123
|
+
const rendered = await renderAnnotatedPng(captured.originalImage, captured.imageWidth, captured.imageHeight, strokes, [], draft.description, sketchFrameHeading(captured.capture.url));
|
|
95
124
|
const saved = await saveSketch({
|
|
96
125
|
capture: captured.capture,
|
|
97
126
|
description: draft.description,
|
|
98
127
|
strokes,
|
|
99
|
-
annotations,
|
|
128
|
+
annotations: [],
|
|
100
129
|
imageWidth: rendered.width,
|
|
101
130
|
imageHeight: rendered.height,
|
|
102
131
|
originalImage: captured.originalImage,
|
|
103
132
|
annotatedImage: rendered.blob,
|
|
104
133
|
});
|
|
105
134
|
assignSketchToDraft(owner, saved.id);
|
|
135
|
+
recordSketchCreation(saved, owner);
|
|
106
136
|
closeSketchEditor();
|
|
137
|
+
return rendered.blob;
|
|
107
138
|
}
|
|
108
|
-
|
|
139
|
+
function saveNote(description) {
|
|
109
140
|
if (!noteDocument)
|
|
110
|
-
return;
|
|
141
|
+
return Promise.resolve();
|
|
142
|
+
return saveAndCopySketch(() => saveNoteSketch(noteDocument, description));
|
|
143
|
+
}
|
|
144
|
+
async function saveNoteSketch(noteDocument, description) {
|
|
111
145
|
const nextDescription = description.trim();
|
|
112
|
-
const rendered = await renderAnnotatedPng(noteDocument.originalImage, noteDocument.capture.imageWidth, noteDocument.capture.imageHeight, noteDocument.strokes, noteDocument.annotations ?? [], nextDescription);
|
|
146
|
+
const rendered = await renderAnnotatedPng(noteDocument.originalImage, noteDocument.capture.imageWidth, noteDocument.capture.imageHeight, noteDocument.strokes, noteDocument.annotations ?? [], nextDescription, sketchFrameHeading(noteDocument.capture.url));
|
|
113
147
|
await saveSketch({
|
|
114
148
|
id: noteDocument.id,
|
|
115
149
|
capture: noteDocument.capture,
|
|
@@ -122,8 +156,9 @@ export function SketchWorkspace({ hostElement, projectId }) {
|
|
|
122
156
|
annotatedImage: rendered.blob,
|
|
123
157
|
});
|
|
124
158
|
closeSketchNote();
|
|
159
|
+
return rendered.blob;
|
|
125
160
|
}
|
|
126
|
-
return (_jsxs(_Fragment, { children: [hostElement ? (_jsx(SketchOverlay, { open: liveOpen, hostElement: hostElement,
|
|
161
|
+
return (_jsxs(_Fragment, { children: [hostElement ? (_jsx(SketchOverlay, { open: liveOpen, hostElement: hostElement, selectTarget: selectTarget, onCancel: cancelSketchInteraction, onDone: saveLive })) : null, _jsx(SketchEditor, { open: editorOpen, document: editingDocument, captured: interaction.captured, onCancel: cancelSketchInteraction, onSave: save }), noteDocument ? (_jsx(SavedSketchNote, { document: noteDocument, onSave: saveNote, onCancel: closeSketchNote })) : null] }));
|
|
127
162
|
}
|
|
128
163
|
function SavedSketchNote({ document, onSave, onCancel, }) {
|
|
129
164
|
const [description, setDescription] = useState(document.description);
|
|
@@ -148,10 +183,10 @@ function SavedSketchNote({ document, onSave, onCancel, }) {
|
|
|
148
183
|
}
|
|
149
184
|
return (_jsx(SketchPromptPanel, { dataTest: "sketch-note-prompt", className: "sketch__saved-note-panel", description: description, error: error, saving: saving, autoFocus: true, onDescriptionChange: setDescription, onDone: done, onCancel: onCancel }));
|
|
150
185
|
}
|
|
151
|
-
export function startSketchCapture(hostElement
|
|
186
|
+
export function startSketchCapture(hostElement) {
|
|
152
187
|
if (!hostElement)
|
|
153
188
|
return;
|
|
154
|
-
beginSketchCapture(
|
|
189
|
+
beginSketchCapture();
|
|
155
190
|
}
|
|
156
191
|
export function editSketch(sketchId) {
|
|
157
192
|
openSketchEditor(sketchId);
|
|
@@ -2,6 +2,7 @@ import { domToBlob } from "modern-screenshot";
|
|
|
2
2
|
import { getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
|
|
3
3
|
import { SKETCH_LIMITS } from "./model.js";
|
|
4
4
|
import { getPngDimensions } from "./raster.js";
|
|
5
|
+
import { prepareSketchVideoFrames } from "./videoFrames.js";
|
|
5
6
|
const MEDIA_LOAD_TIMEOUT_MS = 500;
|
|
6
7
|
const FONT_READY_TIMEOUT_MS = 2_000;
|
|
7
8
|
const DEFAULT_PROCESSING_TIMEOUT_MS = 20_000;
|
|
@@ -258,7 +259,17 @@ async function captureDomFrame(options, signal) {
|
|
|
258
259
|
let scale = viewport.scale;
|
|
259
260
|
for (let attempt = 0; attempt < 7; attempt += 1) {
|
|
260
261
|
assertNotAborted(signal);
|
|
262
|
+
const videoFrames = prepareSketchVideoFrames(surface.document);
|
|
261
263
|
const restoreMedia = suspendOffscreenLazyImages(surface.document, viewport);
|
|
264
|
+
let restored = false;
|
|
265
|
+
const restore = () => {
|
|
266
|
+
if (restored)
|
|
267
|
+
return;
|
|
268
|
+
restored = true;
|
|
269
|
+
videoFrames.restore();
|
|
270
|
+
restoreMedia();
|
|
271
|
+
};
|
|
272
|
+
signal?.addEventListener("abort", restore, { once: true });
|
|
262
273
|
let blob;
|
|
263
274
|
try {
|
|
264
275
|
blob = await domToBlob(surface.document.documentElement, {
|
|
@@ -272,18 +283,23 @@ async function captureDomFrame(options, signal) {
|
|
|
272
283
|
height: `${viewport.height}px`,
|
|
273
284
|
overflow: "hidden",
|
|
274
285
|
},
|
|
275
|
-
filter: (node) => node !== options.hostElement && !isGoogleFontStylesheet(node),
|
|
286
|
+
filter: (node) => node !== options.hostElement && !isGoogleFontStylesheet(node) && videoFrames.filter(node),
|
|
276
287
|
features: { restoreScrollPosition: true },
|
|
277
288
|
timeout: MEDIA_LOAD_TIMEOUT_MS,
|
|
278
289
|
// Cross-origin Google CSS cannot be read by modern-screenshot. Skipping
|
|
279
290
|
// its font embedding avoids waiting on a stylesheet that the renderer
|
|
280
291
|
// cannot serialize anyway; computed font styles still remain inline.
|
|
281
292
|
font: hasGoogleFontStylesheet(surface.document) ? false : undefined,
|
|
282
|
-
onCloneEachNode:
|
|
293
|
+
onCloneEachNode: (node) => {
|
|
294
|
+
prepareDomClone(node);
|
|
295
|
+
videoFrames.prepareClone(node);
|
|
296
|
+
},
|
|
297
|
+
onCloneNode: restore,
|
|
283
298
|
});
|
|
284
299
|
}
|
|
285
300
|
finally {
|
|
286
|
-
|
|
301
|
+
signal?.removeEventListener("abort", restore);
|
|
302
|
+
restore();
|
|
287
303
|
}
|
|
288
304
|
assertNotAborted(signal);
|
|
289
305
|
const dimensions = await getPngDimensions(blob);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Copies a saved sketch image automatically after an edit and reports the outcome as a toast. */
|
|
2
|
+
export declare function copySketchImage(image: Blob): Promise<void>;
|
|
3
|
+
/** Copies a sketch image the user asked for; only a failure needs a toast. */
|
|
4
|
+
export declare function copySketchImageOnRequest(image: Blob): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Starts the clipboard write synchronously (inside the "Done" gesture) and
|
|
7
|
+
* fills it with the image that `save` renders. A failed save skips the toast
|
|
8
|
+
* and rethrows so the caller can show its own error.
|
|
9
|
+
*/
|
|
10
|
+
export declare function saveAndCopySketch(save: () => Promise<Blob>): Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { showToast } from "../ui/Toast.js";
|
|
2
|
+
import { copyImageToClipboard } from "./raster.js";
|
|
3
|
+
function reportCopyFailure(title, error) {
|
|
4
|
+
showToast({
|
|
5
|
+
tone: "error",
|
|
6
|
+
title,
|
|
7
|
+
description: error instanceof Error ? error.message : "The sketch image could not be copied.",
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Reports a copy made automatically on save. The toast is the only sign the
|
|
12
|
+
* clipboard changed, so success is announced as well as failure.
|
|
13
|
+
*/
|
|
14
|
+
function reportAutoCopy(copied) {
|
|
15
|
+
void copied.then(() => showToast({ title: "Copied to clipboard" }), (error) => reportCopyFailure("Sketch saved, but not copied", error));
|
|
16
|
+
}
|
|
17
|
+
/** Copies a saved sketch image automatically after an edit and reports the outcome as a toast. */
|
|
18
|
+
export function copySketchImage(image) {
|
|
19
|
+
const copied = copyImageToClipboard(image);
|
|
20
|
+
reportAutoCopy(copied);
|
|
21
|
+
return copied.catch(() => undefined);
|
|
22
|
+
}
|
|
23
|
+
/** Copies a sketch image the user asked for; only a failure needs a toast. */
|
|
24
|
+
export function copySketchImageOnRequest(image) {
|
|
25
|
+
const copied = copyImageToClipboard(image);
|
|
26
|
+
void copied.catch((error) => reportCopyFailure("Sketch not copied", error));
|
|
27
|
+
return copied.catch(() => undefined);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Starts the clipboard write synchronously (inside the "Done" gesture) and
|
|
31
|
+
* fills it with the image that `save` renders. A failed save skips the toast
|
|
32
|
+
* and rethrows so the caller can show its own error.
|
|
33
|
+
*/
|
|
34
|
+
export async function saveAndCopySketch(save) {
|
|
35
|
+
let resolveImage = () => undefined;
|
|
36
|
+
let rejectImage = () => undefined;
|
|
37
|
+
const image = new Promise((resolve, reject) => {
|
|
38
|
+
resolveImage = resolve;
|
|
39
|
+
rejectImage = reject;
|
|
40
|
+
});
|
|
41
|
+
image.catch(() => undefined);
|
|
42
|
+
const copied = copyImageToClipboard(image);
|
|
43
|
+
copied.catch(() => undefined);
|
|
44
|
+
try {
|
|
45
|
+
resolveImage(await save());
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
rejectImage(error);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
reportAutoCopy(copied);
|
|
52
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type SketchPenColor } from "./model.js";
|
|
2
|
+
/** Drawing mode chosen from the canvas toolbar while the sketch tool is active. */
|
|
3
|
+
export type SketchDrawingTool = "pen" | "eraser" | "rectangle";
|
|
4
|
+
export interface SketchDrawingToolsSnapshot {
|
|
5
|
+
readonly tool: SketchDrawingTool;
|
|
6
|
+
readonly color: SketchPenColor;
|
|
7
|
+
}
|
|
8
|
+
export declare function getSketchDrawingTools(): SketchDrawingToolsSnapshot;
|
|
9
|
+
export declare function subscribeSketchDrawingTools(listener: () => void): () => void;
|
|
10
|
+
export declare function useSketchDrawingTools(): SketchDrawingToolsSnapshot;
|
|
11
|
+
export declare function setSketchDrawingTool(tool: SketchDrawingTool): void;
|
|
12
|
+
/** Choosing a color implies drawing with it, so the eraser hands back to the pen. */
|
|
13
|
+
export declare function setSketchStrokeColor(color: SketchPenColor): void;
|
|
14
|
+
/** Every sketch session starts with the pen; the chosen color is kept. */
|
|
15
|
+
export declare function resetSketchDrawingTool(): void;
|
|
16
|
+
/** Restores the default tool and color. */
|
|
17
|
+
export declare function restoreDefaultSketchDrawingTools(): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
import { SKETCH_PEN_COLORS, SKETCH_STROKE_COLOR } from "./model.js";
|
|
3
|
+
const DEFAULT_COLOR = SKETCH_PEN_COLORS.find((color) => color.value === SKETCH_STROKE_COLOR) ?? SKETCH_PEN_COLORS[0];
|
|
4
|
+
const DEFAULT_SNAPSHOT = {
|
|
5
|
+
tool: "pen",
|
|
6
|
+
color: DEFAULT_COLOR,
|
|
7
|
+
};
|
|
8
|
+
let snapshot = DEFAULT_SNAPSHOT;
|
|
9
|
+
const listeners = new Set();
|
|
10
|
+
function publish(next) {
|
|
11
|
+
if (next.tool === snapshot.tool && next.color === snapshot.color)
|
|
12
|
+
return;
|
|
13
|
+
snapshot = next;
|
|
14
|
+
for (const listener of listeners) {
|
|
15
|
+
try {
|
|
16
|
+
listener();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// A view subscriber must not stop the others from seeing the new tool.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function getSketchDrawingTools() {
|
|
24
|
+
return snapshot;
|
|
25
|
+
}
|
|
26
|
+
export function subscribeSketchDrawingTools(listener) {
|
|
27
|
+
listeners.add(listener);
|
|
28
|
+
return () => listeners.delete(listener);
|
|
29
|
+
}
|
|
30
|
+
export function useSketchDrawingTools() {
|
|
31
|
+
return useSyncExternalStore(subscribeSketchDrawingTools, getSketchDrawingTools, getSketchDrawingTools);
|
|
32
|
+
}
|
|
33
|
+
export function setSketchDrawingTool(tool) {
|
|
34
|
+
publish({ ...snapshot, tool });
|
|
35
|
+
}
|
|
36
|
+
/** Choosing a color implies drawing with it, so the eraser hands back to the pen. */
|
|
37
|
+
export function setSketchStrokeColor(color) {
|
|
38
|
+
publish({ tool: snapshot.tool === "eraser" ? "pen" : snapshot.tool, color });
|
|
39
|
+
}
|
|
40
|
+
/** Every sketch session starts with the pen; the chosen color is kept. */
|
|
41
|
+
export function resetSketchDrawingTool() {
|
|
42
|
+
setSketchDrawingTool("pen");
|
|
43
|
+
}
|
|
44
|
+
/** Restores the default tool and color. */
|
|
45
|
+
export function restoreDefaultSketchDrawingTools() {
|
|
46
|
+
snapshot = DEFAULT_SNAPSHOT;
|
|
47
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type CanvasCard } from "../canvas/canvasStore.js";
|
|
2
|
+
/**
|
|
3
|
+
* Names the frame an annotated sketch targets, so the PNG stays actionable when
|
|
4
|
+
* a user copies it to an agent without the handoff prompt. Iterations also name
|
|
5
|
+
* their artifact file: the folder ID is the only stable identity an agent can
|
|
6
|
+
* use to find the right iteration, since canvas iteration numbers are live
|
|
7
|
+
* labels that nothing persists.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sketchFrameHeading(captureUrl: string, frameCards?: readonly CanvasCard[]): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getCanvasCards } from "../canvas/canvasStore.js";
|
|
2
|
+
import { contentSourceUrl } from "../canvas/frameContent.js";
|
|
3
|
+
import { artifactIdFromUrl, htmlTarget } from "../drafts/model.js";
|
|
4
|
+
/** The page path shown in sketch headings; origin, query, and hash stay out. */
|
|
5
|
+
function routePath(url) {
|
|
6
|
+
try {
|
|
7
|
+
return new URL(url).pathname || "/";
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return url;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Names the frame an annotated sketch targets, so the PNG stays actionable when
|
|
15
|
+
* a user copies it to an agent without the handoff prompt. Iterations also name
|
|
16
|
+
* their artifact file: the folder ID is the only stable identity an agent can
|
|
17
|
+
* use to find the right iteration, since canvas iteration numbers are live
|
|
18
|
+
* labels that nothing persists.
|
|
19
|
+
*/
|
|
20
|
+
export function sketchFrameHeading(captureUrl, frameCards = getCanvasCards()) {
|
|
21
|
+
const artifactId = artifactIdFromUrl(captureUrl);
|
|
22
|
+
if (!artifactId)
|
|
23
|
+
return `Implement these changes for "${routePath(captureUrl)}"`;
|
|
24
|
+
const card = frameCards.find((candidate) => candidate.content.kind === "iteration"
|
|
25
|
+
&& candidate.content.artifactId === artifactId);
|
|
26
|
+
const path = htmlTarget(artifactId).path;
|
|
27
|
+
return card
|
|
28
|
+
? `Implement these changes for the HTML iteration "${routePath(contentSourceUrl(card.content))}" (${path})`
|
|
29
|
+
: `Implement these changes for the HTML iteration (${path})`;
|
|
30
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { getStroke } from "perfect-freehand";
|
|
3
|
-
import { SKETCH_STROKE_COLOR, } from "./model.js";
|
|
3
|
+
import { SKETCH_ACCENT_COLOR, SKETCH_RECTANGLE_FILL_OPACITY, SKETCH_STROKE_COLOR, } from "./model.js";
|
|
4
4
|
const FREEHAND_OPTIONS = {
|
|
5
5
|
thinning: 0.45,
|
|
6
6
|
smoothing: 0.5,
|
|
@@ -56,9 +56,9 @@ export function SketchSvgLayer({ strokes, annotations = [], annotationScale = 1,
|
|
|
56
56
|
if (!path)
|
|
57
57
|
return null;
|
|
58
58
|
const rectangle = stroke.kind === "rectangle";
|
|
59
|
-
return (_jsx("path", { d: path, fill:
|
|
59
|
+
return (_jsx("path", { d: path, fill: stroke.color || SKETCH_STROKE_COLOR, fillOpacity: rectangle ? SKETCH_RECTANGLE_FILL_OPACITY : undefined, stroke: rectangle ? stroke.color || SKETCH_STROKE_COLOR : "none", strokeWidth: rectangle ? stroke.width : undefined, strokeLinecap: "round", strokeLinejoin: "round", pointerEvents: "none" }, stroke.id));
|
|
60
60
|
}), annotations.map((annotation) => {
|
|
61
61
|
const radius = 16 * annotationScale;
|
|
62
|
-
return (_jsxs("g", { transform: `translate(${number(annotation.point.x)} ${number(annotation.point.y)})`, pointerEvents: "none", children: [_jsx("circle", { r: radius, fill:
|
|
62
|
+
return (_jsxs("g", { transform: `translate(${number(annotation.point.x)} ${number(annotation.point.y)})`, pointerEvents: "none", children: [_jsx("circle", { r: radius, fill: SKETCH_ACCENT_COLOR }), _jsx("text", { x: "0", y: "0", fill: "#ffffff", fontFamily: "system-ui, sans-serif", fontSize: 12 * annotationScale, fontWeight: "600", textAnchor: "middle", dominantBaseline: "central", children: annotation.number })] }, annotation.id));
|
|
63
63
|
})] }));
|
|
64
64
|
}
|
|
@@ -15,3 +15,9 @@ export declare function distanceToSegment(point: SketchPoint, start: SketchPoint
|
|
|
15
15
|
export declare function findSketchElementAt(point: SketchPoint, strokes: readonly SketchStroke[], annotations: readonly SketchAnnotation[]): SketchElementReference | null;
|
|
16
16
|
export declare function translateSketchStroke(stroke: SketchStroke, delta: SketchPoint, bounds: SketchBounds): SketchStroke;
|
|
17
17
|
export declare function translateSketchAnnotation(annotation: SketchAnnotation, delta: SketchPoint, bounds: SketchBounds): SketchAnnotation;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the ids of strokes whose drawn line passes within `radius` of the
|
|
20
|
+
* eraser path from `start` to `end`. Rectangles are hit on their outline only,
|
|
21
|
+
* so erasing across the inside of a box leaves it in place.
|
|
22
|
+
*/
|
|
23
|
+
export declare function findErasedSketchStrokeIds(start: SketchPoint, end: SketchPoint, strokes: readonly SketchStroke[], radius: number): string[];
|
|
@@ -96,3 +96,22 @@ export function translateSketchAnnotation(annotation, delta, bounds) {
|
|
|
96
96
|
},
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Returns the ids of strokes whose drawn line passes within `radius` of the
|
|
101
|
+
* eraser path from `start` to `end`. Rectangles are hit on their outline only,
|
|
102
|
+
* so erasing across the inside of a box leaves it in place.
|
|
103
|
+
*/
|
|
104
|
+
export function findErasedSketchStrokeIds(start, end, strokes, radius) {
|
|
105
|
+
const steps = Math.max(1, Math.ceil(distance(start, end) / Math.max(1, radius / 2)));
|
|
106
|
+
const samples = [];
|
|
107
|
+
for (let step = 0; step <= steps; step += 1) {
|
|
108
|
+
const position = step / steps;
|
|
109
|
+
samples.push({ x: start.x + (end.x - start.x) * position, y: start.y + (end.y - start.y) * position });
|
|
110
|
+
}
|
|
111
|
+
return strokes
|
|
112
|
+
.filter((stroke) => {
|
|
113
|
+
const tolerance = radius + stroke.width / 2;
|
|
114
|
+
return samples.some((sample) => distanceToStroke(sample, stroke) <= tolerance);
|
|
115
|
+
})
|
|
116
|
+
.map((stroke) => stroke.id);
|
|
117
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SketchStroke } from "./model.js";
|
|
2
|
+
interface ErasedStroke {
|
|
3
|
+
readonly stroke: SketchStroke;
|
|
4
|
+
/** Position in the stroke list at the moment it was erased. */
|
|
5
|
+
readonly index: number;
|
|
6
|
+
}
|
|
7
|
+
type SketchHistoryEntry = {
|
|
8
|
+
readonly kind: "add";
|
|
9
|
+
readonly stroke: SketchStroke;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: "erase";
|
|
12
|
+
readonly gesture: number;
|
|
13
|
+
readonly erased: readonly ErasedStroke[];
|
|
14
|
+
};
|
|
15
|
+
/** Strokes plus the undo and redo stacks of the edits that produced them. */
|
|
16
|
+
export interface SketchHistory {
|
|
17
|
+
readonly strokes: readonly SketchStroke[];
|
|
18
|
+
readonly undo: readonly SketchHistoryEntry[];
|
|
19
|
+
readonly redo: readonly SketchHistoryEntry[];
|
|
20
|
+
}
|
|
21
|
+
export declare const EMPTY_SKETCH_HISTORY: SketchHistory;
|
|
22
|
+
export declare function addSketchStroke(history: SketchHistory, stroke: SketchStroke): SketchHistory;
|
|
23
|
+
/**
|
|
24
|
+
* Erases strokes as one undoable edit. Strokes erased by the same `gesture`
|
|
25
|
+
* share an entry, so one eraser drag undoes in a single step.
|
|
26
|
+
*/
|
|
27
|
+
export declare function eraseSketchStrokes(history: SketchHistory, ids: readonly string[], gesture: number): SketchHistory;
|
|
28
|
+
export declare function undoSketchEdit(history: SketchHistory): SketchHistory;
|
|
29
|
+
export declare function redoSketchEdit(history: SketchHistory): SketchHistory;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const EMPTY_SKETCH_HISTORY = { strokes: [], undo: [], redo: [] };
|
|
2
|
+
export function addSketchStroke(history, stroke) {
|
|
3
|
+
return {
|
|
4
|
+
strokes: [...history.strokes, stroke],
|
|
5
|
+
undo: [...history.undo, { kind: "add", stroke }],
|
|
6
|
+
redo: [],
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function removeStrokes(strokes, ids) {
|
|
10
|
+
const remaining = [];
|
|
11
|
+
const erased = [];
|
|
12
|
+
strokes.forEach((stroke, index) => {
|
|
13
|
+
if (ids.has(stroke.id))
|
|
14
|
+
erased.push({ stroke, index: index - erased.length });
|
|
15
|
+
else
|
|
16
|
+
remaining.push(stroke);
|
|
17
|
+
});
|
|
18
|
+
return { strokes: remaining, erased };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Erases strokes as one undoable edit. Strokes erased by the same `gesture`
|
|
22
|
+
* share an entry, so one eraser drag undoes in a single step.
|
|
23
|
+
*/
|
|
24
|
+
export function eraseSketchStrokes(history, ids, gesture) {
|
|
25
|
+
const { strokes, erased } = removeStrokes(history.strokes, new Set(ids));
|
|
26
|
+
if (erased.length === 0)
|
|
27
|
+
return history;
|
|
28
|
+
const last = history.undo.at(-1);
|
|
29
|
+
const undo = last?.kind === "erase" && last.gesture === gesture
|
|
30
|
+
? [...history.undo.slice(0, -1), { ...last, erased: [...last.erased, ...erased] }]
|
|
31
|
+
: [...history.undo, { kind: "erase", gesture, erased }];
|
|
32
|
+
return { strokes, undo, redo: [] };
|
|
33
|
+
}
|
|
34
|
+
function restoreErased(strokes, erased) {
|
|
35
|
+
const restored = [...strokes];
|
|
36
|
+
// Reinsert in reverse removal order so each recorded index is valid again.
|
|
37
|
+
for (const { stroke, index } of [...erased].reverse())
|
|
38
|
+
restored.splice(index, 0, stroke);
|
|
39
|
+
return restored;
|
|
40
|
+
}
|
|
41
|
+
export function undoSketchEdit(history) {
|
|
42
|
+
const entry = history.undo.at(-1);
|
|
43
|
+
if (!entry)
|
|
44
|
+
return history;
|
|
45
|
+
const strokes = entry.kind === "add"
|
|
46
|
+
? history.strokes.filter((stroke) => stroke.id !== entry.stroke.id)
|
|
47
|
+
: restoreErased(history.strokes, entry.erased);
|
|
48
|
+
return { strokes, undo: history.undo.slice(0, -1), redo: [...history.redo, entry] };
|
|
49
|
+
}
|
|
50
|
+
export function redoSketchEdit(history) {
|
|
51
|
+
const entry = history.redo.at(-1);
|
|
52
|
+
if (!entry)
|
|
53
|
+
return history;
|
|
54
|
+
const strokes = entry.kind === "add"
|
|
55
|
+
? [...history.strokes, entry.stroke]
|
|
56
|
+
: removeStrokes(history.strokes, new Set(entry.erased.map(({ stroke }) => stroke.id))).strokes;
|
|
57
|
+
return { strokes, undo: [...history.undo, entry], redo: history.redo.slice(0, -1) };
|
|
58
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { type CapturedSketch } from "./capture.js";
|
|
2
2
|
export type SketchCaptureState = "idle" | "sketching" | "capturing" | "ready" | "error";
|
|
3
|
-
export type SketchEntryTool = "pen" | "annotate";
|
|
4
3
|
export interface SketchInteractionSnapshot {
|
|
5
4
|
readonly captureState: SketchCaptureState;
|
|
6
5
|
readonly captured: CapturedSketch | null;
|
|
7
6
|
readonly editingId: string | null;
|
|
8
7
|
readonly error: string | null;
|
|
9
|
-
readonly initialTool: SketchEntryTool;
|
|
10
8
|
readonly draftId: string | null;
|
|
11
9
|
}
|
|
12
10
|
export declare function getSketchInteractionSnapshot(): SketchInteractionSnapshot;
|
|
@@ -14,7 +12,7 @@ export declare function subscribeSketchInteraction(listener: () => void): () =>
|
|
|
14
12
|
export declare function useSketchInteraction(): SketchInteractionSnapshot;
|
|
15
13
|
export declare function isSketchInteractionActive(): boolean;
|
|
16
14
|
export declare function useSketchInteractionActive(): boolean;
|
|
17
|
-
export declare function beginSketchCapture(
|
|
15
|
+
export declare function beginSketchCapture(): void;
|
|
18
16
|
export declare function completeSketchCapture(hostElement: HTMLElement): Promise<CapturedSketch>;
|
|
19
17
|
export declare function cancelSketchInteraction(): void;
|
|
20
18
|
export declare function openSketchEditor(sketchId: string): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getActiveDraftChanges } from "../
|
|
1
|
+
import { getActiveDraftChanges } from "../drafts/store.js";
|
|
2
2
|
import { useSyncExternalStore } from "react";
|
|
3
3
|
import { captureViewport, } from "./capture.js";
|
|
4
4
|
const EMPTY_SNAPSHOT = {
|
|
@@ -6,7 +6,6 @@ const EMPTY_SNAPSHOT = {
|
|
|
6
6
|
captured: null,
|
|
7
7
|
editingId: null,
|
|
8
8
|
error: null,
|
|
9
|
-
initialTool: "pen",
|
|
10
9
|
draftId: null,
|
|
11
10
|
};
|
|
12
11
|
let snapshot = EMPTY_SNAPSHOT;
|
|
@@ -40,25 +39,24 @@ export function useSketchInteractionActive() {
|
|
|
40
39
|
const current = useSketchInteraction();
|
|
41
40
|
return current.captureState !== "idle" || current.captured !== null || current.editingId !== null;
|
|
42
41
|
}
|
|
43
|
-
export function beginSketchCapture(
|
|
42
|
+
export function beginSketchCapture() {
|
|
44
43
|
captureController?.abort();
|
|
45
44
|
captureController = null;
|
|
46
|
-
publish({ captureState: "sketching", captured: null, editingId: null, error: null,
|
|
45
|
+
publish({ captureState: "sketching", captured: null, editingId: null, error: null, draftId: getActiveDraftChanges().draftId });
|
|
47
46
|
}
|
|
48
47
|
export function completeSketchCapture(hostElement) {
|
|
49
48
|
captureController?.abort();
|
|
50
49
|
const controller = new AbortController();
|
|
51
50
|
captureController = controller;
|
|
52
|
-
const initialTool = snapshot.initialTool;
|
|
53
51
|
const draftId = snapshot.draftId;
|
|
54
|
-
publish({ captureState: "capturing", captured: null, editingId: null, error: null,
|
|
52
|
+
publish({ captureState: "capturing", captured: null, editingId: null, error: null, draftId });
|
|
55
53
|
const options = { hostElement, signal: controller.signal };
|
|
56
54
|
return captureViewport(options)
|
|
57
55
|
.then((captured) => {
|
|
58
56
|
if (captureController !== controller)
|
|
59
57
|
return captured;
|
|
60
58
|
captureController = null;
|
|
61
|
-
publish({ captureState: "ready", captured, editingId: null, error: null,
|
|
59
|
+
publish({ captureState: "ready", captured, editingId: null, error: null, draftId });
|
|
62
60
|
return captured;
|
|
63
61
|
})
|
|
64
62
|
.catch((error) => {
|
|
@@ -74,7 +72,6 @@ export function completeSketchCapture(hostElement) {
|
|
|
74
72
|
captured: null,
|
|
75
73
|
editingId: null,
|
|
76
74
|
error: error instanceof Error ? error.message : "The viewport could not be captured.",
|
|
77
|
-
initialTool,
|
|
78
75
|
draftId,
|
|
79
76
|
});
|
|
80
77
|
throw error;
|
|
@@ -88,7 +85,7 @@ export function cancelSketchInteraction() {
|
|
|
88
85
|
export function openSketchEditor(sketchId) {
|
|
89
86
|
captureController?.abort();
|
|
90
87
|
captureController = null;
|
|
91
|
-
publish({ captureState: "idle", captured: null, editingId: sketchId, error: null,
|
|
88
|
+
publish({ captureState: "idle", captured: null, editingId: sketchId, error: null, draftId: null });
|
|
92
89
|
}
|
|
93
90
|
export function closeSketchEditor() {
|
|
94
91
|
if (snapshot.captureState !== "ready" && snapshot.editingId === null)
|