@yuzhouu/canvas-kit 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +145 -0
  2. package/_vendor/canvas-core/bindings/BindingUtil.ts +92 -0
  3. package/_vendor/canvas-core/bindings/BindingUtilRegistry.ts +22 -0
  4. package/_vendor/canvas-core/bindings/arrow/arrowBindingHelpers.ts +52 -0
  5. package/_vendor/canvas-core/contextMenu.ts +85 -0
  6. package/_vendor/canvas-core/editor/Editor.ts +2614 -0
  7. package/_vendor/canvas-core/editor/HistoryManager.ts +83 -0
  8. package/_vendor/canvas-core/editor/StateNode.ts +162 -0
  9. package/_vendor/canvas-core/editor/camera.ts +6 -0
  10. package/_vendor/canvas-core/editor/clipboard.ts +478 -0
  11. package/_vendor/canvas-core/editor/commands.ts +833 -0
  12. package/_vendor/canvas-core/editor/edgeScroll.ts +37 -0
  13. package/_vendor/canvas-core/editor/editorComputedHelpers.ts +257 -0
  14. package/_vendor/canvas-core/editor/editorSelectionTransforms.ts +116 -0
  15. package/_vendor/canvas-core/editor/events.ts +93 -0
  16. package/_vendor/canvas-core/editor/index.ts +12 -0
  17. package/_vendor/canvas-core/editor/interactions.ts +101 -0
  18. package/_vendor/canvas-core/editor/internal.ts +22 -0
  19. package/_vendor/canvas-core/editor/managers/BindingManager.ts +262 -0
  20. package/_vendor/canvas-core/editor/managers/CameraManager.ts +147 -0
  21. package/_vendor/canvas-core/editor/managers/ClipboardManager.ts +32 -0
  22. package/_vendor/canvas-core/editor/managers/DocumentSnapshotManager.ts +256 -0
  23. package/_vendor/canvas-core/editor/managers/InputsManager.ts +319 -0
  24. package/_vendor/canvas-core/editor/managers/InteractionTransientManager.ts +218 -0
  25. package/_vendor/canvas-core/editor/managers/QueryManager.ts +837 -0
  26. package/_vendor/canvas-core/editor/managers/RenderWindowManager.ts +347 -0
  27. package/_vendor/canvas-core/editor/managers/SelectionLayoutManager.ts +196 -0
  28. package/_vendor/canvas-core/editor/managers/SelectionManager.ts +98 -0
  29. package/_vendor/canvas-core/editor/managers/SelectionOverlayManager.ts +308 -0
  30. package/_vendor/canvas-core/editor/managers/ShapeEditingManager.ts +226 -0
  31. package/_vendor/canvas-core/editor/managers/ShapeGeometryManager.ts +162 -0
  32. package/_vendor/canvas-core/editor/managers/ShapeHierarchyManager.ts +797 -0
  33. package/_vendor/canvas-core/editor/managers/SideEffectManager.ts +83 -0
  34. package/_vendor/canvas-core/editor/managers/SnapManager.ts +433 -0
  35. package/_vendor/canvas-core/editor/managers/SnapSnapshotBuilder.ts +430 -0
  36. package/_vendor/canvas-core/editor/managers/SpatialIndexManager.ts +354 -0
  37. package/_vendor/canvas-core/editor/managers/ToolManager.ts +81 -0
  38. package/_vendor/canvas-core/editor/managers/queryState.ts +583 -0
  39. package/_vendor/canvas-core/editor/managers/selectionGeometry.ts +287 -0
  40. package/_vendor/canvas-core/editor/managers/shapeOrder.ts +350 -0
  41. package/_vendor/canvas-core/editor/managers/shapeReparenting.ts +160 -0
  42. package/_vendor/canvas-core/editor/managers/snapCalculations.ts +412 -0
  43. package/_vendor/canvas-core/editor/managers/snapGeometry.ts +202 -0
  44. package/_vendor/canvas-core/editor/managers/snapIndicators.ts +403 -0
  45. package/_vendor/canvas-core/editor/managers/snapTypes.ts +122 -0
  46. package/_vendor/canvas-core/editor/queries/shapeTransforms.ts +171 -0
  47. package/_vendor/canvas-core/editor/renderWindow.ts +439 -0
  48. package/_vendor/canvas-core/editor/resizeGeometry.ts +654 -0
  49. package/_vendor/canvas-core/editor/selection.ts +489 -0
  50. package/_vendor/canvas-core/editor/selectionHandleInteractions.ts +43 -0
  51. package/_vendor/canvas-core/editor/selectionOverlay.ts +567 -0
  52. package/_vendor/canvas-core/exports/integrity.ts +174 -0
  53. package/_vendor/canvas-core/exports/svg.ts +494 -0
  54. package/_vendor/canvas-core/geometry/Box.ts +80 -0
  55. package/_vendor/canvas-core/geometry/Ellipse2d.ts +67 -0
  56. package/_vendor/canvas-core/geometry/Geometry2d.ts +52 -0
  57. package/_vendor/canvas-core/geometry/Mat.ts +92 -0
  58. package/_vendor/canvas-core/geometry/Polyline2d.ts +63 -0
  59. package/_vendor/canvas-core/geometry/Rectangle2d.ts +64 -0
  60. package/_vendor/canvas-core/geometry/Vec.ts +123 -0
  61. package/_vendor/canvas-core/geometry/index.ts +13 -0
  62. package/_vendor/canvas-core/geometry/segments.ts +65 -0
  63. package/_vendor/canvas-core/index.ts +391 -0
  64. package/_vendor/canvas-core/plugins/index.ts +5 -0
  65. package/_vendor/canvas-core/plugins/types.ts +53 -0
  66. package/_vendor/canvas-core/reactivity.ts +16 -0
  67. package/_vendor/canvas-core/schema/records.ts +221 -0
  68. package/_vendor/canvas-core/schema/snapshots.ts +527 -0
  69. package/_vendor/canvas-core/shapes/ShapeUtil.ts +516 -0
  70. package/_vendor/canvas-core/shapes/ShapeUtilRegistry.ts +26 -0
  71. package/_vendor/canvas-core/shapes/capabilities.ts +67 -0
  72. package/_vendor/canvas-core/shapes/types.ts +20 -0
  73. package/_vendor/canvas-core/toolbar.ts +148 -0
  74. package/_vendor/canvas-core/tools/hand/HandTool.ts +75 -0
  75. package/_vendor/canvas-core/tools/index.ts +2 -0
  76. package/_vendor/canvas-core/tools/select/SelectTool.ts +33 -0
  77. package/_vendor/canvas-core/tools/select/selectContainment.ts +104 -0
  78. package/_vendor/canvas-core/tools/select/selectToolHelpers.ts +801 -0
  79. package/_vendor/canvas-core/tools/select/states/BrushingState.ts +145 -0
  80. package/_vendor/canvas-core/tools/select/states/DraggingSelectionHandleInteractionState.ts +116 -0
  81. package/_vendor/canvas-core/tools/select/states/IdleState.ts +159 -0
  82. package/_vendor/canvas-core/tools/select/states/PointingCanvasState.ts +41 -0
  83. package/_vendor/canvas-core/tools/select/states/PointingResizeHandleState.ts +78 -0
  84. package/_vendor/canvas-core/tools/select/states/PointingRotateHandleState.ts +64 -0
  85. package/_vendor/canvas-core/tools/select/states/PointingSelectionHandleInteractionState.ts +34 -0
  86. package/_vendor/canvas-core/tools/select/states/PointingShapeState.ts +140 -0
  87. package/_vendor/canvas-core/tools/select/states/ResizingState.ts +171 -0
  88. package/_vendor/canvas-core/tools/select/states/RotatingState.ts +124 -0
  89. package/_vendor/canvas-core/tools/select/states/SelectStateNode.ts +128 -0
  90. package/_vendor/canvas-core/tools/select/states/TranslatingState.ts +139 -0
  91. package/_vendor/canvas-core/tools/select/states/index.ts +11 -0
  92. package/_vendor/canvas-plugin-api/feature.ts +204 -0
  93. package/_vendor/canvas-plugin-api/index.ts +142 -0
  94. package/_vendor/canvas-product-react/api/apiRegistry.ts +35 -0
  95. package/_vendor/canvas-product-react/api/canvasProductApi.ts +422 -0
  96. package/_vendor/canvas-product-react/api/index.ts +31 -0
  97. package/_vendor/canvas-product-react/capabilities/clipboard/clipboardService.ts +36 -0
  98. package/_vendor/canvas-product-react/capabilities/clipboard/systemClipboard.ts +245 -0
  99. package/_vendor/canvas-product-react/capabilities/context-menu/systemContextMenu.ts +396 -0
  100. package/_vendor/canvas-product-react/capabilities/context-menu/useContextMenuSurface.ts +59 -0
  101. package/_vendor/canvas-product-react/capabilities/export/useSvgExport.ts +156 -0
  102. package/_vendor/canvas-product-react/capabilities/history/useHistoryControls.ts +23 -0
  103. package/_vendor/canvas-product-react/capabilities/pages/usePageControls.ts +41 -0
  104. package/_vendor/canvas-product-react/capabilities/toolbar/systemToolbar.ts +490 -0
  105. package/_vendor/canvas-product-react/capabilities/toolbar/useToolbarSurfaces.ts +92 -0
  106. package/_vendor/canvas-product-react/capabilities/zoom/useZoomControls.ts +27 -0
  107. package/_vendor/canvas-product-react/index.ts +84 -0
  108. package/_vendor/canvas-product-react/persistence/index.ts +18 -0
  109. package/_vendor/canvas-product-react/persistence/localSnapshotPersistence.ts +294 -0
  110. package/_vendor/canvas-product-react/runtime/CanvasProductRoot.tsx +250 -0
  111. package/_vendor/canvas-product-react/runtime/productDefinition.ts +72 -0
  112. package/_vendor/canvas-product-react/runtime/productServices.ts +96 -0
  113. package/_vendor/canvas-react/components/CanvasView.tsx +796 -0
  114. package/_vendor/canvas-react/components/OverlayLayer.tsx +879 -0
  115. package/_vendor/canvas-react/components/PageSpace.tsx +34 -0
  116. package/_vendor/canvas-react/components/ShapeLayer.tsx +428 -0
  117. package/_vendor/canvas-react/components/index.ts +4 -0
  118. package/_vendor/canvas-react/hooks/useEditor.ts +11 -0
  119. package/_vendor/canvas-react/hooks/useValue.ts +14 -0
  120. package/_vendor/canvas-react/host/CanvasRendererRegistryContext.tsx +14 -0
  121. package/_vendor/canvas-react/host/contextMenuRegistry.ts +61 -0
  122. package/_vendor/canvas-react/host/domEventGuards.ts +61 -0
  123. package/_vendor/canvas-react/host/index.ts +38 -0
  124. package/_vendor/canvas-react/host/pluginHost.ts +635 -0
  125. package/_vendor/canvas-react/host/rendererRegistry.tsx +58 -0
  126. package/_vendor/canvas-react/host/toolbarRegistry.ts +100 -0
  127. package/_vendor/canvas-react/index.ts +3 -0
  128. package/_vendor/plugin-arrow/ArrowBindingUtil.ts +239 -0
  129. package/_vendor/plugin-arrow/ArrowShapeUtil.ts +311 -0
  130. package/_vendor/plugin-arrow/ArrowTool.ts +208 -0
  131. package/_vendor/plugin-arrow/arrowGeometry.ts +441 -0
  132. package/_vendor/plugin-arrow/arrowTerminalBindingResolver.ts +253 -0
  133. package/_vendor/plugin-arrow/arrowTerminalCommands.ts +230 -0
  134. package/_vendor/plugin-arrow/canvas.tsx +92 -0
  135. package/_vendor/plugin-arrow/core.ts +409 -0
  136. package/_vendor/plugin-arrow/feature.ts +20 -0
  137. package/_vendor/plugin-arrow/index.ts +28 -0
  138. package/_vendor/plugin-arrow/selectionHandleInteractions.ts +154 -0
  139. package/_vendor/plugin-arrow/types.ts +63 -0
  140. package/_vendor/plugin-draw/DrawShapeRenderer.tsx +60 -0
  141. package/_vendor/plugin-draw/DrawShapeUtil.ts +372 -0
  142. package/_vendor/plugin-draw/DrawTool.ts +444 -0
  143. package/_vendor/plugin-draw/canvas.tsx +24 -0
  144. package/_vendor/plugin-draw/core.ts +515 -0
  145. package/_vendor/plugin-draw/drawGeometry.ts +491 -0
  146. package/_vendor/plugin-draw/drawHitTest.ts +271 -0
  147. package/_vendor/plugin-draw/drawInterpolation.ts +250 -0
  148. package/_vendor/plugin-draw/drawPath.ts +550 -0
  149. package/_vendor/plugin-draw/drawSegments.ts +106 -0
  150. package/_vendor/plugin-draw/drawSelectionTransform.ts +67 -0
  151. package/_vendor/plugin-draw/drawShapeGeometry.ts +307 -0
  152. package/_vendor/plugin-draw/drawStrokeGeometry.ts +454 -0
  153. package/_vendor/plugin-draw/feature.ts +20 -0
  154. package/_vendor/plugin-draw/index.ts +31 -0
  155. package/_vendor/plugin-draw/types.ts +36 -0
  156. package/_vendor/plugin-eraser/EraserTool.ts +297 -0
  157. package/_vendor/plugin-eraser/canvas.tsx +616 -0
  158. package/_vendor/plugin-eraser/core.ts +49 -0
  159. package/_vendor/plugin-eraser/feature.ts +20 -0
  160. package/_vendor/plugin-eraser/index.ts +4 -0
  161. package/_vendor/plugin-frame/FrameShapeUtil.ts +173 -0
  162. package/_vendor/plugin-frame/FrameTool.ts +225 -0
  163. package/_vendor/plugin-frame/canvas.tsx +75 -0
  164. package/_vendor/plugin-frame/core.ts +534 -0
  165. package/_vendor/plugin-frame/feature.ts +20 -0
  166. package/_vendor/plugin-frame/index.ts +6 -0
  167. package/_vendor/plugin-geo/GeoShapeUtil.ts +463 -0
  168. package/_vendor/plugin-geo/GeoTools.ts +247 -0
  169. package/_vendor/plugin-geo/canvas.tsx +90 -0
  170. package/_vendor/plugin-geo/core.ts +495 -0
  171. package/_vendor/plugin-geo/feature.ts +20 -0
  172. package/_vendor/plugin-geo/index.ts +7 -0
  173. package/_vendor/plugin-geo/types.ts +27 -0
  174. package/_vendor/plugin-group/GroupShapeUtil.ts +59 -0
  175. package/_vendor/plugin-group/canvas.tsx +59 -0
  176. package/_vendor/plugin-group/core.ts +40 -0
  177. package/_vendor/plugin-group/feature.ts +20 -0
  178. package/_vendor/plugin-group/index.ts +5 -0
  179. package/_vendor/plugin-image/ImageShapeUtil.ts +95 -0
  180. package/_vendor/plugin-image/canvas.tsx +212 -0
  181. package/_vendor/plugin-image/core.ts +542 -0
  182. package/_vendor/plugin-image/crop.ts +770 -0
  183. package/_vendor/plugin-image/cropInteractions.ts +350 -0
  184. package/_vendor/plugin-image/cropSession.ts +38 -0
  185. package/_vendor/plugin-image/feature.ts +20 -0
  186. package/_vendor/plugin-image/index.ts +29 -0
  187. package/_vendor/plugin-image/types.ts +55 -0
  188. package/_vendor/plugin-image-product-react/ImageImportController.tsx +143 -0
  189. package/_vendor/plugin-image-product-react/imageAssetService.ts +52 -0
  190. package/_vendor/plugin-image-product-react/imageBlobStore.ts +315 -0
  191. package/_vendor/plugin-image-product-react/imageHostActions.ts +438 -0
  192. package/_vendor/plugin-image-product-react/imageProductApi.ts +88 -0
  193. package/_vendor/plugin-image-product-react/imageToolbarHostBridge.ts +48 -0
  194. package/_vendor/plugin-image-product-react/imageToolbarRuntime.ts +21 -0
  195. package/_vendor/plugin-image-product-react/index.ts +60 -0
  196. package/_vendor/plugin-image-product-react/snapshotGuards.ts +33 -0
  197. package/_vendor/plugin-image-product-react/svgImageAssetInliner.ts +54 -0
  198. package/_vendor/plugin-image-product-react/useImageImport.ts +64 -0
  199. package/_vendor/plugin-laser/LaserTool.ts +154 -0
  200. package/_vendor/plugin-laser/canvas.tsx +645 -0
  201. package/_vendor/plugin-laser/core.ts +49 -0
  202. package/_vendor/plugin-laser/feature.ts +20 -0
  203. package/_vendor/plugin-laser/index.ts +4 -0
  204. package/_vendor/plugin-text/ArrowLabelBindingUtil.ts +336 -0
  205. package/_vendor/plugin-text/GeoLabelBindingUtil.ts +391 -0
  206. package/_vendor/plugin-text/TextShapeSvgExporter.ts +101 -0
  207. package/_vendor/plugin-text/TextShapeUtil.ts +284 -0
  208. package/_vendor/plugin-text/TextTool.ts +228 -0
  209. package/_vendor/plugin-text/canvas.tsx +115 -0
  210. package/_vendor/plugin-text/core.ts +422 -0
  211. package/_vendor/plugin-text/feature.ts +20 -0
  212. package/_vendor/plugin-text/host/RichTextEditingBox.tsx +144 -0
  213. package/_vendor/plugin-text/host/RichTextToolbar.tsx +266 -0
  214. package/_vendor/plugin-text/host/TiptapTextEditor.tsx +191 -0
  215. package/_vendor/plugin-text/host/richTextRendering.tsx +49 -0
  216. package/_vendor/plugin-text/host/textDomSelection.ts +93 -0
  217. package/_vendor/plugin-text/host/textEditingBoxStyle.ts +36 -0
  218. package/_vendor/plugin-text/host/textEntryFocus.ts +307 -0
  219. package/_vendor/plugin-text/host/textFontLoader.ts +149 -0
  220. package/_vendor/plugin-text/host/textMeasurement.ts +258 -0
  221. package/_vendor/plugin-text/host/textMeasurementSync.ts +77 -0
  222. package/_vendor/plugin-text/host/textRangeSelection.ts +134 -0
  223. package/_vendor/plugin-text/host/textShapeRendererRuntime.tsx +89 -0
  224. package/_vendor/plugin-text/host/textShapeSync.ts +173 -0
  225. package/_vendor/plugin-text/host/tiptapTextEditorController.ts +148 -0
  226. package/_vendor/plugin-text/index.ts +18 -0
  227. package/_vendor/plugin-text/richTextUtils.ts +259 -0
  228. package/_vendor/plugin-text/textInteractions.ts +366 -0
  229. package/_vendor/plugin-text/textLabelHelpers.ts +235 -0
  230. package/_vendor/plugin-text/textShapeHelpers.ts +123 -0
  231. package/_vendor/plugin-text/textStyle.ts +96 -0
  232. package/_vendor/plugin-text/textTypes.ts +48 -0
  233. package/_vendor/signals/atom.ts +74 -0
  234. package/_vendor/signals/computed.ts +132 -0
  235. package/_vendor/signals/core.ts +130 -0
  236. package/_vendor/signals/effect.ts +117 -0
  237. package/_vendor/signals/index.ts +9 -0
  238. package/_vendor/signals/types.ts +17 -0
  239. package/_vendor/store/Store.ts +171 -0
  240. package/_vendor/store/diff.ts +229 -0
  241. package/_vendor/store/history.ts +177 -0
  242. package/_vendor/store/index.ts +32 -0
  243. package/_vendor/store/types.ts +37 -0
  244. package/api/canvasKitApi.d.ts +29 -0
  245. package/index.css +3 -0
  246. package/index.d.ts +16 -0
  247. package/index.mjs +1 -0
  248. package/internal-modules.d.ts +187 -0
  249. package/package.json +48 -0
  250. package/product/Shell.d.ts +19 -0
  251. package/product/chromeContext.d.ts +70 -0
  252. package/product/createCanvasKitEditor.d.ts +6 -0
  253. package/product/index.d.ts +7 -0
  254. package/product/persistence.d.ts +3 -0
  255. package/product/product.d.ts +22 -0
  256. package/product/useCanvasClipboardShortcuts.d.ts +6 -0
  257. package/product-iyBbEPIs.mjs +4 -0
  258. package/product.mjs +1 -0
  259. package/standard/extensions.d.ts +1 -0
  260. package/standard/index.d.ts +3 -0
  261. package/standard/standardApi.d.ts +91 -0
  262. package/standard-BmrxwqpJ.mjs +44 -0
  263. package/standard.mjs +1 -0
