@bendyline/squisq-editor-react 2.0.1 → 2.2.0
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/LICENSE +21 -0
- package/NOTICE.md +40 -0
- package/README.md +10 -2
- package/dist/index.d.ts +683 -60
- package/dist/index.js +9648 -3750
- package/dist/monaco.js +0 -1
- package/dist/styles/index.css +1160 -241
- package/package.json +10 -6
- package/dist/index.js.map +0 -1
- package/dist/monaco.js.map +0 -1
- package/src/BlockCardView.tsx +0 -121
- package/src/BlockPreviewPanel.tsx +0 -69
- package/src/BlockPropertiesPopover.tsx +0 -222
- package/src/DocumentSettingsDialog.tsx +0 -272
- package/src/DropZoneOverlay.tsx +0 -137
- package/src/EditorContext.tsx +0 -1247
- package/src/EditorShell.tsx +0 -1334
- package/src/EmojiPicker.tsx +0 -332
- package/src/FolderView.tsx +0 -131
- package/src/Icon.tsx +0 -26
- package/src/ImageEditor.tsx +0 -395
- package/src/ImageNodeView.tsx +0 -284
- package/src/ImageViewer.tsx +0 -221
- package/src/InlineIcon.ts +0 -84
- package/src/InlinePreviewGutter.tsx +0 -590
- package/src/LinkDialog.tsx +0 -276
- package/src/MediaBin.tsx +0 -500
- package/src/MentionExtension.tsx +0 -261
- package/src/OutlinePanel.tsx +0 -547
- package/src/PlainHtmlPreview.tsx +0 -277
- package/src/PreviewControls.tsx +0 -1250
- package/src/PreviewPanel.tsx +0 -422
- package/src/RawEditor.tsx +0 -731
- package/src/RecorderEntry.tsx +0 -159
- package/src/StatusBar.tsx +0 -51
- package/src/TemplateAnnotation.ts +0 -167
- package/src/TemplateContentPreview.tsx +0 -56
- package/src/TemplatePicker.tsx +0 -1314
- package/src/ThemeCustomizerPanel.tsx +0 -304
- package/src/ThemePicker.tsx +0 -433
- package/src/TimelineBlockPreview.tsx +0 -37
- package/src/TimelineTrack.tsx +0 -671
- package/src/Toolbar.tsx +0 -2587
- package/src/Tooltip.tsx +0 -142
- package/src/TransitionPicker.tsx +0 -390
- package/src/VersionHistoryPanel.tsx +0 -359
- package/src/ViewMenuPanel.tsx +0 -189
- package/src/ViewSwitcher.tsx +0 -56
- package/src/WysiwygEditor.tsx +0 -792
- package/src/__tests__/blockProperties.test.ts +0 -92
- package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +0 -33
- package/src/__tests__/blockRange.test.ts +0 -105
- package/src/__tests__/blockTagActivity.test.ts +0 -183
- package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +0 -75
- package/src/__tests__/buildPreviewDocContent.test.ts +0 -48
- package/src/__tests__/buildPreviewDocTransition.test.ts +0 -92
- package/src/__tests__/canvasSurfaceTextEditing.test.tsx +0 -60
- package/src/__tests__/codeContextSectionView.test.tsx +0 -97
- package/src/__tests__/codeContextZoneManager.test.ts +0 -127
- package/src/__tests__/createShapeLayer.test.ts +0 -46
- package/src/__tests__/customLayoutManagerTheme.test.tsx +0 -34
- package/src/__tests__/detectMarkdown.test.ts +0 -73
- package/src/__tests__/diffContextSections.test.ts +0 -39
- package/src/__tests__/documentSettingsDialog.test.tsx +0 -197
- package/src/__tests__/drawingShapeRoundTrip.test.ts +0 -49
- package/src/__tests__/editorScrollHandoff.test.tsx +0 -102
- package/src/__tests__/editorScrollSync.test.ts +0 -65
- package/src/__tests__/editorShellCodeContext.test.tsx +0 -86
- package/src/__tests__/editorShellProps.test.tsx +0 -485
- package/src/__tests__/embeddedMedia.test.ts +0 -48
- package/src/__tests__/emojiPicker.test.tsx +0 -93
- package/src/__tests__/fileKind.test.ts +0 -112
- package/src/__tests__/headingTransition.test.ts +0 -176
- package/src/__tests__/imageEditAffordance.test.tsx +0 -272
- package/src/__tests__/imageEditModalContract.test.tsx +0 -119
- package/src/__tests__/imageEditorShell.test.tsx +0 -94
- package/src/__tests__/imageEditorState.test.ts +0 -171
- package/src/__tests__/inlinePreviewGutter.test.tsx +0 -62
- package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +0 -103
- package/src/__tests__/jsonEditor.test.tsx +0 -223
- package/src/__tests__/layersPanel.test.tsx +0 -167
- package/src/__tests__/layoutChildRoundTrip.test.ts +0 -71
- package/src/__tests__/linkDialogDocPicker.test.tsx +0 -137
- package/src/__tests__/majorApiTypes.test.ts +0 -26
- package/src/__tests__/mediaAttachmentFlow.test.ts +0 -110
- package/src/__tests__/mediaBinDrop.test.tsx +0 -90
- package/src/__tests__/mediaReferences.test.ts +0 -82
- package/src/__tests__/narrationSave.test.ts +0 -148
- package/src/__tests__/outlinePanel.test.tsx +0 -169
- package/src/__tests__/outlineSource.test.ts +0 -217
- package/src/__tests__/plainHtmlPreview.test.tsx +0 -125
- package/src/__tests__/presentationMode.test.tsx +0 -290
- package/src/__tests__/previewControls.test.tsx +0 -540
- package/src/__tests__/previewPanelAudioFailure.test.tsx +0 -46
- package/src/__tests__/previewPanelPresentation.test.tsx +0 -126
- package/src/__tests__/previewPanelTransforms.test.tsx +0 -117
- package/src/__tests__/propertiesPanel.test.tsx +0 -69
- package/src/__tests__/rawEditorModelIsolation.test.ts +0 -19
- package/src/__tests__/recorderFormats.test.ts +0 -146
- package/src/__tests__/recorderMediaInsert.test.ts +0 -86
- package/src/__tests__/recorderTheme.test.tsx +0 -42
- package/src/__tests__/recorderTimingJson.test.ts +0 -41
- package/src/__tests__/resolveBlockVisual.test.ts +0 -43
- package/src/__tests__/selectionConversions.test.ts +0 -80
- package/src/__tests__/statusBar.test.tsx +0 -27
- package/src/__tests__/teleprompter.test.tsx +0 -190
- package/src/__tests__/templateAnnotationRoundTrip.test.ts +0 -99
- package/src/__tests__/templateContentPreview.test.ts +0 -101
- package/src/__tests__/templatePickerMetadata.test.ts +0 -32
- package/src/__tests__/templatePickerPortal.test.tsx +0 -62
- package/src/__tests__/timelineSource.test.ts +0 -134
- package/src/__tests__/tiptapBridge.test.ts +0 -566
- package/src/__tests__/tiptapBridgeConformance.test.ts +0 -47
- package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +0 -109
- package/src/__tests__/tiptapImageRoundTrip.test.ts +0 -73
- package/src/__tests__/toolbarSelectionConversion.test.tsx +0 -164
- package/src/__tests__/tooltip.test.tsx +0 -72
- package/src/__tests__/transformStyleId.test.ts +0 -13
- package/src/__tests__/transitionCatalog.test.ts +0 -64
- package/src/__tests__/useBlockNavigator.test.tsx +0 -67
- package/src/__tests__/useImageEditor.test.tsx +0 -226
- package/src/__tests__/useJsonEditorTokens.test.ts +0 -59
- package/src/__tests__/useMediaRecorder.test.ts +0 -302
- package/src/__tests__/useTimelineClock.test.ts +0 -21
- package/src/__tests__/versionHistory.test.tsx +0 -197
- package/src/__tests__/viewMenuPanel.test.tsx +0 -130
- package/src/__tests__/wysiwygImageUpload.test.ts +0 -69
- package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +0 -56
- package/src/asciiDiagram/AsciiDiagramExtension.ts +0 -343
- package/src/asciiDiagram/AsciiDiagramWidget.tsx +0 -254
- package/src/asciiDiagram/RepairableDiagramExtension.ts +0 -200
- package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +0 -145
- package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +0 -159
- package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +0 -284
- package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +0 -114
- package/src/asciiDiagram/__tests__/asciiPaste.test.ts +0 -43
- package/src/asciiDiagram/asciiDiagramCommands.ts +0 -174
- package/src/asciiDiagram/asciiDiagramData.ts +0 -149
- package/src/asciiDiagram/asciiDiagramOps.ts +0 -238
- package/src/asciiDiagram/asciiPaste.ts +0 -19
- package/src/blockProperties.ts +0 -88
- package/src/blockRange.ts +0 -132
- package/src/blockSlice.ts +0 -75
- package/src/blockTagActivity.ts +0 -233
- package/src/buildDocumentPreviewMarkdown.ts +0 -168
- package/src/buildPreviewDoc.ts +0 -405
- package/src/codeContext/CodeContextSectionView.tsx +0 -124
- package/src/codeContext/CodeContextZoneManager.ts +0 -149
- package/src/codeContext/CodeContextZones.tsx +0 -121
- package/src/codeContext/diffContextSections.ts +0 -38
- package/src/codeContext/types.ts +0 -75
- package/src/customTemplates/AddBin.tsx +0 -126
- package/src/customTemplates/CustomLayoutManager.tsx +0 -234
- package/src/customTemplates/CustomTemplateContext.tsx +0 -188
- package/src/customTemplates/LayerToolbar.tsx +0 -580
- package/src/customTemplates/ShapeGlyph.tsx +0 -47
- package/src/customTemplates/TemplateDesigner.tsx +0 -433
- package/src/customTemplates/__tests__/library.test.ts +0 -96
- package/src/customTemplates/__tests__/normalizePositions.test.ts +0 -109
- package/src/customTemplates/__tests__/shapeDefs.test.ts +0 -49
- package/src/customTemplates/__tests__/useMemoryLayerAdapter.test.ts +0 -108
- package/src/customTemplates/designer.css +0 -732
- package/src/customTemplates/index.ts +0 -31
- package/src/customTemplates/library.ts +0 -110
- package/src/customTemplates/normalizePositions.ts +0 -75
- package/src/customTemplates/shapeDefs.ts +0 -131
- package/src/customTemplates/thumbnail.tsx +0 -63
- package/src/customTemplates/tokenDefs.ts +0 -60
- package/src/customTemplates/useDocCustomTemplates.ts +0 -52
- package/src/customTemplates/useMemoryLayerAdapter.ts +0 -129
- package/src/customThemes/CustomThemeContext.tsx +0 -185
- package/src/customThemes/CustomThemeDialog.tsx +0 -316
- package/src/customThemes/ImportThemeSection.tsx +0 -178
- package/src/customThemes/__tests__/CustomThemeContext.test.tsx +0 -64
- package/src/customThemes/__tests__/CustomThemeDialog.test.tsx +0 -47
- package/src/customThemes/__tests__/customThemeLibrary.test.ts +0 -60
- package/src/customThemes/__tests__/importThemeSection.test.tsx +0 -192
- package/src/customThemes/customThemeLibrary.ts +0 -100
- package/src/customThemes/index.ts +0 -37
- package/src/customThemes/themeControls.tsx +0 -229
- package/src/customThemes/themeDraft.ts +0 -288
- package/src/customThemes/useDocCustomThemes.ts +0 -49
- package/src/detectMarkdown.ts +0 -62
- package/src/diagram/DiagramCanvas.tsx +0 -259
- package/src/diagram/DiagramMaximizedOverlay.tsx +0 -46
- package/src/diagram/diagramCommands.ts +0 -604
- package/src/diagram/diagramConstants.ts +0 -17
- package/src/diagram/types.ts +0 -35
- package/src/editorScrollSync.ts +0 -73
- package/src/embeddedMedia.ts +0 -78
- package/src/emojiData.ts +0 -1317
- package/src/fileKind.ts +0 -158
- package/src/frontmatter.ts +0 -29
- package/src/frontmatterSettings.ts +0 -23
- package/src/headingTransition.ts +0 -291
- package/src/hooks/useFileDrop.ts +0 -266
- package/src/imageEditor/CanvasSurface.tsx +0 -703
- package/src/imageEditor/ImageVersionHistoryDropdown.tsx +0 -396
- package/src/imageEditor/LayersPanel.tsx +0 -218
- package/src/imageEditor/PropertiesPanel.tsx +0 -474
- package/src/imageEditor/Toolbar.tsx +0 -459
- package/src/imageEditor/createShapeLayer.ts +0 -280
- package/src/imageEditor/icons.tsx +0 -68
- package/src/imageEditor/image-editor.css +0 -574
- package/src/imageEditor/layers/EditorImageLayer.tsx +0 -45
- package/src/imageEditor/layers/EditorShapeLayer.tsx +0 -62
- package/src/imageEditor/layers/EditorTextLayer.tsx +0 -45
- package/src/imageEditor/layers/SelectionHandles.tsx +0 -86
- package/src/imageEditor/state.ts +0 -173
- package/src/imageEditor/useImageEditor.ts +0 -385
- package/src/imageEditor/useImageEditorTokens.ts +0 -70
- package/src/index.ts +0 -470
- package/src/jsonEditor/EmbeddedRichTextField.tsx +0 -81
- package/src/jsonEditor/JsonEditor.tsx +0 -81
- package/src/jsonEditor/JsonEditorContext.tsx +0 -81
- package/src/jsonEditor/RenderNode.tsx +0 -87
- package/src/jsonEditor/editors.tsx +0 -745
- package/src/jsonEditor/index.ts +0 -2
- package/src/jsonEditor/json-editor.css +0 -463
- package/src/jsonEditor/useJsonEditorTokens.ts +0 -33
- package/src/mediaDragMime.ts +0 -32
- package/src/mediaEntries.ts +0 -12
- package/src/mediaReferences.ts +0 -299
- package/src/monaco.ts +0 -35
- package/src/monacoWorkers.ts +0 -89
- package/src/outlineSource.ts +0 -171
- package/src/presentation/PresentationMode.tsx +0 -596
- package/src/rawEditorIsolation.ts +0 -18
- package/src/recorder/RecorderButton.tsx +0 -80
- package/src/recorder/RecorderModal.tsx +0 -706
- package/src/recorder/RecorderPanel.tsx +0 -77
- package/src/recorder/formats.ts +0 -159
- package/src/recorder/hooks/useMediaRecorder.ts +0 -436
- package/src/recorder/hooks/useStreamPreview.ts +0 -47
- package/src/recorder/insertMediaBlock.ts +0 -30
- package/src/recorder/sources/cameraStream.ts +0 -32
- package/src/recorder/sources/micStream.ts +0 -25
- package/src/recorder/sources/screenStream.ts +0 -162
- package/src/recorder/timingJson.ts +0 -66
- package/src/resolveBlockVisual.ts +0 -39
- package/src/scene/Scene.tsx +0 -616
- package/src/scene/SceneBlockExtension.ts +0 -205
- package/src/scene/SceneBlockToolbar.tsx +0 -201
- package/src/scene/SceneBlockWidget.tsx +0 -441
- package/src/scene/ScenePropsBar.tsx +0 -85
- package/src/scene/SceneSelection.tsx +0 -111
- package/src/scene/SceneSideToolbar.tsx +0 -89
- package/src/scene/SceneViewport.tsx +0 -106
- package/src/scene/ShapePalette.tsx +0 -196
- package/src/scene/__tests__/DiagramAdapter.test.ts +0 -142
- package/src/scene/__tests__/SceneBlockExtension.test.ts +0 -33
- package/src/scene/__tests__/bezierEdit.test.ts +0 -85
- package/src/scene/__tests__/blockLayers.test.ts +0 -57
- package/src/scene/__tests__/sceneIsolation.test.tsx +0 -119
- package/src/scene/__tests__/shapeLayers.test.ts +0 -106
- package/src/scene/__tests__/useSceneHitTest.test.ts +0 -90
- package/src/scene/__tests__/useScenePanZoom.test.ts +0 -103
- package/src/scene/adapters/DiagramAdapter.ts +0 -79
- package/src/scene/adapters/DrawingAdapter.ts +0 -420
- package/src/scene/adapters/LayoutAdapter.ts +0 -313
- package/src/scene/adapters/blockLayers.ts +0 -159
- package/src/scene/commands/SceneCommand.ts +0 -84
- package/src/scene/commands/drawingCommands.ts +0 -318
- package/src/scene/commands/layoutCommands.ts +0 -301
- package/src/scene/hooks/useSceneHitTest.ts +0 -105
- package/src/scene/hooks/useScenePanZoom.ts +0 -147
- package/src/scene/hooks/useSceneSelection.ts +0 -62
- package/src/scene/index.ts +0 -90
- package/src/scene/layers/DiagramEdges.tsx +0 -142
- package/src/scene/layers/edgeGeometry.ts +0 -104
- package/src/scene/layers/nodeCard.tsx +0 -164
- package/src/scene/layers/renderLayer.tsx +0 -70
- package/src/scene/layers/shapeLayers.ts +0 -201
- package/src/scene/paths/bezierEdit.ts +0 -208
- package/src/scene/scene.css +0 -792
- package/src/scene/text/SceneTextOverlay.tsx +0 -161
- package/src/scene/text/sceneTextChannel.ts +0 -46
- package/src/scene/text/sceneTextConfig.ts +0 -31
- package/src/scene/text/sceneTiptap.ts +0 -36
- package/src/scene/text/useSceneTextEditing.ts +0 -39
- package/src/scene/tools/ConnectTool.ts +0 -209
- package/src/scene/tools/DrawingConnectTool.ts +0 -231
- package/src/scene/tools/PathTool.ts +0 -158
- package/src/scene/tools/PlaceTool.ts +0 -47
- package/src/scene/tools/SceneTool.ts +0 -85
- package/src/scene/tools/SelectTool.ts +0 -300
- package/src/scene/tools/ShapeTool.ts +0 -143
- package/src/scene/tools/TextTool.ts +0 -72
- package/src/scene/tools/TokenTool.ts +0 -95
- package/src/scene/tools/createDrawShapeTool.ts +0 -82
- package/src/selectionConversions.ts +0 -155
- package/src/styles/ascii-diagram.css +0 -79
- package/src/styles/ascii-timeline.css +0 -596
- package/src/styles/code-context.css +0 -155
- package/src/styles/diagram.css +0 -183
- package/src/styles/editor.css +0 -6811
- package/src/styles/folder-view.css +0 -210
- package/src/styles/image-edit-affordance.css +0 -201
- package/src/styles/index.css +0 -18
- package/src/styles/tree-view.css +0 -189
- package/src/teleprompter/TeleprompterControls.tsx +0 -218
- package/src/teleprompter/TeleprompterSelfView.tsx +0 -22
- package/src/teleprompter/TeleprompterSurface.tsx +0 -267
- package/src/teleprompter/TeleprompterView.tsx +0 -338
- package/src/teleprompter/canvasRenderer.ts +0 -161
- package/src/teleprompter/floatingWindow.ts +0 -352
- package/src/teleprompter/index.ts +0 -61
- package/src/teleprompter/pcmWorklet.ts +0 -62
- package/src/teleprompter/recording/insertPreamble.ts +0 -80
- package/src/teleprompter/recording/narrationSave.ts +0 -134
- package/src/teleprompter/recording/useNarrationRecorder.ts +0 -367
- package/src/teleprompter/scrollModel.ts +0 -85
- package/src/teleprompter/teleprompterTheme.ts +0 -303
- package/src/teleprompter/types.ts +0 -38
- package/src/teleprompter/useFloatingWindow.ts +0 -74
- package/src/teleprompter/useMicAnalysis.ts +0 -211
- package/src/teleprompter/useTeleprompter.ts +0 -421
- package/src/templateContentPreviewResolver.ts +0 -360
- package/src/timeline/TimelineEditorWidget.tsx +0 -861
- package/src/timeline/TimelineViewExtension.ts +0 -252
- package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +0 -414
- package/src/timeline/__tests__/TimelineViewExtension.test.ts +0 -163
- package/src/timeline/__tests__/timelineCommands.test.ts +0 -359
- package/src/timeline/__tests__/timelineOps.test.ts +0 -277
- package/src/timeline/__tests__/timelinePaste.test.ts +0 -34
- package/src/timeline/timelineCommands.ts +0 -421
- package/src/timeline/timelineData.ts +0 -54
- package/src/timeline/timelineOps.ts +0 -381
- package/src/timeline/timelinePaste.ts +0 -8
- package/src/timelineSource.ts +0 -244
- package/src/tiptap/TiptapAudio.tsx +0 -86
- package/src/tiptap/TiptapVideo.tsx +0 -119
- package/src/tiptap/useResolvedMediaSrc.ts +0 -47
- package/src/tiptapBridge.ts +0 -986
- package/src/tooltipPlacement.ts +0 -13
- package/src/transformStyleId.ts +0 -17
- package/src/transitionCatalog.ts +0 -159
- package/src/treeview/TreeOutlineWidget.tsx +0 -390
- package/src/treeview/TreeViewExtension.ts +0 -250
- package/src/treeview/__tests__/TreeOutlineWidget.test.tsx +0 -156
- package/src/treeview/__tests__/TreeViewExtension.test.ts +0 -122
- package/src/treeview/__tests__/treeOps.test.ts +0 -177
- package/src/treeview/__tests__/treePaste.test.ts +0 -40
- package/src/treeview/__tests__/treeViewCommands.test.ts +0 -179
- package/src/treeview/treeOps.ts +0 -231
- package/src/treeview/treePaste.ts +0 -13
- package/src/treeview/treeViewCommands.ts +0 -99
- package/src/treeview/treeViewData.ts +0 -41
- package/src/types/monaco-shims.d.ts +0 -10
- package/src/useBlockNavigator.ts +0 -153
- package/src/useHeadingLayout.ts +0 -294
- package/src/useMonacoLoader.ts +0 -82
- package/src/useTimelineClock.ts +0 -76
- package/src/utils/collectInlineFontAwesomeCss.ts +0 -69
- package/src/utils/dropUtils.ts +0 -136
- package/src/utils/normalizeMalformedAssetUrl.ts +0 -22
- package/src/wysiwygImageUpload.ts +0 -113
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* blockLayers — base64-JSON encode/decode round-trip for Layer arrays.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from 'vitest';
|
|
6
|
-
import { encodeLayers, decodeLayers } from '../adapters/blockLayers';
|
|
7
|
-
import type { Layer, ShapeLayer, TextLayer } from '@bendyline/squisq/schemas';
|
|
8
|
-
|
|
9
|
-
describe('encodeLayers / decodeLayers', () => {
|
|
10
|
-
it('round-trips a single ShapeLayer', () => {
|
|
11
|
-
const layer: ShapeLayer = {
|
|
12
|
-
id: 'rect-1',
|
|
13
|
-
type: 'shape',
|
|
14
|
-
position: { x: 100, y: 200, width: 50, height: 30 },
|
|
15
|
-
content: { shape: 'rect', fill: '#abc', stroke: '#000', strokeWidth: 2 },
|
|
16
|
-
};
|
|
17
|
-
expect(decodeLayers(encodeLayers([layer]))).toEqual([layer]);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('round-trips a mixed-type array preserving order', () => {
|
|
21
|
-
const layers: Layer[] = [
|
|
22
|
-
{
|
|
23
|
-
id: 'a',
|
|
24
|
-
type: 'shape',
|
|
25
|
-
position: { x: 0, y: 0, width: 10, height: 10 },
|
|
26
|
-
content: { shape: 'circle', fill: 'red' },
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: 'b',
|
|
30
|
-
type: 'text',
|
|
31
|
-
position: { x: 20, y: 20, width: 80, height: 24 },
|
|
32
|
-
content: { text: 'Hi there', style: { fontSize: 18, color: '#111' } },
|
|
33
|
-
} as TextLayer,
|
|
34
|
-
];
|
|
35
|
-
expect(decodeLayers(encodeLayers(layers))).toEqual(layers);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('returns [] for invalid base64 / JSON', () => {
|
|
39
|
-
expect(decodeLayers('not-valid-base64!!')).toEqual([]);
|
|
40
|
-
expect(decodeLayers(Buffer.from('not json', 'utf-8').toString('base64'))).toEqual([]);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('returns [] when the decoded value is not an array', () => {
|
|
44
|
-
const encoded = Buffer.from(JSON.stringify({ not: 'an array' }), 'utf-8').toString('base64');
|
|
45
|
-
expect(decodeLayers(encoded)).toEqual([]);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('encodes deterministically (same input → same output)', () => {
|
|
49
|
-
const layer: ShapeLayer = {
|
|
50
|
-
id: 'x',
|
|
51
|
-
type: 'shape',
|
|
52
|
-
position: { x: 0, y: 0, width: 5, height: 5 },
|
|
53
|
-
content: { shape: 'rect' },
|
|
54
|
-
};
|
|
55
|
-
expect(encodeLayers([layer])).toBe(encodeLayers([layer]));
|
|
56
|
-
});
|
|
57
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/** @vitest-environment jsdom */
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
3
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
-
import type { SceneToolContext } from '../tools/SceneTool';
|
|
5
|
-
import { SelectTool, beginHandleDrag, getActiveMoveOffset } from '../tools/SelectTool';
|
|
6
|
-
import { ConnectTool } from '../tools/ConnectTool';
|
|
7
|
-
import { createSceneTextChannel } from '../text/sceneTextChannel';
|
|
8
|
-
import { SceneViewport } from '../SceneViewport';
|
|
9
|
-
import { DiagramEdges } from '../layers/DiagramEdges';
|
|
10
|
-
|
|
11
|
-
function context(id = 'layer'): SceneToolContext {
|
|
12
|
-
const layer = {
|
|
13
|
-
id,
|
|
14
|
-
type: 'shape' as const,
|
|
15
|
-
position: { x: 0, y: 0, width: 20, height: 20 },
|
|
16
|
-
content: { shape: 'rect' as const },
|
|
17
|
-
};
|
|
18
|
-
return {
|
|
19
|
-
interaction: {},
|
|
20
|
-
viewport: { width: 100, height: 100 },
|
|
21
|
-
transform: { tx: 0, ty: 0, scale: 1 },
|
|
22
|
-
layers: [layer],
|
|
23
|
-
edges: [],
|
|
24
|
-
selection: new Set(),
|
|
25
|
-
hitItems: [{ id, layer, bounds: { x: 0, y: 0, width: 20, height: 20 } }],
|
|
26
|
-
screenToViewport: (x, y) => ({ x, y }),
|
|
27
|
-
viewportToScreen: (x, y) => ({ x, y }),
|
|
28
|
-
hit: () => id,
|
|
29
|
-
setSelection: vi.fn(),
|
|
30
|
-
dispatch: vi.fn(),
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function pointer(clientX: number, clientY: number) {
|
|
35
|
-
return {
|
|
36
|
-
button: 0,
|
|
37
|
-
clientX,
|
|
38
|
-
clientY,
|
|
39
|
-
pointerId: 1,
|
|
40
|
-
shiftKey: false,
|
|
41
|
-
currentTarget: {
|
|
42
|
-
getBoundingClientRect: () => ({ left: 0, top: 0 }),
|
|
43
|
-
setPointerCapture: vi.fn(),
|
|
44
|
-
releasePointerCapture: vi.fn(),
|
|
45
|
-
},
|
|
46
|
-
stopPropagation: vi.fn(),
|
|
47
|
-
} as unknown as React.PointerEvent;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
describe('Scene instance isolation', () => {
|
|
51
|
-
it('keeps select and connect gestures in the owning Scene context', () => {
|
|
52
|
-
const first = context('node-card-a');
|
|
53
|
-
const second = context('node-card-a');
|
|
54
|
-
SelectTool.onPointerDown!(pointer(0, 0), first);
|
|
55
|
-
SelectTool.onPointerMove!(pointer(10, 0), first);
|
|
56
|
-
expect(getActiveMoveOffset(first.interaction)).toEqual({ dx: 10, dy: 0 });
|
|
57
|
-
expect(getActiveMoveOffset(second.interaction)).toBeNull();
|
|
58
|
-
|
|
59
|
-
ConnectTool.onPointerDown!(pointer(5, 5), first);
|
|
60
|
-
expect(first.interaction.connect).toBeTruthy();
|
|
61
|
-
expect(second.interaction.connect).toBeUndefined();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('commits a resize as one command containing origin and size', () => {
|
|
65
|
-
const ctx = context('node-card-a');
|
|
66
|
-
beginHandleDrag(
|
|
67
|
-
{
|
|
68
|
-
layerId: 'node-card-a',
|
|
69
|
-
corner: 'nw',
|
|
70
|
-
startV: { x: 10, y: 20 },
|
|
71
|
-
startBounds: { x: 10, y: 20, width: 100, height: 50 },
|
|
72
|
-
},
|
|
73
|
-
ctx.interaction,
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
SelectTool.onPointerMove!(pointer(0, 0), ctx);
|
|
77
|
-
SelectTool.onPointerUp!(pointer(0, 0), ctx);
|
|
78
|
-
|
|
79
|
-
expect(ctx.dispatch).toHaveBeenCalledTimes(1);
|
|
80
|
-
expect(ctx.dispatch).toHaveBeenCalledWith({
|
|
81
|
-
kind: 'resizeLayer',
|
|
82
|
-
id: 'node-card-a',
|
|
83
|
-
x: 0,
|
|
84
|
-
y: 0,
|
|
85
|
-
width: 110,
|
|
86
|
-
height: 70,
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('creates independent text-toolbar channels', () => {
|
|
91
|
-
const first = createSceneTextChannel();
|
|
92
|
-
const second = createSceneTextChannel();
|
|
93
|
-
const listener = vi.fn();
|
|
94
|
-
second.subscribe(listener);
|
|
95
|
-
first.set({ editor: {} as never, level: 'inline' });
|
|
96
|
-
expect(listener).not.toHaveBeenCalled();
|
|
97
|
-
expect(second.get()).toBeNull();
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('namespaces grid and edge marker definitions per canvas', () => {
|
|
101
|
-
const edge = { id: 'e', source: 'a', target: 'b', endMarker: 'arrow' as const };
|
|
102
|
-
const nodes = [
|
|
103
|
-
{ id: 'a', x: 0, y: 0, width: 10, height: 10, cx: 5, cy: 5 },
|
|
104
|
-
{ id: 'b', x: 30, y: 0, width: 10, height: 10, cx: 35, cy: 5 },
|
|
105
|
-
];
|
|
106
|
-
const { container } = render(
|
|
107
|
-
<>
|
|
108
|
-
<SceneViewport width={100} height={100} transform={{ tx: 0, ty: 0, scale: 1 }}>
|
|
109
|
-
<DiagramEdges nodes={nodes} edges={[edge]} />
|
|
110
|
-
</SceneViewport>
|
|
111
|
-
<SceneViewport width={100} height={100} transform={{ tx: 0, ty: 0, scale: 1 }}>
|
|
112
|
-
<DiagramEdges nodes={nodes} edges={[edge]} />
|
|
113
|
-
</SceneViewport>
|
|
114
|
-
</>,
|
|
115
|
-
);
|
|
116
|
-
const ids = Array.from(container.querySelectorAll('[id]')).map((node) => node.id);
|
|
117
|
-
expect(new Set(ids).size).toBe(ids.length);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* shapeLayers — pure read transform. Given the shapes `computeDrawingLayout`
|
|
3
|
-
* derives from a drawing's children, verifies the Scene Layer(s), boxes, and
|
|
4
|
-
* id helpers the DrawingAdapter relies on.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect } from 'vitest';
|
|
8
|
-
import { computeDrawingLayout } from '@bendyline/squisq/doc';
|
|
9
|
-
import type { Block } from '@bendyline/squisq/schemas';
|
|
10
|
-
import {
|
|
11
|
-
shapesToSceneLayers,
|
|
12
|
-
shapeBoxes,
|
|
13
|
-
drawingLayerFollows,
|
|
14
|
-
shapeIdFromLayerId,
|
|
15
|
-
isPrimaryShapeLayer,
|
|
16
|
-
primaryLayerId,
|
|
17
|
-
} from '../layers/shapeLayers';
|
|
18
|
-
|
|
19
|
-
function shapeBlock(
|
|
20
|
-
id: string,
|
|
21
|
-
template: string,
|
|
22
|
-
overrides: Record<string, string>,
|
|
23
|
-
title?: string,
|
|
24
|
-
): Block {
|
|
25
|
-
return {
|
|
26
|
-
id,
|
|
27
|
-
startTime: 0,
|
|
28
|
-
duration: 0,
|
|
29
|
-
audioSegment: 0,
|
|
30
|
-
template,
|
|
31
|
-
templateOverrides: overrides,
|
|
32
|
-
...(title ? { title } : {}),
|
|
33
|
-
} as Block;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
describe('shapeLayers read transform', () => {
|
|
37
|
-
it('emits a shape + follower label per rect, with dshape-/dlabel- ids', () => {
|
|
38
|
-
const layout = computeDrawingLayout([
|
|
39
|
-
shapeBlock('ceo', 'rectangle', { x: '20', y: '20', width: '120', height: '80' }, 'CEO'),
|
|
40
|
-
]);
|
|
41
|
-
const layers = shapesToSceneLayers(layout.shapes);
|
|
42
|
-
expect(layers.map((l) => l.id)).toEqual(['dshape-ceo', 'dlabel-ceo']);
|
|
43
|
-
const [shape, label] = layers;
|
|
44
|
-
expect(shape.type).toBe('shape');
|
|
45
|
-
expect(shape.position).toMatchObject({ x: 20, y: 20, width: 120, height: 80 });
|
|
46
|
-
expect(label.type).toBe('text');
|
|
47
|
-
if (label.type === 'text') expect(label.content.text).toBe('CEO');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('maps each shape kind to the right Layer type', () => {
|
|
51
|
-
const layout = computeDrawingLayout([
|
|
52
|
-
shapeBlock('r', 'rectangle', { x: '0', y: '0', width: '10', height: '10' }),
|
|
53
|
-
shapeBlock('c', 'circle', { x: '0', y: '0', width: '10', height: '10' }),
|
|
54
|
-
shapeBlock('l', 'line', { x: '0', y: '0', width: '10', height: '10' }),
|
|
55
|
-
shapeBlock('p', 'path', { x: '0', y: '0', d: 'M 0 0 L 5 5' }),
|
|
56
|
-
shapeBlock('t', 'text', { x: '0', y: '0', text: 'hi' }),
|
|
57
|
-
]);
|
|
58
|
-
const byId = new Map(shapesToSceneLayers(layout.shapes).map((l) => [l.id, l]));
|
|
59
|
-
expect(byId.get('dshape-r')?.type).toBe('shape');
|
|
60
|
-
expect(byId.get('dshape-c')?.type).toBe('shape');
|
|
61
|
-
expect(byId.get('dshape-l')?.type).toBe('shape'); // plain line → bbox shape
|
|
62
|
-
expect(byId.get('dshape-p')?.type).toBe('path');
|
|
63
|
-
expect(byId.get('dshape-t')?.type).toBe('text');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('does not emit a label layer for a text shape', () => {
|
|
67
|
-
const layout = computeDrawingLayout([
|
|
68
|
-
shapeBlock('t', 'text', { x: '0', y: '0', text: 'Note' }),
|
|
69
|
-
]);
|
|
70
|
-
expect(shapesToSceneLayers(layout.shapes).map((l) => l.id)).toEqual(['dshape-t']);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('exposes {id,x,y,width,height} boxes for the connector renderer', () => {
|
|
74
|
-
const layout = computeDrawingLayout([
|
|
75
|
-
shapeBlock('a', 'rect', { x: '5', y: '6', width: '7', height: '8' }),
|
|
76
|
-
]);
|
|
77
|
-
expect(shapeBoxes(layout.shapes)).toEqual([{ id: 'a', x: 5, y: 6, width: 7, height: 8 }]);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('links a label to its shape and recovers shape ids from layer ids', () => {
|
|
81
|
-
expect(drawingLayerFollows('dlabel-ceo')).toBe('dshape-ceo');
|
|
82
|
-
expect(drawingLayerFollows('dshape-ceo')).toBeNull();
|
|
83
|
-
expect(shapeIdFromLayerId('dshape-ceo')).toBe('ceo');
|
|
84
|
-
expect(shapeIdFromLayerId('dlabel-ceo')).toBe('ceo');
|
|
85
|
-
expect(shapeIdFromLayerId('whatever')).toBeNull();
|
|
86
|
-
expect(isPrimaryShapeLayer('dshape-ceo')).toBe(true);
|
|
87
|
-
expect(isPrimaryShapeLayer('dlabel-ceo')).toBe(false);
|
|
88
|
-
expect(primaryLayerId('ceo')).toBe('dshape-ceo');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('connectors (line/arrow with from/to) are edges, not shapes', () => {
|
|
92
|
-
const layout = computeDrawingLayout([
|
|
93
|
-
shapeBlock('a', 'rect', { x: '0', y: '0', width: '50', height: '50' }),
|
|
94
|
-
shapeBlock('b', 'rect', { x: '0', y: '200', width: '50', height: '50' }),
|
|
95
|
-
shapeBlock('e', 'arrow', { from: 'a', to: 'b' }, 'reports to'),
|
|
96
|
-
]);
|
|
97
|
-
expect(shapesToSceneLayers(layout.shapes).map((l) => l.id)).toEqual(['dshape-a', 'dshape-b']);
|
|
98
|
-
expect(layout.connectors).toHaveLength(1);
|
|
99
|
-
expect(layout.connectors[0]).toMatchObject({
|
|
100
|
-
id: 'e',
|
|
101
|
-
from: 'a',
|
|
102
|
-
to: 'b',
|
|
103
|
-
label: 'reports to',
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
});
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useSceneHitTest + layerBounds — coordinate resolution and topmost-wins
|
|
3
|
-
* hit testing. Pure logic; no DOM needed.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect } from 'vitest';
|
|
7
|
-
import { renderHook } from '@testing-library/react';
|
|
8
|
-
import { useSceneHitTest, layerBounds, type HitTestable } from '../hooks/useSceneHitTest';
|
|
9
|
-
import type { ShapeLayer } from '@bendyline/squisq/schemas';
|
|
10
|
-
|
|
11
|
-
const viewport = { width: 1000, height: 800 };
|
|
12
|
-
|
|
13
|
-
function rectLayer(id: string, x: number, y: number, w: number, h: number): ShapeLayer {
|
|
14
|
-
return {
|
|
15
|
-
id,
|
|
16
|
-
type: 'shape',
|
|
17
|
-
position: { x, y, width: w, height: h },
|
|
18
|
-
content: { shape: 'rect' },
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe('layerBounds', () => {
|
|
23
|
-
it('resolves numeric pixel positions', () => {
|
|
24
|
-
const b = layerBounds(rectLayer('a', 10, 20, 100, 50), viewport);
|
|
25
|
-
expect(b).toEqual({ x: 10, y: 20, width: 100, height: 50 });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('resolves percentage strings against the viewport', () => {
|
|
29
|
-
const l: ShapeLayer = {
|
|
30
|
-
id: 'p',
|
|
31
|
-
type: 'shape',
|
|
32
|
-
position: { x: '10%', y: '25%', width: '50%', height: '20%' },
|
|
33
|
-
content: { shape: 'rect' },
|
|
34
|
-
};
|
|
35
|
-
const b = layerBounds(l, viewport);
|
|
36
|
-
expect(b).toEqual({ x: 100, y: 200, width: 500, height: 160 });
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('applies center anchor by shifting the origin to the bounding-box top-left', () => {
|
|
40
|
-
const l: ShapeLayer = {
|
|
41
|
-
id: 'c',
|
|
42
|
-
type: 'shape',
|
|
43
|
-
position: { x: 500, y: 400, width: 200, height: 100, anchor: 'center' },
|
|
44
|
-
content: { shape: 'rect' },
|
|
45
|
-
};
|
|
46
|
-
const b = layerBounds(l, viewport);
|
|
47
|
-
expect(b).toEqual({ x: 400, y: 350, width: 200, height: 100 });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('returns null when width or height cannot be resolved', () => {
|
|
51
|
-
const l: ShapeLayer = {
|
|
52
|
-
id: 'n',
|
|
53
|
-
type: 'shape',
|
|
54
|
-
position: { x: 0, y: 0 },
|
|
55
|
-
content: { shape: 'rect' },
|
|
56
|
-
};
|
|
57
|
-
expect(layerBounds(l, viewport)).toBeNull();
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('useSceneHitTest', () => {
|
|
62
|
-
function items(...layers: ShapeLayer[]): HitTestable[] {
|
|
63
|
-
return layers.map((l) => ({
|
|
64
|
-
id: l.id,
|
|
65
|
-
layer: l,
|
|
66
|
-
bounds: layerBounds(l, viewport)!,
|
|
67
|
-
}));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
it('returns the topmost layer at a point (last-wins)', () => {
|
|
71
|
-
const { result } = renderHook(() => useSceneHitTest());
|
|
72
|
-
const list = items(rectLayer('a', 0, 0, 100, 100), rectLayer('b', 50, 50, 100, 100));
|
|
73
|
-
// Point (60, 60) is inside both — `b` is later in the array, so wins.
|
|
74
|
-
expect(result.current.hit({ x: 60, y: 60 }, list)).toBe('b');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('returns null when the point is outside every layer', () => {
|
|
78
|
-
const { result } = renderHook(() => useSceneHitTest());
|
|
79
|
-
const list = items(rectLayer('a', 0, 0, 50, 50));
|
|
80
|
-
expect(result.current.hit({ x: 500, y: 500 }, list)).toBeNull();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('treats the bounding-box edge as inside (>= and <=)', () => {
|
|
84
|
-
const { result } = renderHook(() => useSceneHitTest());
|
|
85
|
-
const list = items(rectLayer('a', 10, 10, 100, 100));
|
|
86
|
-
expect(result.current.hit({ x: 10, y: 10 }, list)).toBe('a');
|
|
87
|
-
expect(result.current.hit({ x: 110, y: 110 }, list)).toBe('a');
|
|
88
|
-
expect(result.current.hit({ x: 9.9, y: 10 }, list)).toBeNull();
|
|
89
|
-
});
|
|
90
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useScenePanZoom — verify pan, zoom-at, fit, and screen↔viewport
|
|
3
|
-
* coordinate conversions. The hook is pure state + math, so tests use
|
|
4
|
-
* @testing-library/react's `renderHook` and don't need DOM events.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect } from 'vitest';
|
|
8
|
-
import { act, renderHook } from '@testing-library/react';
|
|
9
|
-
import { useScenePanZoom, IDENTITY_TRANSFORM } from '../hooks/useScenePanZoom';
|
|
10
|
-
|
|
11
|
-
describe('useScenePanZoom', () => {
|
|
12
|
-
it('starts at identity by default', () => {
|
|
13
|
-
const { result } = renderHook(() => useScenePanZoom());
|
|
14
|
-
expect(result.current.transform).toEqual(IDENTITY_TRANSFORM);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('panBy adds to translation', () => {
|
|
18
|
-
const { result } = renderHook(() => useScenePanZoom());
|
|
19
|
-
act(() => result.current.panBy(50, 30));
|
|
20
|
-
expect(result.current.transform.tx).toBe(50);
|
|
21
|
-
expect(result.current.transform.ty).toBe(30);
|
|
22
|
-
act(() => result.current.panBy(-20, 0));
|
|
23
|
-
expect(result.current.transform.tx).toBe(30);
|
|
24
|
-
expect(result.current.transform.ty).toBe(30);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('zoomAt keeps the focus point stationary in viewport space', () => {
|
|
28
|
-
const { result } = renderHook(() => useScenePanZoom());
|
|
29
|
-
// Pre-zoom: identity. The screen point (200, 100) maps to viewport (200, 100).
|
|
30
|
-
const before = result.current.screenToViewport(200, 100);
|
|
31
|
-
expect(before).toEqual({ x: 200, y: 100 });
|
|
32
|
-
|
|
33
|
-
act(() => result.current.zoomAt(2, 200, 100));
|
|
34
|
-
expect(result.current.transform.scale).toBe(2);
|
|
35
|
-
// After zooming to scale=2 around screen-point (200, 100), the viewport
|
|
36
|
-
// point under that screen pixel must still be (200, 100).
|
|
37
|
-
const after = result.current.screenToViewport(200, 100);
|
|
38
|
-
expect(after.x).toBeCloseTo(200, 5);
|
|
39
|
-
expect(after.y).toBeCloseTo(100, 5);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('clamps scale to the allowed range', () => {
|
|
43
|
-
const { result } = renderHook(() => useScenePanZoom());
|
|
44
|
-
act(() => result.current.zoomAt(100, 0, 0));
|
|
45
|
-
expect(result.current.transform.scale).toBeLessThanOrEqual(8);
|
|
46
|
-
act(() => result.current.zoomAt(0.0001, 0, 0));
|
|
47
|
-
expect(result.current.transform.scale).toBeGreaterThanOrEqual(0.1);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('reset returns to identity', () => {
|
|
51
|
-
const { result } = renderHook(() => useScenePanZoom());
|
|
52
|
-
act(() => {
|
|
53
|
-
result.current.panBy(100, 100);
|
|
54
|
-
result.current.zoomAt(1.5, 50, 50);
|
|
55
|
-
});
|
|
56
|
-
expect(result.current.transform).not.toEqual(IDENTITY_TRANSFORM);
|
|
57
|
-
act(() => result.current.reset());
|
|
58
|
-
expect(result.current.transform).toEqual(IDENTITY_TRANSFORM);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('fitBox centers and scales the box inside the container with padding', () => {
|
|
62
|
-
const { result } = renderHook(() => useScenePanZoom());
|
|
63
|
-
// 100x100 box, 200x200 container, 0 padding → scale=2, centered at (0,0).
|
|
64
|
-
act(() =>
|
|
65
|
-
result.current.fitBox(
|
|
66
|
-
{ x: 0, y: 0, width: 100, height: 100 },
|
|
67
|
-
{ width: 200, height: 200 },
|
|
68
|
-
0,
|
|
69
|
-
),
|
|
70
|
-
);
|
|
71
|
-
expect(result.current.transform.scale).toBe(2);
|
|
72
|
-
// Box origin (0,0) viewport → (0,0) screen (since the box is sized 200,
|
|
73
|
-
// matching the container after scaling); centered → tx=ty=0.
|
|
74
|
-
expect(result.current.transform.tx).toBe(0);
|
|
75
|
-
expect(result.current.transform.ty).toBe(0);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('fitBox honors padding by leaving room around the content', () => {
|
|
79
|
-
const { result } = renderHook(() => useScenePanZoom());
|
|
80
|
-
act(() =>
|
|
81
|
-
result.current.fitBox(
|
|
82
|
-
{ x: 0, y: 0, width: 100, height: 100 },
|
|
83
|
-
{ width: 200, height: 200 },
|
|
84
|
-
20,
|
|
85
|
-
),
|
|
86
|
-
);
|
|
87
|
-
// Available area after padding: 160x160 → scale 1.6.
|
|
88
|
-
expect(result.current.transform.scale).toBeCloseTo(1.6, 5);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('screenToViewport and viewportToScreen are inverses', () => {
|
|
92
|
-
const { result } = renderHook(() => useScenePanZoom());
|
|
93
|
-
act(() => {
|
|
94
|
-
result.current.panBy(30, -45);
|
|
95
|
-
result.current.zoomAt(1.7, 100, 100);
|
|
96
|
-
});
|
|
97
|
-
const screenPoint = { x: 250, y: 75 };
|
|
98
|
-
const v = result.current.screenToViewport(screenPoint.x, screenPoint.y);
|
|
99
|
-
const back = result.current.viewportToScreen(v.x, v.y);
|
|
100
|
-
expect(back.x).toBeCloseTo(screenPoint.x, 5);
|
|
101
|
-
expect(back.y).toBeCloseTo(screenPoint.y, 5);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DiagramAdapter — translate between Scene's generic command vocabulary
|
|
3
|
-
* and the diagram-specific Tiptap commands in `diagramCommands.ts`.
|
|
4
|
-
*
|
|
5
|
-
* Read direction: heading list + connectsTo → SceneLayer[] + SceneEdge[]
|
|
6
|
-
* Write direction: SceneCommand → moveNode / addConnection / addNode / etc.
|
|
7
|
-
*
|
|
8
|
-
* The diagram surface uses `node-card-<id>` / `node-label-<id>` ids for
|
|
9
|
-
* its synthetic card layers. The adapter maps those back to the
|
|
10
|
-
* underlying heading id when dispatching writes.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { Layer } from '@bendyline/squisq/schemas';
|
|
14
|
-
import type { SceneEdge } from '../commands/SceneCommand';
|
|
15
|
-
import {
|
|
16
|
-
nodesToCardLayers,
|
|
17
|
-
NODE_WIDTH,
|
|
18
|
-
NODE_HEIGHT,
|
|
19
|
-
type DiagramNodeDescriptor,
|
|
20
|
-
} from '../layers/nodeCard';
|
|
21
|
-
import type { DiagramNode, DiagramEdge } from '../../diagram/types';
|
|
22
|
-
|
|
23
|
-
export interface DiagramSceneData {
|
|
24
|
-
layers: Layer[];
|
|
25
|
-
edges: SceneEdge[];
|
|
26
|
-
/** Diagram node descriptors (id, label, x, y). Useful for the ConnectTool overlay. */
|
|
27
|
-
nodes: DiagramNodeDescriptor[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Convert the (nodes, edges) shape currently produced by `useDiagramData`
|
|
32
|
-
* into Scene's layer/edge vocabulary. We accept the React-Flow-flavored
|
|
33
|
-
* shape so the existing data hook can stay unchanged during the swap.
|
|
34
|
-
*/
|
|
35
|
-
export function buildDiagramScene(
|
|
36
|
-
nodes: readonly DiagramNode[],
|
|
37
|
-
edges: readonly DiagramEdge[],
|
|
38
|
-
): DiagramSceneData {
|
|
39
|
-
const descriptors: DiagramNodeDescriptor[] = nodes.map((n) => ({
|
|
40
|
-
id: n.id,
|
|
41
|
-
label: n.data.label,
|
|
42
|
-
x: n.position.x,
|
|
43
|
-
y: n.position.y,
|
|
44
|
-
...(n.width != null ? { width: n.width } : {}),
|
|
45
|
-
...(n.height != null ? { height: n.height } : {}),
|
|
46
|
-
...(n.kind === 'container' ? { kind: 'container' as const } : {}),
|
|
47
|
-
}));
|
|
48
|
-
const layers = nodesToCardLayers(descriptors);
|
|
49
|
-
const sceneEdges: SceneEdge[] = edges.map((e) => ({
|
|
50
|
-
id: e.id,
|
|
51
|
-
source: e.source,
|
|
52
|
-
target: e.target,
|
|
53
|
-
...(e.label ? { label: e.label } : {}),
|
|
54
|
-
...(e.directed === false ? { endMarker: 'none' as const } : {}),
|
|
55
|
-
...(e.sourceAnchor ? { sourceAnchor: e.sourceAnchor } : {}),
|
|
56
|
-
...(e.targetAnchor ? { targetAnchor: e.targetAnchor } : {}),
|
|
57
|
-
...(e.routing ? { routing: e.routing } : {}),
|
|
58
|
-
}));
|
|
59
|
-
return { layers, edges: sceneEdges, nodes: descriptors };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Re-export of the diagram-specific `layerFollows` helper for hosts
|
|
64
|
-
* that want to pass it straight to `<Scene layerFollows={...} />`.
|
|
65
|
-
*/
|
|
66
|
-
export { diagramLayerFollows } from '../layers/nodeCard';
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Compute a node's center point in viewport units. Used by the
|
|
70
|
-
* ConnectTool to draw the in-flight connection preview and to find a
|
|
71
|
-
* drop target.
|
|
72
|
-
*/
|
|
73
|
-
export function nodeCenter(node: DiagramNodeDescriptor): { x: number; y: number } {
|
|
74
|
-
return { x: node.x + NODE_WIDTH / 2, y: node.y + NODE_HEIGHT / 2 };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// (The heading-based `makeDiagramDispatch` was removed with the ASCII-fence
|
|
78
|
-
// cutover — canvas commands for diagrams now flow through
|
|
79
|
-
// `asciiDiagram/asciiDiagramCommands.ts` instead of heading mutations.)
|