@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.
Files changed (357) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +40 -0
  3. package/README.md +10 -2
  4. package/dist/index.d.ts +683 -60
  5. package/dist/index.js +9648 -3750
  6. package/dist/monaco.js +0 -1
  7. package/dist/styles/index.css +1160 -241
  8. package/package.json +10 -6
  9. package/dist/index.js.map +0 -1
  10. package/dist/monaco.js.map +0 -1
  11. package/src/BlockCardView.tsx +0 -121
  12. package/src/BlockPreviewPanel.tsx +0 -69
  13. package/src/BlockPropertiesPopover.tsx +0 -222
  14. package/src/DocumentSettingsDialog.tsx +0 -272
  15. package/src/DropZoneOverlay.tsx +0 -137
  16. package/src/EditorContext.tsx +0 -1247
  17. package/src/EditorShell.tsx +0 -1334
  18. package/src/EmojiPicker.tsx +0 -332
  19. package/src/FolderView.tsx +0 -131
  20. package/src/Icon.tsx +0 -26
  21. package/src/ImageEditor.tsx +0 -395
  22. package/src/ImageNodeView.tsx +0 -284
  23. package/src/ImageViewer.tsx +0 -221
  24. package/src/InlineIcon.ts +0 -84
  25. package/src/InlinePreviewGutter.tsx +0 -590
  26. package/src/LinkDialog.tsx +0 -276
  27. package/src/MediaBin.tsx +0 -500
  28. package/src/MentionExtension.tsx +0 -261
  29. package/src/OutlinePanel.tsx +0 -547
  30. package/src/PlainHtmlPreview.tsx +0 -277
  31. package/src/PreviewControls.tsx +0 -1250
  32. package/src/PreviewPanel.tsx +0 -422
  33. package/src/RawEditor.tsx +0 -731
  34. package/src/RecorderEntry.tsx +0 -159
  35. package/src/StatusBar.tsx +0 -51
  36. package/src/TemplateAnnotation.ts +0 -167
  37. package/src/TemplateContentPreview.tsx +0 -56
  38. package/src/TemplatePicker.tsx +0 -1314
  39. package/src/ThemeCustomizerPanel.tsx +0 -304
  40. package/src/ThemePicker.tsx +0 -433
  41. package/src/TimelineBlockPreview.tsx +0 -37
  42. package/src/TimelineTrack.tsx +0 -671
  43. package/src/Toolbar.tsx +0 -2587
  44. package/src/Tooltip.tsx +0 -142
  45. package/src/TransitionPicker.tsx +0 -390
  46. package/src/VersionHistoryPanel.tsx +0 -359
  47. package/src/ViewMenuPanel.tsx +0 -189
  48. package/src/ViewSwitcher.tsx +0 -56
  49. package/src/WysiwygEditor.tsx +0 -792
  50. package/src/__tests__/blockProperties.test.ts +0 -92
  51. package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +0 -33
  52. package/src/__tests__/blockRange.test.ts +0 -105
  53. package/src/__tests__/blockTagActivity.test.ts +0 -183
  54. package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +0 -75
  55. package/src/__tests__/buildPreviewDocContent.test.ts +0 -48
  56. package/src/__tests__/buildPreviewDocTransition.test.ts +0 -92
  57. package/src/__tests__/canvasSurfaceTextEditing.test.tsx +0 -60
  58. package/src/__tests__/codeContextSectionView.test.tsx +0 -97
  59. package/src/__tests__/codeContextZoneManager.test.ts +0 -127
  60. package/src/__tests__/createShapeLayer.test.ts +0 -46
  61. package/src/__tests__/customLayoutManagerTheme.test.tsx +0 -34
  62. package/src/__tests__/detectMarkdown.test.ts +0 -73
  63. package/src/__tests__/diffContextSections.test.ts +0 -39
  64. package/src/__tests__/documentSettingsDialog.test.tsx +0 -197
  65. package/src/__tests__/drawingShapeRoundTrip.test.ts +0 -49
  66. package/src/__tests__/editorScrollHandoff.test.tsx +0 -102
  67. package/src/__tests__/editorScrollSync.test.ts +0 -65
  68. package/src/__tests__/editorShellCodeContext.test.tsx +0 -86
  69. package/src/__tests__/editorShellProps.test.tsx +0 -485
  70. package/src/__tests__/embeddedMedia.test.ts +0 -48
  71. package/src/__tests__/emojiPicker.test.tsx +0 -93
  72. package/src/__tests__/fileKind.test.ts +0 -112
  73. package/src/__tests__/headingTransition.test.ts +0 -176
  74. package/src/__tests__/imageEditAffordance.test.tsx +0 -272
  75. package/src/__tests__/imageEditModalContract.test.tsx +0 -119
  76. package/src/__tests__/imageEditorShell.test.tsx +0 -94
  77. package/src/__tests__/imageEditorState.test.ts +0 -171
  78. package/src/__tests__/inlinePreviewGutter.test.tsx +0 -62
  79. package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +0 -103
  80. package/src/__tests__/jsonEditor.test.tsx +0 -223
  81. package/src/__tests__/layersPanel.test.tsx +0 -167
  82. package/src/__tests__/layoutChildRoundTrip.test.ts +0 -71
  83. package/src/__tests__/linkDialogDocPicker.test.tsx +0 -137
  84. package/src/__tests__/majorApiTypes.test.ts +0 -26
  85. package/src/__tests__/mediaAttachmentFlow.test.ts +0 -110
  86. package/src/__tests__/mediaBinDrop.test.tsx +0 -90
  87. package/src/__tests__/mediaReferences.test.ts +0 -82
  88. package/src/__tests__/narrationSave.test.ts +0 -148
  89. package/src/__tests__/outlinePanel.test.tsx +0 -169
  90. package/src/__tests__/outlineSource.test.ts +0 -217
  91. package/src/__tests__/plainHtmlPreview.test.tsx +0 -125
  92. package/src/__tests__/presentationMode.test.tsx +0 -290
  93. package/src/__tests__/previewControls.test.tsx +0 -540
  94. package/src/__tests__/previewPanelAudioFailure.test.tsx +0 -46
  95. package/src/__tests__/previewPanelPresentation.test.tsx +0 -126
  96. package/src/__tests__/previewPanelTransforms.test.tsx +0 -117
  97. package/src/__tests__/propertiesPanel.test.tsx +0 -69
  98. package/src/__tests__/rawEditorModelIsolation.test.ts +0 -19
  99. package/src/__tests__/recorderFormats.test.ts +0 -146
  100. package/src/__tests__/recorderMediaInsert.test.ts +0 -86
  101. package/src/__tests__/recorderTheme.test.tsx +0 -42
  102. package/src/__tests__/recorderTimingJson.test.ts +0 -41
  103. package/src/__tests__/resolveBlockVisual.test.ts +0 -43
  104. package/src/__tests__/selectionConversions.test.ts +0 -80
  105. package/src/__tests__/statusBar.test.tsx +0 -27
  106. package/src/__tests__/teleprompter.test.tsx +0 -190
  107. package/src/__tests__/templateAnnotationRoundTrip.test.ts +0 -99
  108. package/src/__tests__/templateContentPreview.test.ts +0 -101
  109. package/src/__tests__/templatePickerMetadata.test.ts +0 -32
  110. package/src/__tests__/templatePickerPortal.test.tsx +0 -62
  111. package/src/__tests__/timelineSource.test.ts +0 -134
  112. package/src/__tests__/tiptapBridge.test.ts +0 -566
  113. package/src/__tests__/tiptapBridgeConformance.test.ts +0 -47
  114. package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +0 -109
  115. package/src/__tests__/tiptapImageRoundTrip.test.ts +0 -73
  116. package/src/__tests__/toolbarSelectionConversion.test.tsx +0 -164
  117. package/src/__tests__/tooltip.test.tsx +0 -72
  118. package/src/__tests__/transformStyleId.test.ts +0 -13
  119. package/src/__tests__/transitionCatalog.test.ts +0 -64
  120. package/src/__tests__/useBlockNavigator.test.tsx +0 -67
  121. package/src/__tests__/useImageEditor.test.tsx +0 -226
  122. package/src/__tests__/useJsonEditorTokens.test.ts +0 -59
  123. package/src/__tests__/useMediaRecorder.test.ts +0 -302
  124. package/src/__tests__/useTimelineClock.test.ts +0 -21
  125. package/src/__tests__/versionHistory.test.tsx +0 -197
  126. package/src/__tests__/viewMenuPanel.test.tsx +0 -130
  127. package/src/__tests__/wysiwygImageUpload.test.ts +0 -69
  128. package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +0 -56
  129. package/src/asciiDiagram/AsciiDiagramExtension.ts +0 -343
  130. package/src/asciiDiagram/AsciiDiagramWidget.tsx +0 -254
  131. package/src/asciiDiagram/RepairableDiagramExtension.ts +0 -200
  132. package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +0 -145
  133. package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +0 -159
  134. package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +0 -284
  135. package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +0 -114
  136. package/src/asciiDiagram/__tests__/asciiPaste.test.ts +0 -43
  137. package/src/asciiDiagram/asciiDiagramCommands.ts +0 -174
  138. package/src/asciiDiagram/asciiDiagramData.ts +0 -149
  139. package/src/asciiDiagram/asciiDiagramOps.ts +0 -238
  140. package/src/asciiDiagram/asciiPaste.ts +0 -19
  141. package/src/blockProperties.ts +0 -88
  142. package/src/blockRange.ts +0 -132
  143. package/src/blockSlice.ts +0 -75
  144. package/src/blockTagActivity.ts +0 -233
  145. package/src/buildDocumentPreviewMarkdown.ts +0 -168
  146. package/src/buildPreviewDoc.ts +0 -405
  147. package/src/codeContext/CodeContextSectionView.tsx +0 -124
  148. package/src/codeContext/CodeContextZoneManager.ts +0 -149
  149. package/src/codeContext/CodeContextZones.tsx +0 -121
  150. package/src/codeContext/diffContextSections.ts +0 -38
  151. package/src/codeContext/types.ts +0 -75
  152. package/src/customTemplates/AddBin.tsx +0 -126
  153. package/src/customTemplates/CustomLayoutManager.tsx +0 -234
  154. package/src/customTemplates/CustomTemplateContext.tsx +0 -188
  155. package/src/customTemplates/LayerToolbar.tsx +0 -580
  156. package/src/customTemplates/ShapeGlyph.tsx +0 -47
  157. package/src/customTemplates/TemplateDesigner.tsx +0 -433
  158. package/src/customTemplates/__tests__/library.test.ts +0 -96
  159. package/src/customTemplates/__tests__/normalizePositions.test.ts +0 -109
  160. package/src/customTemplates/__tests__/shapeDefs.test.ts +0 -49
  161. package/src/customTemplates/__tests__/useMemoryLayerAdapter.test.ts +0 -108
  162. package/src/customTemplates/designer.css +0 -732
  163. package/src/customTemplates/index.ts +0 -31
  164. package/src/customTemplates/library.ts +0 -110
  165. package/src/customTemplates/normalizePositions.ts +0 -75
  166. package/src/customTemplates/shapeDefs.ts +0 -131
  167. package/src/customTemplates/thumbnail.tsx +0 -63
  168. package/src/customTemplates/tokenDefs.ts +0 -60
  169. package/src/customTemplates/useDocCustomTemplates.ts +0 -52
  170. package/src/customTemplates/useMemoryLayerAdapter.ts +0 -129
  171. package/src/customThemes/CustomThemeContext.tsx +0 -185
  172. package/src/customThemes/CustomThemeDialog.tsx +0 -316
  173. package/src/customThemes/ImportThemeSection.tsx +0 -178
  174. package/src/customThemes/__tests__/CustomThemeContext.test.tsx +0 -64
  175. package/src/customThemes/__tests__/CustomThemeDialog.test.tsx +0 -47
  176. package/src/customThemes/__tests__/customThemeLibrary.test.ts +0 -60
  177. package/src/customThemes/__tests__/importThemeSection.test.tsx +0 -192
  178. package/src/customThemes/customThemeLibrary.ts +0 -100
  179. package/src/customThemes/index.ts +0 -37
  180. package/src/customThemes/themeControls.tsx +0 -229
  181. package/src/customThemes/themeDraft.ts +0 -288
  182. package/src/customThemes/useDocCustomThemes.ts +0 -49
  183. package/src/detectMarkdown.ts +0 -62
  184. package/src/diagram/DiagramCanvas.tsx +0 -259
  185. package/src/diagram/DiagramMaximizedOverlay.tsx +0 -46
  186. package/src/diagram/diagramCommands.ts +0 -604
  187. package/src/diagram/diagramConstants.ts +0 -17
  188. package/src/diagram/types.ts +0 -35
  189. package/src/editorScrollSync.ts +0 -73
  190. package/src/embeddedMedia.ts +0 -78
  191. package/src/emojiData.ts +0 -1317
  192. package/src/fileKind.ts +0 -158
  193. package/src/frontmatter.ts +0 -29
  194. package/src/frontmatterSettings.ts +0 -23
  195. package/src/headingTransition.ts +0 -291
  196. package/src/hooks/useFileDrop.ts +0 -266
  197. package/src/imageEditor/CanvasSurface.tsx +0 -703
  198. package/src/imageEditor/ImageVersionHistoryDropdown.tsx +0 -396
  199. package/src/imageEditor/LayersPanel.tsx +0 -218
  200. package/src/imageEditor/PropertiesPanel.tsx +0 -474
  201. package/src/imageEditor/Toolbar.tsx +0 -459
  202. package/src/imageEditor/createShapeLayer.ts +0 -280
  203. package/src/imageEditor/icons.tsx +0 -68
  204. package/src/imageEditor/image-editor.css +0 -574
  205. package/src/imageEditor/layers/EditorImageLayer.tsx +0 -45
  206. package/src/imageEditor/layers/EditorShapeLayer.tsx +0 -62
  207. package/src/imageEditor/layers/EditorTextLayer.tsx +0 -45
  208. package/src/imageEditor/layers/SelectionHandles.tsx +0 -86
  209. package/src/imageEditor/state.ts +0 -173
  210. package/src/imageEditor/useImageEditor.ts +0 -385
  211. package/src/imageEditor/useImageEditorTokens.ts +0 -70
  212. package/src/index.ts +0 -470
  213. package/src/jsonEditor/EmbeddedRichTextField.tsx +0 -81
  214. package/src/jsonEditor/JsonEditor.tsx +0 -81
  215. package/src/jsonEditor/JsonEditorContext.tsx +0 -81
  216. package/src/jsonEditor/RenderNode.tsx +0 -87
  217. package/src/jsonEditor/editors.tsx +0 -745
  218. package/src/jsonEditor/index.ts +0 -2
  219. package/src/jsonEditor/json-editor.css +0 -463
  220. package/src/jsonEditor/useJsonEditorTokens.ts +0 -33
  221. package/src/mediaDragMime.ts +0 -32
  222. package/src/mediaEntries.ts +0 -12
  223. package/src/mediaReferences.ts +0 -299
  224. package/src/monaco.ts +0 -35
  225. package/src/monacoWorkers.ts +0 -89
  226. package/src/outlineSource.ts +0 -171
  227. package/src/presentation/PresentationMode.tsx +0 -596
  228. package/src/rawEditorIsolation.ts +0 -18
  229. package/src/recorder/RecorderButton.tsx +0 -80
  230. package/src/recorder/RecorderModal.tsx +0 -706
  231. package/src/recorder/RecorderPanel.tsx +0 -77
  232. package/src/recorder/formats.ts +0 -159
  233. package/src/recorder/hooks/useMediaRecorder.ts +0 -436
  234. package/src/recorder/hooks/useStreamPreview.ts +0 -47
  235. package/src/recorder/insertMediaBlock.ts +0 -30
  236. package/src/recorder/sources/cameraStream.ts +0 -32
  237. package/src/recorder/sources/micStream.ts +0 -25
  238. package/src/recorder/sources/screenStream.ts +0 -162
  239. package/src/recorder/timingJson.ts +0 -66
  240. package/src/resolveBlockVisual.ts +0 -39
  241. package/src/scene/Scene.tsx +0 -616
  242. package/src/scene/SceneBlockExtension.ts +0 -205
  243. package/src/scene/SceneBlockToolbar.tsx +0 -201
  244. package/src/scene/SceneBlockWidget.tsx +0 -441
  245. package/src/scene/ScenePropsBar.tsx +0 -85
  246. package/src/scene/SceneSelection.tsx +0 -111
  247. package/src/scene/SceneSideToolbar.tsx +0 -89
  248. package/src/scene/SceneViewport.tsx +0 -106
  249. package/src/scene/ShapePalette.tsx +0 -196
  250. package/src/scene/__tests__/DiagramAdapter.test.ts +0 -142
  251. package/src/scene/__tests__/SceneBlockExtension.test.ts +0 -33
  252. package/src/scene/__tests__/bezierEdit.test.ts +0 -85
  253. package/src/scene/__tests__/blockLayers.test.ts +0 -57
  254. package/src/scene/__tests__/sceneIsolation.test.tsx +0 -119
  255. package/src/scene/__tests__/shapeLayers.test.ts +0 -106
  256. package/src/scene/__tests__/useSceneHitTest.test.ts +0 -90
  257. package/src/scene/__tests__/useScenePanZoom.test.ts +0 -103
  258. package/src/scene/adapters/DiagramAdapter.ts +0 -79
  259. package/src/scene/adapters/DrawingAdapter.ts +0 -420
  260. package/src/scene/adapters/LayoutAdapter.ts +0 -313
  261. package/src/scene/adapters/blockLayers.ts +0 -159
  262. package/src/scene/commands/SceneCommand.ts +0 -84
  263. package/src/scene/commands/drawingCommands.ts +0 -318
  264. package/src/scene/commands/layoutCommands.ts +0 -301
  265. package/src/scene/hooks/useSceneHitTest.ts +0 -105
  266. package/src/scene/hooks/useScenePanZoom.ts +0 -147
  267. package/src/scene/hooks/useSceneSelection.ts +0 -62
  268. package/src/scene/index.ts +0 -90
  269. package/src/scene/layers/DiagramEdges.tsx +0 -142
  270. package/src/scene/layers/edgeGeometry.ts +0 -104
  271. package/src/scene/layers/nodeCard.tsx +0 -164
  272. package/src/scene/layers/renderLayer.tsx +0 -70
  273. package/src/scene/layers/shapeLayers.ts +0 -201
  274. package/src/scene/paths/bezierEdit.ts +0 -208
  275. package/src/scene/scene.css +0 -792
  276. package/src/scene/text/SceneTextOverlay.tsx +0 -161
  277. package/src/scene/text/sceneTextChannel.ts +0 -46
  278. package/src/scene/text/sceneTextConfig.ts +0 -31
  279. package/src/scene/text/sceneTiptap.ts +0 -36
  280. package/src/scene/text/useSceneTextEditing.ts +0 -39
  281. package/src/scene/tools/ConnectTool.ts +0 -209
  282. package/src/scene/tools/DrawingConnectTool.ts +0 -231
  283. package/src/scene/tools/PathTool.ts +0 -158
  284. package/src/scene/tools/PlaceTool.ts +0 -47
  285. package/src/scene/tools/SceneTool.ts +0 -85
  286. package/src/scene/tools/SelectTool.ts +0 -300
  287. package/src/scene/tools/ShapeTool.ts +0 -143
  288. package/src/scene/tools/TextTool.ts +0 -72
  289. package/src/scene/tools/TokenTool.ts +0 -95
  290. package/src/scene/tools/createDrawShapeTool.ts +0 -82
  291. package/src/selectionConversions.ts +0 -155
  292. package/src/styles/ascii-diagram.css +0 -79
  293. package/src/styles/ascii-timeline.css +0 -596
  294. package/src/styles/code-context.css +0 -155
  295. package/src/styles/diagram.css +0 -183
  296. package/src/styles/editor.css +0 -6811
  297. package/src/styles/folder-view.css +0 -210
  298. package/src/styles/image-edit-affordance.css +0 -201
  299. package/src/styles/index.css +0 -18
  300. package/src/styles/tree-view.css +0 -189
  301. package/src/teleprompter/TeleprompterControls.tsx +0 -218
  302. package/src/teleprompter/TeleprompterSelfView.tsx +0 -22
  303. package/src/teleprompter/TeleprompterSurface.tsx +0 -267
  304. package/src/teleprompter/TeleprompterView.tsx +0 -338
  305. package/src/teleprompter/canvasRenderer.ts +0 -161
  306. package/src/teleprompter/floatingWindow.ts +0 -352
  307. package/src/teleprompter/index.ts +0 -61
  308. package/src/teleprompter/pcmWorklet.ts +0 -62
  309. package/src/teleprompter/recording/insertPreamble.ts +0 -80
  310. package/src/teleprompter/recording/narrationSave.ts +0 -134
  311. package/src/teleprompter/recording/useNarrationRecorder.ts +0 -367
  312. package/src/teleprompter/scrollModel.ts +0 -85
  313. package/src/teleprompter/teleprompterTheme.ts +0 -303
  314. package/src/teleprompter/types.ts +0 -38
  315. package/src/teleprompter/useFloatingWindow.ts +0 -74
  316. package/src/teleprompter/useMicAnalysis.ts +0 -211
  317. package/src/teleprompter/useTeleprompter.ts +0 -421
  318. package/src/templateContentPreviewResolver.ts +0 -360
  319. package/src/timeline/TimelineEditorWidget.tsx +0 -861
  320. package/src/timeline/TimelineViewExtension.ts +0 -252
  321. package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +0 -414
  322. package/src/timeline/__tests__/TimelineViewExtension.test.ts +0 -163
  323. package/src/timeline/__tests__/timelineCommands.test.ts +0 -359
  324. package/src/timeline/__tests__/timelineOps.test.ts +0 -277
  325. package/src/timeline/__tests__/timelinePaste.test.ts +0 -34
  326. package/src/timeline/timelineCommands.ts +0 -421
  327. package/src/timeline/timelineData.ts +0 -54
  328. package/src/timeline/timelineOps.ts +0 -381
  329. package/src/timeline/timelinePaste.ts +0 -8
  330. package/src/timelineSource.ts +0 -244
  331. package/src/tiptap/TiptapAudio.tsx +0 -86
  332. package/src/tiptap/TiptapVideo.tsx +0 -119
  333. package/src/tiptap/useResolvedMediaSrc.ts +0 -47
  334. package/src/tiptapBridge.ts +0 -986
  335. package/src/tooltipPlacement.ts +0 -13
  336. package/src/transformStyleId.ts +0 -17
  337. package/src/transitionCatalog.ts +0 -159
  338. package/src/treeview/TreeOutlineWidget.tsx +0 -390
  339. package/src/treeview/TreeViewExtension.ts +0 -250
  340. package/src/treeview/__tests__/TreeOutlineWidget.test.tsx +0 -156
  341. package/src/treeview/__tests__/TreeViewExtension.test.ts +0 -122
  342. package/src/treeview/__tests__/treeOps.test.ts +0 -177
  343. package/src/treeview/__tests__/treePaste.test.ts +0 -40
  344. package/src/treeview/__tests__/treeViewCommands.test.ts +0 -179
  345. package/src/treeview/treeOps.ts +0 -231
  346. package/src/treeview/treePaste.ts +0 -13
  347. package/src/treeview/treeViewCommands.ts +0 -99
  348. package/src/treeview/treeViewData.ts +0 -41
  349. package/src/types/monaco-shims.d.ts +0 -10
  350. package/src/useBlockNavigator.ts +0 -153
  351. package/src/useHeadingLayout.ts +0 -294
  352. package/src/useMonacoLoader.ts +0 -82
  353. package/src/useTimelineClock.ts +0 -76
  354. package/src/utils/collectInlineFontAwesomeCss.ts +0 -69
  355. package/src/utils/dropUtils.ts +0 -136
  356. package/src/utils/normalizeMalformedAssetUrl.ts +0 -22
  357. package/src/wysiwygImageUpload.ts +0 -113
