@excalidraw/excalidraw 0.17.1-7441-4e2c539 → 0.17.1-a38e82f

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 (249) hide show
  1. package/CHANGELOG.md +52 -2
  2. package/dist/browser/dev/excalidraw-assets-dev/chunk-5VWQDKDR.js +20279 -0
  3. package/dist/browser/dev/excalidraw-assets-dev/chunk-5VWQDKDR.js.map +7 -0
  4. package/dist/browser/dev/excalidraw-assets-dev/{chunk-2W5GQUR4.js → chunk-IM4WTX2M.js} +12 -6
  5. package/dist/browser/dev/excalidraw-assets-dev/chunk-IM4WTX2M.js.map +7 -0
  6. package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js → en-IOBA4CS2.js} +4 -2
  7. package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css +6 -0
  8. package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css.map +7 -0
  9. package/dist/browser/dev/excalidraw-assets-dev/{image-HYNUJ3XL.js → image-VKDAL6BQ.js} +2 -4
  10. package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js +161 -0
  11. package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js.map +7 -0
  12. package/dist/browser/dev/index.css +189 -129
  13. package/dist/browser/dev/index.css.map +3 -3
  14. package/dist/browser/dev/index.js +34707 -26
  15. package/dist/browser/dev/index.js.map +4 -4
  16. package/dist/browser/prod/excalidraw-assets/chunk-LIG3S5TN.js +11 -0
  17. package/dist/browser/prod/excalidraw-assets/chunk-N2C5DK3B.js +55 -0
  18. package/dist/browser/prod/excalidraw-assets/en-WFZVQ7I6.js +1 -0
  19. package/dist/browser/prod/excalidraw-assets/image-4AT7LYMR.js +1 -0
  20. package/dist/browser/prod/excalidraw-assets/image-X66R2EM5.css +1 -0
  21. package/dist/browser/prod/excalidraw-assets/roundRect-2ACQK4DA.js +1 -0
  22. package/dist/browser/prod/index.css +1 -1
  23. package/dist/browser/prod/index.js +203 -1
  24. package/dist/{prod/en-RLIAOBCI.json → dev/en-TDNWCAOT.json} +9 -5
  25. package/dist/dev/index.css +189 -129
  26. package/dist/dev/index.css.map +3 -3
  27. package/dist/dev/index.js +38445 -39402
  28. package/dist/dev/index.js.map +4 -4
  29. package/dist/excalidraw/actions/actionAddToLibrary.d.ts +12 -12
  30. package/dist/excalidraw/actions/actionAlign.d.ts +6 -6
  31. package/dist/excalidraw/actions/actionAlign.js +2 -1
  32. package/dist/excalidraw/actions/actionBoundText.d.ts +8 -8
  33. package/dist/excalidraw/actions/actionBoundText.js +8 -8
  34. package/dist/excalidraw/actions/actionCanvas.d.ts +46 -46
  35. package/dist/excalidraw/actions/actionClipboard.d.ts +27 -27
  36. package/dist/excalidraw/actions/actionClipboard.js +9 -2
  37. package/dist/excalidraw/actions/actionDeleteSelected.d.ts +12 -12
  38. package/dist/excalidraw/actions/actionDeleteSelected.js +3 -2
  39. package/dist/excalidraw/actions/actionDistribute.d.ts +2 -2
  40. package/dist/excalidraw/actions/actionDistribute.js +1 -1
  41. package/dist/excalidraw/actions/actionDuplicateSelection.d.ts +1 -1
  42. package/dist/excalidraw/actions/actionDuplicateSelection.js +4 -3
  43. package/dist/excalidraw/actions/actionElementLock.d.ts +8 -8
  44. package/dist/excalidraw/actions/actionExport.d.ts +35 -35
  45. package/dist/excalidraw/actions/actionExport.js +4 -4
  46. package/dist/excalidraw/actions/actionFinalize.d.ts +7 -7
  47. package/dist/excalidraw/actions/actionFinalize.js +7 -6
  48. package/dist/excalidraw/actions/actionFlip.d.ts +2 -2
  49. package/dist/excalidraw/actions/actionFlip.js +11 -11
  50. package/dist/excalidraw/actions/actionFrame.d.ts +13 -13
  51. package/dist/excalidraw/actions/actionFrame.js +1 -1
  52. package/dist/excalidraw/actions/actionGroup.d.ts +8 -8
  53. package/dist/excalidraw/actions/actionGroup.js +3 -2
  54. package/dist/excalidraw/actions/actionLinearEditor.d.ts +4 -4
  55. package/dist/excalidraw/actions/actionLinearEditor.js +1 -1
  56. package/dist/excalidraw/{element/Hyperlink.d.ts → actions/actionLink.d.ts} +28 -50
  57. package/dist/excalidraw/actions/actionLink.js +40 -0
  58. package/dist/excalidraw/actions/actionMenu.d.ts +11 -11
  59. package/dist/excalidraw/actions/actionNavigate.d.ts +8 -8
  60. package/dist/excalidraw/actions/actionNavigate.js +1 -1
  61. package/dist/excalidraw/actions/actionProperties.d.ts +64 -64
  62. package/dist/excalidraw/actions/actionProperties.js +32 -27
  63. package/dist/excalidraw/actions/actionSelectAll.d.ts +4 -4
  64. package/dist/excalidraw/actions/actionSelectAll.js +1 -1
  65. package/dist/excalidraw/actions/actionStyles.d.ts +6 -6
  66. package/dist/excalidraw/actions/actionStyles.js +4 -4
  67. package/dist/excalidraw/actions/actionToggleGridMode.d.ts +4 -4
  68. package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +4 -4
  69. package/dist/excalidraw/actions/actionToggleStats.d.ts +4 -4
  70. package/dist/excalidraw/actions/actionToggleViewMode.d.ts +4 -4
  71. package/dist/excalidraw/actions/actionToggleZenMode.d.ts +4 -4
  72. package/dist/excalidraw/actions/index.d.ts +1 -1
  73. package/dist/excalidraw/actions/index.js +1 -1
  74. package/dist/excalidraw/actions/manager.js +2 -1
  75. package/dist/excalidraw/align.d.ts +2 -2
  76. package/dist/excalidraw/align.js +2 -2
  77. package/dist/excalidraw/animated-trail.d.ts +33 -0
  78. package/dist/excalidraw/animated-trail.js +96 -0
  79. package/dist/excalidraw/animation-frame-handler.d.ts +16 -0
  80. package/dist/excalidraw/animation-frame-handler.js +55 -0
  81. package/dist/excalidraw/appState.d.ts +1 -1
  82. package/dist/excalidraw/appState.js +1 -3
  83. package/dist/excalidraw/clipboard.js +5 -5
  84. package/dist/excalidraw/components/Actions.d.ts +3 -3
  85. package/dist/excalidraw/components/Actions.js +18 -7
  86. package/dist/excalidraw/components/App.d.ts +32 -17
  87. package/dist/excalidraw/components/App.js +474 -339
  88. package/dist/excalidraw/components/Button.d.ts +1 -1
  89. package/dist/excalidraw/components/FilledButton.d.ts +2 -2
  90. package/dist/excalidraw/components/FilledButton.js +27 -3
  91. package/dist/excalidraw/components/FollowMode/FollowMode.js +1 -1
  92. package/dist/excalidraw/components/ImageExportDialog.d.ts +2 -1
  93. package/dist/excalidraw/components/ImageExportDialog.js +16 -12
  94. package/dist/excalidraw/components/JSONExportDialog.js +1 -1
  95. package/dist/excalidraw/components/{LaserTool/LaserPointerButton.d.ts → LaserPointerButton.d.ts} +1 -1
  96. package/dist/excalidraw/components/{LaserTool/LaserPointerButton.js → LaserPointerButton.js} +2 -2
  97. package/dist/excalidraw/components/LayerUI.js +3 -3
  98. package/dist/excalidraw/components/MobileMenu.js +1 -1
  99. package/dist/excalidraw/components/ProjectName.d.ts +0 -1
  100. package/dist/excalidraw/components/ProjectName.js +1 -1
  101. package/dist/excalidraw/components/SVGLayer.d.ts +8 -0
  102. package/dist/excalidraw/components/SVGLayer.js +20 -0
  103. package/dist/excalidraw/components/ShareableLinkDialog.js +10 -10
  104. package/dist/excalidraw/components/Stack.d.ts +2 -2
  105. package/dist/excalidraw/components/TTDDialog/common.js +10 -1
  106. package/dist/excalidraw/components/TextField.d.ts +5 -2
  107. package/dist/excalidraw/components/TextField.js +6 -3
  108. package/dist/excalidraw/components/Toast.d.ts +3 -2
  109. package/dist/excalidraw/components/Toast.js +2 -2
  110. package/dist/excalidraw/components/ToolButton.js +2 -1
  111. package/dist/excalidraw/components/canvases/InteractiveCanvas.d.ts +2 -2
  112. package/dist/excalidraw/components/canvases/InteractiveCanvas.js +6 -5
  113. package/dist/excalidraw/components/canvases/StaticCanvas.d.ts +4 -3
  114. package/dist/excalidraw/components/canvases/StaticCanvas.js +7 -5
  115. package/dist/excalidraw/components/dropdownMenu/DropdownMenuContent.js +22 -2
  116. package/dist/excalidraw/components/dropdownMenu/common.d.ts +1 -1
  117. package/dist/excalidraw/components/hyperlink/Hyperlink.d.ts +19 -0
  118. package/dist/excalidraw/{element → components/hyperlink}/Hyperlink.js +40 -115
  119. package/dist/excalidraw/components/hyperlink/helpers.d.ts +7 -0
  120. package/dist/excalidraw/components/hyperlink/helpers.js +49 -0
  121. package/dist/excalidraw/components/icons.d.ts +2 -1
  122. package/dist/excalidraw/components/icons.js +2 -1
  123. package/dist/excalidraw/components/live-collaboration/LiveCollaborationTrigger.js +3 -2
  124. package/dist/excalidraw/components/main-menu/DefaultItems.js +5 -2
  125. package/dist/excalidraw/constants.d.ts +8 -0
  126. package/dist/excalidraw/constants.js +10 -0
  127. package/dist/excalidraw/data/blob.js +13 -14
  128. package/dist/excalidraw/data/filesystem.d.ts +1 -1
  129. package/dist/excalidraw/data/index.d.ts +2 -1
  130. package/dist/excalidraw/data/index.js +20 -16
  131. package/dist/excalidraw/data/json.d.ts +1 -1
  132. package/dist/excalidraw/data/json.js +5 -3
  133. package/dist/excalidraw/data/resave.d.ts +1 -1
  134. package/dist/excalidraw/data/resave.js +2 -2
  135. package/dist/excalidraw/data/restore.js +8 -13
  136. package/dist/excalidraw/data/transform.js +13 -9
  137. package/dist/excalidraw/distribute.d.ts +2 -2
  138. package/dist/excalidraw/distribute.js +2 -2
  139. package/dist/excalidraw/element/ElementCanvasButtons.d.ts +3 -2
  140. package/dist/excalidraw/element/ElementCanvasButtons.js +4 -4
  141. package/dist/excalidraw/element/binding.d.ts +9 -9
  142. package/dist/excalidraw/element/binding.js +61 -59
  143. package/dist/excalidraw/element/bounds.d.ts +5 -5
  144. package/dist/excalidraw/element/bounds.js +29 -32
  145. package/dist/excalidraw/element/collision.d.ts +11 -11
  146. package/dist/excalidraw/element/collision.js +49 -46
  147. package/dist/excalidraw/element/containerCache.d.ts +11 -0
  148. package/dist/excalidraw/element/containerCache.js +14 -0
  149. package/dist/excalidraw/element/dragElements.js +10 -19
  150. package/dist/excalidraw/element/embeddable.d.ts +11 -12
  151. package/dist/excalidraw/element/embeddable.js +17 -27
  152. package/dist/excalidraw/element/image.js +1 -2
  153. package/dist/excalidraw/element/index.d.ts +0 -1
  154. package/dist/excalidraw/element/index.js +0 -1
  155. package/dist/excalidraw/element/linearElementEditor.d.ts +35 -35
  156. package/dist/excalidraw/element/linearElementEditor.js +79 -80
  157. package/dist/excalidraw/element/newElement.d.ts +4 -6
  158. package/dist/excalidraw/element/newElement.js +11 -16
  159. package/dist/excalidraw/element/resizeElements.d.ts +6 -6
  160. package/dist/excalidraw/element/resizeElements.js +40 -46
  161. package/dist/excalidraw/element/resizeTest.d.ts +3 -3
  162. package/dist/excalidraw/element/resizeTest.js +4 -4
  163. package/dist/excalidraw/element/sizeHelpers.d.ts +2 -2
  164. package/dist/excalidraw/element/sizeHelpers.js +2 -2
  165. package/dist/excalidraw/element/textElement.d.ts +18 -20
  166. package/dist/excalidraw/element/textElement.js +80 -111
  167. package/dist/excalidraw/element/textWysiwyg.d.ts +1 -6
  168. package/dist/excalidraw/element/textWysiwyg.js +15 -37
  169. package/dist/excalidraw/element/transformHandles.d.ts +4 -4
  170. package/dist/excalidraw/element/transformHandles.js +6 -6
  171. package/dist/excalidraw/element/typeChecks.js +4 -1
  172. package/dist/excalidraw/element/types.d.ts +24 -11
  173. package/dist/excalidraw/emitter.d.ts +5 -9
  174. package/dist/excalidraw/emitter.js +12 -12
  175. package/dist/excalidraw/frame.d.ts +26 -20
  176. package/dist/excalidraw/frame.js +157 -84
  177. package/dist/excalidraw/groups.d.ts +3 -3
  178. package/dist/excalidraw/groups.js +11 -3
  179. package/dist/excalidraw/history.d.ts +1 -1
  180. package/dist/excalidraw/index.d.ts +7 -3
  181. package/dist/excalidraw/index.js +14 -5
  182. package/dist/excalidraw/laser-trails.d.ts +19 -0
  183. package/dist/excalidraw/laser-trails.js +95 -0
  184. package/dist/excalidraw/locales/en.json +9 -5
  185. package/dist/excalidraw/reactUtils.d.ts +14 -0
  186. package/dist/excalidraw/reactUtils.js +45 -0
  187. package/dist/excalidraw/renderer/helpers.d.ts +13 -0
  188. package/dist/excalidraw/renderer/helpers.js +39 -0
  189. package/dist/excalidraw/renderer/interactiveScene.d.ts +20 -0
  190. package/dist/excalidraw/renderer/{renderScene.js → interactiveScene.js} +199 -474
  191. package/dist/excalidraw/renderer/renderElement.d.ts +6 -6
  192. package/dist/excalidraw/renderer/renderElement.js +54 -366
  193. package/dist/excalidraw/renderer/staticScene.d.ts +11 -0
  194. package/dist/excalidraw/renderer/staticScene.js +205 -0
  195. package/dist/excalidraw/renderer/staticSvgScene.d.ts +5 -0
  196. package/dist/excalidraw/renderer/staticSvgScene.js +385 -0
  197. package/dist/excalidraw/scene/Fonts.js +2 -1
  198. package/dist/excalidraw/scene/Renderer.d.ts +1 -1
  199. package/dist/excalidraw/scene/Renderer.js +32 -20
  200. package/dist/excalidraw/scene/Scene.d.ts +10 -9
  201. package/dist/excalidraw/scene/Scene.js +45 -21
  202. package/dist/excalidraw/scene/Shape.d.ts +3 -1
  203. package/dist/excalidraw/scene/Shape.js +7 -5
  204. package/dist/excalidraw/scene/ShapeCache.d.ts +2 -1
  205. package/dist/excalidraw/scene/ShapeCache.js +1 -0
  206. package/dist/excalidraw/scene/comparisons.js +2 -1
  207. package/dist/excalidraw/scene/export.d.ts +3 -0
  208. package/dist/excalidraw/scene/export.js +20 -40
  209. package/dist/excalidraw/scene/index.d.ts +0 -1
  210. package/dist/excalidraw/scene/index.js +0 -1
  211. package/dist/excalidraw/scene/scrollbars.d.ts +1 -1
  212. package/dist/excalidraw/scene/scrollbars.js +1 -1
  213. package/dist/excalidraw/scene/selection.d.ts +5 -5
  214. package/dist/excalidraw/scene/selection.js +16 -14
  215. package/dist/excalidraw/scene/types.d.ts +11 -5
  216. package/dist/excalidraw/snapping.d.ts +7 -7
  217. package/dist/excalidraw/snapping.js +21 -20
  218. package/dist/excalidraw/types.d.ts +11 -12
  219. package/dist/excalidraw/utility-types.d.ts +5 -0
  220. package/dist/excalidraw/utils.d.ts +25 -16
  221. package/dist/excalidraw/utils.js +52 -45
  222. package/dist/{dev/en-RLIAOBCI.json → prod/en-TDNWCAOT.json} +9 -5
  223. package/dist/prod/index.css +1 -1
  224. package/dist/prod/index.js +45 -45
  225. package/dist/utils/export.d.ts +0 -6
  226. package/dist/utils/export.js +0 -6
  227. package/dist/utils/index.d.ts +3 -0
  228. package/dist/utils/index.js +3 -0
  229. package/dist/utils/withinBounds.js +2 -1
  230. package/package.json +4 -4
  231. package/dist/browser/dev/excalidraw-assets-dev/chunk-2W5GQUR4.js.map +0 -7
  232. package/dist/browser/dev/excalidraw-assets-dev/chunk-SUHLFFEF.js +0 -53449
  233. package/dist/browser/dev/excalidraw-assets-dev/chunk-SUHLFFEF.js.map +0 -7
  234. package/dist/browser/dev/excalidraw-assets-dev/image-NOPDRTTM.css +0 -5797
  235. package/dist/browser/dev/excalidraw-assets-dev/image-NOPDRTTM.css.map +0 -7
  236. package/dist/browser/prod/excalidraw-assets/chunk-HE2P7BQ6.js +0 -257
  237. package/dist/browser/prod/excalidraw-assets/chunk-OWLL6VOG.js +0 -11
  238. package/dist/browser/prod/excalidraw-assets/en-ERQOR3OC.js +0 -1
  239. package/dist/browser/prod/excalidraw-assets/image-DZ6B4AID.js +0 -1
  240. package/dist/browser/prod/excalidraw-assets/image-J2QCCYAR.css +0 -1
  241. package/dist/excalidraw/components/LaserTool/LaserPathManager.d.ts +0 -28
  242. package/dist/excalidraw/components/LaserTool/LaserPathManager.js +0 -225
  243. package/dist/excalidraw/components/LaserTool/LaserTool.d.ts +0 -8
  244. package/dist/excalidraw/components/LaserTool/LaserTool.js +0 -15
  245. package/dist/excalidraw/renderer/renderScene.d.ts +0 -25
  246. package/dist/excalidraw/vite.config.d.mts +0 -2
  247. package/dist/excalidraw/vite.config.mjs +0 -13
  248. /package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js.map → en-IOBA4CS2.js.map} +0 -0
  249. /package/dist/browser/dev/excalidraw-assets-dev/{image-HYNUJ3XL.js.map → image-VKDAL6BQ.js.map} +0 -0
