@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/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A React-based canvas library built with Fabric.js for video and image manipulation.
|
|
4
4
|
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package provides a comprehensive canvas solution for video and image editing, built on top of Fabric.js with React integration. It offers advanced manipulation capabilities for creating professional video editing applications.
|
|
8
|
+
|
|
5
9
|
## Requirements
|
|
6
10
|
|
|
7
11
|
- Browser environment with Canvas and Video support
|
|
@@ -11,10 +15,12 @@ A React-based canvas library built with Fabric.js for video and image manipulati
|
|
|
11
15
|
## Installation
|
|
12
16
|
|
|
13
17
|
```bash
|
|
14
|
-
|
|
18
|
+
npm install @twick/canvas
|
|
19
|
+
# or
|
|
20
|
+
pnpm add @twick/canvas
|
|
15
21
|
```
|
|
16
22
|
|
|
17
|
-
##
|
|
23
|
+
## Quick Start
|
|
18
24
|
|
|
19
25
|
### Basic Canvas Setup
|
|
20
26
|
|
|
@@ -116,6 +122,8 @@ addElementToCanvas({ element: textElement, index: 1 });
|
|
|
116
122
|
- `CanvasElementProps`: Canvas element props interface
|
|
117
123
|
- `CaptionProps`: Caption configuration props
|
|
118
124
|
|
|
125
|
+
For complete API documentation, refer to the generated documentation.
|
|
126
|
+
|
|
119
127
|
## Browser Support
|
|
120
128
|
|
|
121
129
|
This library requires a browser environment with support for:
|
|
@@ -125,6 +133,18 @@ This library requires a browser environment with support for:
|
|
|
125
133
|
|
|
126
134
|
The library will throw appropriate errors if used in an unsupported environment.
|
|
127
135
|
|
|
136
|
+
## Documentation
|
|
137
|
+
|
|
138
|
+
For complete documentation, refer to the project documentation site.
|
|
139
|
+
|
|
128
140
|
## License
|
|
129
141
|
|
|
130
|
-
|
|
142
|
+
This package is licensed under the **Sustainable Use License (SUL) Version 1.0**.
|
|
143
|
+
|
|
144
|
+
- Free for use in commercial and non-commercial apps
|
|
145
|
+
- Can be modified and self-hosted
|
|
146
|
+
- Cannot be sold, rebranded, or distributed as a standalone SDK
|
|
147
|
+
|
|
148
|
+
For commercial licensing inquiries, contact: contact@kifferai.com
|
|
149
|
+
|
|
150
|
+
For full license terms, see the main LICENSE.md file in the project root.
|
package/dist/index.js
CHANGED
|
@@ -3,36 +3,61 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const fabric = require("fabric");
|
|
4
4
|
const react = require("react");
|
|
5
5
|
const DEFAULT_TEXT_PROPS = {
|
|
6
|
+
/** Font family for text elements */
|
|
6
7
|
family: "Poppins",
|
|
8
|
+
/** Font size in pixels */
|
|
7
9
|
size: 48,
|
|
10
|
+
/** Text fill color */
|
|
8
11
|
fill: "#FFFFFF",
|
|
12
|
+
/** Text stroke color */
|
|
9
13
|
stroke: "#000000",
|
|
14
|
+
/** Stroke line width */
|
|
10
15
|
lineWidth: 0
|
|
11
16
|
};
|
|
12
17
|
const DEFAULT_CAPTION_PROPS = {
|
|
18
|
+
/** Font family for caption elements */
|
|
13
19
|
family: "Poppins",
|
|
20
|
+
/** Font size in pixels */
|
|
14
21
|
size: 48,
|
|
22
|
+
/** Text fill color */
|
|
15
23
|
fill: "#FFFFFF",
|
|
24
|
+
/** Font weight */
|
|
16
25
|
fontWeight: 600,
|
|
26
|
+
/** Text stroke color */
|
|
17
27
|
stroke: "#000000",
|
|
28
|
+
/** Stroke line width */
|
|
18
29
|
lineWidth: 0.2,
|
|
30
|
+
/** Shadow color */
|
|
19
31
|
shadowColor: "#000000",
|
|
32
|
+
/** Shadow blur radius */
|
|
20
33
|
shadowBlur: 2,
|
|
34
|
+
/** Shadow offset [x, y] */
|
|
21
35
|
shadowOffset: [0, 0]
|
|
22
36
|
};
|
|
23
37
|
const CANVAS_OPERATIONS = {
|
|
38
|
+
/** An item has been selected on the canvas */
|
|
24
39
|
ITEM_SELECTED: "ITEM_SELECTED",
|
|
40
|
+
/** An item has been updated/modified on the canvas */
|
|
25
41
|
ITEM_UPDATED: "ITEM_UPDATED",
|
|
42
|
+
/** An item has been deleted from the canvas */
|
|
26
43
|
ITEM_DELETED: "ITEM_DELETED",
|
|
44
|
+
/** A new item has been added to the canvas */
|
|
27
45
|
ITEM_ADDED: "ITEM_ADDED",
|
|
46
|
+
/** Items have been grouped together */
|
|
28
47
|
ITEM_GROUPED: "ITEM_GROUPED",
|
|
48
|
+
/** Items have been ungrouped */
|
|
29
49
|
ITEM_UNGROUPED: "ITEM_UNGROUPED"
|
|
30
50
|
};
|
|
31
51
|
const ELEMENT_TYPES = {
|
|
52
|
+
/** Text element type */
|
|
32
53
|
TEXT: "text",
|
|
54
|
+
/** Caption element type */
|
|
33
55
|
CAPTION: "caption",
|
|
56
|
+
/** Image element type */
|
|
34
57
|
IMAGE: "image",
|
|
58
|
+
/** Video element type */
|
|
35
59
|
VIDEO: "video",
|
|
60
|
+
/** Rectangle element type */
|
|
36
61
|
RECT: "rect"
|
|
37
62
|
};
|
|
38
63
|
const isBrowser = typeof window !== "undefined";
|
|
@@ -106,10 +131,19 @@ const reorderElementsByZIndex = (canvas) => {
|
|
|
106
131
|
objects.forEach((obj) => canvas.add(obj));
|
|
107
132
|
canvas.renderAll();
|
|
108
133
|
};
|
|
134
|
+
const getCanvasContext = (canvas) => {
|
|
135
|
+
var _a, _b, _c, _d;
|
|
136
|
+
if (!canvas || !((_b = (_a = canvas.elements) == null ? void 0 : _a.lower) == null ? void 0 : _b.ctx)) return;
|
|
137
|
+
return (_d = (_c = canvas.elements) == null ? void 0 : _c.lower) == null ? void 0 : _d.ctx;
|
|
138
|
+
};
|
|
109
139
|
const clearCanvas = (canvas) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
140
|
+
try {
|
|
141
|
+
if (!canvas || !getCanvasContext(canvas)) return;
|
|
142
|
+
canvas.clear();
|
|
143
|
+
canvas.renderAll();
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.warn("Error clearing canvas:", error);
|
|
146
|
+
}
|
|
113
147
|
};
|
|
114
148
|
const convertToCanvasPosition = (x, y, canvasMetadata) => {
|
|
115
149
|
return {
|
|
@@ -135,14 +169,21 @@ const getCurrentFrameEffect = (item, seekTime) => {
|
|
|
135
169
|
return currentFrameEffect;
|
|
136
170
|
};
|
|
137
171
|
const disabledControl = new fabric.Control({
|
|
172
|
+
/** X position offset */
|
|
138
173
|
x: 0,
|
|
174
|
+
/** Y position offset */
|
|
139
175
|
y: -0.5,
|
|
176
|
+
/** Additional Y offset */
|
|
140
177
|
offsetY: 0,
|
|
178
|
+
/** Cursor style when hovering */
|
|
141
179
|
cursorStyle: "pointer",
|
|
180
|
+
/** Action handler that does nothing */
|
|
142
181
|
actionHandler: () => {
|
|
143
182
|
return true;
|
|
144
183
|
},
|
|
184
|
+
/** Name of the action */
|
|
145
185
|
actionName: "scale",
|
|
186
|
+
/** Render function for the control */
|
|
146
187
|
render: function(ctx, left, top) {
|
|
147
188
|
const size = 0;
|
|
148
189
|
ctx.save();
|
|
@@ -153,12 +194,19 @@ const disabledControl = new fabric.Control({
|
|
|
153
194
|
}
|
|
154
195
|
});
|
|
155
196
|
const rotateControl = new fabric.Control({
|
|
197
|
+
/** X position offset */
|
|
156
198
|
x: 0,
|
|
199
|
+
/** Y position offset */
|
|
157
200
|
y: -0.5,
|
|
201
|
+
/** Additional Y offset for positioning */
|
|
158
202
|
offsetY: -25,
|
|
203
|
+
/** Cursor style when hovering */
|
|
159
204
|
cursorStyle: "crosshair",
|
|
205
|
+
/** Action handler with rotation and snapping */
|
|
160
206
|
actionHandler: fabric.controlsUtils.rotationWithSnapping,
|
|
207
|
+
/** Name of the action */
|
|
161
208
|
actionName: "rotate",
|
|
209
|
+
/** Whether to show connection line */
|
|
162
210
|
withConnection: true
|
|
163
211
|
});
|
|
164
212
|
const getThumbnail = async (videoUrl, seekTime = 0.1, playbackRate = 1) => {
|
|
@@ -241,7 +289,7 @@ const getThumbnail = async (videoUrl, seekTime = 0.1, playbackRate = 1) => {
|
|
|
241
289
|
timeoutId = window.setTimeout(() => {
|
|
242
290
|
cleanup();
|
|
243
291
|
reject(new Error("Video loading timed out"));
|
|
244
|
-
},
|
|
292
|
+
}, 15e3);
|
|
245
293
|
video.src = videoUrl;
|
|
246
294
|
document.body.appendChild(video);
|
|
247
295
|
});
|
|
@@ -292,7 +340,7 @@ const addTextElement = ({
|
|
|
292
340
|
canvas,
|
|
293
341
|
canvasMetadata
|
|
294
342
|
}) => {
|
|
295
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
343
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
|
296
344
|
const { x, y } = convertToCanvasPosition(
|
|
297
345
|
((_a = element.props) == null ? void 0 : _a.x) || 0,
|
|
298
346
|
((_b = element.props) == null ? void 0 : _b.y) || 0,
|
|
@@ -311,15 +359,16 @@ const addTextElement = ({
|
|
|
311
359
|
fontStyle: ((_g = element.props) == null ? void 0 : _g.fontStyle) || "normal",
|
|
312
360
|
fontWeight: ((_h = element.props) == null ? void 0 : _h.fontWeight) || "normal",
|
|
313
361
|
fill: ((_i = element.props) == null ? void 0 : _i.fill) || DEFAULT_TEXT_PROPS.fill,
|
|
362
|
+
opacity: ((_j = element.props) == null ? void 0 : _j.opacity) ?? 1,
|
|
314
363
|
skipWrapping: false,
|
|
315
|
-
textAlign: ((
|
|
316
|
-
stroke: ((
|
|
317
|
-
strokeWidth: ((
|
|
318
|
-
shadow: ((
|
|
319
|
-
offsetX: ((
|
|
320
|
-
offsetY: ((
|
|
321
|
-
blur: (((
|
|
322
|
-
color: (
|
|
364
|
+
textAlign: ((_k = element.props) == null ? void 0 : _k.textAlign) || "center",
|
|
365
|
+
stroke: ((_l = element.props) == null ? void 0 : _l.stroke) || DEFAULT_TEXT_PROPS.stroke,
|
|
366
|
+
strokeWidth: ((_m = element.props) == null ? void 0 : _m.lineWidth) || DEFAULT_TEXT_PROPS.lineWidth,
|
|
367
|
+
shadow: ((_n = element.props) == null ? void 0 : _n.shadowColor) ? new fabric.Shadow({
|
|
368
|
+
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,
|
|
369
|
+
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,
|
|
370
|
+
blur: (((_v = element.props) == null ? void 0 : _v.shadowBlur) || 2) / 2,
|
|
371
|
+
color: (_w = element.props) == null ? void 0 : _w.shadowColor
|
|
323
372
|
}) : void 0
|
|
324
373
|
});
|
|
325
374
|
text.set("id", element.id);
|
|
@@ -342,7 +391,7 @@ const setImageProps = ({
|
|
|
342
391
|
index,
|
|
343
392
|
canvasMetadata
|
|
344
393
|
}) => {
|
|
345
|
-
var _a, _b, _c, _d;
|
|
394
|
+
var _a, _b, _c, _d, _e;
|
|
346
395
|
const width = (((_a = element.props) == null ? void 0 : _a.width) || 0) * canvasMetadata.scaleX || canvasMetadata.width;
|
|
347
396
|
const height = (((_b = element.props) == null ? void 0 : _b.height) || 0) * canvasMetadata.scaleY || canvasMetadata.height;
|
|
348
397
|
const { x, y } = convertToCanvasPosition(
|
|
@@ -357,6 +406,7 @@ const setImageProps = ({
|
|
|
357
406
|
img.set("height", height);
|
|
358
407
|
img.set("left", x);
|
|
359
408
|
img.set("top", y);
|
|
409
|
+
img.set("opacity", ((_e = element.props) == null ? void 0 : _e.opacity) ?? 1);
|
|
360
410
|
img.set("selectable", true);
|
|
361
411
|
img.set("hasControls", true);
|
|
362
412
|
img.set("touchAction", "all");
|
|
@@ -368,7 +418,7 @@ const addCaptionElement = ({
|
|
|
368
418
|
captionProps,
|
|
369
419
|
canvasMetadata
|
|
370
420
|
}) => {
|
|
371
|
-
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;
|
|
421
|
+
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;
|
|
372
422
|
const { x, y } = convertToCanvasPosition(
|
|
373
423
|
((_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,
|
|
374
424
|
((_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,
|
|
@@ -387,13 +437,14 @@ const addCaptionElement = ({
|
|
|
387
437
|
fill: ((_o = element.props) == null ? void 0 : _o.fill) || ((_p = captionProps.color) == null ? void 0 : _p.text) || DEFAULT_CAPTION_PROPS.fill,
|
|
388
438
|
fontWeight: DEFAULT_CAPTION_PROPS.fontWeight,
|
|
389
439
|
stroke: ((_q = element.props) == null ? void 0 : _q.stroke) || DEFAULT_CAPTION_PROPS.stroke,
|
|
440
|
+
opacity: ((_r = element.props) == null ? void 0 : _r.opacity) ?? 1,
|
|
390
441
|
shadow: new fabric.Shadow({
|
|
391
|
-
offsetX: ((
|
|
392
|
-
offsetY: ((
|
|
393
|
-
blur: ((
|
|
394
|
-
color: ((
|
|
442
|
+
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]),
|
|
443
|
+
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]),
|
|
444
|
+
blur: ((_y = element.props) == null ? void 0 : _y.shadowBlur) || DEFAULT_CAPTION_PROPS.shadowBlur,
|
|
445
|
+
color: ((_z = element.props) == null ? void 0 : _z.shadowColor) || DEFAULT_CAPTION_PROPS.shadowColor
|
|
395
446
|
}),
|
|
396
|
-
strokeWidth: ((
|
|
447
|
+
strokeWidth: ((_A = element.props) == null ? void 0 : _A.lineWidth) || DEFAULT_CAPTION_PROPS.lineWidth
|
|
397
448
|
});
|
|
398
449
|
caption.set("id", element.id);
|
|
399
450
|
caption.set("zIndex", index);
|
|
@@ -484,7 +535,7 @@ const addMediaGroup = ({
|
|
|
484
535
|
canvasMetadata,
|
|
485
536
|
currentFrameEffect
|
|
486
537
|
}) => {
|
|
487
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
538
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
488
539
|
let frameSize;
|
|
489
540
|
let angle;
|
|
490
541
|
let framePosition;
|
|
@@ -539,7 +590,8 @@ const addMediaGroup = ({
|
|
|
539
590
|
originX: "center",
|
|
540
591
|
originY: "center",
|
|
541
592
|
scaleX: newSize.width / img.width,
|
|
542
|
-
scaleY: newSize.height / img.height
|
|
593
|
+
scaleY: newSize.height / img.height,
|
|
594
|
+
opacity: ((_n = element.props) == null ? void 0 : _n.opacity) ?? 1
|
|
543
595
|
});
|
|
544
596
|
const { x, y } = convertToCanvasPosition(
|
|
545
597
|
(framePosition == null ? void 0 : framePosition.x) || 0,
|
|
@@ -659,6 +711,7 @@ const useTwickCanvas = ({
|
|
|
659
711
|
const elementFrameMap = react.useRef({});
|
|
660
712
|
const twickCanvasRef = react.useRef(null);
|
|
661
713
|
const videoSizeRef = react.useRef({ width: 1, height: 1 });
|
|
714
|
+
const canvasResolutionRef = react.useRef({ width: 1, height: 1 });
|
|
662
715
|
const canvasMetadataRef = react.useRef({
|
|
663
716
|
width: 0,
|
|
664
717
|
height: 0,
|
|
@@ -682,9 +735,13 @@ const useTwickCanvas = ({
|
|
|
682
735
|
selectionLineWidth = 2,
|
|
683
736
|
uniScaleTransform = true,
|
|
684
737
|
enableRetinaScaling = true,
|
|
685
|
-
touchZoomThreshold = 10
|
|
738
|
+
touchZoomThreshold = 10,
|
|
739
|
+
forceBuild = false
|
|
686
740
|
}) => {
|
|
687
741
|
if (!canvasRef) return;
|
|
742
|
+
if (!forceBuild && canvasResolutionRef.current.width === canvasSize.width && canvasResolutionRef.current.height === canvasSize.height) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
688
745
|
if (twickCanvasRef.current) {
|
|
689
746
|
console.log("Destroying twickCanvas");
|
|
690
747
|
twickCanvasRef.current.off("mouse:up", handleMouseUp);
|
|
@@ -704,6 +761,7 @@ const useTwickCanvas = ({
|
|
|
704
761
|
canvasMetadataRef.current = canvasMetadata;
|
|
705
762
|
videoSizeRef.current = videoSize;
|
|
706
763
|
canvas == null ? void 0 : canvas.on("mouse:up", handleMouseUp);
|
|
764
|
+
canvasResolutionRef.current = canvasSize;
|
|
707
765
|
setTwickCanvas(canvas);
|
|
708
766
|
twickCanvasRef.current = canvas;
|
|
709
767
|
if (onCanvasReady) {
|
|
@@ -844,13 +902,18 @@ const useTwickCanvas = ({
|
|
|
844
902
|
captionProps,
|
|
845
903
|
cleanAndAdd = false
|
|
846
904
|
}) => {
|
|
847
|
-
if (!twickCanvas) {
|
|
848
|
-
console.warn("Canvas not initialized");
|
|
905
|
+
if (!twickCanvas || !getCanvasContext(twickCanvas)) {
|
|
906
|
+
console.warn("Canvas not properly initialized");
|
|
849
907
|
return;
|
|
850
908
|
}
|
|
851
909
|
try {
|
|
852
|
-
if (cleanAndAdd) {
|
|
910
|
+
if (cleanAndAdd && getCanvasContext(twickCanvas)) {
|
|
911
|
+
const backgroundColor = twickCanvas.backgroundColor;
|
|
853
912
|
clearCanvas(twickCanvas);
|
|
913
|
+
if (backgroundColor) {
|
|
914
|
+
twickCanvas.backgroundColor = backgroundColor;
|
|
915
|
+
twickCanvas.renderAll();
|
|
916
|
+
}
|
|
854
917
|
}
|
|
855
918
|
await Promise.all(
|
|
856
919
|
elements.map(async (element, index) => {
|