@twick/canvas 0.14.2 → 0.14.4

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.mjs CHANGED
@@ -1,36 +1,61 @@
1
1
  import { Canvas, Control, controlsUtils, FabricImage, Rect, FabricText, Shadow, Group } from "fabric";
2
2
  import { useState, useRef } from "react";
3
3
  const DEFAULT_TEXT_PROPS = {
4
+ /** Font family for text elements */
4
5
  family: "Poppins",
6
+ /** Font size in pixels */
5
7
  size: 48,
8
+ /** Text fill color */
6
9
  fill: "#FFFFFF",
10
+ /** Text stroke color */
7
11
  stroke: "#000000",
12
+ /** Stroke line width */
8
13
  lineWidth: 0
9
14
  };
10
15
  const DEFAULT_CAPTION_PROPS = {
16
+ /** Font family for caption elements */
11
17
  family: "Poppins",
18
+ /** Font size in pixels */
12
19
  size: 48,
20
+ /** Text fill color */
13
21
  fill: "#FFFFFF",
22
+ /** Font weight */
14
23
  fontWeight: 600,
24
+ /** Text stroke color */
15
25
  stroke: "#000000",
26
+ /** Stroke line width */
16
27
  lineWidth: 0.2,
28
+ /** Shadow color */
17
29
  shadowColor: "#000000",
30
+ /** Shadow blur radius */
18
31
  shadowBlur: 2,
32
+ /** Shadow offset [x, y] */
19
33
  shadowOffset: [0, 0]
20
34
  };
21
35
  const CANVAS_OPERATIONS = {
36
+ /** An item has been selected on the canvas */
22
37
  ITEM_SELECTED: "ITEM_SELECTED",
38
+ /** An item has been updated/modified on the canvas */
23
39
  ITEM_UPDATED: "ITEM_UPDATED",
40
+ /** An item has been deleted from the canvas */
24
41
  ITEM_DELETED: "ITEM_DELETED",
42
+ /** A new item has been added to the canvas */
25
43
  ITEM_ADDED: "ITEM_ADDED",
44
+ /** Items have been grouped together */
26
45
  ITEM_GROUPED: "ITEM_GROUPED",
46
+ /** Items have been ungrouped */
27
47
  ITEM_UNGROUPED: "ITEM_UNGROUPED"
28
48
  };
29
49
  const ELEMENT_TYPES = {
50
+ /** Text element type */
30
51
  TEXT: "text",
52
+ /** Caption element type */
31
53
  CAPTION: "caption",
54
+ /** Image element type */
32
55
  IMAGE: "image",
56
+ /** Video element type */
33
57
  VIDEO: "video",
58
+ /** Rectangle element type */
34
59
  RECT: "rect"
35
60
  };
36
61
  const isBrowser = typeof window !== "undefined";
@@ -104,10 +129,19 @@ const reorderElementsByZIndex = (canvas) => {
104
129
  objects.forEach((obj) => canvas.add(obj));
105
130
  canvas.renderAll();
106
131
  };
132
+ const getCanvasContext = (canvas) => {
133
+ var _a, _b, _c, _d;
134
+ if (!canvas || !((_b = (_a = canvas.elements) == null ? void 0 : _a.lower) == null ? void 0 : _b.ctx)) return;
135
+ return (_d = (_c = canvas.elements) == null ? void 0 : _c.lower) == null ? void 0 : _d.ctx;
136
+ };
107
137
  const clearCanvas = (canvas) => {
108
- if (!canvas) return;
109
- canvas.clear();
110
- canvas.renderAll();
138
+ try {
139
+ if (!canvas || !getCanvasContext(canvas)) return;
140
+ canvas.clear();
141
+ canvas.renderAll();
142
+ } catch (error) {
143
+ console.warn("Error clearing canvas:", error);
144
+ }
111
145
  };
