@twick/canvas 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,972 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const fabric = require("fabric");
4
+ const react = require("react");
5
+ const isBrowser = typeof window !== "undefined";
6
+ const isCanvasSupported = isBrowser && !!window.HTMLCanvasElement;
7
+ function assertBrowser() {
8
+ if (!isBrowser) {
9
+ throw new Error("This code can only run in a browser environment");
10
+ }
11
+ }
12
+ function assertCanvasSupport() {
13
+ if (!isCanvasSupported) {
14
+ throw new Error("Canvas is not supported in this environment");
15
+ }
16
+ }
17
+ function createCanvas({
18
+ videoSize,
19
+ canvasSize,
20
+ canvasRef,
21
+ backgroundColor = "#000000",
22
+ selectionBorderColor = "#2563eb",
23
+ selectionLineWidth = 2,
24
+ uniScaleTransform = true,
25
+ enableRetinaScaling = true,
26
+ touchZoomThreshold = 10
27
+ }) {
28
+ assertBrowser();
29
+ assertCanvasSupport();
30
+ const canvasMetadata = {
31
+ width: canvasSize.width,
32
+ height: canvasSize.height,
33
+ aspectRatio: canvasSize.width / canvasSize.height,
34
+ scaleX: canvasSize.width / videoSize.width,
35
+ scaleY: canvasSize.height / videoSize.height
36
+ };
37
+ const canvas = new fabric.Canvas(canvasRef, {
38
+ backgroundColor,
39
+ width: canvasSize.width,
40
+ height: canvasSize.height,
41
+ preserveObjectStacking: true,
42
+ enableRetinaScaling,
43
+ selectionBorderColor,
44
+ selectionLineWidth,
45
+ uniScaleTransform,
46
+ touchZoomThreshold,
47
+ renderOnAddRemove: false,
48
+ stateful: false,
49
+ selection: true,
50
+ skipTargetFind: false,
51
+ controlsAboveOverlay: true
52
+ });
53
+ if (canvasRef) {
54
+ canvas.setDimensions({
55
+ width: canvasMetadata.width,
56
+ height: canvasMetadata.height
57
+ });
58
+ canvas.renderAll();
59
+ }
60
+ return {
61
+ canvas,
62
+ canvasMetadata
63
+ };
64
+ }
65
+ function reorderElementsByZIndex(canvas) {
66
+ if (!canvas) return;
67
+ let backgroundColor = canvas.backgroundColor;
68
+ const objects = canvas.getObjects();
69
+ objects.sort((a, b) => (a.zIndex || 0) - (b.zIndex || 0));
70
+ canvas.clear();
71
+ canvas.backgroundColor = backgroundColor;
72
+ objects.forEach((obj) => canvas.add(obj));
73
+ canvas.renderAll();
74
+ }
75
+ function clearCanvas(canvas) {
76
+ if (!canvas) return;
77
+ canvas.clear();
78
+ canvas.renderAll();
79
+ }
80
+ function convertToCanvasPosition(x, y, canvasMetadata) {
81
+ return {
82
+ x: x * canvasMetadata.scaleX + canvasMetadata.width / 2,
83
+ y: y * canvasMetadata.scaleY + canvasMetadata.height / 2
84
+ };
85
+ }
86
+ function convertToVideoPosition(x, y, canvasMetadata, videoSize) {
87
+ return {
88
+ x: x / canvasMetadata.scaleX - videoSize.width / 2,
89
+ y: y / canvasMetadata.scaleY - videoSize.height / 2
90
+ };
91
+ }
92
+ function getCurrentFrameEffect(item, seekTime) {
93
+ var _a;
94
+ let currentFrameEffect;
95
+ for (let i = 0; i < ((_a = item == null ? void 0 : item.frameEffects) == null ? void 0 : _a.length); i++) {
96
+ if (item.frameEffects[i].s <= seekTime && item.frameEffects[i].e >= seekTime) {
97
+ currentFrameEffect = item.frameEffects[i];
98
+ break;
99
+ }
100
+ }
101
+ return currentFrameEffect;
102
+ }
103
+ const DEFAULT_TEXT_PROPS = {
104
+ family: "Poppins",
105
+ size: 48,
106
+ fill: "#FFFFFF",
107
+ stroke: "#000000",
108
+ lineWidth: 0
109
+ };
110
+ const DEFAULT_CAPTION_PROPS = {
111
+ family: "Poppins",
112
+ size: 48,
113
+ fill: "#FFFFFF",
114
+ fontWeight: 600,
115
+ stroke: "#000000",
116
+ lineWidth: 0.2,
117
+ shadowColor: "#000000",
118
+ shadowBlur: 2,
119
+ shadowOffset: [0, 0]
120
+ };
121
+ const CANVAS_OPERATIONS = {
122
+ ITEM_SELECTED: "ITEM_SELECTED",
123
+ ITEM_UPDATED: "ITEM_UPDATED",
124
+ ITEM_DELETED: "ITEM_DELETED",
125
+ ITEM_ADDED: "ITEM_ADDED",
126
+ ITEM_GROUPED: "ITEM_GROUPED",
127
+ ITEM_UNGROUPED: "ITEM_UNGROUPED"
128
+ };
129
+ const ELEMENT_TYPES = {
130
+ TEXT: "text",
131
+ CAPTION: "caption",
132
+ IMAGE: "image",
133
+ VIDEO: "video",
134
+ RECT: "rect"
135
+ };
136
+ const disabledControl = new fabric.Control({
137
+ x: 0,
138
+ y: -0.5,
139
+ offsetY: 0,
140
+ cursorStyle: "pointer",
141
+ actionHandler: () => {
142
+ return true;
143
+ },
144
+ actionName: "scale",
145
+ render: function(ctx, left, top) {
146
+ const size = 0;
147
+ ctx.save();
148
+ ctx.translate(left, top);
149
+ ctx.fillStyle = "#red";
150
+ ctx.fillRect(-0 / 2, -0 / 2, size, size);
151
+ ctx.restore();
152
+ }
153
+ });
154
+ const rotateControl = new fabric.Control({
155
+ x: 0,
156
+ y: -0.5,
157
+ offsetY: -25,
158
+ cursorStyle: "crosshair",
159
+ actionHandler: fabric.controlsUtils.rotationWithSnapping,
160
+ actionName: "rotate",
161
+ withConnection: true
162
+ });
163
+ async function getThumbnail(videoUrl, seekTime = 0.1, playbackRate = 1) {
164
+ return new Promise((resolve, reject) => {
165
+ const video = document.createElement("video");
166
+ video.crossOrigin = "anonymous";
167
+ video.muted = true;
168
+ video.playsInline = true;
169
+ video.autoplay = false;
170
+ video.preload = "auto";
171
+ video.playbackRate = playbackRate;
172
+ let timeoutId;
173
+ const cleanup = () => {
174
+ if (video.parentNode) video.remove();
175
+ if (timeoutId) clearTimeout(timeoutId);
176
+ };
177
+ const handleError = () => {
178
+ var _a;
179
+ cleanup();
180
+ reject(new Error(`Failed to load video: ${((_a = video.error) == null ? void 0 : _a.message) || "Unknown error"}`));
181
+ };
182
+ const handleSeeked = () => {
183
+ try {
184
+ video.pause();
185
+ const canvas = document.createElement("canvas");
186
+ const width = video.videoWidth || 640;
187
+ const height = video.videoHeight || 360;
188
+ canvas.width = width;
189
+ canvas.height = height;
190
+ const ctx = canvas.getContext("2d");
191
+ if (!ctx) {
192
+ cleanup();
193
+ reject(new Error("Failed to get canvas context"));
194
+ return;
195
+ }
196
+ ctx.drawImage(video, 0, 0, width, height);
197
+ try {
198
+ const dataUrl = canvas.toDataURL("image/jpeg", 0.8);
199
+ cleanup();
200
+ resolve(dataUrl);
201
+ } catch {
202
+ canvas.toBlob((blob) => {
203
+ if (!blob) {
204
+ cleanup();
205
+ reject(new Error("Failed to create Blob"));
206
+ return;
207
+ }
208
+ const blobUrl = URL.createObjectURL(blob);
209
+ cleanup();
210
+ resolve(blobUrl);
211
+ }, "image/jpeg", 0.8);
212
+ }
213
+ } catch (err) {
214
+ cleanup();
215
+ reject(new Error(`Error creating thumbnail: ${err}`));
216
+ }
217
+ };
218
+ video.addEventListener("error", handleError, { once: true });
219
+ video.addEventListener("seeked", handleSeeked, { once: true });
220
+ video.addEventListener("loadedmetadata", () => {
221
+ const playPromise = video.play();
222
+ if (playPromise !== void 0) {
223
+ playPromise.then(() => {
224
+ video.currentTime = seekTime;
225
+ }).catch(() => {
226
+ video.currentTime = seekTime;
227
+ });
228
+ } else {
229
+ video.currentTime = seekTime;
230
+ }
231
+ }, { once: true });
232
+ timeoutId = window.setTimeout(() => {
233
+ cleanup();
234
+ reject(new Error("Video loading timed out"));
235
+ }, 5e3);
236
+ video.src = videoUrl;
237
+ document.body.appendChild(video);
238
+ });
239
+ }
240
+ function getObjectFitSize(objectFit, elementSize, containerSize) {
241
+ const elementAspectRatio = elementSize.width / elementSize.height;
242
+ const containerAspectRatio = containerSize.width / containerSize.height;
243
+ switch (objectFit) {
244
+ case "contain":
245
+ if (elementAspectRatio > containerAspectRatio) {
246
+ return {
247
+ width: containerSize.width,
248
+ height: containerSize.width / elementAspectRatio
249
+ };
250
+ } else {
251
+ return {
252
+ width: containerSize.height * elementAspectRatio,
253
+ height: containerSize.height
254
+ };
255
+ }
256
+ case "cover":
257
+ if (elementAspectRatio > containerAspectRatio) {
258
+ return {
259
+ width: containerSize.height * elementAspectRatio,
260
+ height: containerSize.height
261
+ };
262
+ } else {
263
+ return {
264
+ width: containerSize.width,
265
+ height: containerSize.width / elementAspectRatio
266
+ };
267
+ }
268
+ case "fill":
269
+ return {
270
+ width: containerSize.width,
271
+ height: containerSize.height
272
+ };
273
+ default:
274
+ return {
275
+ width: elementSize.width,
276
+ height: elementSize.height
277
+ };
278
+ }
279
+ }
280
+ const addTextElement = ({
281
+ element,
282
+ index,
283
+ canvas,
284
+ canvasMetadata
285
+ }) => {
286
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
287
+ const { x, y } = convertToCanvasPosition(
288
+ ((_a = element.props) == null ? void 0 : _a.x) || 0,
289
+ ((_b = element.props) == null ? void 0 : _b.y) || 0,
290
+ canvasMetadata
291
+ );
292
+ const text = new fabric.FabricText(((_c = element.props) == null ? void 0 : _c.text) || "", {
293
+ left: x,
294
+ top: y,
295
+ originX: "center",
296
+ originY: "center",
297
+ angle: ((_d = element.props) == null ? void 0 : _d.rotation) || 0,
298
+ fontSize: Math.round(
299
+ (((_e = element.props) == null ? void 0 : _e.fontSize) || DEFAULT_TEXT_PROPS.size) * canvasMetadata.scaleX
300
+ ),
301
+ fontFamily: ((_f = element.props) == null ? void 0 : _f.fontFamily) || DEFAULT_TEXT_PROPS.family,
302
+ fontStyle: ((_g = element.props) == null ? void 0 : _g.fontStyle) || "normal",
303
+ fontWeight: ((_h = element.props) == null ? void 0 : _h.fontWeight) || "normal",
304
+ fill: ((_i = element.props) == null ? void 0 : _i.fill) || DEFAULT_TEXT_PROPS.fill,
305
+ stroke: ((_j = element.props) == null ? void 0 : _j.stroke) || DEFAULT_TEXT_PROPS.stroke,
306
+ strokeWidth: ((_k = element.props) == null ? void 0 : _k.lineWidth) || DEFAULT_TEXT_PROPS.lineWidth,
307
+ shadow: ((_l = element.props) == null ? void 0 : _l.shadowColor) ? new fabric.Shadow({
308
+ offsetX: ((_n = (_m = element.props) == null ? void 0 : _m.shadowOffset) == null ? void 0 : _n.length) && ((_p = (_o = element.props) == null ? void 0 : _o.shadowOffset) == null ? void 0 : _p.length) > 1 ? element.props.shadowOffset[0] / 2 : 1,
309
+ offsetY: ((_r = (_q = element.props) == null ? void 0 : _q.shadowOffset) == null ? void 0 : _r.length) && ((_s = element.props) == null ? void 0 : _s.shadowOffset.length) > 1 ? element.props.shadowOffset[1] / 2 : 1,
310
+ blur: (((_t = element.props) == null ? void 0 : _t.shadowBlur) || 2) / 2,
311
+ color: (_u = element.props) == null ? void 0 : _u.shadowColor
312
+ }) : void 0
313
+ });
314
+ text.set("id", element.id);
315
+ text.set("zIndex", index);
316
+ text.controls.mt = disabledControl;
317
+ text.controls.mb = disabledControl;
318
+ text.controls.ml = disabledControl;
319
+ text.controls.mr = disabledControl;
320
+ text.controls.bl = disabledControl;
321
+ text.controls.br = disabledControl;
322
+ text.controls.tl = disabledControl;
323
+ text.controls.tr = disabledControl;
324
+ text.controls.mtr = rotateControl;
325
+ canvas.add(text);
326
+ return text;
327
+ };
328
+ const setImageProps = ({
329
+ img,
330
+ element,
331
+ index,
332
+ canvasMetadata
333
+ }) => {
334
+ var _a, _b, _c, _d;
335
+ const width = (((_a = element.props) == null ? void 0 : _a.width) || 0) * canvasMetadata.scaleX || canvasMetadata.width;
336
+ const height = (((_b = element.props) == null ? void 0 : _b.height) || 0) * canvasMetadata.scaleY || canvasMetadata.height;
337
+ const { x, y } = convertToCanvasPosition(
338
+ ((_c = element.props) == null ? void 0 : _c.x) || 0,
339
+ ((_d = element.props) == null ? void 0 : _d.y) || 0,
340
+ canvasMetadata
341
+ );
342
+ console.log(width, height, x, y);
343
+ img.set("id", element.id);
344
+ img.set("zIndex", index);
345
+ img.set("width", width);
346
+ img.set("height", height);
347
+ img.set("left", x);
348
+ img.set("top", y);
349
+ img.set("selectable", true);
350
+ img.set("hasControls", true);
351
+ img.set("touchAction", "all");
352
+ };
353
+ const addCaptionElement = ({
354
+ element,
355
+ index,
356
+ canvas,
357
+ captionProps,
358
+ canvasMetadata
359
+ }) => {
360
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
361
+ const { x, y } = convertToCanvasPosition(
362
+ ((_b = (_a = element.props) == null ? void 0 : _a.pos) == null ? void 0 : _b.x) || ((_c = captionProps == null ? void 0 : captionProps.pos) == null ? void 0 : _c.x) || 0,
363
+ ((_e = (_d = element.props) == null ? void 0 : _d.pos) == null ? void 0 : _e.y) || ((_f = captionProps == null ? void 0 : captionProps.pos) == null ? void 0 : _f.y) || 0,
364
+ canvasMetadata
365
+ );
366
+ const caption = new fabric.FabricText(((_g = element.props) == null ? void 0 : _g.text) || "", {
367
+ left: x,
368
+ top: y,
369
+ originX: "center",
370
+ originY: "center",
371
+ angle: ((_h = element.props) == null ? void 0 : _h.rotation) || 0,
372
+ fontSize: Math.round(
373
+ (((_j = (_i = element.props) == null ? void 0 : _i.font) == null ? void 0 : _j.size) || ((_k = captionProps.font) == null ? void 0 : _k.size) || DEFAULT_CAPTION_PROPS.size) * canvasMetadata.scaleX
374
+ ),
375
+ fontFamily: ((_m = (_l = element.props) == null ? void 0 : _l.font) == null ? void 0 : _m.family) || ((_n = captionProps.font) == null ? void 0 : _n.family) || DEFAULT_CAPTION_PROPS.family,
376
+ fill: ((_o = element.props) == null ? void 0 : _o.fill) || ((_p = captionProps.color) == null ? void 0 : _p.text) || DEFAULT_CAPTION_PROPS.fill,
377
+ fontWeight: DEFAULT_CAPTION_PROPS.fontWeight,
378
+ stroke: ((_q = element.props) == null ? void 0 : _q.stroke) || DEFAULT_CAPTION_PROPS.stroke,
379
+ shadow: new fabric.Shadow({
380
+ offsetX: ((_s = (_r = element.props) == null ? void 0 : _r.shadowOffset) == null ? void 0 : _s[0]) || ((_t = DEFAULT_CAPTION_PROPS.shadowOffset) == null ? void 0 : _t[0]),
381
+ offsetY: ((_v = (_u = element.props) == null ? void 0 : _u.shadowOffset) == null ? void 0 : _v[1]) || ((_w = DEFAULT_CAPTION_PROPS.shadowOffset) == null ? void 0 : _w[1]),
382
+ blur: ((_x = element.props) == null ? void 0 : _x.shadowBlur) || DEFAULT_CAPTION_PROPS.shadowBlur,
383
+ color: ((_y = element.props) == null ? void 0 : _y.shadowColor) || DEFAULT_CAPTION_PROPS.shadowColor
384
+ }),
385
+ strokeWidth: ((_z = element.props) == null ? void 0 : _z.lineWidth) || DEFAULT_CAPTION_PROPS.lineWidth
386
+ });
387
+ caption.set("id", element.id);
388
+ caption.set("zIndex", index);
389
+ caption.controls.mt = disabledControl;
390
+ caption.controls.mb = disabledControl;
391
+ caption.controls.ml = disabledControl;
392
+ caption.controls.mr = disabledControl;
393
+ caption.controls.bl = disabledControl;
394
+ caption.controls.br = disabledControl;
395
+ caption.controls.tl = disabledControl;
396
+ caption.controls.tr = disabledControl;
397
+ caption.controls.mtr = disabledControl;
398
+ canvas.add(caption);
399
+ return caption;
400
+ };
401
+ const addVideoElement = async ({
402
+ element,
403
+ index,
404
+ canvas,
405
+ snapTime,
406
+ canvasMetadata,
407
+ currentFrameEffect
408
+ }) => {
409
+ var _a;
410
+ try {
411
+ const thumbnailUrl = await getThumbnail(
412
+ ((_a = element == null ? void 0 : element.props) == null ? void 0 : _a.src) || "",
413
+ snapTime
414
+ );
415
+ if (!thumbnailUrl) {
416
+ console.error("Failed to get thumbnail");
417
+ return;
418
+ }
419
+ return addImageElement({
420
+ imageUrl: thumbnailUrl,
421
+ element,
422
+ index,
423
+ canvas,
424
+ canvasMetadata,
425
+ currentFrameEffect
426
+ });
427
+ } catch (error) {
428
+ console.error("Error loading image:", error);
429
+ }
430
+ };
431
+ const addImageElement = async ({
432
+ imageUrl,
433
+ element,
434
+ index,
435
+ canvas,
436
+ canvasMetadata,
437
+ currentFrameEffect
438
+ }) => {
439
+ try {
440
+ const img = await fabric.FabricImage.fromURL(imageUrl || element.props.src || "");
441
+ img.set({
442
+ originX: "center",
443
+ originY: "center",
444
+ lockMovementX: false,
445
+ lockMovementY: false,
446
+ lockUniScaling: true,
447
+ hasControls: false,
448
+ selectable: false
449
+ });
450
+ if (element.frame) {
451
+ return addMediaGroup({
452
+ element,
453
+ img,
454
+ index,
455
+ canvas,
456
+ canvasMetadata,
457
+ currentFrameEffect
458
+ });
459
+ } else {
460
+ setImageProps({ img, element, index, canvasMetadata });
461
+ canvas.add(img);
462
+ return img;
463
+ }
464
+ } catch (error) {
465
+ console.error("Error loading image:", error);
466
+ }
467
+ };
468
+ const addMediaGroup = ({
469
+ element,
470
+ img,
471
+ index,
472
+ canvas,
473
+ canvasMetadata,
474
+ currentFrameEffect
475
+ }) => {
476
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
477
+ let frameSize;
478
+ let angle;
479
+ let framePosition;
480
+ let frameRadius = 0;
481
+ if (currentFrameEffect) {
482
+ frameSize = {
483
+ width: (((_a = currentFrameEffect.props.frameSize) == null ? void 0 : _a[0]) || 0) * canvasMetadata.scaleX || canvasMetadata.width,
484
+ height: (((_b = currentFrameEffect.props.frameSize) == null ? void 0 : _b[1]) || 0) * canvasMetadata.scaleY || canvasMetadata.height
485
+ };
486
+ angle = currentFrameEffect.props.rotation || 0;
487
+ framePosition = currentFrameEffect.props.framePosition;
488
+ if (currentFrameEffect.props.shape === "circle") {
489
+ frameRadius = frameSize.width / 2;
490
+ } else {
491
+ frameRadius = ((_c = currentFrameEffect == null ? void 0 : currentFrameEffect.props) == null ? void 0 : _c.radius) || 0;
492
+ }
493
+ } else {
494
+ frameRadius = ((_d = element == null ? void 0 : element.frame) == null ? void 0 : _d.radius) || 0;
495
+ frameSize = {
496
+ width: (((_f = (_e = element == null ? void 0 : element.frame) == null ? void 0 : _e.size) == null ? void 0 : _f[0]) || 0) * canvasMetadata.scaleX || canvasMetadata.width,
497
+ height: (((_h = (_g = element == null ? void 0 : element.frame) == null ? void 0 : _g.size) == null ? void 0 : _h[1]) || 0) * canvasMetadata.scaleY || canvasMetadata.height
498
+ };
499
+ angle = ((_i = element == null ? void 0 : element.frame) == null ? void 0 : _i.rotation) || 0;
500
+ framePosition = {
501
+ x: ((_j = element == null ? void 0 : element.frame) == null ? void 0 : _j.x) || 0,
502
+ y: ((_k = element == null ? void 0 : element.frame) == null ? void 0 : _k.y) || 0
503
+ };
504
+ }
505
+ const newSize = getObjectFitSize(
506
+ element.objectFit,
507
+ { width: img.width, height: img.height },
508
+ frameSize
509
+ );
510
+ const frameRect = new fabric.Rect({
511
+ originX: "center",
512
+ originY: "center",
513
+ lockMovementX: false,
514
+ lockMovementY: false,
515
+ lockUniScaling: true,
516
+ hasControls: false,
517
+ selectable: false,
518
+ width: frameSize.width,
519
+ height: frameSize.height,
520
+ stroke: ((_l = element == null ? void 0 : element.frame) == null ? void 0 : _l.stroke) || "#ffffff",
521
+ strokeWidth: ((_m = element == null ? void 0 : element.frame) == null ? void 0 : _m.lineWidth) || 0,
522
+ hasRotatingPoint: true,
523
+ rx: frameRadius || 0,
524
+ ry: frameRadius || 0
525
+ });
526
+ img.set({
527
+ lockUniScaling: true,
528
+ originX: "center",
529
+ originY: "center",
530
+ width: newSize.width,
531
+ height: newSize.height
532
+ });
533
+ const { x, y } = convertToCanvasPosition(
534
+ (framePosition == null ? void 0 : framePosition.x) || 0,
535
+ (framePosition == null ? void 0 : framePosition.y) || 0,
536
+ canvasMetadata
537
+ );
538
+ const groupProps = {
539
+ left: x,
540
+ top: y,
541
+ width: frameSize.width,
542
+ height: frameSize.height,
543
+ angle
544
+ };
545
+ const group = new fabric.Group([frameRect, img], {
546
+ ...groupProps,
547
+ originX: "center",
548
+ originY: "center",
549
+ angle: groupProps.angle,
550
+ selectable: true,
551
+ hasControls: true,
552
+ hasBorders: true,
553
+ clipPath: frameRect
554
+ });
555
+ group.controls.mt = disabledControl;
556
+ group.controls.mb = disabledControl;
557
+ group.controls.ml = disabledControl;
558
+ group.controls.mr = disabledControl;
559
+ group.controls.mtr = rotateControl;
560
+ group.set("id", element.id);
561
+ group.set("zIndex", index);
562
+ canvas.add(group);
563
+ return group;
564
+ };
565
+ const addRectElement = ({
566
+ element,
567
+ index,
568
+ canvas,
569
+ canvasMetadata
570
+ }) => {
571
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
572
+ const { x, y } = convertToCanvasPosition(
573
+ ((_a = element.props) == null ? void 0 : _a.x) || 0,
574
+ ((_b = element.props) == null ? void 0 : _b.y) || 0,
575
+ canvasMetadata
576
+ );
577
+ const rect = new fabric.Rect({
578
+ left: x,
579
+ // X-coordinate on the canvas
580
+ top: y,
581
+ // Y-coordinate on the canvas
582
+ originX: "center",
583
+ // Center the rectangle based on its position
584
+ originY: "center",
585
+ // Center the rectangle based on its position
586
+ angle: ((_c = element.props) == null ? void 0 : _c.rotation) || 0,
587
+ // Rotation angle
588
+ rx: (((_d = element.props) == null ? void 0 : _d.radius) || 0) * canvasMetadata.scaleX,
589
+ // Horizontal radius for rounded corners
590
+ ry: (((_e = element.props) == null ? void 0 : _e.radius) || 0) * canvasMetadata.scaleY,
591
+ // Vertical radius for rounded corners
592
+ stroke: ((_f = element.props) == null ? void 0 : _f.stroke) || "#000000",
593
+ // Stroke color
594
+ strokeWidth: (((_g = element.props) == null ? void 0 : _g.lineWidth) || 0) * canvasMetadata.scaleX,
595
+ // Scaled stroke width
596
+ fill: ((_h = element.props) == null ? void 0 : _h.fill) || "#000000",
597
+ // Fill color
598
+ opacity: ((_i = element.props) == null ? void 0 : _i.opacity) || 1,
599
+ // Opacity level
600
+ width: (((_j = element.props) == null ? void 0 : _j.width) || 0) * canvasMetadata.scaleX,
601
+ // Scaled width
602
+ height: (((_k = element.props) == null ? void 0 : _k.height) || 0) * canvasMetadata.scaleY
603
+ // Scaled height
604
+ });
605
+ rect.set("id", element.id);
606
+ rect.set("zIndex", index);
607
+ rect.controls.mtr = rotateControl;
608
+ canvas.add(rect);
609
+ return rect;
610
+ };
611
+ const addBackgroundColor = ({
612
+ element,
613
+ index,
614
+ canvas,
615
+ canvasMetadata
616
+ }) => {
617
+ const bgRect = new fabric.Rect({
618
+ width: canvasMetadata.width,
619
+ height: canvasMetadata.height,
620
+ left: canvasMetadata.width / 2,
621
+ top: canvasMetadata.height / 2,
622
+ fill: element.backgoundColor ?? "#000000",
623
+ originX: "center",
624
+ originY: "center",
625
+ hasControls: false,
626
+ hasBorders: false,
627
+ selectable: false
628
+ });
629
+ bgRect.controls.mt = disabledControl;
630
+ bgRect.controls.mb = disabledControl;
631
+ bgRect.controls.ml = disabledControl;
632
+ bgRect.controls.mr = disabledControl;
633
+ bgRect.controls.bl = disabledControl;
634
+ bgRect.controls.br = disabledControl;
635
+ bgRect.controls.tl = disabledControl;
636
+ bgRect.controls.tr = disabledControl;
637
+ bgRect.controls.mtr = disabledControl;
638
+ bgRect.set("zIndex", index - 0.5);
639
+ canvas.add(bgRect);
640
+ return bgRect;
641
+ };
642
+ const useTwickCanvas = ({
643
+ onCanvasReady,
644
+ onCanvasOperation
645
+ }) => {
646
+ const [twickCanvas, setTwickCanvas] = react.useState(null);
647
+ const elementMap = react.useRef({});
648
+ const elementFrameMap = react.useRef({});
649
+ const twickCanvasRef = react.useRef(null);
650
+ const videoSizeRef = react.useRef({ width: 1, height: 1 });
651
+ const canvasMetadataRef = react.useRef({
652
+ width: 0,
653
+ height: 0,
654
+ aspectRatio: 0,
655
+ scaleX: 1,
656
+ scaleY: 1
657
+ });
658
+ const onVideoSizeChange = (videoSize) => {
659
+ if (videoSize) {
660
+ videoSizeRef.current = videoSize;
661
+ canvasMetadataRef.current.scaleX = canvasMetadataRef.current.width / videoSize.width;
662
+ canvasMetadataRef.current.scaleY = canvasMetadataRef.current.height / videoSize.height;
663
+ }
664
+ };
665
+ const buildCanvas = ({
666
+ videoSize,
667
+ canvasSize,
668
+ canvasRef,
669
+ backgroundColor = "#000000",
670
+ selectionBorderColor = "#2563eb",
671
+ selectionLineWidth = 2,
672
+ uniScaleTransform = true,
673
+ enableRetinaScaling = true,
674
+ touchZoomThreshold = 10
675
+ }) => {
676
+ if (!canvasRef) return;
677
+ if (twickCanvasRef.current) {
678
+ console.log("Destroying twickCanvas");
679
+ twickCanvasRef.current.off("mouse:up", handleMouseUp);
680
+ twickCanvasRef.current.dispose();
681
+ }
682
+ const { canvas, canvasMetadata } = createCanvas({
683
+ videoSize,
684
+ canvasSize,
685
+ canvasRef,
686
+ backgroundColor,
687
+ selectionBorderColor,
688
+ selectionLineWidth,
689
+ uniScaleTransform,
690
+ enableRetinaScaling,
691
+ touchZoomThreshold
692
+ });
693
+ canvasMetadataRef.current = canvasMetadata;
694
+ videoSizeRef.current = videoSize;
695
+ canvas == null ? void 0 : canvas.on("mouse:up", handleMouseUp);
696
+ setTwickCanvas(canvas);
697
+ twickCanvasRef.current = canvas;
698
+ if (onCanvasReady) {
699
+ onCanvasReady(canvas);
700
+ }
701
+ };
702
+ const handleMouseUp = (event) => {
703
+ var _a, _b;
704
+ if (event.target) {
705
+ const object = event.target;
706
+ const elementId = object.get("id");
707
+ if (((_a = event.transform) == null ? void 0 : _a.action) === "drag") {
708
+ const original = event.transform.original;
709
+ if (object.left === original.left && object.top === original.top) {
710
+ onCanvasOperation == null ? void 0 : onCanvasOperation(
711
+ CANVAS_OPERATIONS.ITEM_SELECTED,
712
+ elementMap.current[elementId]
713
+ );
714
+ return;
715
+ }
716
+ }
717
+ switch ((_b = event.transform) == null ? void 0 : _b.action) {
718
+ case "drag":
719
+ case "scale":
720
+ case "scaleX":
721
+ case "scaleY":
722
+ case "rotate":
723
+ const { x, y } = convertToVideoPosition(
724
+ object.left,
725
+ object.top,
726
+ canvasMetadataRef.current,
727
+ videoSizeRef.current
728
+ );
729
+ if (elementMap.current[elementId].type === "caption") {
730
+ elementMap.current[elementId] = {
731
+ ...elementMap.current[elementId],
732
+ props: {
733
+ ...elementMap.current[elementId].props,
734
+ pos: {
735
+ x,
736
+ y
737
+ }
738
+ }
739
+ };
740
+ onCanvasOperation == null ? void 0 : onCanvasOperation(
741
+ CANVAS_OPERATIONS.ITEM_UPDATED,
742
+ elementMap.current[elementId]
743
+ );
744
+ } else {
745
+ if ((object == null ? void 0 : object.type) === "group") {
746
+ const currentFrameEffect = elementFrameMap.current[elementId];
747
+ let updatedFrameSize;
748
+ if (currentFrameEffect) {
749
+ updatedFrameSize = [
750
+ currentFrameEffect.props.frameSize[0] * object.scaleX,
751
+ currentFrameEffect.props.frameSize[1] * object.scaleY
752
+ ];
753
+ } else {
754
+ updatedFrameSize = [
755
+ elementMap.current[elementId].frame.size[0] * object.scaleX,
756
+ elementMap.current[elementId].frame.size[1] * object.scaleY
757
+ ];
758
+ }
759
+ if (currentFrameEffect) {
760
+ elementMap.current[elementId] = {
761
+ ...elementMap.current[elementId],
762
+ frameEffects: (elementMap.current[elementId].frameEffects || []).map(
763
+ (frameEffect) => frameEffect.id === (currentFrameEffect == null ? void 0 : currentFrameEffect.id) ? {
764
+ ...frameEffect,
765
+ props: {
766
+ ...frameEffect.props,
767
+ framePosition: {
768
+ x,
769
+ y
770
+ },
771
+ frameSize: updatedFrameSize
772
+ }
773
+ } : frameEffect
774
+ )
775
+ };
776
+ elementFrameMap.current[elementId] = {
777
+ ...elementFrameMap.current[elementId],
778
+ framePosition: {
779
+ x,
780
+ y
781
+ },
782
+ frameSize: updatedFrameSize
783
+ };
784
+ } else {
785
+ elementMap.current[elementId] = {
786
+ ...elementMap.current[elementId],
787
+ frame: {
788
+ ...elementMap.current[elementId].frame,
789
+ rotation: object.angle,
790
+ size: updatedFrameSize,
791
+ x,
792
+ y
793
+ }
794
+ };
795
+ }
796
+ } else {
797
+ if ((object == null ? void 0 : object.type) === "text") {
798
+ elementMap.current[elementId] = {
799
+ ...elementMap.current[elementId],
800
+ props: {
801
+ ...elementMap.current[elementId].props,
802
+ rotation: object.angle,
803
+ x,
804
+ y
805
+ }
806
+ };
807
+ } else {
808
+ elementMap.current[elementId] = {
809
+ ...elementMap.current[elementId],
810
+ props: {
811
+ ...elementMap.current[elementId].props,
812
+ rotation: object.angle,
813
+ width: elementMap.current[elementId].props.width * object.scaleX,
814
+ height: elementMap.current[elementId].props.height * object.scaleY,
815
+ x,
816
+ y
817
+ }
818
+ };
819
+ }
820
+ }
821
+ onCanvasOperation == null ? void 0 : onCanvasOperation(
822
+ CANVAS_OPERATIONS.ITEM_UPDATED,
823
+ elementMap.current[elementId]
824
+ );
825
+ }
826
+ break;
827
+ }
828
+ }
829
+ };
830
+ const setCanvasElements = async ({
831
+ elements,
832
+ seekTime = 0,
833
+ captionProps,
834
+ cleanAndAdd = false
835
+ }) => {
836
+ if (!twickCanvas) {
837
+ console.warn("Canvas not initialized");
838
+ return;
839
+ }
840
+ try {
841
+ if (cleanAndAdd) {
842
+ clearCanvas(twickCanvas);
843
+ }
844
+ await Promise.all(
845
+ elements.map(async (element, index) => {
846
+ try {
847
+ if (!element) {
848
+ console.warn("Element not found");
849
+ return;
850
+ }
851
+ await addElementToCanvas({
852
+ element,
853
+ index,
854
+ reorder: false,
855
+ seekTime,
856
+ captionProps
857
+ });
858
+ } catch (error) {
859
+ console.error(`Error adding element ${element.id}:`, error);
860
+ }
861
+ })
862
+ );
863
+ reorderElementsByZIndex(twickCanvas);
864
+ } catch (error) {
865
+ console.error("Error in setCanvasElements:", error);
866
+ }
867
+ };
868
+ const addElementToCanvas = async ({
869
+ element,
870
+ index,
871
+ reorder = true,
872
+ seekTime,
873
+ captionProps
874
+ }) => {
875
+ var _a, _b;
876
+ if (!twickCanvas) {
877
+ console.warn("Canvas not initialized");
878
+ return;
879
+ }
880
+ switch (element.type) {
881
+ case ELEMENT_TYPES.VIDEO:
882
+ const currentFrameEffect = getCurrentFrameEffect(element, seekTime || 0);
883
+ elementFrameMap.current[element.id] = currentFrameEffect;
884
+ const snapTime = ((seekTime || 0) - ((element == null ? void 0 : element.s) || 0)) * (((_a = element == null ? void 0 : element.props) == null ? void 0 : _a.playbackRate) || 1) + (((_b = element == null ? void 0 : element.props) == null ? void 0 : _b.time) || 0);
885
+ await addVideoElement({
886
+ element,
887
+ index,
888
+ canvas: twickCanvas,
889
+ canvasMetadata: canvasMetadataRef.current,
890
+ currentFrameEffect,
891
+ snapTime
892
+ });
893
+ if (element.timelineType === "scene") {
894
+ await addBackgroundColor({
895
+ element,
896
+ index,
897
+ canvas: twickCanvas,
898
+ canvasMetadata: canvasMetadataRef.current
899
+ });
900
+ }
901
+ break;
902
+ case ELEMENT_TYPES.IMAGE:
903
+ await addImageElement({
904
+ element,
905
+ index,
906
+ canvas: twickCanvas,
907
+ canvasMetadata: canvasMetadataRef.current
908
+ });
909
+ if (element.timelineType === "scene") {
910
+ await addBackgroundColor({
911
+ element,
912
+ index,
913
+ canvas: twickCanvas,
914
+ canvasMetadata: canvasMetadataRef.current
915
+ });
916
+ }
917
+ break;
918
+ case ELEMENT_TYPES.RECT:
919
+ await addRectElement({
920
+ element,
921
+ index,
922
+ canvas: twickCanvas,
923
+ canvasMetadata: canvasMetadataRef.current
924
+ });
925
+ break;
926
+ case ELEMENT_TYPES.TEXT:
927
+ await addTextElement({
928
+ element,
929
+ index,
930
+ canvas: twickCanvas,
931
+ canvasMetadata: canvasMetadataRef.current
932
+ });
933
+ break;
934
+ case ELEMENT_TYPES.CAPTION:
935
+ await addCaptionElement({
936
+ element,
937
+ index,
938
+ canvas: twickCanvas,
939
+ captionProps,
940
+ canvasMetadata: canvasMetadataRef.current
941
+ });
942
+ break;
943
+ }
944
+ elementMap.current[element.id] = element;
945
+ if (reorder) {
946
+ reorderElementsByZIndex(twickCanvas);
947
+ }
948
+ };
949
+ return {
950
+ twickCanvas,
951
+ buildCanvas,
952
+ onVideoSizeChange,
953
+ addElementToCanvas,
954
+ setCanvasElements
955
+ };
956
+ };
957
+ exports.CANVAS_OPERATIONS = CANVAS_OPERATIONS;
958
+ exports.addBackgroundColor = addBackgroundColor;
959
+ exports.addCaptionElement = addCaptionElement;
960
+ exports.addImageElement = addImageElement;
961
+ exports.addRectElement = addRectElement;
962
+ exports.addTextElement = addTextElement;
963
+ exports.addVideoElement = addVideoElement;
964
+ exports.convertToCanvasPosition = convertToCanvasPosition;
965
+ exports.convertToVideoPosition = convertToVideoPosition;
966
+ exports.createCanvas = createCanvas;
967
+ exports.disabledControl = disabledControl;
968
+ exports.getCurrentFrameEffect = getCurrentFrameEffect;
969
+ exports.reorderElementsByZIndex = reorderElementsByZIndex;
970
+ exports.rotateControl = rotateControl;
971
+ exports.useTwickCanvas = useTwickCanvas;
972
+ //# sourceMappingURL=index.js.map