@twick/canvas 0.15.12 → 0.15.14

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 CHANGED
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
2
5
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
6
  const fabric = require("fabric");
4
7
  const react = require("react");
@@ -48,7 +51,9 @@ const CANVAS_OPERATIONS = {
48
51
  /** Items have been ungrouped */
49
52
  ITEM_UNGROUPED: "ITEM_UNGROUPED",
50
53
  /** Caption properties have been updated */
51
- CAPTION_PROPS_UPDATED: "CAPTION_PROPS_UPDATED"
54
+ CAPTION_PROPS_UPDATED: "CAPTION_PROPS_UPDATED",
55
+ /** Watermark has been updated */
56
+ WATERMARK_UPDATED: "WATERMARK_UPDATED"
52
57
  };
53
58
  const ELEMENT_TYPES = {
54
59
  /** Text element type */
@@ -62,7 +67,11 @@ const ELEMENT_TYPES = {
62
67
  /** Rectangle element type */
63
68
  RECT: "rect",
64
69
  /** Circle element type */
65
- CIRCLE: "circle"
70
+ CIRCLE: "circle",
71
+ /** Icon element type */
72
+ ICON: "icon",
73
+ /** Background color element type */
74
+ BACKGROUND_COLOR: "backgroundColor"
66
75
  };
67
76
  const isBrowser = typeof window !== "undefined";
68
77
  const isCanvasSupported = isBrowser && !!window.HTMLCanvasElement;
@@ -124,6 +133,26 @@ const createCanvas = ({
124
133
  canvasMetadata
125
134
  };
126
135
  };
136
+ function measureTextWidth(text, options) {
137
+ if (typeof document === "undefined" || !text) return 0;
138
+ const canvas = document.createElement("canvas");
139
+ const ctx = canvas.getContext("2d");
140
+ if (!ctx) return 0;
141
+ const {
142
+ fontSize,
143
+ fontFamily,
144
+ fontStyle = "normal",
145
+ fontWeight = "normal"
146
+ } = options;
147
+ ctx.font = `${fontStyle} ${String(fontWeight)} ${fontSize}px ${fontFamily}`;
148
+ const lines = text.split("\n");
149
+ let maxWidth = 0;
150
+ for (const line of lines) {
151
+ const { width } = ctx.measureText(line);
152
+ if (width > maxWidth) maxWidth = width;
153
+ }
154
+ return Math.ceil(maxWidth);
155
+ }
127
156
  const reorderElementsByZIndex = (canvas) => {
128
157
  if (!canvas) return;
129
158
  const backgroundColor = canvas.backgroundColor;
@@ -171,6 +200,17 @@ const getCurrentFrameEffect = (item, seekTime) => {
171
200
  }
172
201
  return currentFrameEffect;
173
202
  };
203
+ const hexToRgba = (hex, alpha) => {
204
+ const color = hex.replace(/^#/, "");
205
+ const fullHex = color.length === 3 ? color.split("").map((c) => c + c).join("") : color;
206
+ if (fullHex.length !== 6) {
207
+ return hex;
208
+ }
209
+ const r = parseInt(fullHex.slice(0, 2), 16);
210
+ const g = parseInt(fullHex.slice(2, 4), 16);
211
+ const b = parseInt(fullHex.slice(4, 6), 16);
212
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
213
+ };
174
214
  const disabledControl = new fabric.Control({
175
215
  /** X position offset */
176
216
  x: 0,
@@ -602,40 +642,66 @@ const addTextElement = ({
602
642
  canvas,
603
643
  canvasMetadata
604
644
  }) => {
605
- 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;
645
+ 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, _B, _C;
606
646
  const { x, y } = convertToCanvasPosition(
607
647
  ((_a = element.props) == null ? void 0 : _a.x) || 0,
608
648
  ((_b = element.props) == null ? void 0 : _b.y) || 0,
609
649
  canvasMetadata
610
650
  );
611
- let width = ((_c = element.props) == null ? void 0 : _c.width) ? element.props.width * canvasMetadata.scaleX : canvasMetadata.width - 2 * MARGIN;
612
- if ((_d = element.props) == null ? void 0 : _d.maxWidth) {
613
- width = Math.min(width, element.props.maxWidth * canvasMetadata.scaleX);
651
+ const fontSize = Math.floor(
652
+ (((_c = element.props) == null ? void 0 : _c.fontSize) || DEFAULT_TEXT_PROPS.size) * canvasMetadata.scaleX
653
+ );
654
+ const fontFamily = ((_d = element.props) == null ? void 0 : _d.fontFamily) || DEFAULT_TEXT_PROPS.family;
655
+ const fontStyle = ((_e = element.props) == null ? void 0 : _e.fontStyle) || "normal";
656
+ const fontWeight = ((_f = element.props) == null ? void 0 : _f.fontWeight) || "normal";
657
+ let width;
658
+ if (((_g = element.props) == null ? void 0 : _g.width) != null && element.props.width > 0) {
659
+ width = element.props.width * canvasMetadata.scaleX;
660
+ if ((_h = element.props) == null ? void 0 : _h.maxWidth) {
661
+ width = Math.min(width, element.props.maxWidth * canvasMetadata.scaleX);
662
+ }
663
+ } else {
664
+ const textContent = ((_i = element.props) == null ? void 0 : _i.text) ?? element.t ?? "";
665
+ width = measureTextWidth(textContent, {
666
+ fontSize,
667
+ fontFamily,
668
+ fontStyle,
669
+ fontWeight
670
+ });
671
+ const padding = 4;
672
+ width = width + padding * 2;
673
+ if ((_j = element.props) == null ? void 0 : _j.maxWidth) {
674
+ width = Math.min(width, element.props.maxWidth * canvasMetadata.scaleX);
675
+ }
676
+ if (width <= 0) width = 100;
614
677
  }
615
- const text = new fabric.Textbox(((_e = element.props) == null ? void 0 : _e.text) || element.t || "", {
678
+ const backgroundColor = ((_k = element.props) == null ? void 0 : _k.backgroundColor) ? hexToRgba(
679
+ element.props.backgroundColor,
680
+ ((_l = element.props) == null ? void 0 : _l.backgroundOpacity) ?? 1
681
+ ) : void 0;
682
+ const text = new fabric.Textbox(((_m = element.props) == null ? void 0 : _m.text) || element.t || "", {
616
683
  left: x,
617
684
  top: y,
618
685
  originX: "center",
619
686
  originY: "center",
620
- angle: ((_f = element.props) == null ? void 0 : _f.rotation) || 0,
621
- fontSize: Math.floor(
622
- (((_g = element.props) == null ? void 0 : _g.fontSize) || DEFAULT_TEXT_PROPS.size) * canvasMetadata.scaleX
623
- ),
624
- fontFamily: ((_h = element.props) == null ? void 0 : _h.fontFamily) || DEFAULT_TEXT_PROPS.family,
625
- fontStyle: ((_i = element.props) == null ? void 0 : _i.fontStyle) || "normal",
626
- fontWeight: ((_j = element.props) == null ? void 0 : _j.fontWeight) || "normal",
627
- fill: ((_k = element.props) == null ? void 0 : _k.fill) || DEFAULT_TEXT_PROPS.fill,
628
- opacity: ((_l = element.props) == null ? void 0 : _l.opacity) ?? 1,
687
+ angle: ((_n = element.props) == null ? void 0 : _n.rotation) || 0,
688
+ fontSize,
689
+ fontFamily,
690
+ fontStyle,
691
+ fontWeight,
692
+ fill: ((_o = element.props) == null ? void 0 : _o.fill) || DEFAULT_TEXT_PROPS.fill,
693
+ opacity: ((_p = element.props) == null ? void 0 : _p.opacity) ?? 1,
629
694
  width,
630
695
  splitByGrapheme: false,
631
- textAlign: ((_m = element.props) == null ? void 0 : _m.textAlign) || "center",
632
- stroke: ((_n = element.props) == null ? void 0 : _n.stroke) || DEFAULT_TEXT_PROPS.stroke,
633
- strokeWidth: ((_o = element.props) == null ? void 0 : _o.lineWidth) || DEFAULT_TEXT_PROPS.lineWidth,
634
- shadow: ((_p = element.props) == null ? void 0 : _p.shadowColor) ? new fabric.Shadow({
635
- offsetX: ((_r = (_q = element.props) == null ? void 0 : _q.shadowOffset) == null ? void 0 : _r.length) && ((_t = (_s = element.props) == null ? void 0 : _s.shadowOffset) == null ? void 0 : _t.length) > 1 ? element.props.shadowOffset[0] / 2 : 1,
636
- offsetY: ((_v = (_u = element.props) == null ? void 0 : _u.shadowOffset) == null ? void 0 : _v.length) && ((_w = element.props) == null ? void 0 : _w.shadowOffset.length) > 1 ? element.props.shadowOffset[1] / 2 : 1,
637
- blur: (((_x = element.props) == null ? void 0 : _x.shadowBlur) || 2) / 2,
638
- color: (_y = element.props) == null ? void 0 : _y.shadowColor
696
+ textAlign: ((_q = element.props) == null ? void 0 : _q.textAlign) || "center",
697
+ stroke: ((_r = element.props) == null ? void 0 : _r.stroke) || DEFAULT_TEXT_PROPS.stroke,
698
+ strokeWidth: ((_s = element.props) == null ? void 0 : _s.lineWidth) || DEFAULT_TEXT_PROPS.lineWidth,
699
+ ...backgroundColor && { backgroundColor },
700
+ shadow: ((_t = element.props) == null ? void 0 : _t.shadowColor) ? new fabric.Shadow({
701
+ offsetX: ((_v = (_u = element.props) == null ? void 0 : _u.shadowOffset) == null ? void 0 : _v.length) && ((_x = (_w = element.props) == null ? void 0 : _w.shadowOffset) == null ? void 0 : _x.length) > 1 ? element.props.shadowOffset[0] / 2 : 1,
702
+ offsetY: ((_z = (_y = element.props) == null ? void 0 : _y.shadowOffset) == null ? void 0 : _z.length) && ((_A = element.props) == null ? void 0 : _A.shadowOffset.length) > 1 ? element.props.shadowOffset[1] / 2 : 1,
703
+ blur: (((_B = element.props) == null ? void 0 : _B.shadowBlur) || 2) / 2,
704
+ color: (_C = element.props) == null ? void 0 : _C.shadowColor
639
705
  }) : void 0
640
706
  });
641
707
  text.set("id", element.id);
@@ -1007,12 +1073,419 @@ const addBackgroundColor = ({
1007
1073
  canvas.add(bgRect);
1008
1074
  return bgRect;
1009
1075
  };
1076
+ const VideoElement = {
1077
+ name: ELEMENT_TYPES.VIDEO,
1078
+ async add(params) {
1079
+ var _a, _b;
1080
+ const {
1081
+ element,
1082
+ index,
1083
+ canvas,
1084
+ canvasMetadata,
1085
+ seekTime = 0,
1086
+ elementFrameMapRef,
1087
+ getCurrentFrameEffect: getFrameEffect
1088
+ } = params;
1089
+ if (!getFrameEffect || !elementFrameMapRef) return;
1090
+ const currentFrameEffect = getFrameEffect(element, seekTime);
1091
+ elementFrameMapRef.current[element.id] = currentFrameEffect;
1092
+ const snapTime = (seekTime - ((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);
1093
+ await addVideoElement({
1094
+ element,
1095
+ index,
1096
+ canvas,
1097
+ canvasMetadata,
1098
+ currentFrameEffect,
1099
+ snapTime
1100
+ });
1101
+ if (element.timelineType === "scene") {
1102
+ await addBackgroundColor({
1103
+ element,
1104
+ index,
1105
+ canvas,
1106
+ canvasMetadata
1107
+ });
1108
+ }
1109
+ },
1110
+ updateFromFabricObject(object, element, context) {
1111
+ const { x, y } = convertToVideoPosition(
1112
+ object.left,
1113
+ object.top,
1114
+ context.canvasMetadata,
1115
+ context.videoSize
1116
+ );
1117
+ const currentFrameEffect = context.elementFrameMapRef.current[element.id];
1118
+ let updatedFrameSize;
1119
+ if (currentFrameEffect) {
1120
+ updatedFrameSize = [
1121
+ currentFrameEffect.props.frameSize[0] * object.scaleX,
1122
+ currentFrameEffect.props.frameSize[1] * object.scaleY
1123
+ ];
1124
+ context.elementFrameMapRef.current[element.id] = {
1125
+ ...currentFrameEffect,
1126
+ props: {
1127
+ ...currentFrameEffect.props,
1128
+ framePosition: { x, y },
1129
+ frameSize: updatedFrameSize
1130
+ }
1131
+ };
1132
+ return {
1133
+ element: {
1134
+ ...element,
1135
+ frameEffects: (element.frameEffects || []).map(
1136
+ (fe) => fe.id === (currentFrameEffect == null ? void 0 : currentFrameEffect.id) ? {
1137
+ ...fe,
1138
+ props: {
1139
+ ...fe.props,
1140
+ framePosition: { x, y },
1141
+ frameSize: updatedFrameSize
1142
+ }
1143
+ } : fe
1144
+ )
1145
+ }
1146
+ };
1147
+ }
1148
+ const frame = element.frame;
1149
+ updatedFrameSize = [
1150
+ (frame.size[0] ?? 0) * object.scaleX,
1151
+ (frame.size[1] ?? 0) * object.scaleY
1152
+ ];
1153
+ return {
1154
+ element: {
1155
+ ...element,
1156
+ frame: {
1157
+ ...frame,
1158
+ rotation: object.angle,
1159
+ size: updatedFrameSize,
1160
+ x,
1161
+ y
1162
+ }
1163
+ }
1164
+ };
1165
+ }
1166
+ };
1167
+ const ImageElement = {
1168
+ name: ELEMENT_TYPES.IMAGE,
1169
+ async add(params) {
1170
+ const { element, index, canvas, canvasMetadata } = params;
1171
+ await addImageElement({
1172
+ element,
1173
+ index,
1174
+ canvas,
1175
+ canvasMetadata
1176
+ });
1177
+ if (element.timelineType === "scene") {
1178
+ await addBackgroundColor({
1179
+ element,
1180
+ index,
1181
+ canvas,
1182
+ canvasMetadata
1183
+ });
1184
+ }
1185
+ },
1186
+ updateFromFabricObject(object, element, context) {
1187
+ var _a, _b;
1188
+ const { x, y } = convertToVideoPosition(
1189
+ object.left,
1190
+ object.top,
1191
+ context.canvasMetadata,
1192
+ context.videoSize
1193
+ );
1194
+ const currentFrameEffect = context.elementFrameMapRef.current[element.id];
1195
+ if (object.type === "group") {
1196
+ let updatedFrameSize;
1197
+ if (currentFrameEffect) {
1198
+ updatedFrameSize = [
1199
+ currentFrameEffect.props.frameSize[0] * object.scaleX,
1200
+ currentFrameEffect.props.frameSize[1] * object.scaleY
1201
+ ];
1202
+ context.elementFrameMapRef.current[element.id] = {
1203
+ ...currentFrameEffect,
1204
+ props: {
1205
+ ...currentFrameEffect.props,
1206
+ framePosition: { x, y },
1207
+ frameSize: updatedFrameSize
1208
+ }
1209
+ };
1210
+ return {
1211
+ element: {
1212
+ ...element,
1213
+ frameEffects: (element.frameEffects || []).map(
1214
+ (fe) => fe.id === (currentFrameEffect == null ? void 0 : currentFrameEffect.id) ? {
1215
+ ...fe,
1216
+ props: {
1217
+ ...fe.props,
1218
+ framePosition: { x, y },
1219
+ frameSize: updatedFrameSize
1220
+ }
1221
+ } : fe
1222
+ )
1223
+ }
1224
+ };
1225
+ }
1226
+ const frame = element.frame;
1227
+ updatedFrameSize = [
1228
+ (frame.size[0] ?? 0) * object.scaleX,
1229
+ (frame.size[1] ?? 0) * object.scaleY
1230
+ ];
1231
+ return {
1232
+ element: {
1233
+ ...element,
1234
+ frame: {
1235
+ ...frame,
1236
+ rotation: object.angle,
1237
+ size: updatedFrameSize,
1238
+ x,
1239
+ y
1240
+ }
1241
+ }
1242
+ };
1243
+ }
1244
+ return {
1245
+ element: {
1246
+ ...element,
1247
+ props: {
1248
+ ...element.props,
1249
+ rotation: object.angle,
1250
+ width: (((_a = element.props) == null ? void 0 : _a.width) ?? 0) * object.scaleX,
1251
+ height: (((_b = element.props) == null ? void 0 : _b.height) ?? 0) * object.scaleY,
1252
+ x,
1253
+ y
1254
+ }
1255
+ }
1256
+ };
1257
+ }
1258
+ };
1259
+ const RectElement = {
1260
+ name: ELEMENT_TYPES.RECT,
1261
+ async add(params) {
1262
+ const { element, index, canvas, canvasMetadata } = params;
1263
+ await addRectElement({
1264
+ element,
1265
+ index,
1266
+ canvas,
1267
+ canvasMetadata
1268
+ });
1269
+ },
1270
+ updateFromFabricObject(object, element, context) {
1271
+ var _a, _b;
1272
+ const { x, y } = convertToVideoPosition(
1273
+ object.left,
1274
+ object.top,
1275
+ context.canvasMetadata,
1276
+ context.videoSize
1277
+ );
1278
+ return {
1279
+ element: {
1280
+ ...element,
1281
+ props: {
1282
+ ...element.props,
1283
+ rotation: object.angle,
1284
+ width: (((_a = element.props) == null ? void 0 : _a.width) ?? 0) * object.scaleX,
1285
+ height: (((_b = element.props) == null ? void 0 : _b.height) ?? 0) * object.scaleY,
1286
+ x,
1287
+ y
1288
+ }
1289
+ }
1290
+ };
1291
+ }
1292
+ };
1293
+ const CircleElement = {
1294
+ name: ELEMENT_TYPES.CIRCLE,
1295
+ async add(params) {
1296
+ const { element, index, canvas, canvasMetadata } = params;
1297
+ await addCircleElement({
1298
+ element,
1299
+ index,
1300
+ canvas,
1301
+ canvasMetadata
1302
+ });
1303
+ },
1304
+ updateFromFabricObject(object, element, context) {
1305
+ var _a;
1306
+ const { x, y } = convertToVideoPosition(
1307
+ object.left,
1308
+ object.top,
1309
+ context.canvasMetadata,
1310
+ context.videoSize
1311
+ );
1312
+ const radius = Number(
1313
+ ((((_a = element.props) == null ? void 0 : _a.radius) ?? 0) * object.scaleX).toFixed(2)
1314
+ );
1315
+ return {
1316
+ element: {
1317
+ ...element,
1318
+ props: {
1319
+ ...element.props,
1320
+ rotation: object.angle,
1321
+ radius,
1322
+ height: radius * 2,
1323
+ width: radius * 2,
1324
+ x,
1325
+ y
1326
+ }
1327
+ }
1328
+ };
1329
+ }
1330
+ };
1331
+ const TextElement = {
1332
+ name: ELEMENT_TYPES.TEXT,
1333
+ async add(params) {
1334
+ const { element, index, canvas, canvasMetadata } = params;
1335
+ await addTextElement({
1336
+ element,
1337
+ index,
1338
+ canvas,
1339
+ canvasMetadata
1340
+ });
1341
+ },
1342
+ updateFromFabricObject(object, element, context) {
1343
+ const { x, y } = convertToVideoPosition(
1344
+ object.left,
1345
+ object.top,
1346
+ context.canvasMetadata,
1347
+ context.videoSize
1348
+ );
1349
+ return {
1350
+ element: {
1351
+ ...element,
1352
+ props: {
1353
+ ...element.props,
1354
+ rotation: object.angle,
1355
+ x,
1356
+ y
1357
+ }
1358
+ }
1359
+ };
1360
+ }
1361
+ };
1362
+ const CaptionElement = {
1363
+ name: ELEMENT_TYPES.CAPTION,
1364
+ async add(params) {
1365
+ const { element, index, canvas, captionProps, canvasMetadata } = params;
1366
+ await addCaptionElement({
1367
+ element,
1368
+ index,
1369
+ canvas,
1370
+ captionProps: captionProps ?? {},
1371
+ canvasMetadata
1372
+ });
1373
+ },
1374
+ updateFromFabricObject(object, element, context) {
1375
+ var _a;
1376
+ const { x, y } = convertToVideoPosition(
1377
+ object.left,
1378
+ object.top,
1379
+ context.canvasMetadata,
1380
+ context.videoSize
1381
+ );
1382
+ if ((_a = context.captionPropsRef.current) == null ? void 0 : _a.applyToAll) {
1383
+ return {
1384
+ element,
1385
+ operation: CANVAS_OPERATIONS.CAPTION_PROPS_UPDATED,
1386
+ payload: {
1387
+ element,
1388
+ props: {
1389
+ ...context.captionPropsRef.current,
1390
+ x,
1391
+ y
1392
+ }
1393
+ }
1394
+ };
1395
+ }
1396
+ return {
1397
+ element: {
1398
+ ...element,
1399
+ props: {
1400
+ ...element.props,
1401
+ x,
1402
+ y
1403
+ }
1404
+ }
1405
+ };
1406
+ }
1407
+ };
1408
+ const WatermarkElement = {
1409
+ name: "watermark",
1410
+ async add(params) {
1411
+ const { element, index, canvas, canvasMetadata, watermarkPropsRef } = params;
1412
+ if (element.type === ELEMENT_TYPES.TEXT) {
1413
+ if (watermarkPropsRef) watermarkPropsRef.current = element.props;
1414
+ await addTextElement({
1415
+ element,
1416
+ index,
1417
+ canvas,
1418
+ canvasMetadata
1419
+ });
1420
+ } else if (element.type === ELEMENT_TYPES.IMAGE) {
1421
+ await addImageElement({
1422
+ element,
1423
+ index,
1424
+ canvas,
1425
+ canvasMetadata
1426
+ });
1427
+ }
1428
+ },
1429
+ updateFromFabricObject(object, element, context) {
1430
+ const { x, y } = convertToVideoPosition(
1431
+ object.left,
1432
+ object.top,
1433
+ context.canvasMetadata,
1434
+ context.videoSize
1435
+ );
1436
+ const rotation = object.angle != null ? object.angle : void 0;
1437
+ const opacity = object.opacity != null ? object.opacity : void 0;
1438
+ const baseProps = element.type === ELEMENT_TYPES.TEXT ? context.watermarkPropsRef.current ?? element.props ?? {} : { ...element.props };
1439
+ const props = element.type === ELEMENT_TYPES.IMAGE && (object.scaleX != null || object.scaleY != null) ? {
1440
+ ...baseProps,
1441
+ width: baseProps.width != null && object.scaleX != null ? baseProps.width * object.scaleX : baseProps.width,
1442
+ height: baseProps.height != null && object.scaleY != null ? baseProps.height * object.scaleY : baseProps.height
1443
+ } : baseProps;
1444
+ const payload = {
1445
+ position: { x, y },
1446
+ ...rotation != null && { rotation },
1447
+ ...opacity != null && { opacity },
1448
+ ...Object.keys(props).length > 0 && { props }
1449
+ };
1450
+ return {
1451
+ element: { ...element, props: { ...element.props, x, y, rotation, opacity, ...props } },
1452
+ operation: CANVAS_OPERATIONS.WATERMARK_UPDATED,
1453
+ payload
1454
+ };
1455
+ }
1456
+ };
1457
+ class ElementController {
1458
+ constructor() {
1459
+ __publicField(this, "elements", /* @__PURE__ */ new Map());
1460
+ }
1461
+ register(handler) {
1462
+ this.elements.set(handler.name, handler);
1463
+ }
1464
+ get(name) {
1465
+ return this.elements.get(name);
1466
+ }
1467
+ list() {
1468
+ return Array.from(this.elements.keys());
1469
+ }
1470
+ }
1471
+ const elementController = new ElementController();
1472
+ function registerElements() {
1473
+ elementController.register(VideoElement);
1474
+ elementController.register(ImageElement);
1475
+ elementController.register(RectElement);
1476
+ elementController.register(CircleElement);
1477
+ elementController.register(TextElement);
1478
+ elementController.register(CaptionElement);
1479
+ elementController.register(WatermarkElement);
1480
+ }
1481
+ registerElements();
1010
1482
  const useTwickCanvas = ({
1011
1483
  onCanvasReady,
1012
1484
  onCanvasOperation
1013
1485
  }) => {
1014
1486
  const [twickCanvas, setTwickCanvas] = react.useState(null);
1015
1487
  const elementMap = react.useRef({});
1488
+ const watermarkPropsRef = react.useRef(null);
1016
1489
  const elementFrameMap = react.useRef({});
1017
1490
  const twickCanvasRef = react.useRef(null);
1018
1491
  const videoSizeRef = react.useRef({ width: 1, height: 1 });
@@ -1112,141 +1585,33 @@ const useTwickCanvas = ({
1112
1585
  case "scale":
1113
1586
  case "scaleX":
1114
1587
  case "scaleY":
1115
- case "rotate":
1116
- const { x, y } = convertToVideoPosition(
1117
- object.left,
1118
- object.top,
1119
- canvasMetadataRef.current,
1120
- videoSizeRef.current
1588
+ case "rotate": {
1589
+ const currentElement = elementMap.current[elementId];
1590
+ const handler = elementController.get(
1591
+ elementId === "e-watermark" ? "watermark" : currentElement == null ? void 0 : currentElement.type
1121
1592
  );
1122
- if (elementMap.current[elementId].type === "caption") {
1123
- if ((_c = captionPropsRef.current) == null ? void 0 : _c.applyToAll) {
1124
- onCanvasOperation == null ? void 0 : onCanvasOperation(CANVAS_OPERATIONS.CAPTION_PROPS_UPDATED, {
1125
- element: elementMap.current[elementId],
1126
- props: {
1127
- ...captionPropsRef.current,
1128
- x,
1129
- y
1130
- }
1131
- });
1132
- } else {
1133
- elementMap.current[elementId] = {
1134
- ...elementMap.current[elementId],
1135
- props: {
1136
- ...elementMap.current[elementId].props,
1137
- x,
1138
- y
1139
- }
1140
- };
1141
- onCanvasOperation == null ? void 0 : onCanvasOperation(
1142
- CANVAS_OPERATIONS.ITEM_UPDATED,
1143
- elementMap.current[elementId]
1144
- );
1145
- }
1146
- } else {
1147
- if ((object == null ? void 0 : object.type) === "group") {
1148
- const currentFrameEffect = elementFrameMap.current[elementId];
1149
- let updatedFrameSize;
1150
- if (currentFrameEffect) {
1151
- updatedFrameSize = [
1152
- currentFrameEffect.props.frameSize[0] * object.scaleX,
1153
- currentFrameEffect.props.frameSize[1] * object.scaleY
1154
- ];
1155
- } else {
1156
- updatedFrameSize = [
1157
- elementMap.current[elementId].frame.size[0] * object.scaleX,
1158
- elementMap.current[elementId].frame.size[1] * object.scaleY
1159
- ];
1160
- }
1161
- if (currentFrameEffect) {
1162
- elementMap.current[elementId] = {
1163
- ...elementMap.current[elementId],
1164
- frameEffects: (elementMap.current[elementId].frameEffects || []).map(
1165
- (frameEffect) => frameEffect.id === (currentFrameEffect == null ? void 0 : currentFrameEffect.id) ? {
1166
- ...frameEffect,
1167
- props: {
1168
- ...frameEffect.props,
1169
- framePosition: {
1170
- x,
1171
- y
1172
- },
1173
- frameSize: updatedFrameSize
1174
- }
1175
- } : frameEffect
1176
- )
1177
- };
1178
- elementFrameMap.current[elementId] = {
1179
- ...elementFrameMap.current[elementId],
1180
- framePosition: {
1181
- x,
1182
- y
1183
- },
1184
- frameSize: updatedFrameSize
1185
- };
1186
- } else {
1187
- elementMap.current[elementId] = {
1188
- ...elementMap.current[elementId],
1189
- frame: {
1190
- ...elementMap.current[elementId].frame,
1191
- rotation: object.angle,
1192
- size: updatedFrameSize,
1193
- x,
1194
- y
1195
- }
1196
- };
1197
- }
1198
- } else {
1199
- if ((object == null ? void 0 : object.type) === "text") {
1200
- elementMap.current[elementId] = {
1201
- ...elementMap.current[elementId],
1202
- props: {
1203
- ...elementMap.current[elementId].props,
1204
- rotation: object.angle,
1205
- x,
1206
- y
1207
- }
1208
- };
1209
- } else if ((object == null ? void 0 : object.type) === "circle") {
1210
- const radius = Number(
1211
- (elementMap.current[elementId].props.radius * object.scaleX).toFixed(2)
1212
- );
1213
- elementMap.current[elementId] = {
1214
- ...elementMap.current[elementId],
1215
- props: {
1216
- ...elementMap.current[elementId].props,
1217
- rotation: object.angle,
1218
- radius,
1219
- height: radius * 2,
1220
- width: radius * 2,
1221
- x,
1222
- y
1223
- }
1224
- };
1225
- } else {
1226
- elementMap.current[elementId] = {
1227
- ...elementMap.current[elementId],
1228
- props: {
1229
- ...elementMap.current[elementId].props,
1230
- rotation: object.angle,
1231
- width: elementMap.current[elementId].props.width * object.scaleX,
1232
- height: elementMap.current[elementId].props.height * object.scaleY,
1233
- x,
1234
- y
1235
- }
1236
- };
1237
- }
1238
- }
1593
+ const result = (_c = handler == null ? void 0 : handler.updateFromFabricObject) == null ? void 0 : _c.call(handler, object, currentElement ?? { id: elementId, type: "text", props: {} }, {
1594
+ canvasMetadata: canvasMetadataRef.current,
1595
+ videoSize: videoSizeRef.current,
1596
+ elementFrameMapRef: elementFrameMap,
1597
+ captionPropsRef,
1598
+ watermarkPropsRef
1599
+ });
1600
+ if (result) {
1601
+ elementMap.current[elementId] = result.element;
1239
1602
  onCanvasOperation == null ? void 0 : onCanvasOperation(
1240
- CANVAS_OPERATIONS.ITEM_UPDATED,
1241
- elementMap.current[elementId]
1603
+ result.operation ?? CANVAS_OPERATIONS.ITEM_UPDATED,
1604
+ result.payload ?? result.element
1242
1605
  );
1243
1606
  }
1244
1607
  break;
1608
+ }
1245
1609
  }
1246
1610
  }
1247
1611
  };
1248
1612
  const setCanvasElements = async ({
1249
1613
  elements,
1614
+ watermark,
1250
1615
  seekTime = 0,
1251
1616
  captionProps,
1252
1617
  cleanAndAdd = false
@@ -1277,6 +1642,11 @@ const useTwickCanvas = ({
1277
1642
  }
1278
1643
  })
1279
1644
  );
1645
+ if (watermark) {
1646
+ addWatermarkToCanvas({
1647
+ element: watermark
1648
+ });
1649
+ }
1280
1650
  reorderElementsByZIndex(twickCanvas);
1281
1651
  } catch {
1282
1652
  }
@@ -1288,97 +1658,53 @@ const useTwickCanvas = ({
1288
1658
  seekTime,
1289
1659
  captionProps
1290
1660
  }) => {
1291
- var _a, _b;
1292
1661
  if (!twickCanvas) return;
1293
- switch (element.type) {
1294
- case ELEMENT_TYPES.VIDEO:
1295
- const currentFrameEffect = getCurrentFrameEffect(
1296
- element,
1297
- seekTime || 0
1298
- );
1299
- elementFrameMap.current[element.id] = currentFrameEffect;
1300
- 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);
1301
- await addVideoElement({
1302
- element,
1303
- index,
1304
- canvas: twickCanvas,
1305
- canvasMetadata: canvasMetadataRef.current,
1306
- currentFrameEffect,
1307
- snapTime
1308
- });
1309
- if (element.timelineType === "scene") {
1310
- await addBackgroundColor({
1311
- element,
1312
- index,
1313
- canvas: twickCanvas,
1314
- canvasMetadata: canvasMetadataRef.current
1315
- });
1316
- }
1317
- break;
1318
- case ELEMENT_TYPES.IMAGE:
1319
- await addImageElement({
1320
- element,
1321
- index,
1322
- canvas: twickCanvas,
1323
- canvasMetadata: canvasMetadataRef.current
1324
- });
1325
- if (element.timelineType === "scene") {
1326
- await addBackgroundColor({
1327
- element,
1328
- index,
1329
- canvas: twickCanvas,
1330
- canvasMetadata: canvasMetadataRef.current
1331
- });
1332
- }
1333
- break;
1334
- case ELEMENT_TYPES.RECT:
1335
- await addRectElement({
1336
- element,
1337
- index,
1338
- canvas: twickCanvas,
1339
- canvasMetadata: canvasMetadataRef.current
1340
- });
1341
- break;
1342
- case ELEMENT_TYPES.CIRCLE:
1343
- await addCircleElement({
1344
- element,
1345
- index,
1346
- canvas: twickCanvas,
1347
- canvasMetadata: canvasMetadataRef.current
1348
- });
1349
- break;
1350
- case ELEMENT_TYPES.TEXT:
1351
- await addTextElement({
1352
- element,
1353
- index,
1354
- canvas: twickCanvas,
1355
- canvasMetadata: canvasMetadataRef.current
1356
- });
1357
- break;
1358
- case ELEMENT_TYPES.CAPTION:
1359
- await addCaptionElement({
1360
- element,
1361
- index,
1362
- canvas: twickCanvas,
1363
- captionProps,
1364
- canvasMetadata: canvasMetadataRef.current
1365
- });
1366
- break;
1662
+ const handler = elementController.get(element.type);
1663
+ if (handler) {
1664
+ await handler.add({
1665
+ element,
1666
+ index,
1667
+ canvas: twickCanvas,
1668
+ canvasMetadata: canvasMetadataRef.current,
1669
+ seekTime,
1670
+ captionProps: captionProps ?? null,
1671
+ elementFrameMapRef: elementFrameMap,
1672
+ getCurrentFrameEffect
1673
+ });
1367
1674
  }
1368
1675
  elementMap.current[element.id] = element;
1369
1676
  if (reorder) {
1370
1677
  reorderElementsByZIndex(twickCanvas);
1371
1678
  }
1372
1679
  };
1680
+ const addWatermarkToCanvas = ({
1681
+ element
1682
+ }) => {
1683
+ if (!twickCanvas) return;
1684
+ const handler = elementController.get("watermark");
1685
+ if (handler) {
1686
+ handler.add({
1687
+ element,
1688
+ index: Object.keys(elementMap.current).length,
1689
+ canvas: twickCanvas,
1690
+ canvasMetadata: canvasMetadataRef.current,
1691
+ watermarkPropsRef
1692
+ });
1693
+ elementMap.current[element.id] = element;
1694
+ }
1695
+ };
1373
1696
  return {
1374
1697
  twickCanvas,
1375
1698
  buildCanvas,
1376
1699
  onVideoSizeChange,
1700
+ addWatermarkToCanvas,
1377
1701
  addElementToCanvas,
1378
1702
  setCanvasElements
1379
1703
  };
1380
1704
  };
1381
1705
  exports.CANVAS_OPERATIONS = CANVAS_OPERATIONS;
1706
+ exports.ELEMENT_TYPES = ELEMENT_TYPES;
1707
+ exports.ElementController = ElementController;
1382
1708
  exports.addBackgroundColor = addBackgroundColor;
1383
1709
  exports.addCaptionElement = addCaptionElement;
1384
1710
  exports.addImageElement = addImageElement;
@@ -1389,6 +1715,7 @@ exports.convertToCanvasPosition = convertToCanvasPosition;
1389
1715
  exports.convertToVideoPosition = convertToVideoPosition;
1390
1716
  exports.createCanvas = createCanvas;
1391
1717
  exports.disabledControl = disabledControl;
1718
+ exports.elementController = elementController;
1392
1719
  exports.getCurrentFrameEffect = getCurrentFrameEffect;
1393
1720
  exports.reorderElementsByZIndex = reorderElementsByZIndex;
1394
1721
  exports.rotateControl = rotateControl;