112
146
  const convertToCanvasPosition = (x, y, canvasMetadata) => {
113
147
  return {
@@ -133,14 +167,21 @@ const getCurrentFrameEffect = (item, seekTime) => {
133
167
  return currentFrameEffect;
134
168
  };
135
169
  const disabledControl = new Control({
170
+ /** X position offset */
136
171
  x: 0,
172
+ /** Y position offset */
137
173
  y: -0.5,
174
+ /** Additional Y offset */
138
175
  offsetY: 0,
176
+ /** Cursor style when hovering */
139
177
  cursorStyle: "pointer",
178
+ /** Action handler that does nothing */
140
179
  actionHandler: () => {
141
180
  return true;
142
181
  },
182
+ /** Name of the action */
143
183
  actionName: "scale",
184
+ /** Render function for the control */
144
185
  render: function(ctx, left, top) {
145
186
  const size = 0;
146
187
  ctx.save();
@@ -151,12 +192,19 @@ const disabledControl = new Control({
151
192
  }
152
193
  });
153
194
  const rotateControl = new Control({
195
+ /** X position offset */
154
196
  x: 0,
197
+ /** Y position offset */
155
198
  y: -0.5,
199
+ /** Additional Y offset for positioning */
156
200
  offsetY: -25,
201
+ /** Cursor style when hovering */
157
202
  cursorStyle: "crosshair",
203
+ /** Action handler with rotation and snapping */
158
204
  actionHandler: controlsUtils.rotationWithSnapping,
205
+ /** Name of the action */
159
206
  actionName: "rotate",
207
+ /** Whether to show connection line */
160
208
  withConnection: true
161
209
  });
162
210
  const getThumbnail = async (videoUrl, seekTime = 0.1, playbackRate = 1) => {
@@ -239,7 +287,7 @@ const getThumbnail = async (videoUrl, seekTime = 0.1, playbackRate = 1) => {
239
287
  timeoutId = window.setTimeout(() => {
240
288
  cleanup();
241
289
  reject(new Error("Video loading timed out"));
242
- }, 5e3);
290
+ }, 15e3);
243
291
  video.src = videoUrl;
244
292
  document.body.appendChild(video);
245
293
  });
@@ -290,7 +338,7 @@ const addTextElement = ({
290
338
  canvas,
291
339
  canvasMetadata
292
340
  }) => {
293
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
341
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w;
294
342
  const { x, y } = convertToCanvasPosition(
295
343
  ((_a = element.props) == null ? void 0 : _a.x) || 0,
296
344
  ((_b = element.props) == null ? void 0 : _b.y) || 0,
@@ -309,15 +357,16 @@ const addTextElement = ({
309
357
  fontStyle: ((_g = element.props) == null ? void 0 : _g.fontStyle) || "normal",
310
358
  fontWeight: ((_h = element.props) == null ? void 0 : _h.fontWeight) || "normal",
311
359
  fill: ((_i = element.props) == null ? void 0 : _i.fill) || DEFAULT_TEXT_PROPS.fill,
360
+ opacity: ((_j = element.props) == null ? void 0 : _j.opacity) ?? 1,
312
361
  skipWrapping: false,
313
- textAlign: ((_j = element.props) == null ? void 0 : _j.textAlign) || "center",
314
- stroke: ((_k = element.props) == null ? void 0 : _k.stroke) || DEFAULT_TEXT_PROPS.stroke,
315
- strokeWidth: ((_l = element.props) == null ? void 0 : _l.lineWidth) || DEFAULT_TEXT_PROPS.lineWidth,
316
- shadow: ((_m = element.props) == null ? void 0 : _m.shadowColor) ? new Shadow({
317
- offsetX: ((_o = (_n = element.props) == null ? void 0 : _n.shadowOffset) == null ? void 0 : _o.length) && ((_q = (_p = element.props) == null ? void 0 : _p.shadowOffset) == null ? void 0 : _q.length) > 1 ? element.props.shadowOffset[0] / 2 : 1,
318
- offsetY: ((_s = (_r = element.props) == null ? void 0 : _r.shadowOffset) == null ? void 0 : _s.length) && ((_t = element.props) == null ? void 0 : _t.shadowOffset.length) > 1 ? element.props.shadowOffset[1] / 2 : 1,
319
- blur: (((_u = element.props) == null ? void 0 : _u.shadowBlur) || 2) / 2,
320
- color: (_v = element.props) == null ? void 0 : _v.shadowColor
362
+ textAlign: ((_k = element.props) == null ? void 0 : _k.textAlign) || "center",
363
+ stroke: ((_l = element.props) == null ? void 0 : _l.stroke) || DEFAULT_TEXT_PROPS.stroke,
364
+ strokeWidth: ((_m = element.props) == null ? void 0 : _m.lineWidth) || DEFAULT_TEXT_PROPS.lineWidth,
365
+ shadow: ((_n = element.props) == null ? void 0 : _n.shadowColor) ? new Shadow({
366
+ offsetX: ((_p = (_o = element.props) == null ? void 0 : _o.shadowOffset) == null ? void 0 : _p.length) && ((_r = (_q = element.props) == null ? void 0 : _q.shadowOffset) == null ? void 0 : _r.length) > 1 ? element.props.shadowOffset[0] / 2 : 1,
367
+ offsetY: ((_t = (_s = element.props) == null ? void 0 : _s.shadowOffset) == null ? void 0 : _t.length) && ((_u = element.props) == null ? void 0 : _u.shadowOffset.length) > 1 ? element.props.shadowOffset[1] / 2 : 1,
368
+ blur: (((_v = element.props) == null ? void 0 : _v.shadowBlur) || 2) / 2,
369
+ color: (_w = element.props) == null ? void 0 : _w.shadowColor
321
370
  }) : void 0
322
371
  });
323
372
  text.set("id", element.id);
@@ -340,7 +389,7 @@ const setImageProps = ({
340
389
  index,
341
390
  canvasMetadata
342
391
  }) => {
343
- var _a, _b, _c, _d;
392
+ var _a, _b, _c, _d, _e;
344
393
  const width = (((_a = element.props) == null ? void 0 : _a.width) || 0) * canvasMetadata.scaleX || canvasMetadata.width;
345
394
  const height = (((_b = element.props) == null ? void 0 : _b.height) || 0) * canvasMetadata.scaleY || canvasMetadata.height;
346
395
  const { x, y } = convertToCanvasPosition(
@@ -355,6 +404,7 @@ const setImageProps = ({
355
404
  img.set("height", height);
356
405
  img.set("left", x);
357
406
  img.set("top", y);
407
+ img.set("opacity", ((_e = element.props) == null ? void 0 : _e.opacity) ?? 1);
358
408
  img.set("selectable", true);
359
409
  img.set("hasControls", true);
360
410
  img.set("touchAction", "all");
@@ -366,7 +416,7 @@ const addCaptionElement = ({
366
416
  captionProps,
367
417
  canvasMetadata
368
418
  }) => {
369
- 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;
419
+ 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, _A;
370
420
  const { x, y } = convertToCanvasPosition(
371
421
  ((_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,
372
422
  ((_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,
@@ -385,13 +435,14 @@ const addCaptionElement = ({
385
435
  fill: ((_o = element.props) == null ? void 0 : _o.fill) || ((_p = captionProps.color) == null ? void 0 : _p.text) || DEFAULT_CAPTION_PROPS.fill,
386
436
  fontWeight: DEFAULT_CAPTION_PROPS.fontWeight,
387
437
  stroke: ((_q = element.props) == null ? void 0 : _q.stroke) || DEFAULT_CAPTION_PROPS.stroke,
438
+ opacity: ((_r = element.props) == null ? void 0 : _r.opacity) ?? 1,
388
439
  shadow: new Shadow({
389
- 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]),
390
- 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]),
391
- blur: ((_x = element.props) == null ? void 0 : _x.shadowBlur) || DEFAULT_CAPTION_PROPS.shadowBlur,
392
- color: ((_y = element.props) == null ? void 0 : _y.shadowColor) || DEFAULT_CAPTION_PROPS.shadowColor
440
+ offsetX: ((_t = (_s = element.props) == null ? void 0 : _s.shadowOffset) == null ? void 0 : _t[0]) || ((_u = DEFAULT_CAPTION_PROPS.shadowOffset) == null ? void 0 : _u[0]),
441
+ offsetY: ((_w = (_v = element.props) == null ? void 0 : _v.shadowOffset) == null ? void 0 : _w[1]) || ((_x = DEFAULT_CAPTION_PROPS.shadowOffset) == null ? void 0 : _x[1]),
442
+ blur: ((_y = element.props) == null ? void 0 : _y.shadowBlur) || DEFAULT_CAPTION_PROPS.shadowBlur,
443
+ color: ((_z = element.props) == null ? void 0 : _z.shadowColor) || DEFAULT_CAPTION_PROPS.shadowColor
393
444
  }),
394
- strokeWidth: ((_z = element.props) == null ? void 0 : _z.lineWidth) || DEFAULT_CAPTION_PROPS.lineWidth
445
+ strokeWidth: ((_A = element.props) == null ? void 0 : _A.lineWidth) || DEFAULT_CAPTION_PROPS.lineWidth
395
446
  });
396
447
  caption.set("id", element.id);
397
448
  caption.set("zIndex", index);
@@ -482,7 +533,7 @@ const addMediaGroup = ({
482
533
  canvasMetadata,
483
534
  currentFrameEffect
484
535
  }) => {
485
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
536
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
486
537
  let frameSize;
487
538
  let angle;
488
539
  let framePosition;
@@ -537,7 +588,8 @@ const addMediaGroup = ({
537
588
  originX: "center",
538
589
  originY: "center",
539
590
  scaleX: newSize.width / img.width,
540
- scaleY: newSize.height / img.height
591
+ scaleY: newSize.height / img.height,
592
+ opacity: ((_n = element.props) == null ? void 0 : _n.opacity) ?? 1
541
593
  });
542
594
  const { x, y } = convertToCanvasPosition(
543
595
  (framePosition == null ? void 0 : framePosition.x) || 0,
@@ -657,6 +709,7 @@ const useTwickCanvas = ({
657
709
  const elementFrameMap = useRef({});
658
710
  const twickCanvasRef = useRef(null);
659
711
  const videoSizeRef = useRef({ width: 1, height: 1 });
712
+ const canvasResolutionRef = useRef({ width: 1, height: 1 });
660
713
  const canvasMetadataRef = useRef({
661
714
  width: 0,
662
715
  height: 0,
@@ -680,9 +733,13 @@ const useTwickCanvas = ({
680
733
  selectionLineWidth = 2,
681
734
  uniScaleTransform = true,
682
735
  enableRetinaScaling = true,
683
- touchZoomThreshold = 10
736
+ touchZoomThreshold = 10,
737
+ forceBuild = false
684
738
  }) => {
685
739
  if (!canvasRef) return;
740
+ if (!forceBuild && canvasResolutionRef.current.width === canvasSize.width && canvasResolutionRef.current.height === canvasSize.height) {
741
+ return;
742
+ }
686
743
  if (twickCanvasRef.current) {
687
744
  console.log("Destroying twickCanvas");
688
745
  twickCanvasRef.current.off("mouse:up", handleMouseUp);
@@ -702,6 +759,7 @@ const useTwickCanvas = ({
702
759
  canvasMetadataRef.current = canvasMetadata;
703
760
  videoSizeRef.current = videoSize;
704
761
  canvas == null ? void 0 : canvas.on("mouse:up", handleMouseUp);
762
+ canvasResolutionRef.current = canvasSize;
705
763
  setTwickCanvas(canvas);
706
764
  twickCanvasRef.current = canvas;
707
765
  if (onCanvasReady) {
@@ -842,13 +900,18 @@ const useTwickCanvas = ({
842
900
  captionProps,
843
901
  cleanAndAdd = false
844
902
  }) => {
845
- if (!twickCanvas) {
846
- console.warn("Canvas not initialized");
903
+ if (!twickCanvas || !getCanvasContext(twickCanvas)) {
904
+ console.warn("Canvas not properly initialized");
847
905
  return;
848
906
  }
849
907
  try {
850
- if (cleanAndAdd) {
908
+ if (cleanAndAdd && getCanvasContext(twickCanvas)) {
909
+ const backgroundColor = twickCanvas.backgroundColor;
851
910
  clearCanvas(twickCanvas);
911
+ if (backgroundColor) {
912
+ twickCanvas.backgroundColor = backgroundColor;
913
+ twickCanvas.renderAll();
914
+ }
852
915
  }
853
916
  await Promise.all(
854
917
  elements.map(async (element, index) => {