@@ -1,396 +0,0 @@
1
- /**
2
- * ImageVersionHistoryDropdown
3
- *
4
- * Toolbar-anchored popover that lists image-edit version snapshots from
5
- * the sidecar and lets the user revert to any of them.
6
- *
7
- * For each snapshot we render a small thumbnail (rasterized via
8
- * `exportImageEditDoc` at a thumbnail-sized scale) plus a one-line diff
9
- * summary describing what changed relative to the *previous* (older)
10
- * snapshot — e.g. "Resized 4800×920 → 1200×920", "Added text layer",
11
- * "Removed Background". This makes it possible to identify the right
12
- * snapshot to revert to without having to revert blindly.
13
- *
14
- * Thumbnails and diffs are computed lazily and cached by version path.
15
- * Loads happen in the background after the popover opens.
16
- */
17
-
18
- import { useCallback, useEffect, useRef, useState } from 'react';
19
- import {
20
- exportImageEditDoc,
21
- type ImageEditDoc,
22
- type ImageEditLayer,
23
- type ImageEditVersionManager,
24
- } from '@bendyline/squisq/imageEdit';
25
- import type { ContentContainer } from '@bendyline/squisq/storage';
26
- import type { Version } from '@bendyline/squisq/versions';
27
-
28
- interface Props {
29
- versioning: ImageEditVersionManager;
30
- /**
31
- * Sidecar container backing the snapshots — same one passed to the
32
- * `<ImageEditor>`. Required for thumbnail rendering since
33
- * `exportImageEditDoc` reads asset bytes from the container.
34
- */
35
- container: ContentContainer;
36
- /** Called when the user picks a snapshot to load into the editor. */
37
- onRevert: (version: Version) => void | Promise<void>;
38
- /**
39
- * Persistence revision counter. When the host saves a new snapshot
40
- * (e.g. on flush), bumping this triggers a re-list so the popover
41
- * stays in sync without polling.
42
- */
43
- refreshKey?: number;
44
- }
45
-
46
- interface VersionMeta {
47
- doc: ImageEditDoc;
48
- thumbUrl: string | null;
49
- }
50
-
51
- const THUMB_MAX_DIM = 96;
52
-
53
- export function ImageVersionHistoryDropdown({
54
- versioning,
55
- container,
56
- onRevert,
57
- refreshKey,
58
- }: Props) {
59
- const [open, setOpen] = useState(false);
60
- const [versions, setVersions] = useState<Version[]>([]);
61
- const [loading, setLoading] = useState(false);
62
- const [busyTimestamp, setBusyTimestamp] = useState<number | null>(null);
63
- // Per-version metadata (doc + thumbnail). Keyed by version.path.
64
- const [meta, setMeta] = useState<Record<string, VersionMeta>>({});
65
- const popoverRef = useRef<HTMLDivElement | null>(null);
66
- const triggerRef = useRef<HTMLButtonElement | null>(null);
67
- // Track object URLs so we can revoke them on unmount / cache reset.
68
- const urlsRef = useRef<Set<string>>(new Set());
69
-
70
- // Re-load the list when the popover opens or when the host bumps refreshKey.
71
- useEffect(() => {
72
- if (!open) return;
73
- let cancelled = false;
74
- setLoading(true);
75
- versioning
76
- .listVersions()
77
- .then((list) => {
78
- if (cancelled) return;
79
- // Newest first.
80
- const sorted = [...list].sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
81
- setVersions(sorted);
82
- })
83
- .catch(() => {
84
- if (cancelled) return;
85
- setVersions([]);
86
- })
87
- .finally(() => {
88
- if (!cancelled) setLoading(false);
89
- });
90
- return () => {
91
- cancelled = true;
92
- };
93
- }, [open, versioning, refreshKey]);
94
-
95
- // Lazily load doc + thumbnail for any version we don't have cached yet.
96
- // We do this sequentially to avoid hammering the canvas pipeline.
97
- useEffect(() => {
98
- if (!open) return;
99
- if (versions.length === 0) return;
100
- let cancelled = false;
101
- (async () => {
102
- for (const v of versions) {
103
- if (cancelled) return;
104
- if (meta[v.path]) continue;
105
- try {
106
- const doc = await versioning.readVersion(v);
107
- if (cancelled) return;
108
- if (!doc) {
109
- setMeta((m) => ({ ...m, [v.path]: { doc: emptyDoc(), thumbUrl: null } }));
110
- continue;
111
- }
112
- let thumbUrl: string | null = null;
113
- try {
114
- const scale = computeThumbScale(doc.canvas.width, doc.canvas.height);
115
- const blob = await exportImageEditDoc(doc, container, {
116
- format: 'png',
117
- scale,
118
- });
119
- if (cancelled) return;
120
- thumbUrl = URL.createObjectURL(blob);
121
- urlsRef.current.add(thumbUrl);
122
- } catch {
123
- // Best-effort thumbnail — fall back to a placeholder.
124
- thumbUrl = null;
125
- }
126
- setMeta((m) => ({ ...m, [v.path]: { doc, thumbUrl } }));
127
- } catch {
128
- // Skip on read error; the row will render without a thumb.
129
- }
130
- }
131
- })();
132
- return () => {
133
- cancelled = true;
134
- };
135
- }, [open, versions, versioning, container, meta]);
136
-
137
- // Revoke object URLs on unmount.
138
- useEffect(() => {
139
- const urls = urlsRef.current;
140
- return () => {
141
- for (const url of urls) URL.revokeObjectURL(url);
142
- urls.clear();
143
- };
144
- }, []);
145
-
146
- // When refreshKey changes, blow away the cache so newly-saved
147
- // snapshots are re-thumbnailed (and old thumbs aren't stale).
148
- useEffect(() => {
149
- for (const url of urlsRef.current) URL.revokeObjectURL(url);
150
- urlsRef.current.clear();
151
- setMeta({});
152
- }, [refreshKey]);
153
-
154
- // Close on outside click / Escape.
155
- useEffect(() => {
156
- if (!open) return;
157
- function onDocClick(e: MouseEvent) {
158
- const t = e.target as Node | null;
159
- if (!t) return;
160
- if (popoverRef.current?.contains(t)) return;
161
- if (triggerRef.current?.contains(t)) return;
162
- setOpen(false);
163
- }
164
- function onKey(e: KeyboardEvent) {
165
- if (e.key === 'Escape') setOpen(false);
166
- }
167
- document.addEventListener('mousedown', onDocClick);
168
- document.addEventListener('keydown', onKey);
169
- return () => {
170
- document.removeEventListener('mousedown', onDocClick);
171
- document.removeEventListener('keydown', onKey);
172
- };
173
- }, [open]);
174
-
175
- const handleRevert = useCallback(
176
- async (v: Version) => {
177
- setBusyTimestamp(v.timestamp.getTime());
178
- try {
179
- await onRevert(v);
180
- setOpen(false);
181
- } catch (err: unknown) {
182
- console.warn(
183
- '[squisq-editor] image-edit revert failed:',
184
- err instanceof Error ? err.message : err,
185
- );
186
- } finally {
187
- setBusyTimestamp(null);
188
- }
189
- },
190
- [onRevert],
191
- );
192
-
193
- return (
194
- <div className="squisq-image-editor-version-dropdown">
195
- <button
196
- ref={triggerRef}
197
- type="button"
198
- className="squisq-image-editor-tool-button squisq-image-editor-tool-button--with-label"
199
- onClick={() => setOpen((o) => !o)}
200
- aria-expanded={open}
201
- aria-haspopup="menu"
202
- title="Version history"
203
- data-testid="image-editor-history-trigger"
204
- >
205
- <span>History</span>
206
- <span aria-hidden="true" style={{ fontSize: '0.8em' }}>
207
-
208
- </span>
209
- </button>
210
- {open && (
211
- <div
212
- ref={popoverRef}
213
- className="squisq-image-editor-version-popover"
214
- role="menu"
215
- data-testid="image-editor-history-popover"
216
- >
217
- <div className="squisq-image-editor-version-popover__title">Version history</div>
218
- {loading && <div className="squisq-image-editor-version-popover__empty">Loading…</div>}
219
- {!loading && versions.length === 0 && (
220
- <div className="squisq-image-editor-version-popover__empty">No snapshots yet</div>
221
- )}
222
- {!loading && versions.length > 0 && (
223
- <ul className="squisq-image-editor-version-popover__list">
224
- {versions.map((v, i) => {
225
- const ts = v.timestamp.getTime();
226
- const m = meta[v.path];
227
- const isCurrent = i === 0;
228
- const isOriginal = i + 1 >= versions.length;
229
- // Diff is computed against the *next* (older) version in
230
- // the newest-first list. The oldest snapshot has no
231
- // older neighbor, so we label it "Original".
232
- const olderMeta = i + 1 < versions.length ? meta[versions[i + 1]!.path] : undefined;
233
- const summary = isOriginal
234
- ? 'Original'
235
- : m && olderMeta
236
- ? summarizeDiff(olderMeta.doc, m.doc)
237
- : '';
238
- return (
239
- <li
240
- key={ts}
241
- className={
242
- 'squisq-image-editor-version-popover__row' +
243
- (isCurrent ? ' squisq-image-editor-version-popover__row--current' : '')
244
- }
245
- >
246
- <div className="squisq-image-editor-version-popover__thumb">
247
- {m?.thumbUrl ? (
248
- <img
249
- src={m.thumbUrl}
250
- alt=""
251
- className="squisq-image-editor-version-popover__thumb-img"
252
- />
253
- ) : (
254
- <div
255
- className="squisq-image-editor-version-popover__thumb-placeholder"
256
- aria-hidden="true"
257
- />
258
- )}
259
- </div>
260
- <div className="squisq-image-editor-version-popover__info">
261
- <div className="squisq-image-editor-version-popover__when">
262
- {isCurrent && (
263
- <span className="squisq-image-editor-version-popover__badge">
264
- Current
265
- </span>
266
- )}
267
- {formatTimestamp(v.timestamp)}
268
- </div>
269
- <div className="squisq-image-editor-version-popover__summary" title={summary}>
270
- {summary || (m ? '' : 'Loading…')}
271
- </div>
272
- </div>
273
- <button
274
- type="button"
275
- className="squisq-image-editor-tool-button"
276
- onClick={() => handleRevert(v)}
277
- disabled={isCurrent || busyTimestamp === ts}
278
- title={isCurrent ? 'This is the current version' : 'Revert to this version'}
279
- >
280
- {busyTimestamp === ts ? 'Loading…' : 'Revert'}
281
- </button>
282
- </li>
283
- );
284
- })}
285
- </ul>
286
- )}
287
- </div>
288
- )}
289
- </div>
290
- );
291
- }
292
-
293
- /**
294
- * Pick a `scale` factor for `exportImageEditDoc` so the rendered
295
- * thumbnail's longest side is at most `THUMB_MAX_DIM` pixels. Always
296
- * ≤1 to avoid up-rendering small canvases.
297
- */
298
- function computeThumbScale(width: number, height: number): number {
299
- const longest = Math.max(width, height);
300
- if (longest <= THUMB_MAX_DIM) return 1;
301
- return THUMB_MAX_DIM / longest;
302
- }
303
-
304
- /**
305
- * Convert a Date to a human-readable local time.
306
- */
307
- function formatTimestamp(stamp: Date): string {
308
- if (Number.isNaN(stamp.getTime())) return String(stamp);
309
- return stamp.toLocaleString(undefined, {
310
- month: 'short',
311
- day: 'numeric',
312
- hour: 'numeric',
313
- minute: '2-digit',
314
- second: '2-digit',
315
- });
316
- }
317
-
318
- /**
319
- * Build a one-line, human-readable summary of what changed between
320
- * two snapshots. Reports (in priority order):
321
- *
322
- * 1. Canvas size change — "Resized 4800×920 → 1200×920"
323
- * 2. Layer additions/removals — "Added text", "Removed Background"
324
- * 3. Per-layer position/size — "Moved Text", "Resized Image"
325
- * 4. Per-layer content/style — "Edited Text"
326
- *
327
- * Returns "No changes" if the two docs are byte-equivalent in the
328
- * fields we compare.
329
- */
330
- function summarizeDiff(prev: ImageEditDoc, next: ImageEditDoc): string {
331
- const parts: string[] = [];
332
-
333
- // 1. Canvas size.
334
- if (prev.canvas.width !== next.canvas.width || prev.canvas.height !== next.canvas.height) {
335
- parts.push(
336
- `Resized ${prev.canvas.width}×${prev.canvas.height} → ${next.canvas.width}×${next.canvas.height}`,
337
- );
338
- } else if (prev.canvas.background !== next.canvas.background) {
339
- parts.push('Changed background');
340
- }
341
-
342
- // 2. Layer add / remove (by id).
343
- const prevIds = new Set(prev.layers.map((l) => l.id));
344
- const nextIds = new Set(next.layers.map((l) => l.id));
345
- const added = next.layers.filter((l) => !prevIds.has(l.id));
346
- const removed = prev.layers.filter((l) => !nextIds.has(l.id));
347
- for (const l of added) parts.push(`Added ${describeLayer(l)}`);
348
- for (const l of removed) parts.push(`Removed ${describeLayer(l)}`);
349
-
350
- // 3 & 4. Per-layer changes for layers present in both.
351
- for (const n of next.layers) {
352
- const p = prev.layers.find((l) => l.id === n.id);
353
- if (!p) continue;
354
- const change = describeLayerChange(p, n);
355
- if (change) parts.push(change);
356
- }
357
-
358
- if (parts.length === 0) return 'No changes';
359
- // Cap to a sane length — the popover row is one line.
360
- return parts.slice(0, 3).join(' · ') + (parts.length > 3 ? ` (+${parts.length - 3})` : '');
361
- }
362
-
363
- function describeLayer(layer: ImageEditLayer): string {
364
- const name = layer.name?.trim();
365
- return name && name.length > 0 ? `“${name}”` : layer.type;
366
- }
367
-
368
- function describeLayerChange(p: ImageEditLayer, n: ImageEditLayer): string | null {
369
- const label = describeLayer(n);
370
- const posChanged =
371
- p.position.x !== n.position.x ||
372
- p.position.y !== n.position.y ||
373
- p.position.width !== n.position.width ||
374
- p.position.height !== n.position.height;
375
- const sizeChanged =
376
- p.position.width !== n.position.width || p.position.height !== n.position.height;
377
- // Treat content + style as a single "edited" bucket.
378
- const contentChanged = JSON.stringify(p.content) !== JSON.stringify(n.content);
379
-
380
- if (sizeChanged && !contentChanged) return `Resized ${label}`;
381
- if (posChanged && !contentChanged) return `Moved ${label}`;
382
- if (contentChanged) return `Edited ${label}`;
383
- return null;
384
- }
385
-
386
- /**
387
- * Fallback used when a snapshot fails to load — keeps render code
388
- * branch-free without polluting the diff summary with errors.
389
- */
390
- function emptyDoc(): ImageEditDoc {
391
- return {
392
- version: 1,
393
- canvas: { width: 0, height: 0, background: 'transparent' },
394
- layers: [],
395
- };
396
- }
@@ -1,218 +0,0 @@
1
- /**
2
- * LayersPanel — list of layers with visibility / lock / reorder / delete
3
- * controls. Newest layer is rendered last (top of the SVG stack), so the
4
- * panel shows the array reversed so "top" appears at the top of the list.
5
- */
6
-
7
- import { useEffect, useRef, useState } from 'react';
8
- import type { ImageEditDoc } from '@bendyline/squisq/schemas';
9
- import type { ImageEditorAction } from './state.js';
10
- import {
11
- ChevronDownIcon,
12
- ChevronUpIcon,
13
- CloseIcon,
14
- EyeIcon,
15
- EyeOffIcon,
16
- ImageIcon,
17
- LockIcon,
18
- PlusIcon,
19
- ShapeIcon,
20
- TextIcon,
21
- UnlockIcon,
22
- } from './icons.js';
23
-
24
- export type AddableLayerKind = 'text' | 'shape' | 'image';
25
-
26
- export interface LayersPanelProps {
27
- doc: ImageEditDoc;
28
- selectedLayerId: string | null;
29
- dispatch: (action: ImageEditorAction) => void;
30
- onAddLayer: (kind: AddableLayerKind) => void;
31
- }
32
-
33
- const ADD_LAYER_OPTIONS: ReadonlyArray<{
34
- kind: AddableLayerKind;
35
- label: string;
36
- icon: React.ReactNode;
37
- }> = [
38
- { kind: 'text', label: 'Text layer', icon: <TextIcon /> },
39
- { kind: 'shape', label: 'Shape layer', icon: <ShapeIcon /> },
40
- { kind: 'image', label: 'Image layer…', icon: <ImageIcon /> },
41
- ];
42
-
43
- export function LayersPanel({ doc, selectedLayerId, dispatch, onAddLayer }: LayersPanelProps) {
44
- const [addMenuOpen, setAddMenuOpen] = useState(false);
45
- const addMenuRef = useRef<HTMLSpanElement | null>(null);
46
-
47
- useEffect(() => {
48
- if (!addMenuOpen) return;
49
-
50
- const closeOnOutsideClick = (event: MouseEvent) => {
51
- if (!addMenuRef.current?.contains(event.target as Node)) setAddMenuOpen(false);
52
- };
53
- const closeOnEscape = (event: KeyboardEvent) => {
54
- if (event.key === 'Escape') setAddMenuOpen(false);
55
- };
56
-
57
- document.addEventListener('mousedown', closeOnOutsideClick);
58
- document.addEventListener('keydown', closeOnEscape);
59
- return () => {
60
- document.removeEventListener('mousedown', closeOnOutsideClick);
61
- document.removeEventListener('keydown', closeOnEscape);
62
- };
63
- }, [addMenuOpen]);
64
-
65
- // Visual order: top of stack first.
66
- const ordered = doc.layers.slice().reverse();
67
-
68
- return (
69
- <div className="squisq-image-editor-layers" data-testid="image-editor-layers">
70
- <div className="squisq-image-editor-panel-header squisq-image-editor-layers-header">
71
- <span>Layers</span>
72
- <span ref={addMenuRef} className="squisq-image-editor-layer-add">
73
- <button
74
- type="button"
75
- className="squisq-image-editor-layer-add-button"
76
- onClick={() => setAddMenuOpen((open) => !open)}
77
- aria-label="Add layer"
78
- title="Add layer"
79
- aria-haspopup="menu"
80
- aria-expanded={addMenuOpen}
81
- >
82
- <PlusIcon />
83
- </button>
84
- {addMenuOpen && (
85
- <div className="squisq-image-editor-layer-add-menu" role="menu">
86
- {ADD_LAYER_OPTIONS.map(({ kind, label, icon }) => (
87
- <button
88
- key={kind}
89
- type="button"
90
- className="squisq-image-editor-layer-add-menu-item"
91
- role="menuitem"
92
- onClick={() => {
93
- setAddMenuOpen(false);
94
- onAddLayer(kind);
95
- }}
96
- >
97
- {icon}
98
- <span>{label}</span>
99
- </button>
100
- ))}
101
- </div>
102
- )}
103
- </span>
104
- </div>
105
- <ul className="squisq-image-editor-layer-list">
106
- {ordered.length === 0 && <li className="squisq-image-editor-layer-empty">No layers yet</li>}
107
- {ordered.map((layer) => {
108
- const visible = layer.visible !== false;
109
- const locked = !!layer.locked;
110
- const stackIndex = doc.layers.findIndex((l) => l.id === layer.id);
111
- const canMoveUp = stackIndex < doc.layers.length - 1;
112
- const canMoveDown = stackIndex > 0;
113
- const isSelected = selectedLayerId === layer.id;
114
- const layerName = layer.name ?? defaultLayerName(layer);
115
-
116
- return (
117
- <li
118
- key={layer.id}
119
- className={[
120
- 'squisq-image-editor-layer-item',
121
- isSelected ? 'is-selected' : '',
122
- visible ? '' : 'is-hidden',
123
- ]
124
- .filter(Boolean)
125
- .join(' ')}
126
- >
127
- <button
128
- type="button"
129
- className="squisq-image-editor-layer-toggle"
130
- onClick={() =>
131
- dispatch({
132
- type: 'update-layer',
133
- layerId: layer.id,
134
- patch: { visible: !visible },
135
- })
136
- }
137
- aria-label={visible ? 'Hide layer' : 'Show layer'}
138
- title={visible ? 'Hide layer' : 'Show layer'}
139
- >
140
- {visible ? <EyeIcon /> : <EyeOffIcon />}
141
- </button>
142
- <button
143
- type="button"
144
- className="squisq-image-editor-layer-toggle"
145
- onClick={() =>
146
- dispatch({
147
- type: 'update-layer',
148
- layerId: layer.id,
149
- patch: { locked: !locked },
150
- })
151
- }
152
- aria-label={locked ? 'Unlock layer' : 'Lock layer'}
153
- title={locked ? 'Unlock layer' : 'Lock layer'}
154
- >
155
- {locked ? <LockIcon /> : <UnlockIcon />}
156
- </button>
157
- <button
158
- type="button"
159
- className="squisq-image-editor-layer-name"
160
- onClick={() => dispatch({ type: 'select', layerId: layer.id })}
161
- title={layerName}
162
- >
163
- <span className="squisq-image-editor-layer-name-text">{layerName}</span>
164
- <span className="squisq-image-editor-layer-kind">{layer.type}</span>
165
- </button>
166
- <button
167
- type="button"
168
- className="squisq-image-editor-layer-toggle"
169
- disabled={!canMoveUp}
170
- onClick={() =>
171
- dispatch({ type: 'reorder-layer', layerId: layer.id, toIndex: stackIndex + 1 })
172
- }
173
- aria-label="Move layer up"
174
- title="Move layer up"
175
- >
176
- <ChevronUpIcon />
177
- </button>
178
- <button
179
- type="button"
180
- className="squisq-image-editor-layer-toggle"
181
- disabled={!canMoveDown}
182
- onClick={() =>
183
- dispatch({ type: 'reorder-layer', layerId: layer.id, toIndex: stackIndex - 1 })
184
- }
185
- aria-label="Move layer down"
186
- title="Move layer down"
187
- >
188
- <ChevronDownIcon />
189
- </button>
190
- <button
191
- type="button"
192
- className="squisq-image-editor-layer-toggle"
193
- onClick={() => dispatch({ type: 'remove-layer', layerId: layer.id })}
194
- aria-label="Delete layer"
195
- title="Delete layer"
196
- >
197
- <CloseIcon />
198
- </button>
199
- </li>
200
- );
201
- })}
202
- </ul>
203
- </div>
204
- );
205
- }
206
-
207
- function defaultLayerName(layer: { type: string; content?: unknown }): string {
208
- if (layer.type === 'text') {
209
- const c = layer.content as { text?: string } | undefined;
210
- return c?.text?.split('\n')[0]?.slice(0, 24) || 'Text';
211
- }
212
- if (layer.type === 'image') return 'Image';
213
- if (layer.type === 'shape') {
214
- const c = layer.content as { shape?: string } | undefined;
215
- return c?.shape ? c.shape[0]!.toUpperCase() + c.shape.slice(1) : 'Shape';
216
- }
217
- return layer.type;
218
- }