@@ -1,23 +1,38 @@
1
- import oc from "open-color";
2
1
  import { getElementAbsoluteCoords, OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, getTransformHandlesFromCoords, getTransformHandles, getCommonBounds, } from "../element";
3
- import { roundRect } from "./roundRect";
2
+ import { roundRect } from "../renderer/roundRect";
4
3
  import { getScrollBars, SCROLLBAR_COLOR, SCROLLBAR_WIDTH, } from "../scene/scrollbars";
5
- import { renderElement, renderElementToSvg, renderSelectionElement, } from "./renderElement";
4
+ import { renderSelectionElement } from "../renderer/renderElement";
6
5
  import { getClientColor } from "../clients";
7
- import { LinearElementEditor } from "../element/linearElementEditor";
8
6
  import { isSelectedViaGroup, getSelectedGroupIds, getElementsInGroup, selectGroupsFromGivenElements, } from "../groups";
9
- import { maxBindingGap } from "../element/collision";
10
7
  import { OMIT_SIDES_FOR_FRAME, shouldShowBoundingBox, } from "../element/transformHandles";
11
- import { throttleRAF } from "../utils";
8
+ import { arrayToMap, throttleRAF } from "../utils";
12
9
  import { UserIdleState } from "../types";
13
- import { FRAME_STYLE, THEME_FILTER } from "../constants";
14
- import { EXTERNAL_LINK_IMG, getLinkHandleFromCoords, } from "../element/Hyperlink";
15
- import { renderSnaps } from "./renderSnaps";
16
- import { isEmbeddableElement, isFrameLikeElement, isIframeLikeElement, isLinearElement, } from "../element/typeChecks";
17
- import { isIframeLikeOrItsLabel, createPlaceholderEmbeddableLabel, } from "../element/embeddable";
18
- import { elementOverlapsWithFrame, getTargetFrame, isElementInFrame, } from "../frame";
19
- import "canvas-roundrect-polyfill";
20
- export const DEFAULT_SPACING = 2;
10
+ import { DEFAULT_TRANSFORM_HANDLE_SPACING, FRAME_STYLE } from "../constants";
11
+ import { renderSnaps } from "../renderer/renderSnaps";
12
+ import { maxBindingGap } from "../element/collision";
13
+ import { LinearElementEditor } from "../element/linearElementEditor";
14
+ import { bootstrapCanvas, fillCircle, getNormalizedCanvasDimensions, } from "./helpers";
15
+ import oc from "open-color";
16
+ import { isFrameLikeElement, isLinearElement } from "../element/typeChecks";
17
+ const renderLinearElementPointHighlight = (context, appState, elementsMap) => {
18
+ const { elementId, hoverPointIndex } = appState.selectedLinearElement;
19
+ if (appState.editingLinearElement?.selectedPointsIndices?.includes(hoverPointIndex)) {
20
+ return;
21
+ }
22
+ const element = LinearElementEditor.getElement(elementId, elementsMap);
23
+ if (!element) {
24
+ return;
25
+ }
26
+ const point = LinearElementEditor.getPointAtIndexGlobalCoordinates(element, hoverPointIndex, elementsMap);
27
+ context.save();
28
+ context.translate(appState.scrollX, appState.scrollY);
29
+ highlightPoint(point, context, appState);
30
+ context.restore();
31
+ };
32
+ const highlightPoint = (point, context, appState) => {
33
+ context.fillStyle = "rgba(105, 101, 219, 0.4)";
34
+ fillCircle(context, point[0], point[1], LinearElementEditor.POINT_HANDLE_SIZE / appState.zoom.value, false);
35
+ };
21
36
  const strokeRectWithRotation = (context, x, y, width, height, cx, cy, angle, fill = false,
22
37
  /** should account for zoom */
23
38
  radius = 0) => {
@@ -51,53 +66,6 @@ const strokeDiamondWithRotation = (context, width, height, cx, cy, angle) => {
51
66
  context.stroke();
52
67
  context.restore();
53
68
  };
54
- const strokeEllipseWithRotation = (context, width, height, cx, cy, angle) => {
55
- context.beginPath();
56
- context.ellipse(cx, cy, width / 2, height / 2, angle, 0, Math.PI * 2);
57
- context.stroke();
58
- };
59
- const fillCircle = (context, cx, cy, radius, stroke = true) => {
60
- context.beginPath();
61
- context.arc(cx, cy, radius, 0, Math.PI * 2);
62
- context.fill();
63
- if (stroke) {
64
- context.stroke();
65
- }
66
- };
67
- const strokeGrid = (context, gridSize, scrollX, scrollY, zoom, width, height) => {
68
- const BOLD_LINE_FREQUENCY = 5;
69
- let GridLineColor;
70
- (function (GridLineColor) {
71
- GridLineColor["Bold"] = "#cccccc";
72
- GridLineColor["Regular"] = "#e5e5e5";
73
- })(GridLineColor || (GridLineColor = {}));
74
- const offsetX = -Math.round(zoom.value / gridSize) * gridSize + (scrollX % gridSize);
75
- const offsetY = -Math.round(zoom.value / gridSize) * gridSize + (scrollY % gridSize);
76
- const lineWidth = Math.min(1 / zoom.value, 1);
77
- const spaceWidth = 1 / zoom.value;
78
- const lineDash = [lineWidth * 3, spaceWidth + (lineWidth + spaceWidth)];
79
- context.save();
80
- context.lineWidth = lineWidth;
81
- for (let x = offsetX; x < offsetX + width + gridSize * 2; x += gridSize) {
82
- const isBold = Math.round(x - scrollX) % (BOLD_LINE_FREQUENCY * gridSize) === 0;
83
- context.beginPath();
84
- context.setLineDash(isBold ? [] : lineDash);
85
- context.strokeStyle = isBold ? GridLineColor.Bold : GridLineColor.Regular;
86
- context.moveTo(x, offsetY - gridSize);
87
- context.lineTo(x, offsetY + height + gridSize * 2);
88
- context.stroke();
89
- }
90
- for (let y = offsetY; y < offsetY + height + gridSize * 2; y += gridSize) {
91
- const isBold = Math.round(y - scrollY) % (BOLD_LINE_FREQUENCY * gridSize) === 0;
92
- context.beginPath();
93
- context.setLineDash(isBold ? [] : lineDash);
94
- context.strokeStyle = isBold ? GridLineColor.Bold : GridLineColor.Regular;
95
- context.moveTo(offsetX - gridSize, y);
96
- context.lineTo(offsetX + width + gridSize * 2, y);
97
- context.stroke();
98
- }
99
- context.restore();
100
- };
101
69
  const renderSingleLinearPoint = (context, appState, point, radius, isSelected, isPhantomPoint = false) => {
102
70
  context.strokeStyle = "#5e5ad8";
103
71
  context.setLineDash([]);
@@ -110,14 +78,134 @@ const renderSingleLinearPoint = (context, appState, point, radius, isSelected, i
110
78
  }
111
79
  fillCircle(context, point[0], point[1], radius / appState.zoom.value, !isPhantomPoint);
112
80
  };
113
- const renderLinearPointHandles = (context, appState, element) => {
81
+ const strokeEllipseWithRotation = (context, width, height, cx, cy, angle) => {
82
+ context.beginPath();
83
+ context.ellipse(cx, cy, width / 2, height / 2, angle, 0, Math.PI * 2);
84
+ context.stroke();
85
+ };
86
+ const renderBindingHighlightForBindableElement = (context, element, elementsMap) => {
87
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
88
+ const width = x2 - x1;
89
+ const height = y2 - y1;
90
+ const threshold = maxBindingGap(element, width, height);
91
+ // So that we don't overlap the element itself
92
+ const strokeOffset = 4;
93
+ context.strokeStyle = "rgba(0,0,0,.05)";
94
+ context.lineWidth = threshold - strokeOffset;
95
+ const padding = strokeOffset / 2 + threshold / 2;
96
+ switch (element.type) {
97
+ case "rectangle":
98
+ case "text":
99
+ case "image":
100
+ case "iframe":
101
+ case "embeddable":
102
+ case "frame":
103
+ case "magicframe":
104
+ strokeRectWithRotation(context, x1 - padding, y1 - padding, width + padding * 2, height + padding * 2, x1 + width / 2, y1 + height / 2, element.angle);
105
+ break;
106
+ case "diamond":
107
+ const side = Math.hypot(width, height);
108
+ const wPadding = (padding * side) / height;
109
+ const hPadding = (padding * side) / width;
110
+ strokeDiamondWithRotation(context, width + wPadding * 2, height + hPadding * 2, x1 + width / 2, y1 + height / 2, element.angle);
111
+ break;
112
+ case "ellipse":
113
+ strokeEllipseWithRotation(context, width + padding * 2, height + padding * 2, x1 + width / 2, y1 + height / 2, element.angle);
114
+ break;
115
+ }
116
+ };
117
+ const renderBindingHighlightForSuggestedPointBinding = (context, suggestedBinding, elementsMap) => {
118
+ const [element, startOrEnd, bindableElement] = suggestedBinding;
119
+ const threshold = maxBindingGap(bindableElement, bindableElement.width, bindableElement.height);
120
+ context.strokeStyle = "rgba(0,0,0,0)";
121
+ context.fillStyle = "rgba(0,0,0,.05)";
122
+ const pointIndices = startOrEnd === "both" ? [0, -1] : startOrEnd === "start" ? [0] : [-1];
123
+ pointIndices.forEach((index) => {
124
+ const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(element, index, elementsMap);
125
+ fillCircle(context, x, y, threshold);
126
+ });
127
+ };
128
+ const renderSelectionBorder = (context, appState, elementProperties, padding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2) => {
129
+ const { angle, elementX1, elementY1, elementX2, elementY2, selectionColors, cx, cy, dashed, activeEmbeddable, } = elementProperties;
130
+ const elementWidth = elementX2 - elementX1;
131
+ const elementHeight = elementY2 - elementY1;
132
+ const linePadding = padding / appState.zoom.value;
133
+ const lineWidth = 8 / appState.zoom.value;
134
+ const spaceWidth = 4 / appState.zoom.value;
135
+ context.save();
136
+ context.translate(appState.scrollX, appState.scrollY);
137
+ context.lineWidth = (activeEmbeddable ? 4 : 1) / appState.zoom.value;
138
+ const count = selectionColors.length;
139
+ for (let index = 0; index < count; ++index) {
140
+ context.strokeStyle = selectionColors[index];
141
+ if (dashed) {
142
+ context.setLineDash([
143
+ lineWidth,
144
+ spaceWidth + (lineWidth + spaceWidth) * (count - 1),
145
+ ]);
146
+ }
147
+ context.lineDashOffset = (lineWidth + spaceWidth) * index;
148
+ strokeRectWithRotation(context, elementX1 - linePadding, elementY1 - linePadding, elementWidth + linePadding * 2, elementHeight + linePadding * 2, cx, cy, angle);
149
+ }
150
+ context.restore();
151
+ };
152
+ const renderBindingHighlight = (context, appState, suggestedBinding, elementsMap) => {
153
+ const renderHighlight = Array.isArray(suggestedBinding)
154
+ ? renderBindingHighlightForSuggestedPointBinding
155
+ : renderBindingHighlightForBindableElement;
156
+ context.save();
157
+ context.translate(appState.scrollX, appState.scrollY);
158
+ renderHighlight(context, suggestedBinding, elementsMap);
159
+ context.restore();
160
+ };
161
+ const renderFrameHighlight = (context, appState, frame, elementsMap) => {
162
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap);
163
+ const width = x2 - x1;
164
+ const height = y2 - y1;
165
+ context.strokeStyle = "rgb(0,118,255)";
166
+ context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
167
+ context.save();
168
+ context.translate(appState.scrollX, appState.scrollY);
169
+ strokeRectWithRotation(context, x1, y1, width, height, x1 + width / 2, y1 + height / 2, frame.angle, false, FRAME_STYLE.radius / appState.zoom.value);
170
+ context.restore();
171
+ };
172
+ const renderElementsBoxHighlight = (context, appState, elements) => {
173
+ const individualElements = elements.filter((element) => element.groupIds.length === 0);
174
+ const elementsInGroups = elements.filter((element) => element.groupIds.length > 0);
175
+ const getSelectionFromElements = (elements) => {
176
+ const [elementX1, elementY1, elementX2, elementY2] = getCommonBounds(elements);
177
+ return {
178
+ angle: 0,
179
+ elementX1,
180
+ elementX2,
181
+ elementY1,
182
+ elementY2,
183
+ selectionColors: ["rgb(0,118,255)"],
184
+ dashed: false,
185
+ cx: elementX1 + (elementX2 - elementX1) / 2,
186
+ cy: elementY1 + (elementY2 - elementY1) / 2,
187
+ activeEmbeddable: false,
188
+ };
189
+ };
190
+ const getSelectionForGroupId = (groupId) => {
191
+ const groupElements = getElementsInGroup(elements, groupId);
192
+ return getSelectionFromElements(groupElements);
193
+ };
194
+ Object.entries(selectGroupsFromGivenElements(elementsInGroups, appState))
195
+ .filter(([id, isSelected]) => isSelected)
196
+ .map(([id, isSelected]) => id)
197
+ .map((groupId) => getSelectionForGroupId(groupId))
198
+ .concat(individualElements.map((element) => getSelectionFromElements([element])))
199
+ .forEach((selection) => renderSelectionBorder(context, appState, selection));
200
+ };
201
+ const renderLinearPointHandles = (context, appState, element, elementsMap) => {
114
202
  if (!appState.selectedLinearElement) {
115
203
  return;
116
204
  }
117
205
  context.save();
118
206
  context.translate(appState.scrollX, appState.scrollY);
119
207
  context.lineWidth = 1 / appState.zoom.value;
120
- const points = LinearElementEditor.getPointsGlobalCoordinates(element);
208
+ const points = LinearElementEditor.getPointsGlobalCoordinates(element, elementsMap);
121
209
  const { POINT_HANDLE_SIZE } = LinearElementEditor;
122
210
  const radius = appState.editingLinearElement
123
211
  ? POINT_HANDLE_SIZE
@@ -127,7 +215,7 @@ const renderLinearPointHandles = (context, appState, element) => {
127
215
  renderSingleLinearPoint(context, appState, point, radius, isSelected);
128
216
  });
129
217
  //Rendering segment mid points
130
- const midPoints = LinearElementEditor.getEditorMidPoints(element, appState).filter((midPoint) => midPoint !== null);
218
+ const midPoints = LinearElementEditor.getEditorMidPoints(element, elementsMap, appState).filter((midPoint) => midPoint !== null);
131
219
  midPoints.forEach((segmentMidPoint) => {
132
220
  if (appState?.selectedLinearElement?.segmentMidPointHoveredCoords &&
133
221
  LinearElementEditor.arePointsEqual(segmentMidPoint, appState.selectedLinearElement.segmentMidPointHoveredCoords)) {
@@ -150,70 +238,36 @@ const renderLinearPointHandles = (context, appState, element) => {
150
238
  });
151
239
  context.restore();
152
240
  };
153
- const highlightPoint = (point, context, appState) => {
154
- context.fillStyle = "rgba(105, 101, 219, 0.4)";
155
- fillCircle(context, point[0], point[1], LinearElementEditor.POINT_HANDLE_SIZE / appState.zoom.value, false);
156
- };
157
- const renderLinearElementPointHighlight = (context, appState) => {
158
- const { elementId, hoverPointIndex } = appState.selectedLinearElement;
159
- if (appState.editingLinearElement?.selectedPointsIndices?.includes(hoverPointIndex)) {
160
- return;
161
- }
162
- const element = LinearElementEditor.getElement(elementId);
163
- if (!element) {
164
- return;
165
- }
166
- const point = LinearElementEditor.getPointAtIndexGlobalCoordinates(element, hoverPointIndex);
167
- context.save();
168
- context.translate(appState.scrollX, appState.scrollY);
169
- highlightPoint(point, context, appState);
170
- context.restore();
171
- };
172
- const frameClip = (frame, context, renderConfig, appState) => {
173
- context.translate(frame.x + appState.scrollX, frame.y + appState.scrollY);
174
- context.beginPath();
175
- if (context.roundRect) {
176
- context.roundRect(0, 0, frame.width, frame.height, FRAME_STYLE.radius / appState.zoom.value);
177
- }
178
- else {
179
- context.rect(0, 0, frame.width, frame.height);
180
- }
181
- context.clip();
182
- context.translate(-(frame.x + appState.scrollX), -(frame.y + appState.scrollY));
183
- };
184
- const getNormalizedCanvasDimensions = (canvas, scale) => {
185
- // When doing calculations based on canvas width we should used normalized one
186
- return [canvas.width / scale, canvas.height / scale];
187
- };
188
- const bootstrapCanvas = ({ canvas, scale, normalizedWidth, normalizedHeight, theme, isExporting, viewBackgroundColor, }) => {
189
- const context = canvas.getContext("2d");
190
- context.setTransform(1, 0, 0, 1, 0, 0);
191
- context.scale(scale, scale);
192
- if (isExporting && theme === "dark") {
193
- context.filter = THEME_FILTER;
194
- }
195
- // Paint background
196
- if (typeof viewBackgroundColor === "string") {
197
- const hasTransparence = viewBackgroundColor === "transparent" ||
198
- viewBackgroundColor.length === 5 || // #RGBA
199
- viewBackgroundColor.length === 9 || // #RRGGBBA
200
- /(hsla|rgba)\(/.test(viewBackgroundColor);
201
- if (hasTransparence) {
202
- context.clearRect(0, 0, normalizedWidth, normalizedHeight);
241
+ const renderTransformHandles = (context, renderConfig, appState, transformHandles, angle) => {
242
+ Object.keys(transformHandles).forEach((key) => {
243
+ const transformHandle = transformHandles[key];
244
+ if (transformHandle !== undefined) {
245
+ const [x, y, width, height] = transformHandle;
246
+ context.save();
247
+ context.lineWidth = 1 / appState.zoom.value;
248
+ if (renderConfig.selectionColor) {
249
+ context.strokeStyle = renderConfig.selectionColor;
250
+ }
251
+ if (key === "rotation") {
252
+ fillCircle(context, x + width / 2, y + height / 2, width / 2);
253
+ // prefer round corners if roundRect API is available
254
+ }
255
+ else if (context.roundRect) {
256
+ context.beginPath();
257
+ context.roundRect(x, y, width, height, 2 / appState.zoom.value);
258
+ context.fill();
259
+ context.stroke();
260
+ }
261
+ else {
262
+ strokeRectWithRotation(context, x, y, width, height, x + width / 2, y + height / 2, angle, true);
263
+ }
264
+ context.restore();
203
265
  }
204
- context.save();
205
- context.fillStyle = viewBackgroundColor;
206
- context.fillRect(0, 0, normalizedWidth, normalizedHeight);
207
- context.restore();
208
- }
209
- else {
210
- context.clearRect(0, 0, normalizedWidth, normalizedHeight);
211
- }
212
- return context;
266
+ });
213
267
  };
214
- const _renderInteractiveScene = ({ canvas, elements, visibleElements, selectedElements, scale, appState, renderConfig, }) => {
268
+ const _renderInteractiveScene = ({ canvas, elementsMap, visibleElements, selectedElements, scale, appState, renderConfig, }) => {
215
269
  if (canvas === null) {
216
- return { atLeastOneVisibleElement: false, elements };
270
+ return { atLeastOneVisibleElement: false, elementsMap };
217
271
  }
218
272
  const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(canvas, scale);
219
273
  const context = bootstrapCanvas({
@@ -237,7 +291,7 @@ const _renderInteractiveScene = ({ canvas, elements, visibleElements, selectedEl
237
291
  }
238
292
  });
239
293
  if (editingLinearElement) {
240
- renderLinearPointHandles(context, appState, editingLinearElement);
294
+ renderLinearPointHandles(context, appState, editingLinearElement, elementsMap);
241
295
  }
242
296
  // Paint selection element
243
297
  if (appState.selectionElement) {
@@ -252,11 +306,11 @@ const _renderInteractiveScene = ({ canvas, elements, visibleElements, selectedEl
252
306
  appState.suggestedBindings
253
307
  .filter((binding) => binding != null)
254
308
  .forEach((suggestedBinding) => {
255
- renderBindingHighlight(context, appState, suggestedBinding);
309
+ renderBindingHighlight(context, appState, suggestedBinding, elementsMap);
256
310
  });
257
311
  }
258
312
  if (appState.frameToHighlight) {
259
- renderFrameHighlight(context, appState, appState.frameToHighlight);
313
+ renderFrameHighlight(context, appState, appState.frameToHighlight, elementsMap);
260
314
  }
261
315
  if (appState.elementsToHighlight) {
262
316
  renderElementsBoxHighlight(context, appState, appState.elementsToHighlight);
@@ -267,11 +321,11 @@ const _renderInteractiveScene = ({ canvas, elements, visibleElements, selectedEl
267
321
  // correct element from visible elements
268
322
  if (selectedElements.length === 1 &&
269
323
  appState.editingLinearElement?.elementId === selectedElements[0].id) {
270
- renderLinearPointHandles(context, appState, selectedElements[0]);
324
+ renderLinearPointHandles(context, appState, selectedElements[0], elementsMap);
271
325
  }
272
326
  if (appState.selectedLinearElement &&
273
327
  appState.selectedLinearElement.hoverPointIndex >= 0) {
274
- renderLinearElementPointHighlight(context, appState);
328
+ renderLinearElementPointHighlight(context, appState, elementsMap);
275
329
  }
276
330
  // Paint selected elements
277
331
  if (!appState.multiElement && !appState.editingLinearElement) {
@@ -281,19 +335,17 @@ const _renderInteractiveScene = ({ canvas, elements, visibleElements, selectedEl
281
335
  if (isSingleLinearElementSelected &&
282
336
  appState.selectedLinearElement?.elementId === selectedElements[0].id &&
283
337
  !selectedElements[0].locked) {
284
- renderLinearPointHandles(context, appState, selectedElements[0]);
338
+ renderLinearPointHandles(context, appState, selectedElements[0], elementsMap);
285
339
  }
286
340
  const selectionColor = renderConfig.selectionColor || oc.black;
287
341
  if (showBoundingBox) {
288
342
  // Optimisation for finding quickly relevant element ids
289
- const locallySelectedIds = selectedElements.reduce((acc, element) => {
290
- acc[element.id] = true;
291
- return acc;
292
- }, {});
293
- const selections = elements.reduce((acc, element) => {
343
+ const locallySelectedIds = arrayToMap(selectedElements);
344
+ const selections = [];
345
+ for (const element of elementsMap.values()) {
294
346
  const selectionColors = [];
295
347
  // local user
296
- if (locallySelectedIds[element.id] &&
348
+ if (locallySelectedIds.has(element.id) &&
297
349
  !isSelectedViaGroup(appState, element)) {
298
350
  selectionColors.push(selectionColor);
299
351
  }
@@ -305,8 +357,8 @@ const _renderInteractiveScene = ({ canvas, elements, visibleElements, selectedEl
305
357
  }));
306
358
  }
307
359
  if (selectionColors.length) {
308
- const [elementX1, elementY1, elementX2, elementY2, cx, cy] = getElementAbsoluteCoords(element, true);
309
- acc.push({
360
+ const [elementX1, elementY1, elementX2, elementY2, cx, cy] = getElementAbsoluteCoords(element, elementsMap, true);
361
+ selections.push({
310
362
  angle: element.angle,
311
363
  elementX1,
312
364
  elementY1,
@@ -320,10 +372,9 @@ const _renderInteractiveScene = ({ canvas, elements, visibleElements, selectedEl
320
372
  appState.activeEmbeddable.state === "active",
321
373
  });
322
374
  }
323
- return acc;
324
- }, []);
375
+ }
325
376
  const addSelectionForGroupId = (groupId) => {
326
- const groupElements = getElementsInGroup(elements, groupId);
377
+ const groupElements = getElementsInGroup(elementsMap, groupId);
327
378
  const [elementX1, elementY1, elementX2, elementY2] = getCommonBounds(groupElements);
328
379
  selections.push({
329
380
  angle: 0,
@@ -352,13 +403,13 @@ const _renderInteractiveScene = ({ canvas, elements, visibleElements, selectedEl
352
403
  context.translate(appState.scrollX, appState.scrollY);
353
404
  if (selectedElements.length === 1) {
354
405
  context.fillStyle = oc.white;
355
- const transformHandles = getTransformHandles(selectedElements[0], appState.zoom, "mouse");
406
+ const transformHandles = getTransformHandles(selectedElements[0], appState.zoom, elementsMap, "mouse");
356
407
  if (!appState.viewModeEnabled && showBoundingBox) {
357
408
  renderTransformHandles(context, renderConfig, appState, transformHandles, selectedElements[0].angle);
358
409
  }
359
410
  }
360
411
  else if (selectedElements.length > 1 && !appState.isRotating) {
361
- const dashedLinePadding = (DEFAULT_SPACING * 2) / appState.zoom.value;
412
+ const dashedLinePadding = (DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / appState.zoom.value;
362
413
  context.fillStyle = oc.white;
363
414
  const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
364
415
  const initialLineDash = context.getLineDash();
@@ -496,7 +547,7 @@ const _renderInteractiveScene = ({ canvas, elements, visibleElements, selectedEl
496
547
  // Paint scrollbars
497
548
  let scrollBars;
498
549
  if (renderConfig.renderScrollbars) {
499
- scrollBars = getScrollBars(elements, normalizedWidth, normalizedHeight, appState);
550
+ scrollBars = getScrollBars(visibleElements, normalizedWidth, normalizedHeight, appState);
500
551
  context.save();
501
552
  context.fillStyle = SCROLLBAR_COLOR;
502
553
  context.strokeStyle = "rgba(255,255,255,0.8)";
@@ -510,119 +561,16 @@ const _renderInteractiveScene = ({ canvas, elements, visibleElements, selectedEl
510
561
  return {
511
562
  scrollBars,
512
563
  atLeastOneVisibleElement: visibleElements.length > 0,
513
- elements,
564
+ elementsMap,
514
565
  };
515
566
  };
516
- const _renderStaticScene = ({ canvas, rc, elements, visibleElements, scale, appState, renderConfig, }) => {
517
- if (canvas === null) {
518
- return;
519
- }
520
- const { renderGrid = true, isExporting } = renderConfig;
521
- const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(canvas, scale);
522
- const context = bootstrapCanvas({
523
- canvas,
524
- scale,
525
- normalizedWidth,
526
- normalizedHeight,
527
- theme: appState.theme,
528
- isExporting,
529
- viewBackgroundColor: appState.viewBackgroundColor,
530
- });
531
- // Apply zoom
532
- context.scale(appState.zoom.value, appState.zoom.value);
533
- // Grid
534
- if (renderGrid && appState.gridSize) {
535
- strokeGrid(context, appState.gridSize, appState.scrollX, appState.scrollY, appState.zoom, normalizedWidth / appState.zoom.value, normalizedHeight / appState.zoom.value);
536
- }
537
- const groupsToBeAddedToFrame = new Set();
538
- visibleElements.forEach((element) => {
539
- if (element.groupIds.length > 0 &&
540
- appState.frameToHighlight &&
541
- appState.selectedElementIds[element.id] &&
542
- (elementOverlapsWithFrame(element, appState.frameToHighlight) ||
543
- element.groupIds.find((groupId) => groupsToBeAddedToFrame.has(groupId)))) {
544
- element.groupIds.forEach((groupId) => groupsToBeAddedToFrame.add(groupId));
545
- }
546
- });
547
- // Paint visible elements
548
- visibleElements
549
- .filter((el) => !isIframeLikeOrItsLabel(el))
550
- .forEach((element) => {
551
- try {
552
- const frameId = element.frameId || appState.frameToHighlight?.id;
553
- if (frameId &&
554
- appState.frameRendering.enabled &&
555
- appState.frameRendering.clip) {
556
- context.save();
557
- const frame = getTargetFrame(element, appState);
558
- // TODO do we need to check isElementInFrame here?
559
- if (frame && isElementInFrame(element, elements, appState)) {
560
- frameClip(frame, context, renderConfig, appState);
561
- }
562
- renderElement(element, rc, context, renderConfig, appState);
563
- context.restore();
564
- }
565
- else {
566
- renderElement(element, rc, context, renderConfig, appState);
567
- }
568
- if (!isExporting) {
569
- renderLinkIcon(element, context, appState);
570
- }
571
- }
572
- catch (error) {
573
- console.error(error);
574
- }
575
- });
576
- // render embeddables on top
577
- visibleElements
578
- .filter((el) => isIframeLikeOrItsLabel(el))
579
- .forEach((element) => {
580
- try {
581
- const render = () => {
582
- renderElement(element, rc, context, renderConfig, appState);
583
- if (isIframeLikeElement(element) &&
584
- (isExporting ||
585
- (isEmbeddableElement(element) && !element.validated)) &&
586
- element.width &&
587
- element.height) {
588
- const label = createPlaceholderEmbeddableLabel(element);
589
- renderElement(label, rc, context, renderConfig, appState);
590
- }
591
- if (!isExporting) {
592
- renderLinkIcon(element, context, appState);
593
- }
594
- };
595
- // - when exporting the whole canvas, we DO NOT apply clipping
596
- // - when we are exporting a particular frame, apply clipping
597
- // if the containing frame is not selected, apply clipping
598
- const frameId = element.frameId || appState.frameToHighlight?.id;
599
- if (frameId &&
600
- appState.frameRendering.enabled &&
601
- appState.frameRendering.clip) {
602
- context.save();
603
- const frame = getTargetFrame(element, appState);
604
- if (frame && isElementInFrame(element, elements, appState)) {
605
- frameClip(frame, context, renderConfig, appState);
606
- }
607
- render();
608
- context.restore();
609
- }
610
- else {
611
- render();
612
- }
613
- }
614
- catch (error) {
615
- console.error(error);
616
- }
617
- });
618
- };
619
567
  /** throttled to animation framerate */
620
- const renderInteractiveSceneThrottled = throttleRAF((config) => {
568
+ export const renderInteractiveSceneThrottled = throttleRAF((config) => {
621
569
  const ret = _renderInteractiveScene(config);
622
570
  config.callback?.(ret);
623
571
  }, { trailing: true });
624
572
  /**
625
- * Interactive scene is the ui-canvas where we render boundinb boxes, selections
573
+ * Interactive scene is the ui-canvas where we render bounding boxes, selections
626
574
  * and other ui stuff.
627
575
  */
628
576
  export const renderInteractiveScene = (renderConfig, throttle) => {
@@ -634,226 +582,3 @@ export const renderInteractiveScene = (renderConfig, throttle) => {
634
582
  renderConfig.callback(ret);
635
583
  return ret;
636
584
  };
637
- /** throttled to animation framerate */
638
- const renderStaticSceneThrottled = throttleRAF((config) => {
639
- _renderStaticScene(config);
640
- }, { trailing: true });
641
- /**
642
- * Static scene is the non-ui canvas where we render elements.
643
- */
644
- export const renderStaticScene = (renderConfig, throttle) => {
645
- if (throttle) {
646
- renderStaticSceneThrottled(renderConfig);
647
- return;
648
- }
649
- _renderStaticScene(renderConfig);
650
- };
651
- export const cancelRender = () => {
652
- renderInteractiveSceneThrottled.cancel();
653
- renderStaticSceneThrottled.cancel();
654
- };
655
- const renderTransformHandles = (context, renderConfig, appState, transformHandles, angle) => {
656
- Object.keys(transformHandles).forEach((key) => {
657
- const transformHandle = transformHandles[key];
658
- if (transformHandle !== undefined) {
659
- const [x, y, width, height] = transformHandle;
660
- context.save();
661
- context.lineWidth = 1 / appState.zoom.value;
662
- if (renderConfig.selectionColor) {
663
- context.strokeStyle = renderConfig.selectionColor;
664
- }
665
- if (key === "rotation") {
666
- fillCircle(context, x + width / 2, y + height / 2, width / 2);
667
- // prefer round corners if roundRect API is available
668
- }
669
- else if (context.roundRect) {
670
- context.beginPath();
671
- context.roundRect(x, y, width, height, 2 / appState.zoom.value);
672
- context.fill();
673
- context.stroke();
674
- }
675
- else {
676
- strokeRectWithRotation(context, x, y, width, height, x + width / 2, y + height / 2, angle, true);
677
- }
678
- context.restore();
679
- }
680
- });
681
- };
682
- const renderSelectionBorder = (context, appState, elementProperties, padding = DEFAULT_SPACING * 2) => {
683
- const { angle, elementX1, elementY1, elementX2, elementY2, selectionColors, cx, cy, dashed, activeEmbeddable, } = elementProperties;
684
- const elementWidth = elementX2 - elementX1;
685
- const elementHeight = elementY2 - elementY1;
686
- const linePadding = padding / appState.zoom.value;
687
- const lineWidth = 8 / appState.zoom.value;
688
- const spaceWidth = 4 / appState.zoom.value;
689
- context.save();
690
- context.translate(appState.scrollX, appState.scrollY);
691
- context.lineWidth = (activeEmbeddable ? 4 : 1) / appState.zoom.value;
692
- const count = selectionColors.length;
693
- for (let index = 0; index < count; ++index) {
694
- context.strokeStyle = selectionColors[index];
695
- if (dashed) {
696
- context.setLineDash([
697
- lineWidth,
698
- spaceWidth + (lineWidth + spaceWidth) * (count - 1),
699
- ]);
700
- }
701
- context.lineDashOffset = (lineWidth + spaceWidth) * index;
702
- strokeRectWithRotation(context, elementX1 - linePadding, elementY1 - linePadding, elementWidth + linePadding * 2, elementHeight + linePadding * 2, cx, cy, angle);
703
- }
704
- context.restore();
705
- };
706
- const renderBindingHighlight = (context, appState, suggestedBinding) => {
707
- const renderHighlight = Array.isArray(suggestedBinding)
708
- ? renderBindingHighlightForSuggestedPointBinding
709
- : renderBindingHighlightForBindableElement;
710
- context.save();
711
- context.translate(appState.scrollX, appState.scrollY);
712
- renderHighlight(context, suggestedBinding);
713
- context.restore();
714
- };
715
- const renderBindingHighlightForBindableElement = (context, element) => {
716
- const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
717
- const width = x2 - x1;
718
- const height = y2 - y1;
719
- const threshold = maxBindingGap(element, width, height);
720
- // So that we don't overlap the element itself
721
- const strokeOffset = 4;
722
- context.strokeStyle = "rgba(0,0,0,.05)";
723
- context.lineWidth = threshold - strokeOffset;
724
- const padding = strokeOffset / 2 + threshold / 2;
725
- switch (element.type) {
726
- case "rectangle":
727
- case "text":
728
- case "image":
729
- case "iframe":
730
- case "embeddable":
731
- case "frame":
732
- case "magicframe":
733
- strokeRectWithRotation(context, x1 - padding, y1 - padding, width + padding * 2, height + padding * 2, x1 + width / 2, y1 + height / 2, element.angle);
734
- break;
735
- case "diamond":
736
- const side = Math.hypot(width, height);
737
- const wPadding = (padding * side) / height;
738
- const hPadding = (padding * side) / width;
739
- strokeDiamondWithRotation(context, width + wPadding * 2, height + hPadding * 2, x1 + width / 2, y1 + height / 2, element.angle);
740
- break;
741
- case "ellipse":
742
- strokeEllipseWithRotation(context, width + padding * 2, height + padding * 2, x1 + width / 2, y1 + height / 2, element.angle);
743
- break;
744
- }
745
- };
746
- const renderFrameHighlight = (context, appState, frame) => {
747
- const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame);
748
- const width = x2 - x1;
749
- const height = y2 - y1;
750
- context.strokeStyle = "rgb(0,118,255)";
751
- context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
752
- context.save();
753
- context.translate(appState.scrollX, appState.scrollY);
754
- strokeRectWithRotation(context, x1, y1, width, height, x1 + width / 2, y1 + height / 2, frame.angle, false, FRAME_STYLE.radius / appState.zoom.value);
755
- context.restore();
756
- };
757
- const renderElementsBoxHighlight = (context, appState, elements) => {
758
- const individualElements = elements.filter((element) => element.groupIds.length === 0);
759
- const elementsInGroups = elements.filter((element) => element.groupIds.length > 0);
760
- const getSelectionFromElements = (elements) => {
761
- const [elementX1, elementY1, elementX2, elementY2] = getCommonBounds(elements);
762
- return {
763
- angle: 0,
764
- elementX1,
765
- elementX2,
766
- elementY1,
767
- elementY2,
768
- selectionColors: ["rgb(0,118,255)"],
769
- dashed: false,
770
- cx: elementX1 + (elementX2 - elementX1) / 2,
771
- cy: elementY1 + (elementY2 - elementY1) / 2,
772
- activeEmbeddable: false,
773
- };
774
- };
775
- const getSelectionForGroupId = (groupId) => {
776
- const groupElements = getElementsInGroup(elements, groupId);
777
- return getSelectionFromElements(groupElements);
778
- };
779
- Object.entries(selectGroupsFromGivenElements(elementsInGroups, appState))
780
- .filter(([id, isSelected]) => isSelected)
781
- .map(([id, isSelected]) => id)
782
- .map((groupId) => getSelectionForGroupId(groupId))
783
- .concat(individualElements.map((element) => getSelectionFromElements([element])))
784
- .forEach((selection) => renderSelectionBorder(context, appState, selection));
785
- };
786
- const renderBindingHighlightForSuggestedPointBinding = (context, suggestedBinding) => {
787
- const [element, startOrEnd, bindableElement] = suggestedBinding;
788
- const threshold = maxBindingGap(bindableElement, bindableElement.width, bindableElement.height);
789
- context.strokeStyle = "rgba(0,0,0,0)";
790
- context.fillStyle = "rgba(0,0,0,.05)";
791
- const pointIndices = startOrEnd === "both" ? [0, -1] : startOrEnd === "start" ? [0] : [-1];
792
- pointIndices.forEach((index) => {
793
- const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(element, index);
794
- fillCircle(context, x, y, threshold);
795
- });
796
- };
797
- let linkCanvasCache;
798
- const renderLinkIcon = (element, context, appState) => {
799
- if (element.link && !appState.selectedElementIds[element.id]) {
800
- const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
801
- const [x, y, width, height] = getLinkHandleFromCoords([x1, y1, x2, y2], element.angle, appState);
802
- const centerX = x + width / 2;
803
- const centerY = y + height / 2;
804
- context.save();
805
- context.translate(appState.scrollX + centerX, appState.scrollY + centerY);
806
- context.rotate(element.angle);
807
- if (!linkCanvasCache || linkCanvasCache.zoom !== appState.zoom.value) {
808
- linkCanvasCache = document.createElement("canvas");
809
- linkCanvasCache.zoom = appState.zoom.value;
810
- linkCanvasCache.width =
811
- width * window.devicePixelRatio * appState.zoom.value;
812
- linkCanvasCache.height =
813
- height * window.devicePixelRatio * appState.zoom.value;
814
- const linkCanvasCacheContext = linkCanvasCache.getContext("2d");
815
- linkCanvasCacheContext.scale(window.devicePixelRatio * appState.zoom.value, window.devicePixelRatio * appState.zoom.value);
816
- linkCanvasCacheContext.fillStyle = "#fff";
817
- linkCanvasCacheContext.fillRect(0, 0, width, height);
818
- linkCanvasCacheContext.drawImage(EXTERNAL_LINK_IMG, 0, 0, width, height);
819
- linkCanvasCacheContext.restore();
820
- context.drawImage(linkCanvasCache, x - centerX, y - centerY, width, height);
821
- }
822
- else {
823
- context.drawImage(linkCanvasCache, x - centerX, y - centerY, width, height);
824
- }
825
- context.restore();
826
- }
827
- };
828
- // This should be only called for exporting purposes
829
- export const renderSceneToSvg = (elements, rsvg, svgRoot, files, renderConfig) => {
830
- if (!svgRoot) {
831
- return;
832
- }
833
- // render elements
834
- elements
835
- .filter((el) => !isIframeLikeOrItsLabel(el))
836
- .forEach((element) => {
837
- if (!element.isDeleted) {
838
- try {
839
- renderElementToSvg(element, rsvg, svgRoot, files, element.x + renderConfig.offsetX, element.y + renderConfig.offsetY, renderConfig);
840
- }
841
- catch (error) {
842
- console.error(error);
843
- }
844
- }
845
- });
846
- // render embeddables on top
847
- elements
848
- .filter((el) => isIframeLikeElement(el))
849
- .forEach((element) => {
850
- if (!element.isDeleted) {
851
- try {
852
- renderElementToSvg(element, rsvg, svgRoot, files, element.x + renderConfig.offsetX, element.y + renderConfig.offsetY, renderConfig);
853
- }
854
- catch (error) {
855
- console.error(error);
856
- }
857
- }
858
- });
859
- };