@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/README.md +23 -3
- package/dist/index.js +89 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +89 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
},
|
|
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: ((
|
|
314
|
-
stroke: ((
|
|
315
|
-
strokeWidth: ((
|
|
316
|
-
shadow: ((
|
|
317
|
-
offsetX: ((
|
|
318
|
-
offsetY: ((
|
|
319
|
-
blur: (((
|
|
320
|
-
color: (
|
|
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: ((
|
|
390
|
-
offsetY: ((
|
|
391
|
-
blur: ((
|
|
392
|
-
color: ((
|
|
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: ((
|
|
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) => {
|