@@ -0,0 +1,438 @@
1
+ import {
2
+ type AssetId,
3
+ type Editor,
4
+ type ShapeId,
5
+ Vec,
6
+ } from "@land/canvas-core";
7
+ import type { CanvasPluginHostRuntime } from "@land/canvas-react/host";
8
+ import {
9
+ IMAGE_DOWNLOAD_TOOLBAR_ACTION_ID,
10
+ IMAGE_INSERT_TOOLBAR_ACTION_ID,
11
+ IMAGE_REPLACE_TOOLBAR_ACTION_ID,
12
+ isImageAssetRecord,
13
+ isImageShapeRecord,
14
+ type ImageAssetRecord,
15
+ } from "@land/plugin-image";
16
+ import {
17
+ deleteImageBlob,
18
+ getImageAssetStorageKey,
19
+ readImageBlob,
20
+ writeImageBlob,
21
+ type ImageObjectUrlStore,
22
+ } from "./imageBlobStore";
23
+ import type { ImageToolbarHostBridge } from "./imageToolbarHostBridge";
24
+
25
+ interface LocalImageAssetInput {
26
+ name: string;
27
+ storageKey: string;
28
+ mimeType: string;
29
+ fileSize: number;
30
+ width: number;
31
+ height: number;
32
+ }
33
+
34
+ export type ImageImportSource = "file-picker" | "paste" | "drop";
35
+
36
+ export interface InsertImageFileInput {
37
+ editor: Editor;
38
+ file: File;
39
+ imageObjectUrls: ImageObjectUrlStore;
40
+ pagePoint?: Vec;
41
+ persistEditorSnapshot?: (editor: Editor) => Promise<void>;
42
+ source: ImageImportSource;
43
+ }
44
+
45
+ export interface ReplaceSelectedImageFileInput {
46
+ editor: Editor;
47
+ file: File;
48
+ imageObjectUrls: ImageObjectUrlStore;
49
+ persistEditorSnapshot?: (editor: Editor) => Promise<void>;
50
+ }
51
+
52
+ function decodeImageSize(
53
+ src: string,
54
+ ): Promise<{ width: number; height: number }> {
55
+ return new Promise((resolve, reject) => {
56
+ const image = new Image();
57
+ image.addEventListener("load", () => {
58
+ resolve({
59
+ width: image.naturalWidth || image.width,
60
+ height: image.naturalHeight || image.height,
61
+ });
62
+ });
63
+ image.addEventListener("error", () => {
64
+ reject(new Error("Could not decode image file."));
65
+ });
66
+ image.src = src;
67
+ });
68
+ }
69
+
70
+ async function getLocalImageAssetInput(
71
+ file: File,
72
+ ): Promise<LocalImageAssetInput> {
73
+ const temporaryUrl = URL.createObjectURL(file);
74
+ const size = await decodeImageSize(temporaryUrl).finally(() => {
75
+ URL.revokeObjectURL(temporaryUrl);
76
+ });
77
+ const { storageKey } = await writeImageBlob(file);
78
+ return {
79
+ name: file.name || "Image",
80
+ storageKey,
81
+ mimeType: file.type || "image/png",
82
+ fileSize: file.size,
83
+ width: size.width,
84
+ height: size.height,
85
+ };
86
+ }
87
+
88
+ function createImageAsset(
89
+ editor: Editor,
90
+ input: LocalImageAssetInput,
91
+ ): ImageAssetRecord {
92
+ return editor.commands.createAsset(
93
+ {
94
+ type: "image",
95
+ props: {
96
+ w: input.width,
97
+ h: input.height,
98
+ name: input.name,
99
+ storageKey: input.storageKey,
100
+ mimeType: input.mimeType,
101
+ fileSize: input.fileSize,
102
+ isAnimated: input.mimeType === "image/gif",
103
+ },
104
+ },
105
+ { source: "user" },
106
+ ) as ImageAssetRecord;
107
+ }
108
+
109
+ function fitLocalImageSize({
110
+ height,
111
+ width,
112
+ }: {
113
+ height: number;
114
+ width: number;
115
+ }) {
116
+ const maxSize = 360;
117
+ const scale = Math.min(1, maxSize / Math.max(width, height));
118
+ return {
119
+ w: Math.max(24, Math.round(width * scale)),
120
+ h: Math.max(24, Math.round(height * scale)),
121
+ };
122
+ }
123
+
124
+ function isAssetReferenced(editor: Editor, assetId: AssetId): boolean {
125
+ return editor
126
+ .getDocumentSnapshot()
127
+ .records.some(
128
+ (record) =>
129
+ record.typeName === "shape" &&
130
+ (record.props as Record<string, unknown>).assetId === assetId,
131
+ );
132
+ }
133
+
134
+ export function getFirstImageFile(files: Iterable<File>): File | null {
135
+ for (const file of files) {
136
+ if (file.type.startsWith("image/")) {
137
+ return file;
138
+ }
139
+ }
140
+ return null;
141
+ }
142
+
143
+ export function getFirstImageFileFromDataTransfer(
144
+ dataTransfer: DataTransfer | null,
145
+ ): File | null {
146
+ if (!dataTransfer) {
147
+ return null;
148
+ }
149
+
150
+ const fileFromFiles = getFirstImageFile(dataTransfer.files);
151
+ if (fileFromFiles) {
152
+ return fileFromFiles;
153
+ }
154
+
155
+ for (const item of Array.from(dataTransfer.items)) {
156
+ if (item.kind !== "file" || !item.type.startsWith("image/")) {
157
+ continue;
158
+ }
159
+ const file = item.getAsFile();
160
+ if (file?.type.startsWith("image/")) {
161
+ return file;
162
+ }
163
+ }
164
+
165
+ return null;
166
+ }
167
+
168
+ export function hasImageFileItem(dataTransfer: DataTransfer | null): boolean {
169
+ if (!dataTransfer) {
170
+ return false;
171
+ }
172
+
173
+ return Array.from(dataTransfer.items).some(
174
+ (item) => item.kind === "file" && item.type.startsWith("image/"),
175
+ );
176
+ }
177
+
178
+ function getEditableEventTarget(target: EventTarget | null): HTMLElement | null {
179
+ return target instanceof HTMLElement ? target : null;
180
+ }
181
+
182
+ export function isEditableEventTarget(target: EventTarget | null): boolean {
183
+ const element = getEditableEventTarget(target);
184
+ if (!element) {
185
+ return false;
186
+ }
187
+
188
+ if (element.isContentEditable) {
189
+ return true;
190
+ }
191
+
192
+ if (element.closest('[contenteditable="true"]')) {
193
+ return true;
194
+ }
195
+
196
+ return (
197
+ element instanceof HTMLInputElement ||
198
+ element instanceof HTMLTextAreaElement ||
199
+ element instanceof HTMLSelectElement
200
+ );
201
+ }
202
+
203
+ export function getDropPagePoint(editor: Editor, event: DragEvent): Vec {
204
+ const canvasSurface = document.querySelector<HTMLElement>(
205
+ '[data-ui="canvas-surface"]',
206
+ );
207
+ if (!canvasSurface) {
208
+ return editor.screenToPage(new Vec(event.clientX, event.clientY));
209
+ }
210
+ const rect = canvasSurface.getBoundingClientRect();
211
+ return editor.screenToPage(
212
+ new Vec(event.clientX - rect.left, event.clientY - rect.top),
213
+ );
214
+ }
215
+
216
+ export async function insertImageFileIntoEditor({
217
+ editor,
218
+ file,
219
+ imageObjectUrls,
220
+ pagePoint,
221
+ persistEditorSnapshot,
222
+ source,
223
+ }: InsertImageFileInput): Promise<ShapeId | null> {
224
+ if (!file.type.startsWith("image/")) {
225
+ return null;
226
+ }
227
+ const input = await getLocalImageAssetInput(file);
228
+ await imageObjectUrls.preload(input.storageKey);
229
+ let createdAssetId: AssetId | null = null;
230
+ try {
231
+ const asset = createImageAsset(editor, input);
232
+ createdAssetId = asset.id;
233
+ const size = fitLocalImageSize({
234
+ width: asset.props.w,
235
+ height: asset.props.h,
236
+ });
237
+ const viewportBounds = editor.viewportPageBoundsSignal.get();
238
+ const center =
239
+ pagePoint ??
240
+ viewportBounds?.center ??
241
+ editor.screenToPage(new Vec(360, 240));
242
+ const [shape] = editor.commands.createShapes(
243
+ [
244
+ {
245
+ type: "image",
246
+ x: Math.round(center.x - size.w / 2),
247
+ y: Math.round(center.y - size.h / 2),
248
+ props: {
249
+ ...size,
250
+ assetId: asset.id,
251
+ crop: null,
252
+ altText: input.name,
253
+ },
254
+ },
255
+ ],
256
+ { source: "user" },
257
+ );
258
+ if (shape) {
259
+ editor.commands.setSelectedShapeIds([shape.id], { source: "user" });
260
+ await persistEditorSnapshot?.(editor);
261
+ return shape.id;
262
+ }
263
+ return null;
264
+ } catch (error) {
265
+ if (createdAssetId) {
266
+ editor.commands.deleteAsset(createdAssetId, { source: "system" });
267
+ }
268
+ imageObjectUrls.release(input.storageKey);
269
+ await deleteImageBlob(input.storageKey);
270
+ throw error;
271
+ }
272
+ }
273
+
274
+ export async function replaceSelectedImageFromFile({
275
+ editor,
276
+ file,
277
+ imageObjectUrls,
278
+ persistEditorSnapshot,
279
+ }: ReplaceSelectedImageFileInput): Promise<void> {
280
+ if (!file.type.startsWith("image/")) {
281
+ return;
282
+ }
283
+ const [shape] = editor.getSelectedShapes();
284
+ if (!isImageShapeRecord(shape) || shape.isLocked) {
285
+ return;
286
+ }
287
+ const previousAssetId = shape.props.assetId;
288
+ const input = await getLocalImageAssetInput(file);
289
+ await imageObjectUrls.preload(input.storageKey);
290
+ let createdAssetId: AssetId | null = null;
291
+ try {
292
+ const asset = createImageAsset(editor, input);
293
+ createdAssetId = asset.id;
294
+ editor.commands.updateShapes(
295
+ [
296
+ {
297
+ id: shape.id,
298
+ props: {
299
+ assetId: asset.id,
300
+ crop: null,
301
+ altText: input.name,
302
+ },
303
+ },
304
+ ],
305
+ { source: "user" },
306
+ );
307
+ if (previousAssetId && !isAssetReferenced(editor, previousAssetId)) {
308
+ editor.commands.deleteAsset(previousAssetId, { source: "user" });
309
+ }
310
+ editor.commands.setSelectedShapeIds([shape.id], { source: "user" });
311
+ await persistEditorSnapshot?.(editor);
312
+ } catch (error) {
313
+ if (createdAssetId) {
314
+ editor.commands.deleteAsset(createdAssetId, { source: "system" });
315
+ }
316
+ imageObjectUrls.release(input.storageKey);
317
+ await deleteImageBlob(input.storageKey);
318
+ throw error;
319
+ }
320
+ }
321
+
322
+ function downloadObjectUrl({
323
+ filename,
324
+ url,
325
+ }: {
326
+ filename: string;
327
+ url: string;
328
+ }) {
329
+ const anchor = document.createElement("a");
330
+ anchor.href = url;
331
+ anchor.download = filename;
332
+ anchor.click();
333
+ }
334
+
335
+ async function downloadBlob({
336
+ blob,
337
+ filename,
338
+ }: {
339
+ blob: Blob;
340
+ filename: string;
341
+ }) {
342
+ const url = URL.createObjectURL(blob);
343
+ try {
344
+ downloadObjectUrl({ filename, url });
345
+ } finally {
346
+ window.setTimeout(() => URL.revokeObjectURL(url), 0);
347
+ }
348
+ }
349
+
350
+ export async function downloadSelectedImageOriginal({
351
+ editor,
352
+ imageObjectUrls,
353
+ }: {
354
+ editor: Editor;
355
+ imageObjectUrls: ImageObjectUrlStore;
356
+ }): Promise<void> {
357
+ const [shape] = editor.getSelectedShapes();
358
+ if (!isImageShapeRecord(shape) || !shape.props.assetId) {
359
+ return;
360
+ }
361
+ const asset = editor.getAsset(shape.props.assetId);
362
+ if (!isImageAssetRecord(asset)) {
363
+ return;
364
+ }
365
+ const storageKey = asset.props.storageKey;
366
+ const name =
367
+ typeof asset.props.name === "string" && asset.props.name.length > 0
368
+ ? asset.props.name
369
+ : "land-image";
370
+ if (!storageKey) {
371
+ return;
372
+ }
373
+ const blob = await readImageBlob(storageKey);
374
+ if (blob) {
375
+ await downloadBlob({ blob, filename: name });
376
+ return;
377
+ }
378
+ const url = imageObjectUrls.resolve(storageKey);
379
+ if (url) {
380
+ downloadObjectUrl({ filename: name, url });
381
+ }
382
+ }
383
+
384
+ export function registerImageToolbarActions({
385
+ hostBridge,
386
+ imageObjectUrls,
387
+ pluginHostRuntime,
388
+ }: {
389
+ hostBridge: ImageToolbarHostBridge;
390
+ imageObjectUrls: ImageObjectUrlStore;
391
+ pluginHostRuntime: CanvasPluginHostRuntime;
392
+ }): void {
393
+ pluginHostRuntime.toolbarRegistry.registerHostAction({
394
+ id: IMAGE_INSERT_TOOLBAR_ACTION_ID,
395
+ run() {
396
+ hostBridge.openInsertFilePicker();
397
+ },
398
+ });
399
+ pluginHostRuntime.toolbarRegistry.registerHostAction({
400
+ id: IMAGE_REPLACE_TOOLBAR_ACTION_ID,
401
+ isVisible(context) {
402
+ return (
403
+ context.selection.count === 1 &&
404
+ context.selection.singleShapeType === "image"
405
+ );
406
+ },
407
+ isEnabled(context) {
408
+ const [shape] = context.selection.selectedShapes;
409
+ return isImageShapeRecord(shape) && !shape.isLocked;
410
+ },
411
+ run() {
412
+ hostBridge.openReplaceFilePicker();
413
+ },
414
+ });
415
+ pluginHostRuntime.toolbarRegistry.registerHostAction({
416
+ id: IMAGE_DOWNLOAD_TOOLBAR_ACTION_ID,
417
+ isVisible(context) {
418
+ return (
419
+ context.selection.count === 1 &&
420
+ context.selection.singleShapeType === "image"
421
+ );
422
+ },
423
+ isEnabled(context) {
424
+ const [shape] = context.selection.selectedShapes;
425
+ return (
426
+ isImageShapeRecord(shape) &&
427
+ !!shape.props.assetId &&
428
+ !!getImageAssetStorageKey(context.editor, shape.props.assetId)
429
+ );
430
+ },
431
+ run({ context }) {
432
+ void downloadSelectedImageOriginal({
433
+ editor: context.editor,
434
+ imageObjectUrls,
435
+ });
436
+ },
437
+ });
438
+ }
@@ -0,0 +1,88 @@
1
+ import type { Editor, ShapeId, Vec } from "@land/canvas-core";
2
+ import {
3
+ createProductApiToken,
4
+ type CanvasProductRuntimeSetup,
5
+ } from "@land/canvas-product-react";
6
+ import { isImageShapeRecord } from "@land/plugin-image";
7
+ import {
8
+ downloadSelectedImageOriginal,
9
+ insertImageFileIntoEditor,
10
+ replaceSelectedImageFromFile,
11
+ type ImageImportSource,
12
+ } from "./imageHostActions";
13
+ import { imageAssetServiceToken } from "./imageAssetService";
14
+
15
+ export interface ImageInsertInput {
16
+ file: File;
17
+ pagePoint?: Vec;
18
+ source?: ImageImportSource;
19
+ }
20
+
21
+ export interface ImageReplaceInput {
22
+ file: File;
23
+ }
24
+
25
+ export interface ImageProductApi {
26
+ insertImage(input: ImageInsertInput): Promise<ShapeId | null>;
27
+ replaceSelectedImage(input: ImageReplaceInput): Promise<boolean>;
28
+ downloadSelectedImageOriginal(): Promise<void>;
29
+ }
30
+
31
+ export const imageProductApiToken =
32
+ createProductApiToken<ImageProductApi>("plugin-image.product-api");
33
+
34
+ export interface ImageProductApiRuntimeOptions {
35
+ persistEditorSnapshot?: (editor: Editor) => Promise<void>;
36
+ }
37
+
38
+ export function registerImageProductApi({
39
+ persistEditorSnapshot,
40
+ }: ImageProductApiRuntimeOptions = {}): CanvasProductRuntimeSetup {
41
+ return ({ apiRegistry, editor, services }) => {
42
+ if (apiRegistry.get(imageProductApiToken)) {
43
+ return;
44
+ }
45
+
46
+ const imageAssetService = services.get(imageAssetServiceToken);
47
+ if (!imageAssetService) {
48
+ return;
49
+ }
50
+
51
+ const imageApi: ImageProductApi = {
52
+ insertImage({ file, pagePoint, source = "file-picker" }) {
53
+ return insertImageFileIntoEditor({
54
+ editor,
55
+ file,
56
+ imageObjectUrls: imageAssetService.objectUrls,
57
+ pagePoint,
58
+ persistEditorSnapshot,
59
+ source,
60
+ });
61
+ },
62
+ async replaceSelectedImage({ file }) {
63
+ if (!file.type.startsWith("image/")) {
64
+ return false;
65
+ }
66
+ const [shape] = editor.getSelectedShapes();
67
+ if (!isImageShapeRecord(shape) || shape.isLocked) {
68
+ return false;
69
+ }
70
+ await replaceSelectedImageFromFile({
71
+ editor,
72
+ file,
73
+ imageObjectUrls: imageAssetService.objectUrls,
74
+ persistEditorSnapshot,
75
+ });
76
+ return true;
77
+ },
78
+ downloadSelectedImageOriginal() {
79
+ return downloadSelectedImageOriginal({
80
+ editor,
81
+ imageObjectUrls: imageAssetService.objectUrls,
82
+ });
83
+ },
84
+ };
85
+
86
+ apiRegistry.register(imageProductApiToken, imageApi);
87
+ };
88
+ }
@@ -0,0 +1,48 @@
1
+ import {
2
+ createProductServiceToken,
3
+ type ProductServiceFactory,
4
+ } from "@land/canvas-product-react";
5
+
6
+ export interface ImageToolbarHostBridgeHandlers {
7
+ openInsertFilePicker(): void;
8
+ openReplaceFilePicker(): void;
9
+ }
10
+
11
+ export class ImageToolbarHostBridge {
12
+ private handlers: ImageToolbarHostBridgeHandlers | null = null;
13
+
14
+ register(handlers: ImageToolbarHostBridgeHandlers): () => void {
15
+ this.handlers = handlers;
16
+ return () => {
17
+ if (this.handlers === handlers) {
18
+ this.handlers = null;
19
+ }
20
+ };
21
+ }
22
+
23
+ openInsertFilePicker(): void {
24
+ this.handlers?.openInsertFilePicker();
25
+ }
26
+
27
+ openReplaceFilePicker(): void {
28
+ this.handlers?.openReplaceFilePicker();
29
+ }
30
+ }
31
+
32
+ export function createImageToolbarHostBridge(): ImageToolbarHostBridge {
33
+ return new ImageToolbarHostBridge();
34
+ }
35
+
36
+ export const imageToolbarHostBridgeToken =
37
+ createProductServiceToken<ImageToolbarHostBridge>(
38
+ "plugin-image.toolbar-host-bridge",
39
+ );
40
+
41
+ export function imageToolbarHostBridgeService(): ProductServiceFactory<ImageToolbarHostBridge> {
42
+ return {
43
+ token: imageToolbarHostBridgeToken,
44
+ create() {
45
+ return createImageToolbarHostBridge();
46
+ },
47
+ };
48
+ }
@@ -0,0 +1,21 @@
1
+ import type {
2
+ CanvasProductRuntimeSetupContext,
3
+ } from "@land/canvas-product-react";
4
+ import { imageAssetServiceToken } from "./imageAssetService";
5
+ import { registerImageToolbarActions } from "./imageHostActions";
6
+ import { imageToolbarHostBridgeToken } from "./imageToolbarHostBridge";
7
+
8
+ export function registerImageProductToolbarActions({
9
+ pluginHostRuntime,
10
+ services,
11
+ }: CanvasProductRuntimeSetupContext): void {
12
+ const imageAssetService = services.get(imageAssetServiceToken);
13
+ const hostBridge = services.get(imageToolbarHostBridgeToken);
14
+ if (imageAssetService && hostBridge) {
15
+ registerImageToolbarActions({
16
+ hostBridge,
17
+ imageObjectUrls: imageAssetService.objectUrls,
18
+ pluginHostRuntime,
19
+ });
20
+ }
21
+ }
@@ -0,0 +1,60 @@
1
+ export { ImageImportController } from "./ImageImportController";
2
+ export {
3
+ imageProductApiToken,
4
+ registerImageProductApi,
5
+ } from "./imageProductApi";
6
+ export type {
7
+ ImageInsertInput,
8
+ ImageProductApi,
9
+ ImageProductApiRuntimeOptions,
10
+ ImageReplaceInput,
11
+ } from "./imageProductApi";
12
+ export {
13
+ imageAssetServiceToken,
14
+ localImageAssetService,
15
+ } from "./imageAssetService";
16
+ export type { ImageAssetService } from "./imageAssetService";
17
+ export { registerImageProductToolbarActions } from "./imageToolbarRuntime";
18
+ export {
19
+ createImageToolbarHostBridge,
20
+ ImageToolbarHostBridge,
21
+ imageToolbarHostBridgeService,
22
+ imageToolbarHostBridgeToken,
23
+ } from "./imageToolbarHostBridge";
24
+ export type { ImageToolbarHostBridgeHandlers } from "./imageToolbarHostBridge";
25
+ export {
26
+ cleanupUnreferencedImageBlobs,
27
+ createImageObjectUrlStore,
28
+ createImageStorageKey,
29
+ deleteImageBlob,
30
+ getAllImageBlobStorageKeys,
31
+ getImageAssetStorageKey,
32
+ getReferencedImageStorageKeys,
33
+ readImageBlob,
34
+ readImageBlobAsDataUrl,
35
+ writeImageBlob,
36
+ } from "./imageBlobStore";
37
+ export type {
38
+ ImageBlobWriteResult,
39
+ ImageObjectUrlStore,
40
+ } from "./imageBlobStore";
41
+ export {
42
+ downloadSelectedImageOriginal,
43
+ getDropPagePoint,
44
+ getFirstImageFile,
45
+ getFirstImageFileFromDataTransfer,
46
+ hasImageFileItem,
47
+ insertImageFileIntoEditor,
48
+ isEditableEventTarget,
49
+ registerImageToolbarActions,
50
+ replaceSelectedImageFromFile,
51
+ } from "./imageHostActions";
52
+ export type {
53
+ ImageImportSource,
54
+ InsertImageFileInput,
55
+ ReplaceSelectedImageFileInput,
56
+ } from "./imageHostActions";
57
+ export { createImageSvgAssetInliner } from "./svgImageAssetInliner";
58
+ export { hasLegacyImageDataUrlSnapshot } from "./snapshotGuards";
59
+ export { useImageImport } from "./useImageImport";
60
+ export type { ImageImportControls } from "./useImageImport";
@@ -0,0 +1,33 @@
1
+ export function hasLegacyImageDataUrlSnapshot(snapshot: unknown): boolean {
2
+ if (!snapshot || typeof snapshot !== "object") {
3
+ return false;
4
+ }
5
+ const records = (snapshot as { records?: unknown }).records;
6
+ if (!Array.isArray(records)) {
7
+ return false;
8
+ }
9
+
10
+ return records.some((record) => {
11
+ if (!record || typeof record !== "object") {
12
+ return false;
13
+ }
14
+ const candidate = record as {
15
+ typeName?: unknown;
16
+ type?: unknown;
17
+ props?: unknown;
18
+ };
19
+ if (candidate.typeName !== "asset" || candidate.type !== "image") {
20
+ return false;
21
+ }
22
+ const props =
23
+ candidate.props && typeof candidate.props === "object"
24
+ ? (candidate.props as Record<string, unknown>)
25
+ : {};
26
+ return (
27
+ typeof props.src === "string" ||
28
+ Object.values(props).some(
29
+ (value) => typeof value === "string" && value.startsWith("data:image"),
30
+ )
31
+ );
32
+ });
33
+ }