nudge-ui 0.2.0 → 0.2.2
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/bin/nudge-ui.mjs +3 -1
- package/dist/cli/agentCommand.d.ts +8 -0
- package/dist/cli/agentCommand.js +59 -0
- package/dist/client.mjs +878 -11
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +63 -11
- package/dist/hosts/astro/integration.d.ts +2 -2
- package/dist/hosts/astro/integration.js +4 -3
- package/dist/hosts/environment.d.ts +15 -0
- package/dist/hosts/environment.js +16 -0
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +56 -7
- package/dist/hosts/next/wrapper.d.ts +2 -0
- package/dist/hosts/next/wrapper.js +32 -3
- package/dist/hosts/projectBridge.d.ts +21 -0
- package/dist/hosts/projectBridge.js +98 -0
- package/dist/hosts/static/cli.d.ts +1 -1
- package/dist/hosts/static/cli.js +6 -1
- package/dist/hosts/static/manifest.d.ts +1 -1
- package/dist/hosts/static/manifest.js +3 -1
- package/dist/hosts/static/server.js +45 -3
- package/dist/hosts/vite/vite.d.ts +6 -1
- package/dist/hosts/vite/vite.js +82 -15
- package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
- package/dist/inspector/agent/McpConnectionDialog.js +20 -37
- package/dist/inspector/agent/client.d.ts +16 -3
- package/dist/inspector/agent/client.js +114 -11
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +11 -4
- package/dist/inspector/agent/httpTransport.d.ts +8 -0
- package/dist/inspector/agent/httpTransport.js +78 -18
- package/dist/inspector/agent/protocol.d.ts +11 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +1 -1
- package/dist/inspector/agent/verification.js +69 -12
- package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
- package/dist/inspector/canvas/CanvasCard.js +181 -91
- package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
- package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
- package/dist/inspector/canvas/CanvasToolbar.js +24 -0
- package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
- package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
- package/dist/inspector/canvas/canvasResize.d.ts +30 -0
- package/dist/inspector/canvas/canvasResize.js +52 -0
- package/dist/inspector/canvas/canvasStore.d.ts +30 -6
- package/dist/inspector/canvas/canvasStore.js +128 -61
- package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
- package/dist/inspector/canvas/frameProtocol.js +63 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +16 -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 +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +42 -29
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +385 -58
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
- package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
- package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
- package/dist/inspector/canvas/sessionStore.d.ts +1 -0
- package/dist/inspector/canvas/sessionStore.js +12 -4
- package/dist/inspector/canvas/staleChangeDetector.js +16 -14
- package/dist/inspector/changes/changesLog.js +31 -1
- package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
- package/dist/inspector/changes/managedStyleProjection.js +3 -3
- package/dist/inspector/changes/structuralTypes.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.js +29 -3
- package/dist/inspector/clientManifest.d.ts +7 -0
- package/dist/inspector/clientManifest.js +12 -0
- package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
- package/dist/inspector/index.d.ts +2 -0
- package/dist/inspector/index.js +80 -32
- package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
- package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
- package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
- package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
- package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
- package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
- package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
- package/dist/inspector/inline-text/textEditChange.js +165 -0
- package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
- package/dist/inspector/inspection/selectionProperty.js +1 -0
- package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
- package/dist/inspector/overlay/DomNavigator.js +40 -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/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +351 -0
- package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
- package/dist/inspector/overlay/structuralGestures.js +12 -10
- package/dist/inspector/projection/managedStylesheet.js +4 -1
- package/dist/inspector/projection/renderedInstance.d.ts +2 -0
- package/dist/inspector/projection/renderedInstance.js +5 -1
- package/dist/inspector/projection/structuralProjection.d.ts +2 -0
- package/dist/inspector/projection/structuralProjection.js +85 -1
- package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
- package/dist/inspector/projection/textProjection.d.ts +2 -0
- package/dist/inspector/projection/textProjection.js +10 -4
- package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
- package/dist/inspector/prompt/clipboardHandoff.js +3 -3
- package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
- package/dist/inspector/prompt/generatePrompt.js +19 -2
- package/dist/inspector/runtime/editorShell.d.ts +2 -0
- package/dist/inspector/runtime/editorShell.js +4 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/hierarchy.d.ts +6 -0
- package/dist/inspector/selection/hierarchy.js +8 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
- package/dist/inspector/settings/SettingsDialog.js +12 -2
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +26 -22
- package/dist/inspector/shell/CopyPromptButton.js +102 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +38 -42
- package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalButton.js +57 -0
- package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
- package/dist/inspector/shell/originalPreview.d.ts +9 -0
- package/dist/inspector/shell/originalPreview.js +61 -0
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +15 -0
- package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
- package/dist/inspector/sketch/SketchChanges.js +72 -0
- package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
- package/dist/inspector/sketch/SketchEditor.js +402 -0
- package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
- package/dist/inspector/sketch/SketchEntryButton.js +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
- package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
- package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
- package/dist/inspector/sketch/SketchOverlay.js +420 -0
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
- package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
- package/dist/inspector/sketch/SketchWorkspace.js +152 -0
- package/dist/inspector/sketch/capture.d.ts +55 -0
- package/dist/inspector/sketch/capture.js +350 -0
- package/dist/inspector/sketch/freehand.d.ts +13 -0
- package/dist/inspector/sketch/freehand.js +64 -0
- package/dist/inspector/sketch/geometry.d.ts +17 -0
- package/dist/inspector/sketch/geometry.js +98 -0
- package/dist/inspector/sketch/handoff.d.ts +21 -0
- package/dist/inspector/sketch/handoff.js +72 -0
- package/dist/inspector/sketch/interaction.d.ts +21 -0
- package/dist/inspector/sketch/interaction.js +98 -0
- package/dist/inspector/sketch/model.d.ts +104 -0
- package/dist/inspector/sketch/model.js +282 -0
- package/dist/inspector/sketch/persistence.d.ts +21 -0
- package/dist/inspector/sketch/persistence.js +205 -0
- package/dist/inspector/sketch/raster.d.ts +22 -0
- package/dist/inspector/sketch/raster.js +203 -0
- package/dist/inspector/sketch/sketchNote.d.ts +10 -0
- package/dist/inspector/sketch/sketchNote.js +30 -0
- package/dist/inspector/sketch/store.d.ts +47 -0
- package/dist/inspector/sketch/store.js +434 -0
- package/dist/inspector/styleEditors/BorderEditor.js +15 -19
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
- package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
- package/dist/inspector/styleEditors/ColorPicker.js +11 -36
- package/dist/inspector/styleEditors/GapField.d.ts +3 -2
- package/dist/inspector/styleEditors/GapField.js +2 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +11 -8
- package/dist/inspector/styleEditors/SpacingBox.js +31 -33
- package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
- package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
- package/dist/inspector/styleEditors/layoutValue.js +3 -0
- package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
- package/dist/inspector/styleEditors/nudgeValue.js +93 -5
- package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
- package/dist/inspector/styleEditors/stylePresence.js +22 -0
- package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
- package/dist/inspector/styleEditors/textRelevance.js +35 -0
- package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
- package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
- package/dist/inspector/tokens/TokenField.js +130 -7
- 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 +2 -0
- package/dist/inspector/tokens/editActions.js +2 -2
- package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
- package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
- package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
- package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
- package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
- package/dist/inspector/tokens/resolution.d.ts +3 -26
- package/dist/inspector/tokens/resolution.js +44 -735
- package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
- package/dist/inspector/tokens/tokenAvailability.js +204 -0
- package/dist/inspector/ui/Button.d.ts +1 -1
- package/dist/inspector/ui/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- package/dist/inspector/ui/SideValuesField.js +2 -2
- package/dist/inspector/ui/StatusCallout.d.ts +4 -1
- package/dist/inspector/ui/StatusCallout.js +2 -2
- package/dist/inspector/ui/dragCursor.d.ts +8 -0
- package/dist/inspector/ui/dragCursor.js +21 -0
- package/dist/inspector/ui/styles.js +21 -17
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +276 -58
- package/dist/transport/editor.d.ts +47 -0
- package/dist/transport/editor.js +163 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +10 -0
- package/dist/transport/routes.js +10 -0
- package/package.json +3 -1
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- package/dist/inspector/shell/panelLayout.js +0 -29
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { IconLayoutSidebarRight, IconSettings } from "@tabler/icons-react";
|
|
4
4
|
import { useInspectorOpen, toggleInspector, setInspectorOpen } from "./openStore.js";
|
|
5
|
-
import { useSelectedElement, useSelectedElements,
|
|
6
|
-
import { InspectorOverlay } from "../overlay/InspectorOverlay.js";
|
|
5
|
+
import { useSelectedElement, useSelectedElements, setSelectedElement, removeSelectedElement, } from "../selection/selectionStore.js";
|
|
7
6
|
import { findTokenRow } from "../styleEditors/rowLookup.js";
|
|
8
7
|
import { useBrowserCssInspection } from "../inspection/useBrowserCssInspection.js";
|
|
9
8
|
import { resolveSelectionFromElement } from "../selection/resolveSelection.js";
|
|
10
9
|
import { SpacingBox } from "../styleEditors/SpacingBox.js";
|
|
11
10
|
import { Typography } from "../styleEditors/Typography.js";
|
|
11
|
+
import { isTextRelevant } from "../styleEditors/textRelevance.js";
|
|
12
12
|
import { ColorPicker } from "../styleEditors/ColorPicker.js";
|
|
13
13
|
import { BorderEditor } from "../styleEditors/BorderEditor.js";
|
|
14
14
|
import { AppearanceSection } from "../styleEditors/AppearanceSection.js";
|
|
@@ -20,27 +20,36 @@ import { countSourceSiteMatches, getEditScope, relinkElement, sourceSiteSelector
|
|
|
20
20
|
import { canEditStyles } from "../tokens/editActions.js";
|
|
21
21
|
import { Button } from "../ui/Button.js";
|
|
22
22
|
import { CopyPromptButton } from "./CopyPromptButton.js";
|
|
23
|
+
import { PeekOriginalButton } from "./PeekOriginalButton.js";
|
|
23
24
|
import { StatusCallout } from "../ui/StatusCallout.js";
|
|
24
25
|
import { IconButton } from "../ui/IconButton.js";
|
|
25
26
|
import { UI_STYLES } from "../ui/styles.js";
|
|
26
27
|
import { getActiveStyleState, setActiveStyleState } from "./styleState.js";
|
|
27
28
|
import { isEditableEvent } from "./shortcuts.js";
|
|
28
|
-
import { clearInspectorLayout, setInspectorLayoutOpen } from "./panelLayout.js";
|
|
29
29
|
import { formatInspectorLabel } from "../ui/labels.js";
|
|
30
30
|
import { FieldRow } from "../ui/FieldRow.js";
|
|
31
31
|
import { Select } from "../ui/Select.js";
|
|
32
|
-
import { enterCanvas, useCanvasMode } from "../canvas/canvasStore.js";
|
|
33
32
|
import { clearRestoreCount, clearSession } from "../canvas/sessionStore.js";
|
|
33
|
+
import { hasWriteLease } from "../canvas/workspaceLease.js";
|
|
34
|
+
import { useFocusedCardId, useSelectedCardId, } from "../canvas/canvasStore.js";
|
|
35
|
+
import { getActiveCanvasFrame } from "../canvas/activeCanvasDocument.js";
|
|
34
36
|
import { getElementWindow } from "../runtime/domRealm.js";
|
|
35
37
|
import { deleteElement, nudgeElement } from "../overlay/structuralGestures.js";
|
|
36
38
|
import { AtRuleContextProvider } from "../ui/AtRuleContext.js";
|
|
37
39
|
import { ComponentPropsSection } from "../componentSemantics/ComponentPropsSection.js";
|
|
38
|
-
import { cancelInlineTextEdit,
|
|
40
|
+
import { cancelInlineTextEdit, isInlineTextEditingActive, useInlineTextDiagnostic, } from "../inline-text/inlineTextEditor.js";
|
|
41
|
+
import { getInlineTextFeedback } from "../inline-text/inlineTextFeedback.js";
|
|
39
42
|
import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
|
|
40
|
-
import { DomNavigation } from "./DomNavigation.js";
|
|
41
43
|
import { EmptyState } from "./EmptyState.js";
|
|
42
44
|
import { createStyleSelection } from "../selection/styleSelection.js";
|
|
43
45
|
import { intersectTokenEntries } from "../inspection/selectionProperty.js";
|
|
46
|
+
import { isNudgeUiDev } from "../runtime/devFlag.js";
|
|
47
|
+
import { isDemoRuntime } from "../runtime/runtimeConfig.js";
|
|
48
|
+
import { SketchWorkspace } from "../sketch/SketchWorkspace.js";
|
|
49
|
+
import { cancelSketchInteraction, useSketchInteractionActive } from "../sketch/interaction.js";
|
|
50
|
+
import { clearSketchClipboardHandoff } from "../sketch/handoff.js";
|
|
51
|
+
import { clearSketchesForProject } from "../sketch/store.js";
|
|
52
|
+
import { closeSketchNote } from "../sketch/sketchNote.js";
|
|
44
53
|
function findFirstTokenRow(rows, properties) {
|
|
45
54
|
for (const property of properties) {
|
|
46
55
|
const row = findTokenRow(rows, property);
|
|
@@ -55,13 +64,6 @@ function findFirstTokenRow(rows, properties) {
|
|
|
55
64
|
return null;
|
|
56
65
|
}
|
|
57
66
|
export { toggleInspector, setInspectorOpen };
|
|
58
|
-
let inspectorHost = null;
|
|
59
|
-
export function setInspectorHost(host) {
|
|
60
|
-
inspectorHost = host;
|
|
61
|
-
}
|
|
62
|
-
function resolveHost() {
|
|
63
|
-
return inspectorHost ?? document.getElementById("nudge-ui-root") ?? document.body;
|
|
64
|
-
}
|
|
65
67
|
function nodeMatchesSelector(node, selector) {
|
|
66
68
|
if (!selector || node.nodeType !== node.ELEMENT_NODE)
|
|
67
69
|
return false;
|
|
@@ -87,24 +89,20 @@ function scopeMutationAffectsSelection(records, selected) {
|
|
|
87
89
|
export function InspectorShell() {
|
|
88
90
|
const isOpen = useInspectorOpen();
|
|
89
91
|
const runtimeConfig = useNudgeUiRuntimeConfig();
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
92
|
+
const sketchEnabled = isNudgeUiDev() && !isDemoRuntime();
|
|
93
|
+
const sketchActive = useSketchInteractionActive();
|
|
94
|
+
const selectedCardId = useSelectedCardId();
|
|
95
|
+
const focusedCardId = useFocusedCardId();
|
|
96
|
+
const sketchHost = selectedCardId || focusedCardId ? getActiveCanvasFrame() : null;
|
|
94
97
|
const selected = useSelectedElement();
|
|
95
98
|
const selectedElements = useSelectedElements();
|
|
96
|
-
const
|
|
97
|
-
const inlineTextSession = useInlineTextSession();
|
|
99
|
+
const inlineTextFeedback = getInlineTextFeedback(useInlineTextDiagnostic());
|
|
98
100
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
99
101
|
const [settingsSection, setSettingsSection] = useState("instructions");
|
|
100
102
|
const [scopeRevision, refreshScope] = useState(0);
|
|
101
103
|
const [styleState, setStyleState] = useState(getActiveStyleState());
|
|
102
104
|
const cssInspection = useBrowserCssInspection(selectedElements, styleState);
|
|
103
105
|
const isMultiSelection = selectedElements.length > 1;
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
setInspectorLayoutOpen(isOpen);
|
|
106
|
-
return clearInspectorLayout;
|
|
107
|
-
}, [isOpen]);
|
|
108
106
|
useEffect(() => {
|
|
109
107
|
// A new selection should never inherit an incidental state from the
|
|
110
108
|
// previous element. Base is the inspector's deliberate default.
|
|
@@ -147,7 +145,7 @@ export function InspectorShell() {
|
|
|
147
145
|
return () => observers.forEach((observer) => observer.disconnect());
|
|
148
146
|
}, [selectedElements, scopeRevision]);
|
|
149
147
|
useEffect(() => {
|
|
150
|
-
if (
|
|
148
|
+
if (sketchActive)
|
|
151
149
|
return;
|
|
152
150
|
function onKeydown(event) {
|
|
153
151
|
if (isInlineTextEditingActive())
|
|
@@ -200,13 +198,14 @@ export function InspectorShell() {
|
|
|
200
198
|
}
|
|
201
199
|
window.addEventListener("keydown", onKeydown);
|
|
202
200
|
return () => window.removeEventListener("keydown", onKeydown);
|
|
203
|
-
}, [
|
|
201
|
+
}, [isMultiSelection, selected, sketchActive]);
|
|
204
202
|
const inspectionSnapshot = cssInspection.element;
|
|
205
203
|
const styleSelection = useMemo(() => createStyleSelection(selectedElements, cssInspection.elements, selected), [cssInspection.elements, selected, selectedElements]);
|
|
206
204
|
const selectionEditable = useMemo(() => styleSelection ? canEditStyles(styleSelection.target) : false, [scopeRevision, styleSelection]);
|
|
207
205
|
const tokenEntries = useMemo(() => intersectTokenEntries(cssInspection.elements.map((snapshot) => snapshot.availableTokens)), [cssInspection.elements]);
|
|
208
206
|
const tokenRows = useMemo(() => styleSelection ? [...styleSelection.primaryRows] : [], [styleSelection]);
|
|
209
207
|
const availableInteractionStates = inspectionSnapshot?.availableStates ?? [];
|
|
208
|
+
const showText = selectedElements.length > 0 && selectedElements.every((element, index) => isTextRelevant(element.domElement, cssInspection.elements[index]?.properties ?? []));
|
|
210
209
|
const showInteractionState = !isMultiSelection && availableInteractionStates.length > 2;
|
|
211
210
|
const paintedBackgroundRow = findFirstTokenRow(tokenRows, ["background-color", "background"]);
|
|
212
211
|
const backgroundTokenRow = useMemo(() => {
|
|
@@ -227,24 +226,24 @@ export function InspectorShell() {
|
|
|
227
226
|
function refreshScopeState() {
|
|
228
227
|
refreshScope((revision) => revision + 1);
|
|
229
228
|
}
|
|
230
|
-
function handleCanvasModeButton() {
|
|
231
|
-
// Inline text editing is controller-owned. Canvas renderer documents
|
|
232
|
-
// receive projections only, so dispose the active controller session
|
|
233
|
-
// before mounting cards rather than probing a stale iframe document.
|
|
234
|
-
disposeInlineTextEdit("frame-disposed");
|
|
235
|
-
enterCanvas();
|
|
236
|
-
}
|
|
237
229
|
function openSettings(section) {
|
|
238
230
|
setSettingsSection(section);
|
|
239
231
|
setSettingsOpen(true);
|
|
240
232
|
}
|
|
241
|
-
|
|
233
|
+
function clearInspectorSession() {
|
|
234
|
+
clearSession();
|
|
235
|
+
clearRestoreCount();
|
|
236
|
+
if (!hasWriteLease())
|
|
237
|
+
return;
|
|
238
|
+
cancelSketchInteraction();
|
|
239
|
+
closeSketchNote();
|
|
240
|
+
clearSketchClipboardHandoff();
|
|
241
|
+
void clearSketchesForProject(runtimeConfig.projectId).catch(() => undefined);
|
|
242
|
+
}
|
|
243
|
+
return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "inspector-styles", children: UI_STYLES }), _jsxs("div", { className: "panel", "data-open": isOpen ? "true" : "false", children: [_jsxs("div", { className: "panel__tabs", "aria-label": "Inspector controls", children: [_jsxs("div", { className: "panel__header-row", "data-test": "inspect-tab", children: [_jsx(IconButton, { variant: "quiet", label: "Collapse inspector", "data-test": "collapse-inspector", disabled: sketchActive, style: { marginLeft: "-8px" }, onClick: () => {
|
|
242
244
|
cancelInlineTextEdit();
|
|
243
245
|
setInspectorOpen(false);
|
|
244
|
-
}, children: _jsx(IconLayoutSidebarRight, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }), _jsxs("div", { className: "panel__header-actions", children: [_jsx(IconButton, { variant: "quiet",
|
|
245
|
-
inlineTextSession.scopeChoices.length > 0) ? (_jsxs("section", { className: "inline-text-editor", "data-test": "inline-text-editor", children: [_jsx("div", { className: "inline-text-editor__binding", "data-test": "inline-text-binding", children: inlineTextSession.binding.kind === "component-prop"
|
|
246
|
-
? `${inlineTextSession.binding.target.componentName}.${inlineTextSession.binding.property}`
|
|
247
|
-
: "Rendered text" }), inlineTextSession.bindingChoices.length > 1 ? (_jsxs("div", { className: "inline-text-editor__chooser", "data-test": "inline-binding-chooser", children: [_jsx("div", { className: "inline-text-editor__chooser-label", children: "Choose binding" }), _jsx("div", { className: "inline-text-editor__chooser-options", role: "group", "aria-label": "Text binding", children: inlineTextSession.bindingChoices.map((choice, index) => (_jsxs(Button, { size: "compact", variant: inlineTextSession.selectedBindingIndex === index ? "primary" : "quiet", "data-test": "inline-binding-choice", "data-index": index, "aria-pressed": inlineTextSession.selectedBindingIndex === index, onMouseDown: (event) => event.preventDefault(), onClick: () => inlineTextSession.chooseBinding(index), children: [choice.binding.target.componentName, ".", choice.binding.property] }, `${choice.binding.target.callsiteId}:${choice.binding.property}`))) })] })) : null, inlineTextSession.scopeChoices.length > 0 ? (_jsxs("div", { className: "inline-text-editor__chooser", "data-test": "inline-scope-chooser", children: [_jsx("div", { className: "inline-text-editor__chooser-label", children: "Apply to" }), _jsx("div", { className: "inline-text-editor__chooser-options", role: "group", "aria-label": "Text edit scope", children: inlineTextSession.scopeChoices.map((scope) => (_jsx(Button, { size: "compact", variant: inlineTextSession.scope === scope ? "primary" : "quiet", "data-test": `inline-scope-${scope}`, "aria-pressed": inlineTextSession.scope === scope, onMouseDown: (event) => event.preventDefault(), onClick: () => inlineTextSession.chooseScope(scope), children: scope === "source-site" ? "All outputs" : "This rendered item" }, scope))) })] })) : null, _jsxs("div", { className: "inline-text-editor__actions", children: [_jsx(Button, { size: "compact", variant: "secondary", "data-test": "inline-text-cancel", onClick: () => inlineTextSession.cancel(), children: "Cancel" }), _jsx(Button, { size: "compact", "data-test": "inline-text-commit", disabled: inlineTextSession.bindingChoices.length > 1 && inlineTextSession.selectedBindingIndex === null, onClick: () => inlineTextSession.commit(), children: "Done" })] })] })) : null, selected ? (_jsxs(_Fragment, { children: [domNavigationEnabled && !isMultiSelection ? _jsx(DomNavigation, { selected: selected, hierarchy: hierarchy }) : null, isMultiSelection || showInteractionState || hasEditScopeCallout ? (_jsxs("div", { className: `selection${hasEditScopeCallout ? "" : " selection--without-scope-callout"}`, "data-test": "selection", "data-selected-cid": selected.cid, "data-selected-src": selected.src, "data-selected-count": selectedElements.length, children: [isMultiSelection ? (_jsx("div", { className: "selection__summary", "data-test": "multi-selection-summary", children: _jsxs("span", { className: "selection__count", children: [selectedElements.length, " elements selected"] }) })) : null, hasEditScopeCallout ? (_jsx(StatusCallout, { tone: editScope === "rendered-instance" ? "neutral" : "accent", "data-test": "edit-scope", "data-lost": "false", children: editScope === "rendered-instance" ? (_jsxs("div", { className: "scope__unlinked", children: [_jsx("span", { children: "Element unlinked" }), _jsx(Button, { size: "compact", className: "scope__action", "data-test": "relink-element", onClick: () => {
|
|
246
|
+
}, children: _jsx(IconLayoutSidebarRight, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }), _jsxs("div", { className: "panel__header-actions", children: [_jsx(PeekOriginalButton, {}), _jsx(IconButton, { variant: "quiet", label: "Settings", title: "Settings", "data-test": "settings-button", onClick: () => openSettings("instructions"), children: _jsx(IconSettings, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) })] })] }), _jsx("div", { className: "panel__copy-row", children: _jsx(CopyPromptButton, { settingsOpen: settingsOpen, settingsSection: settingsSection, onOpenSettings: openSettings, onSettingsOpenChange: setSettingsOpen }) })] }), _jsxs("div", { className: "panel__body", children: [inlineTextFeedback ? (_jsx(StatusCallout, { tone: "danger", className: "inline-text-diagnostic", "data-test": "inline-text-diagnostic", role: "status", "aria-live": "polite", "aria-atomic": "true", children: _jsx("span", { children: inlineTextFeedback.message }) })) : null, selected ? (_jsxs(_Fragment, { children: [isMultiSelection || showInteractionState || hasEditScopeCallout ? (_jsxs("div", { className: `selection${hasEditScopeCallout ? "" : " selection--without-scope-callout"}`, "data-test": "selection", "data-selected-cid": selected.cid, "data-selected-src": selected.src, "data-selected-count": selectedElements.length, children: [isMultiSelection ? (_jsx("div", { className: "selection__summary", "data-test": "multi-selection-summary", children: _jsxs("span", { className: "selection__count", children: [selectedElements.length, " elements selected"] }) })) : null, hasEditScopeCallout ? (_jsx(StatusCallout, { tone: editScope === "rendered-instance" ? "neutral" : "accent", "data-test": "edit-scope", "data-lost": "false", children: editScope === "rendered-instance" ? (_jsxs("div", { className: "scope__unlinked", children: [_jsx("span", { children: "Element unlinked" }), _jsx(Button, { size: "compact", className: "scope__action", "data-test": "relink-element", onClick: () => {
|
|
248
247
|
const overrideId = relinkElement(selected.domElement);
|
|
249
248
|
if (overrideId)
|
|
250
249
|
discardChangesForInstanceOverride(overrideId);
|
|
@@ -261,8 +260,5 @@ export function InspectorShell() {
|
|
|
261
260
|
return;
|
|
262
261
|
setActiveStyleState(nextState);
|
|
263
262
|
setStyleState(nextState);
|
|
264
|
-
} }) })) : null] })) : null, !isMultiSelection ? _jsx(ComponentPropsSection, { selected: selected }) : null, selectionEditable ? (_jsx(AtRuleContextProvider, { rows: tokenRows, children: _jsxs("div", { className: "style-editors", "data-test": "style-editors", children: [_jsx(LayoutSection, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `layout-${styleState}`), _jsx(SpacingBox, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `spacing-${styleState}`), _jsx(AppearanceSection, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `appearance-${styleState}`), _jsx(Typography, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `type-${styleState}`), _jsx(ColorPicker, { element: selected, selection: styleSelection, property: "color", entries: tokenEntries, tokenRow: findTokenRow(tokenRows, "color") }, `color-${styleState}`), _jsx(ColorPicker, { element: selected, selection: styleSelection, property: "background-color", entries: tokenEntries, tokenRow: backgroundTokenRow }, `background-${styleState}`), _jsx(BorderEditor, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `border-${styleState}`), _jsx(BoxShadowEditor, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `box-shadow-${styleState}`)] }) })) : (_jsx(StatusCallout, { tone: "neutral", "data-test": "multi-selection-uneditable", children: "These repeated items are indistinguishable, so Nudge UI cannot safely target only part of the group. Select every repeated item to edit them together." }))] })) : (_jsx(EmptyState, {})), _jsx(ChangesLog, { onClearSession: () => {
|
|
265
|
-
clearSession();
|
|
266
|
-
clearRestoreCount();
|
|
267
|
-
} })] })] }), !isOpen ? (_jsx("div", { className: "panel__restore", children: _jsx(IconButton, { variant: "quiet", label: "Show inspector", className: "panel__restore-button", "data-test": "show-inspector", onClick: () => setInspectorOpen(true), children: _jsx(IconLayoutSidebarRight, { size: 16, stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }) }) })) : null] }));
|
|
263
|
+
} }) })) : null] })) : null, !isMultiSelection ? _jsx(ComponentPropsSection, { selected: selected }) : null, selectionEditable ? (_jsx(AtRuleContextProvider, { rows: tokenRows, children: _jsxs("div", { className: "style-editors", "data-test": "style-editors", children: [_jsx(LayoutSection, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `layout-${styleState}`), _jsx(SpacingBox, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `spacing-${styleState}`), _jsx(AppearanceSection, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `appearance-${styleState}`), showText ? _jsxs(_Fragment, { children: [_jsx(Typography, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `type-${styleState}`), _jsx(ColorPicker, { element: selected, selection: styleSelection, property: "color", entries: tokenEntries, tokenRow: findTokenRow(tokenRows, "color") }, `color-${styleState}`)] }) : null, _jsx(ColorPicker, { element: selected, selection: styleSelection, property: "background-color", entries: tokenEntries, tokenRow: backgroundTokenRow }, `background-${styleState}`), _jsx(BorderEditor, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `border-${styleState}`), _jsx(BoxShadowEditor, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `box-shadow-${styleState}`)] }) })) : (_jsx(StatusCallout, { tone: "neutral", "data-test": "multi-selection-uneditable", children: "These repeated items are indistinguishable, so Nudge UI cannot safely target only part of the group. Select every repeated item to edit them together." }))] })) : (_jsx(EmptyState, {})), _jsx(ChangesLog, { onClearSession: clearInspectorSession })] })] }), sketchEnabled ? (_jsx(SketchWorkspace, { hostElement: sketchHost, projectId: runtimeConfig.projectId })) : null, !isOpen ? (_jsx("div", { className: "panel__restore", children: _jsx(IconButton, { variant: "quiet", label: "Show inspector", className: "panel__restore-button", "data-test": "show-inspector", onClick: () => setInspectorOpen(true), children: _jsx(IconLayoutSidebarRight, { size: 16, stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }) }) })) : null] }));
|
|
268
264
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Press-and-hold button that previews the page without inspector changes
|
|
4
|
+
* (NUD-30). Releasing restores the edited preview; canonical intent is never
|
|
5
|
+
* cleared, so nothing is lost while held.
|
|
6
|
+
*/
|
|
7
|
+
export declare function PeekOriginalButton(): ReactElement;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useSyncExternalStore } from "react";
|
|
3
|
+
import { IconButton } from "../ui/IconButton.js";
|
|
4
|
+
import { PeekOriginalIcon } from "./PeekOriginalIcon.js";
|
|
5
|
+
import { workspaceChangeStore } from "../changes/workspaceChanges.js";
|
|
6
|
+
import { setOriginalPreviewActive, useOriginalPreviewActive, } from "./originalPreview.js";
|
|
7
|
+
function useWorkspaceChangeCount() {
|
|
8
|
+
return useSyncExternalStore(workspaceChangeStore.subscribe, () => {
|
|
9
|
+
const snapshot = workspaceChangeStore.getSnapshot();
|
|
10
|
+
return snapshot.changes.length + snapshot.structuralChanges.length;
|
|
11
|
+
}, () => 0);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Press-and-hold button that previews the page without inspector changes
|
|
15
|
+
* (NUD-30). Releasing restores the edited preview; canonical intent is never
|
|
16
|
+
* cleared, so nothing is lost while held.
|
|
17
|
+
*/
|
|
18
|
+
export function PeekOriginalButton() {
|
|
19
|
+
const active = useOriginalPreviewActive();
|
|
20
|
+
const changeCount = useWorkspaceChangeCount();
|
|
21
|
+
const disabled = changeCount === 0;
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!active)
|
|
24
|
+
return;
|
|
25
|
+
const release = () => setOriginalPreviewActive(false);
|
|
26
|
+
window.addEventListener("pointerup", release);
|
|
27
|
+
window.addEventListener("pointercancel", release);
|
|
28
|
+
window.addEventListener("blur", release);
|
|
29
|
+
return () => {
|
|
30
|
+
window.removeEventListener("pointerup", release);
|
|
31
|
+
window.removeEventListener("pointercancel", release);
|
|
32
|
+
window.removeEventListener("blur", release);
|
|
33
|
+
};
|
|
34
|
+
}, [active]);
|
|
35
|
+
useEffect(() => () => {
|
|
36
|
+
setOriginalPreviewActive(false);
|
|
37
|
+
}, []);
|
|
38
|
+
return (_jsx(IconButton, { variant: "quiet", "data-test": "peek-original-button", label: "Hold to view original", title: disabled ? "No changes to preview" : "Hold to view original", "aria-pressed": active, "data-active": active ? "true" : "false", disabled: disabled, onPointerDown: (event) => {
|
|
39
|
+
if (disabled)
|
|
40
|
+
return;
|
|
41
|
+
if (event.pointerType === "mouse" && event.button !== 0)
|
|
42
|
+
return;
|
|
43
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
44
|
+
setOriginalPreviewActive(true);
|
|
45
|
+
}, onPointerUp: () => setOriginalPreviewActive(false), onPointerCancel: () => setOriginalPreviewActive(false), onLostPointerCapture: () => setOriginalPreviewActive(false), onKeyDown: (event) => {
|
|
46
|
+
if (disabled || event.repeat)
|
|
47
|
+
return;
|
|
48
|
+
if (event.key === " " || event.key === "Enter")
|
|
49
|
+
setOriginalPreviewActive(true);
|
|
50
|
+
}, onKeyUp: (event) => {
|
|
51
|
+
if (event.key === " " || event.key === "Enter")
|
|
52
|
+
setOriginalPreviewActive(false);
|
|
53
|
+
}, onBlur: () => setOriginalPreviewActive(false), onContextMenu: (event) => {
|
|
54
|
+
if (active)
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
}, children: _jsx(PeekOriginalIcon, {}) }));
|
|
57
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Before/after comparison glyph for the hold-to-view-original button.
|
|
4
|
+
* Custom artwork rendered with the shared inspector icon tokens so it
|
|
5
|
+
* matches the neighboring header icons.
|
|
6
|
+
*/
|
|
7
|
+
export declare function PeekOriginalIcon(): ReactElement;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Before/after comparison glyph for the hold-to-view-original button.
|
|
4
|
+
* Custom artwork rendered with the shared inspector icon tokens so it
|
|
5
|
+
* matches the neighboring header icons.
|
|
6
|
+
*/
|
|
7
|
+
export function PeekOriginalIcon() {
|
|
8
|
+
return (_jsxs("svg", { viewBox: "0 0 24 24", width: "var(--icon-size-small)", height: "var(--icon-size-small)", "aria-hidden": "true", children: [_jsx("path", { d: "M8.66667 5L6 5C4.34315 5 3 6.34315 3 8L3 16C3 17.6569 4.34315 19 6 19L12 19", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "var(--icon-stroke-width)" }), _jsx("path", { d: "M13 5L18 5C19.6569 5 21 6.34315 21 8L21 16C21 17.6569 19.6569 19 18 19L16.3333 19", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "var(--icon-stroke-width)" }), _jsx("line", { x1: "13.7721", y1: "22.2984", x2: "8.29835", y2: "2.22788", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "var(--icon-stroke-width)" })] }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function isOriginalPreviewActive(): boolean;
|
|
2
|
+
export declare function subscribeOriginalPreview(listener: () => void): () => void;
|
|
3
|
+
export declare function useOriginalPreviewActive(): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Hold-to-view-original (NUD-30). While active the host document shows the
|
|
6
|
+
* page without any inspector changes. Canonical intent stays in the workspace
|
|
7
|
+
* store, so releasing restores the edited preview without loss.
|
|
8
|
+
*/
|
|
9
|
+
export declare function setOriginalPreviewActive(next: boolean): void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
import { workspaceChangeStore } from "../changes/workspaceChanges.js";
|
|
3
|
+
import { applyHostWorkspaceProjection, compileWorkspaceProjection, } from "../projection/workspaceProjection.js";
|
|
4
|
+
import { isEditorShellDocument } from "../runtime/editorShell.js";
|
|
5
|
+
let previewingOriginal = false;
|
|
6
|
+
const listeners = new Set();
|
|
7
|
+
export function isOriginalPreviewActive() {
|
|
8
|
+
return previewingOriginal;
|
|
9
|
+
}
|
|
10
|
+
export function subscribeOriginalPreview(listener) {
|
|
11
|
+
listeners.add(listener);
|
|
12
|
+
return () => {
|
|
13
|
+
listeners.delete(listener);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function useOriginalPreviewActive() {
|
|
17
|
+
return useSyncExternalStore(subscribeOriginalPreview, () => previewingOriginal, () => false);
|
|
18
|
+
}
|
|
19
|
+
function notifyOriginalPreviewListeners() {
|
|
20
|
+
for (const listener of listeners) {
|
|
21
|
+
try {
|
|
22
|
+
listener();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* Subscriber errors must not break the preview toggle. */
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function applyEmptyHostProjection() {
|
|
30
|
+
if (isEditorShellDocument())
|
|
31
|
+
return;
|
|
32
|
+
const { revision } = workspaceChangeStore.getSnapshot();
|
|
33
|
+
applyHostWorkspaceProjection({
|
|
34
|
+
sourceRevision: revision,
|
|
35
|
+
managedStyles: { rules: [], css: "" },
|
|
36
|
+
instanceOverrides: [],
|
|
37
|
+
structuralChanges: [],
|
|
38
|
+
textContentChanges: [],
|
|
39
|
+
componentOverrides: [],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function applyCanonicalHostProjection() {
|
|
43
|
+
if (isEditorShellDocument())
|
|
44
|
+
return;
|
|
45
|
+
applyHostWorkspaceProjection(compileWorkspaceProjection(workspaceChangeStore.getSnapshot()));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Hold-to-view-original (NUD-30). While active the host document shows the
|
|
49
|
+
* page without any inspector changes. Canonical intent stays in the workspace
|
|
50
|
+
* store, so releasing restores the edited preview without loss.
|
|
51
|
+
*/
|
|
52
|
+
export function setOriginalPreviewActive(next) {
|
|
53
|
+
if (next === previewingOriginal)
|
|
54
|
+
return;
|
|
55
|
+
previewingOriginal = next;
|
|
56
|
+
if (previewingOriginal)
|
|
57
|
+
applyEmptyHostProjection();
|
|
58
|
+
else
|
|
59
|
+
applyCanonicalHostProjection();
|
|
60
|
+
notifyOriginalPreviewListeners();
|
|
61
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export declare function isEditableTarget(target: EventTarget | null): boolean;
|
|
2
2
|
export declare function isEditableEvent(event: KeyboardEvent): boolean;
|
|
3
|
+
export declare const SEND_PROMPT_HOTKEY_EVENT = "nudge-ui-send-prompt-hotkey";
|
|
4
|
+
export declare function isSendPromptShortcut(event: KeyboardEvent): boolean;
|
|
3
5
|
export declare function isInspectorToggleShortcut(event: KeyboardEvent): boolean;
|
|
6
|
+
export declare function isOriginalPreviewShortcut(event: KeyboardEvent): boolean;
|
|
@@ -14,6 +14,12 @@ function deeplyFocusedElement() {
|
|
|
14
14
|
export function isEditableEvent(event) {
|
|
15
15
|
return event.composedPath().some(isEditableTarget) || isEditableTarget(deeplyFocusedElement());
|
|
16
16
|
}
|
|
17
|
+
export const SEND_PROMPT_HOTKEY_EVENT = "nudge-ui-send-prompt-hotkey";
|
|
18
|
+
export function isSendPromptShortcut(event) {
|
|
19
|
+
if (event.repeat || event.altKey || isEditableEvent(event))
|
|
20
|
+
return false;
|
|
21
|
+
return event.shiftKey && !event.metaKey && !event.ctrlKey && event.code === "KeyS";
|
|
22
|
+
}
|
|
17
23
|
export function isInspectorToggleShortcut(event) {
|
|
18
24
|
if (isEditableEvent(event))
|
|
19
25
|
return false;
|
|
@@ -23,3 +29,12 @@ export function isInspectorToggleShortcut(event) {
|
|
|
23
29
|
return true;
|
|
24
30
|
return event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey && event.code === "KeyI";
|
|
25
31
|
}
|
|
32
|
+
export function isOriginalPreviewShortcut(event) {
|
|
33
|
+
if (event.repeat)
|
|
34
|
+
return false;
|
|
35
|
+
if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey)
|
|
36
|
+
return false;
|
|
37
|
+
if (event.code !== "Backslash")
|
|
38
|
+
return false;
|
|
39
|
+
return !isEditableEvent(event);
|
|
40
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { IconCheck, IconCopy, IconDownload, IconEdit, IconRefresh, IconTrash } from "@tabler/icons-react";
|
|
4
|
+
import { Button } from "../ui/Button.js";
|
|
5
|
+
import { StatusCallout } from "../ui/StatusCallout.js";
|
|
6
|
+
import { copyImageToClipboard, downloadImage } from "./raster.js";
|
|
7
|
+
import { editSketch } from "./SketchWorkspace.js";
|
|
8
|
+
import { removeSketch, requeueSketch, useSketchStore } from "./store.js";
|
|
9
|
+
import {} from "./model.js";
|
|
10
|
+
function routeLabel(url) {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = new URL(url);
|
|
13
|
+
return `${parsed.pathname}${parsed.search}` || "/";
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return url;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function statusLabel(status) {
|
|
20
|
+
switch (status) {
|
|
21
|
+
case "dispatching": return "Sending";
|
|
22
|
+
case "handing-off": return "Handing off";
|
|
23
|
+
case "needs-review": return "Needs review";
|
|
24
|
+
case "unknown": return "Unknown";
|
|
25
|
+
default: return "Pending";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function Thumbnail({ item }) {
|
|
29
|
+
const [url, setUrl] = useState(null);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function")
|
|
32
|
+
return;
|
|
33
|
+
const next = URL.createObjectURL(item.document.annotatedImage);
|
|
34
|
+
setUrl(next);
|
|
35
|
+
return () => URL.revokeObjectURL(next);
|
|
36
|
+
}, [item.document.annotatedImage]);
|
|
37
|
+
return url ? _jsx("img", { className: "sketch-change__thumbnail", src: url, alt: "Sketch annotation", "data-test": "sketch-thumbnail" }) : _jsx("div", { className: "sketch-change__thumbnail sketch-change__thumbnail--empty", "data-test": "sketch-thumbnail" });
|
|
38
|
+
}
|
|
39
|
+
function SketchRow({ item }) {
|
|
40
|
+
const [actionError, setActionError] = useState(null);
|
|
41
|
+
const busy = item.status === "dispatching" || item.status === "handing-off";
|
|
42
|
+
const canReview = item.status === "needs-review" || item.status === "unknown";
|
|
43
|
+
const document = item.document;
|
|
44
|
+
async function copyImage() {
|
|
45
|
+
setActionError(null);
|
|
46
|
+
try {
|
|
47
|
+
await copyImageToClipboard(document.annotatedImage);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
setActionError(error instanceof Error ? error.message : "The image could not be copied.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function runQueueAction(action, failureMessage) {
|
|
54
|
+
setActionError(null);
|
|
55
|
+
try {
|
|
56
|
+
if (!await action())
|
|
57
|
+
setActionError(failureMessage);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
setActionError(error instanceof Error ? error.message : failureMessage);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return (_jsxs("div", { className: "sketch-change__row", "data-test": "sketch-row", "data-sketch-id": document.id, children: [_jsx(Thumbnail, { item: item }), _jsxs("div", { className: "sketch-change__body", children: [_jsxs("div", { className: "sketch-change__heading", children: [_jsx("span", { className: "sketch-change__name", children: document.filename }), _jsx("span", { className: "sketch-change__status", "data-test": "sketch-status", "data-status": item.status, children: statusLabel(item.status) })] }), _jsxs("span", { className: "sketch-change__route", children: [routeLabel(document.capture.url), " \u00B7 revision ", document.revision] }), document.description ? _jsx("p", { className: "sketch-change__description", children: document.description }) : null, document.annotations?.length ? (_jsx("ol", { className: "sketch-change__annotations", children: document.annotations.map((annotation) => (_jsxs("li", { children: [_jsxs("span", { children: [annotation.number, "."] }), " ", annotation.description] }, annotation.id))) })) : null, _jsxs("div", { className: "sketch-change__actions", children: [_jsxs(Button, { size: "compact", variant: "quiet", type: "button", "data-test": "sketch-copy-image", onClick: () => void copyImage(), children: [_jsx(IconCopy, { size: "var(--icon-size-small)", "aria-hidden": "true" }), " Copy image"] }), _jsxs(Button, { size: "compact", variant: "quiet", type: "button", "data-test": "sketch-download-image", onClick: () => downloadImage(document.annotatedImage, document.filename), children: [_jsx(IconDownload, { size: "var(--icon-size-small)", "aria-hidden": "true" }), " Download"] }), _jsxs(Button, { size: "compact", variant: "quiet", type: "button", "data-test": "sketch-reopen", onClick: () => editSketch(document.id), disabled: busy, children: [_jsx(IconEdit, { size: "var(--icon-size-small)", "aria-hidden": "true" }), " Reopen"] }), canReview ? (_jsxs(Button, { size: "compact", variant: "quiet", type: "button", "data-test": "sketch-requeue", onClick: () => void runQueueAction(() => requeueSketch(document.id), "The sketch could not be requeued."), disabled: busy, children: [_jsx(IconRefresh, { size: "var(--icon-size-small)", "aria-hidden": "true" }), " Requeue"] })) : null, _jsxs(Button, { size: "compact", variant: "quiet", type: "button", "data-test": "sketch-mark-done", onClick: () => void runQueueAction(() => removeSketch(document.id), "The sketch could not be removed yet."), disabled: busy, children: [canReview ? _jsx(IconCheck, { size: "var(--icon-size-small)", "aria-hidden": "true" }) : _jsx(IconTrash, { size: "var(--icon-size-small)", "aria-hidden": "true" }), " ", canReview ? "Mark done" : "Delete"] })] }), actionError ? _jsx("span", { className: "sketch-change__error", role: "status", children: actionError }) : null] })] }));
|
|
64
|
+
}
|
|
65
|
+
export function SketchChanges() {
|
|
66
|
+
const store = useSketchStore();
|
|
67
|
+
const items = store.items;
|
|
68
|
+
const hasError = store.error !== null;
|
|
69
|
+
if (items.length === 0 && !hasError)
|
|
70
|
+
return null;
|
|
71
|
+
return (_jsxs("section", { className: "sketch-change", "data-test": "sketches", children: [_jsxs("div", { className: "sketch-change__title", children: ["Sketch annotations ", _jsx("span", { children: items.length })] }), store.error ? _jsx(StatusCallout, { tone: "warning", "data-test": "sketch-storage-error", children: store.error }) : null, items.length > 0 ? items.map((item) => _jsx(SketchRow, { item: item }, `${item.document.id}:${item.document.revision}`)) : (_jsx(StatusCallout, { tone: "warning", children: "Saved sketches need recovery. Reload the page to try again." }))] }));
|
|
72
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { type SketchAnnotation, type SketchDocument, type SketchStroke } from "./model.js";
|
|
3
|
+
import type { CapturedSketch } from "./capture.js";
|
|
4
|
+
export interface SketchEditorProps {
|
|
5
|
+
readonly open: boolean;
|
|
6
|
+
readonly document: SketchDocument | null;
|
|
7
|
+
readonly captured: CapturedSketch | null;
|
|
8
|
+
readonly onCancel: () => void;
|
|
9
|
+
readonly onSave: (input: {
|
|
10
|
+
readonly id?: string;
|
|
11
|
+
readonly capture: SketchDocument["capture"];
|
|
12
|
+
readonly description: string;
|
|
13
|
+
readonly strokes: readonly SketchStroke[];
|
|
14
|
+
readonly annotations?: readonly SketchAnnotation[];
|
|
15
|
+
readonly imageWidth: number;
|
|
16
|
+
readonly imageHeight: number;
|
|
17
|
+
readonly originalImage: Blob;
|
|
18
|
+
readonly annotatedImage: Blob;
|
|
19
|
+
}) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare function SketchEditor({ open, document: sketchDocument, captured, onCancel, onSave }: SketchEditorProps): ReactElement | null;
|