@twick/canvas 0.15.14 → 0.15.15
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 +311 -79
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +312 -80
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -53,7 +53,11 @@ const CANVAS_OPERATIONS = {
|
|
|
53
53
|
/** Caption properties have been updated */
|
|
54
54
|
CAPTION_PROPS_UPDATED: "CAPTION_PROPS_UPDATED",
|
|
55
55
|
/** Watermark has been updated */
|
|
56
|
-
WATERMARK_UPDATED: "WATERMARK_UPDATED"
|
|
56
|
+
WATERMARK_UPDATED: "WATERMARK_UPDATED",
|
|
57
|
+
/** A new element was added via drop on canvas; payload is { element } */
|
|
58
|
+
ADDED_NEW_ELEMENT: "ADDED_NEW_ELEMENT",
|
|
59
|
+
/** Z-order changed (bring to front / send to back). Payload is { elementId, direction }. Timeline should reorder tracks. */
|
|
60
|
+
Z_ORDER_CHANGED: "Z_ORDER_CHANGED"
|
|
57
61
|
};
|
|
58
62
|
const ELEMENT_TYPES = {
|
|
59
63
|
/** Text element type */
|
|
@@ -163,6 +167,49 @@ const reorderElementsByZIndex = (canvas) => {
|
|
|
163
167
|
objects.forEach((obj) => canvas.add(obj));
|
|
164
168
|
canvas.renderAll();
|
|
165
169
|
};
|
|
170
|
+
const changeZOrder = (canvas, elementId, direction) => {
|
|
171
|
+
var _a, _b;
|
|
172
|
+
if (!canvas) return null;
|
|
173
|
+
const objects = canvas.getObjects();
|
|
174
|
+
const sorted = [...objects].sort((a, b) => (a.zIndex || 0) - (b.zIndex || 0));
|
|
175
|
+
const idx = sorted.findIndex((obj2) => {
|
|
176
|
+
var _a2;
|
|
177
|
+
return ((_a2 = obj2.get) == null ? void 0 : _a2.call(obj2, "id")) === elementId;
|
|
178
|
+
});
|
|
179
|
+
if (idx < 0) return null;
|
|
180
|
+
const minZ = ((_a = sorted[0]) == null ? void 0 : _a.zIndex) ?? 0;
|
|
181
|
+
const maxZ = ((_b = sorted[sorted.length - 1]) == null ? void 0 : _b.zIndex) ?? 0;
|
|
182
|
+
const obj = sorted[idx];
|
|
183
|
+
if (direction === "front") {
|
|
184
|
+
obj.set("zIndex", maxZ + 1);
|
|
185
|
+
reorderElementsByZIndex(canvas);
|
|
186
|
+
return maxZ + 1;
|
|
187
|
+
}
|
|
188
|
+
if (direction === "back") {
|
|
189
|
+
obj.set("zIndex", minZ - 1);
|
|
190
|
+
reorderElementsByZIndex(canvas);
|
|
191
|
+
return minZ - 1;
|
|
192
|
+
}
|
|
193
|
+
if (direction === "forward" && idx < sorted.length - 1) {
|
|
194
|
+
const next = sorted[idx + 1];
|
|
195
|
+
const myZ = obj.zIndex ?? idx;
|
|
196
|
+
const nextZ = next.zIndex ?? idx + 1;
|
|
197
|
+
obj.set("zIndex", nextZ);
|
|
198
|
+
next.set("zIndex", myZ);
|
|
199
|
+
reorderElementsByZIndex(canvas);
|
|
200
|
+
return nextZ;
|
|
201
|
+
}
|
|
202
|
+
if (direction === "backward" && idx > 0) {
|
|
203
|
+
const prev = sorted[idx - 1];
|
|
204
|
+
const myZ = obj.zIndex ?? idx;
|
|
205
|
+
const prevZ = prev.zIndex ?? idx - 1;
|
|
206
|
+
obj.set("zIndex", prevZ);
|
|
207
|
+
prev.set("zIndex", myZ);
|
|
208
|
+
reorderElementsByZIndex(canvas);
|
|
209
|
+
return prevZ;
|
|
210
|
+
}
|
|
211
|
+
return obj.zIndex ?? idx;
|
|
212
|
+
};
|
|
166
213
|
const getCanvasContext = (canvas) => {
|
|
167
214
|
var _a, _b, _c, _d;
|
|
168
215
|
if (!canvas || !((_b = (_a = canvas.elements) == null ? void 0 : _a.lower) == null ? void 0 : _b.ctx)) return;
|
|
@@ -183,12 +230,31 @@ const convertToCanvasPosition = (x, y, canvasMetadata) => {
|
|
|
183
230
|
y: y * canvasMetadata.scaleY + canvasMetadata.height / 2
|
|
184
231
|
};
|
|
185
232
|
};
|
|
233
|
+
const getObjectCanvasCenter = (obj) => {
|
|
234
|
+
if (obj.getCenterPoint) {
|
|
235
|
+
const p = obj.getCenterPoint();
|
|
236
|
+
return { x: p.x, y: p.y };
|
|
237
|
+
}
|
|
238
|
+
return { x: obj.left ?? 0, y: obj.top ?? 0 };
|
|
239
|
+
};
|
|
240
|
+
const getObjectCanvasAngle = (obj) => {
|
|
241
|
+
if (typeof obj.getTotalAngle === "function") {
|
|
242
|
+
return obj.getTotalAngle();
|
|
243
|
+
}
|
|
244
|
+
return obj.angle ?? 0;
|
|
245
|
+
};
|
|
186
246
|
const convertToVideoPosition = (x, y, canvasMetadata, videoSize) => {
|
|
187
247
|
return {
|
|
188
248
|
x: Number((x / canvasMetadata.scaleX - videoSize.width / 2).toFixed(2)),
|
|
189
249
|
y: Number((y / canvasMetadata.scaleY - videoSize.height / 2).toFixed(2))
|
|
190
250
|
};
|
|
191
251
|
};
|
|
252
|
+
const convertToVideoDimensions = (widthCanvas, heightCanvas, canvasMetadata) => {
|
|
253
|
+
return {
|
|
254
|
+
width: Number((widthCanvas / canvasMetadata.scaleX).toFixed(2)),
|
|
255
|
+
height: Number((heightCanvas / canvasMetadata.scaleY).toFixed(2))
|
|
256
|
+
};
|
|
257
|
+
};
|
|
192
258
|
const getCurrentFrameEffect = (item, seekTime) => {
|
|
193
259
|
var _a;
|
|
194
260
|
let currentFrameEffect;
|
|
@@ -722,7 +788,8 @@ const setImageProps = ({
|
|
|
722
788
|
img,
|
|
723
789
|
element,
|
|
724
790
|
index,
|
|
725
|
-
canvasMetadata
|
|
791
|
+
canvasMetadata,
|
|
792
|
+
lockAspectRatio = true
|
|
726
793
|
}) => {
|
|
727
794
|
var _a, _b, _c, _d, _e;
|
|
728
795
|
const width = (((_a = element.props) == null ? void 0 : _a.width) || 0) * canvasMetadata.scaleX || canvasMetadata.width;
|
|
@@ -742,13 +809,15 @@ const setImageProps = ({
|
|
|
742
809
|
img.set("selectable", true);
|
|
743
810
|
img.set("hasControls", true);
|
|
744
811
|
img.set("touchAction", "all");
|
|
812
|
+
img.set("lockUniScaling", lockAspectRatio);
|
|
745
813
|
};
|
|
746
814
|
const addCaptionElement = ({
|
|
747
815
|
element,
|
|
748
816
|
index,
|
|
749
817
|
canvas,
|
|
750
818
|
captionProps,
|
|
751
|
-
canvasMetadata
|
|
819
|
+
canvasMetadata,
|
|
820
|
+
lockAspectRatio = false
|
|
752
821
|
}) => {
|
|
753
822
|
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, _D, _E, _F, _G, _H, _I, _J;
|
|
754
823
|
const { x, y } = convertToCanvasPosition(
|
|
@@ -787,6 +856,7 @@ const addCaptionElement = ({
|
|
|
787
856
|
});
|
|
788
857
|
caption.set("id", element.id);
|
|
789
858
|
caption.set("zIndex", index);
|
|
859
|
+
caption.set("lockUniScaling", lockAspectRatio);
|
|
790
860
|
caption.controls.mt = disabledControl;
|
|
791
861
|
caption.controls.mb = disabledControl;
|
|
792
862
|
caption.controls.ml = disabledControl;
|
|
@@ -834,7 +904,8 @@ const addImageElement = async ({
|
|
|
834
904
|
index,
|
|
835
905
|
canvas,
|
|
836
906
|
canvasMetadata,
|
|
837
|
-
currentFrameEffect
|
|
907
|
+
currentFrameEffect,
|
|
908
|
+
lockAspectRatio = true
|
|
838
909
|
}) => {
|
|
839
910
|
try {
|
|
840
911
|
const img = await fabric.FabricImage.fromURL(imageUrl || element.props.src || "");
|
|
@@ -843,7 +914,7 @@ const addImageElement = async ({
|
|
|
843
914
|
originY: "center",
|
|
844
915
|
lockMovementX: false,
|
|
845
916
|
lockMovementY: false,
|
|
846
|
-
lockUniScaling:
|
|
917
|
+
lockUniScaling: lockAspectRatio,
|
|
847
918
|
hasControls: false,
|
|
848
919
|
selectable: false
|
|
849
920
|
});
|
|
@@ -854,10 +925,11 @@ const addImageElement = async ({
|
|
|
854
925
|
index,
|
|
855
926
|
canvas,
|
|
856
927
|
canvasMetadata,
|
|
857
|
-
currentFrameEffect
|
|
928
|
+
currentFrameEffect,
|
|
929
|
+
lockAspectRatio
|
|
858
930
|
});
|
|
859
931
|
} else {
|
|
860
|
-
setImageProps({ img, element, index, canvasMetadata });
|
|
932
|
+
setImageProps({ img, element, index, canvasMetadata, lockAspectRatio });
|
|
861
933
|
canvas.add(img);
|
|
862
934
|
return img;
|
|
863
935
|
}
|
|
@@ -870,7 +942,8 @@ const addMediaGroup = ({
|
|
|
870
942
|
index,
|
|
871
943
|
canvas,
|
|
872
944
|
canvasMetadata,
|
|
873
|
-
currentFrameEffect
|
|
945
|
+
currentFrameEffect,
|
|
946
|
+
lockAspectRatio = true
|
|
874
947
|
}) => {
|
|
875
948
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
876
949
|
let frameSize;
|
|
@@ -959,6 +1032,7 @@ const addMediaGroup = ({
|
|
|
959
1032
|
group.controls.mtr = rotateControl;
|
|
960
1033
|
group.set("id", element.id);
|
|
961
1034
|
group.set("zIndex", index);
|
|
1035
|
+
group.set("lockUniScaling", lockAspectRatio);
|
|
962
1036
|
canvas.add(group);
|
|
963
1037
|
return group;
|
|
964
1038
|
};
|
|
@@ -966,7 +1040,8 @@ const addRectElement = ({
|
|
|
966
1040
|
element,
|
|
967
1041
|
index,
|
|
968
1042
|
canvas,
|
|
969
|
-
canvasMetadata
|
|
1043
|
+
canvasMetadata,
|
|
1044
|
+
lockAspectRatio = false
|
|
970
1045
|
}) => {
|
|
971
1046
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
972
1047
|
const { x, y } = convertToCanvasPosition(
|
|
@@ -1004,6 +1079,7 @@ const addRectElement = ({
|
|
|
1004
1079
|
});
|
|
1005
1080
|
rect.set("id", element.id);
|
|
1006
1081
|
rect.set("zIndex", index);
|
|
1082
|
+
rect.set("lockUniScaling", lockAspectRatio);
|
|
1007
1083
|
rect.controls.mtr = rotateControl;
|
|
1008
1084
|
canvas.add(rect);
|
|
1009
1085
|
return rect;
|
|
@@ -1012,9 +1088,10 @@ const addCircleElement = ({
|
|
|
1012
1088
|
element,
|
|
1013
1089
|
index,
|
|
1014
1090
|
canvas,
|
|
1015
|
-
canvasMetadata
|
|
1091
|
+
canvasMetadata,
|
|
1092
|
+
lockAspectRatio = true
|
|
1016
1093
|
}) => {
|
|
1017
|
-
var _a, _b, _c, _d, _e, _f;
|
|
1094
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1018
1095
|
const { x, y } = convertToCanvasPosition(
|
|
1019
1096
|
((_a = element.props) == null ? void 0 : _a.x) || 0,
|
|
1020
1097
|
((_b = element.props) == null ? void 0 : _b.y) || 0,
|
|
@@ -1030,7 +1107,9 @@ const addCircleElement = ({
|
|
|
1030
1107
|
stroke: ((_e = element.props) == null ? void 0 : _e.stroke) || "#000000",
|
|
1031
1108
|
strokeWidth: (((_f = element.props) == null ? void 0 : _f.lineWidth) || 0) * canvasMetadata.scaleX,
|
|
1032
1109
|
originX: "center",
|
|
1033
|
-
originY: "center"
|
|
1110
|
+
originY: "center",
|
|
1111
|
+
// Respect element opacity (0–1). Defaults to fully opaque.
|
|
1112
|
+
opacity: ((_g = element.props) == null ? void 0 : _g.opacity) ?? 1
|
|
1034
1113
|
});
|
|
1035
1114
|
circle.controls.mt = disabledControl;
|
|
1036
1115
|
circle.controls.mb = disabledControl;
|
|
@@ -1039,6 +1118,7 @@ const addCircleElement = ({
|
|
|
1039
1118
|
circle.controls.mtr = disabledControl;
|
|
1040
1119
|
circle.set("id", element.id);
|
|
1041
1120
|
circle.set("zIndex", index);
|
|
1121
|
+
circle.set("lockUniScaling", lockAspectRatio);
|
|
1042
1122
|
canvas.add(circle);
|
|
1043
1123
|
return circle;
|
|
1044
1124
|
};
|
|
@@ -1108,19 +1188,23 @@ const VideoElement = {
|
|
|
1108
1188
|
}
|
|
1109
1189
|
},
|
|
1110
1190
|
updateFromFabricObject(object, element, context) {
|
|
1191
|
+
const canvasCenter = getObjectCanvasCenter(object);
|
|
1111
1192
|
const { x, y } = convertToVideoPosition(
|
|
1112
|
-
|
|
1113
|
-
|
|
1193
|
+
canvasCenter.x,
|
|
1194
|
+
canvasCenter.y,
|
|
1114
1195
|
context.canvasMetadata,
|
|
1115
1196
|
context.videoSize
|
|
1116
1197
|
);
|
|
1198
|
+
const scaledW = (object.width ?? 0) * (object.scaleX ?? 1);
|
|
1199
|
+
const scaledH = (object.height ?? 0) * (object.scaleY ?? 1);
|
|
1200
|
+
const { width: fw, height: fh } = convertToVideoDimensions(
|
|
1201
|
+
scaledW,
|
|
1202
|
+
scaledH,
|
|
1203
|
+
context.canvasMetadata
|
|
1204
|
+
);
|
|
1205
|
+
const updatedFrameSize = [fw, fh];
|
|
1117
1206
|
const currentFrameEffect = context.elementFrameMapRef.current[element.id];
|
|
1118
|
-
let updatedFrameSize;
|
|
1119
1207
|
if (currentFrameEffect) {
|
|
1120
|
-
updatedFrameSize = [
|
|
1121
|
-
currentFrameEffect.props.frameSize[0] * object.scaleX,
|
|
1122
|
-
currentFrameEffect.props.frameSize[1] * object.scaleY
|
|
1123
|
-
];
|
|
1124
1208
|
context.elementFrameMapRef.current[element.id] = {
|
|
1125
1209
|
...currentFrameEffect,
|
|
1126
1210
|
props: {
|
|
@@ -1146,16 +1230,12 @@ const VideoElement = {
|
|
|
1146
1230
|
};
|
|
1147
1231
|
}
|
|
1148
1232
|
const frame = element.frame;
|
|
1149
|
-
updatedFrameSize = [
|
|
1150
|
-
(frame.size[0] ?? 0) * object.scaleX,
|
|
1151
|
-
(frame.size[1] ?? 0) * object.scaleY
|
|
1152
|
-
];
|
|
1153
1233
|
return {
|
|
1154
1234
|
element: {
|
|
1155
1235
|
...element,
|
|
1156
1236
|
frame: {
|
|
1157
1237
|
...frame,
|
|
1158
|
-
rotation: object
|
|
1238
|
+
rotation: getObjectCanvasAngle(object),
|
|
1159
1239
|
size: updatedFrameSize,
|
|
1160
1240
|
x,
|
|
1161
1241
|
y
|
|
@@ -1167,12 +1247,14 @@ const VideoElement = {
|
|
|
1167
1247
|
const ImageElement = {
|
|
1168
1248
|
name: ELEMENT_TYPES.IMAGE,
|
|
1169
1249
|
async add(params) {
|
|
1170
|
-
|
|
1250
|
+
var _a;
|
|
1251
|
+
const { element, index, canvas, canvasMetadata, lockAspectRatio } = params;
|
|
1171
1252
|
await addImageElement({
|
|
1172
1253
|
element,
|
|
1173
1254
|
index,
|
|
1174
1255
|
canvas,
|
|
1175
|
-
canvasMetadata
|
|
1256
|
+
canvasMetadata,
|
|
1257
|
+
lockAspectRatio: lockAspectRatio ?? ((_a = element.props) == null ? void 0 : _a.lockAspectRatio)
|
|
1176
1258
|
});
|
|
1177
1259
|
if (element.timelineType === "scene") {
|
|
1178
1260
|
await addBackgroundColor({
|
|
@@ -1184,21 +1266,24 @@ const ImageElement = {
|
|
|
1184
1266
|
}
|
|
1185
1267
|
},
|
|
1186
1268
|
updateFromFabricObject(object, element, context) {
|
|
1187
|
-
|
|
1269
|
+
const canvasCenter = getObjectCanvasCenter(object);
|
|
1188
1270
|
const { x, y } = convertToVideoPosition(
|
|
1189
|
-
|
|
1190
|
-
|
|
1271
|
+
canvasCenter.x,
|
|
1272
|
+
canvasCenter.y,
|
|
1191
1273
|
context.canvasMetadata,
|
|
1192
1274
|
context.videoSize
|
|
1193
1275
|
);
|
|
1194
1276
|
const currentFrameEffect = context.elementFrameMapRef.current[element.id];
|
|
1195
1277
|
if (object.type === "group") {
|
|
1196
|
-
|
|
1278
|
+
const scaledW2 = (object.width ?? 0) * (object.scaleX ?? 1);
|
|
1279
|
+
const scaledH2 = (object.height ?? 0) * (object.scaleY ?? 1);
|
|
1280
|
+
const { width: fw, height: fh } = convertToVideoDimensions(
|
|
1281
|
+
scaledW2,
|
|
1282
|
+
scaledH2,
|
|
1283
|
+
context.canvasMetadata
|
|
1284
|
+
);
|
|
1285
|
+
const updatedFrameSize = [fw, fh];
|
|
1197
1286
|
if (currentFrameEffect) {
|
|
1198
|
-
updatedFrameSize = [
|
|
1199
|
-
currentFrameEffect.props.frameSize[0] * object.scaleX,
|
|
1200
|
-
currentFrameEffect.props.frameSize[1] * object.scaleY
|
|
1201
|
-
];
|
|
1202
1287
|
context.elementFrameMapRef.current[element.id] = {
|
|
1203
1288
|
...currentFrameEffect,
|
|
1204
1289
|
props: {
|
|
@@ -1210,6 +1295,15 @@ const ImageElement = {
|
|
|
1210
1295
|
return {
|
|
1211
1296
|
element: {
|
|
1212
1297
|
...element,
|
|
1298
|
+
// Keep the base frame in sync with the active frame effect
|
|
1299
|
+
// so visualizer `Rect {...element.frame}` reflects the same size/position.
|
|
1300
|
+
frame: element.frame ? {
|
|
1301
|
+
...element.frame,
|
|
1302
|
+
rotation: getObjectCanvasAngle(object),
|
|
1303
|
+
size: updatedFrameSize,
|
|
1304
|
+
x,
|
|
1305
|
+
y
|
|
1306
|
+
} : element.frame,
|
|
1213
1307
|
frameEffects: (element.frameEffects || []).map(
|
|
1214
1308
|
(fe) => fe.id === (currentFrameEffect == null ? void 0 : currentFrameEffect.id) ? {
|
|
1215
1309
|
...fe,
|
|
@@ -1224,16 +1318,12 @@ const ImageElement = {
|
|
|
1224
1318
|
};
|
|
1225
1319
|
}
|
|
1226
1320
|
const frame = element.frame;
|
|
1227
|
-
updatedFrameSize = [
|
|
1228
|
-
(frame.size[0] ?? 0) * object.scaleX,
|
|
1229
|
-
(frame.size[1] ?? 0) * object.scaleY
|
|
1230
|
-
];
|
|
1231
1321
|
return {
|
|
1232
1322
|
element: {
|
|
1233
1323
|
...element,
|
|
1234
1324
|
frame: {
|
|
1235
1325
|
...frame,
|
|
1236
|
-
rotation: object
|
|
1326
|
+
rotation: getObjectCanvasAngle(object),
|
|
1237
1327
|
size: updatedFrameSize,
|
|
1238
1328
|
x,
|
|
1239
1329
|
y
|
|
@@ -1241,14 +1331,21 @@ const ImageElement = {
|
|
|
1241
1331
|
}
|
|
1242
1332
|
};
|
|
1243
1333
|
}
|
|
1334
|
+
const scaledW = (object.width ?? 0) * (object.scaleX ?? 1);
|
|
1335
|
+
const scaledH = (object.height ?? 0) * (object.scaleY ?? 1);
|
|
1336
|
+
const { width, height } = convertToVideoDimensions(
|
|
1337
|
+
scaledW,
|
|
1338
|
+
scaledH,
|
|
1339
|
+
context.canvasMetadata
|
|
1340
|
+
);
|
|
1244
1341
|
return {
|
|
1245
1342
|
element: {
|
|
1246
1343
|
...element,
|
|
1247
1344
|
props: {
|
|
1248
1345
|
...element.props,
|
|
1249
|
-
rotation: object
|
|
1250
|
-
width
|
|
1251
|
-
height
|
|
1346
|
+
rotation: getObjectCanvasAngle(object),
|
|
1347
|
+
width,
|
|
1348
|
+
height,
|
|
1252
1349
|
x,
|
|
1253
1350
|
y
|
|
1254
1351
|
}
|
|
@@ -1259,19 +1356,22 @@ const ImageElement = {
|
|
|
1259
1356
|
const RectElement = {
|
|
1260
1357
|
name: ELEMENT_TYPES.RECT,
|
|
1261
1358
|
async add(params) {
|
|
1262
|
-
|
|
1359
|
+
var _a;
|
|
1360
|
+
const { element, index, canvas, canvasMetadata, lockAspectRatio } = params;
|
|
1263
1361
|
await addRectElement({
|
|
1264
1362
|
element,
|
|
1265
1363
|
index,
|
|
1266
1364
|
canvas,
|
|
1267
|
-
canvasMetadata
|
|
1365
|
+
canvasMetadata,
|
|
1366
|
+
lockAspectRatio: lockAspectRatio ?? ((_a = element.props) == null ? void 0 : _a.lockAspectRatio)
|
|
1268
1367
|
});
|
|
1269
1368
|
},
|
|
1270
1369
|
updateFromFabricObject(object, element, context) {
|
|
1271
1370
|
var _a, _b;
|
|
1371
|
+
const canvasCenter = getObjectCanvasCenter(object);
|
|
1272
1372
|
const { x, y } = convertToVideoPosition(
|
|
1273
|
-
|
|
1274
|
-
|
|
1373
|
+
canvasCenter.x,
|
|
1374
|
+
canvasCenter.y,
|
|
1275
1375
|
context.canvasMetadata,
|
|
1276
1376
|
context.videoSize
|
|
1277
1377
|
);
|
|
@@ -1280,7 +1380,7 @@ const RectElement = {
|
|
|
1280
1380
|
...element,
|
|
1281
1381
|
props: {
|
|
1282
1382
|
...element.props,
|
|
1283
|
-
rotation: object
|
|
1383
|
+
rotation: getObjectCanvasAngle(object),
|
|
1284
1384
|
width: (((_a = element.props) == null ? void 0 : _a.width) ?? 0) * object.scaleX,
|
|
1285
1385
|
height: (((_b = element.props) == null ? void 0 : _b.height) ?? 0) * object.scaleY,
|
|
1286
1386
|
x,
|
|
@@ -1293,36 +1393,41 @@ const RectElement = {
|
|
|
1293
1393
|
const CircleElement = {
|
|
1294
1394
|
name: ELEMENT_TYPES.CIRCLE,
|
|
1295
1395
|
async add(params) {
|
|
1296
|
-
|
|
1396
|
+
var _a;
|
|
1397
|
+
const { element, index, canvas, canvasMetadata, lockAspectRatio } = params;
|
|
1297
1398
|
await addCircleElement({
|
|
1298
1399
|
element,
|
|
1299
1400
|
index,
|
|
1300
1401
|
canvas,
|
|
1301
|
-
canvasMetadata
|
|
1402
|
+
canvasMetadata,
|
|
1403
|
+
lockAspectRatio: lockAspectRatio ?? ((_a = element.props) == null ? void 0 : _a.lockAspectRatio)
|
|
1302
1404
|
});
|
|
1303
1405
|
},
|
|
1304
1406
|
updateFromFabricObject(object, element, context) {
|
|
1305
|
-
var _a;
|
|
1407
|
+
var _a, _b;
|
|
1408
|
+
const canvasCenter = getObjectCanvasCenter(object);
|
|
1306
1409
|
const { x, y } = convertToVideoPosition(
|
|
1307
|
-
|
|
1308
|
-
|
|
1410
|
+
canvasCenter.x,
|
|
1411
|
+
canvasCenter.y,
|
|
1309
1412
|
context.canvasMetadata,
|
|
1310
1413
|
context.videoSize
|
|
1311
1414
|
);
|
|
1312
1415
|
const radius = Number(
|
|
1313
1416
|
((((_a = element.props) == null ? void 0 : _a.radius) ?? 0) * object.scaleX).toFixed(2)
|
|
1314
1417
|
);
|
|
1418
|
+
const opacity = object.opacity != null ? object.opacity : (_b = element.props) == null ? void 0 : _b.opacity;
|
|
1315
1419
|
return {
|
|
1316
1420
|
element: {
|
|
1317
1421
|
...element,
|
|
1318
1422
|
props: {
|
|
1319
1423
|
...element.props,
|
|
1320
|
-
rotation: object
|
|
1424
|
+
rotation: getObjectCanvasAngle(object),
|
|
1321
1425
|
radius,
|
|
1322
1426
|
height: radius * 2,
|
|
1323
1427
|
width: radius * 2,
|
|
1324
1428
|
x,
|
|
1325
|
-
y
|
|
1429
|
+
y,
|
|
1430
|
+
...opacity != null && { opacity }
|
|
1326
1431
|
}
|
|
1327
1432
|
}
|
|
1328
1433
|
};
|
|
@@ -1340,9 +1445,10 @@ const TextElement = {
|
|
|
1340
1445
|
});
|
|
1341
1446
|
},
|
|
1342
1447
|
updateFromFabricObject(object, element, context) {
|
|
1448
|
+
const canvasCenter = getObjectCanvasCenter(object);
|
|
1343
1449
|
const { x, y } = convertToVideoPosition(
|
|
1344
|
-
|
|
1345
|
-
|
|
1450
|
+
canvasCenter.x,
|
|
1451
|
+
canvasCenter.y,
|
|
1346
1452
|
context.canvasMetadata,
|
|
1347
1453
|
context.videoSize
|
|
1348
1454
|
);
|
|
@@ -1351,7 +1457,7 @@ const TextElement = {
|
|
|
1351
1457
|
...element,
|
|
1352
1458
|
props: {
|
|
1353
1459
|
...element.props,
|
|
1354
|
-
rotation: object
|
|
1460
|
+
rotation: getObjectCanvasAngle(object),
|
|
1355
1461
|
x,
|
|
1356
1462
|
y
|
|
1357
1463
|
}
|
|
@@ -1362,20 +1468,23 @@ const TextElement = {
|
|
|
1362
1468
|
const CaptionElement = {
|
|
1363
1469
|
name: ELEMENT_TYPES.CAPTION,
|
|
1364
1470
|
async add(params) {
|
|
1365
|
-
|
|
1471
|
+
var _a;
|
|
1472
|
+
const { element, index, canvas, captionProps, canvasMetadata, lockAspectRatio } = params;
|
|
1366
1473
|
await addCaptionElement({
|
|
1367
1474
|
element,
|
|
1368
1475
|
index,
|
|
1369
1476
|
canvas,
|
|
1370
1477
|
captionProps: captionProps ?? {},
|
|
1371
|
-
canvasMetadata
|
|
1478
|
+
canvasMetadata,
|
|
1479
|
+
lockAspectRatio: lockAspectRatio ?? ((_a = element.props) == null ? void 0 : _a.lockAspectRatio)
|
|
1372
1480
|
});
|
|
1373
1481
|
},
|
|
1374
1482
|
updateFromFabricObject(object, element, context) {
|
|
1375
1483
|
var _a;
|
|
1484
|
+
const canvasCenter = getObjectCanvasCenter(object);
|
|
1376
1485
|
const { x, y } = convertToVideoPosition(
|
|
1377
|
-
|
|
1378
|
-
|
|
1486
|
+
canvasCenter.x,
|
|
1487
|
+
canvasCenter.y,
|
|
1379
1488
|
context.canvasMetadata,
|
|
1380
1489
|
context.videoSize
|
|
1381
1490
|
);
|
|
@@ -1481,7 +1590,15 @@ function registerElements() {
|
|
|
1481
1590
|
registerElements();
|
|
1482
1591
|
const useTwickCanvas = ({
|
|
1483
1592
|
onCanvasReady,
|
|
1484
|
-
onCanvasOperation
|
|
1593
|
+
onCanvasOperation,
|
|
1594
|
+
/**
|
|
1595
|
+
* When true, holding Shift while dragging an object will lock movement to
|
|
1596
|
+
* the dominant axis (horizontal or vertical). This mirrors behavior in
|
|
1597
|
+
* professional editors and improves precise alignment.
|
|
1598
|
+
*
|
|
1599
|
+
* Default: false (opt‑in to avoid surprising existing consumers).
|
|
1600
|
+
*/
|
|
1601
|
+
enableShiftAxisLock = false
|
|
1485
1602
|
}) => {
|
|
1486
1603
|
const [twickCanvas, setTwickCanvas] = react.useState(null);
|
|
1487
1604
|
const elementMap = react.useRef({});
|
|
@@ -1491,6 +1608,7 @@ const useTwickCanvas = ({
|
|
|
1491
1608
|
const videoSizeRef = react.useRef({ width: 1, height: 1 });
|
|
1492
1609
|
const canvasResolutionRef = react.useRef({ width: 1, height: 1 });
|
|
1493
1610
|
const captionPropsRef = react.useRef(null);
|
|
1611
|
+
const axisLockStateRef = react.useRef(null);
|
|
1494
1612
|
const canvasMetadataRef = react.useRef({
|
|
1495
1613
|
width: 0,
|
|
1496
1614
|
height: 0,
|
|
@@ -1505,6 +1623,57 @@ const useTwickCanvas = ({
|
|
|
1505
1623
|
canvasMetadataRef.current.scaleY = canvasMetadataRef.current.height / videoSize.height;
|
|
1506
1624
|
}
|
|
1507
1625
|
};
|
|
1626
|
+
const handleObjectMoving = (event) => {
|
|
1627
|
+
var _a;
|
|
1628
|
+
if (!enableShiftAxisLock) return;
|
|
1629
|
+
const target = event == null ? void 0 : event.target;
|
|
1630
|
+
const transform = event == null ? void 0 : event.transform;
|
|
1631
|
+
const pointerEvent = event == null ? void 0 : event.e;
|
|
1632
|
+
if (!target || !transform || !pointerEvent) {
|
|
1633
|
+
axisLockStateRef.current = null;
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (!pointerEvent.shiftKey) {
|
|
1637
|
+
axisLockStateRef.current = null;
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
const original = transform.original;
|
|
1641
|
+
if (!original || typeof target.left !== "number" || typeof target.top !== "number") {
|
|
1642
|
+
axisLockStateRef.current = null;
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (!axisLockStateRef.current) {
|
|
1646
|
+
const dx = Math.abs(target.left - original.left);
|
|
1647
|
+
const dy = Math.abs(target.top - original.top);
|
|
1648
|
+
axisLockStateRef.current = {
|
|
1649
|
+
axis: dx >= dy ? "x" : "y"
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
if (axisLockStateRef.current.axis === "x") {
|
|
1653
|
+
target.top = original.top;
|
|
1654
|
+
} else {
|
|
1655
|
+
target.left = original.left;
|
|
1656
|
+
}
|
|
1657
|
+
(_a = target.canvas) == null ? void 0 : _a.requestRenderAll();
|
|
1658
|
+
};
|
|
1659
|
+
const applyMarqueeSelectionControls = () => {
|
|
1660
|
+
const canvasInstance = twickCanvasRef.current;
|
|
1661
|
+
if (!canvasInstance) return;
|
|
1662
|
+
const activeObject = canvasInstance.getActiveObject();
|
|
1663
|
+
if (!activeObject) return;
|
|
1664
|
+
if (activeObject instanceof fabric.ActiveSelection) {
|
|
1665
|
+
activeObject.controls.mt = disabledControl;
|
|
1666
|
+
activeObject.controls.mb = disabledControl;
|
|
1667
|
+
activeObject.controls.ml = disabledControl;
|
|
1668
|
+
activeObject.controls.mr = disabledControl;
|
|
1669
|
+
activeObject.controls.bl = disabledControl;
|
|
1670
|
+
activeObject.controls.br = disabledControl;
|
|
1671
|
+
activeObject.controls.tl = disabledControl;
|
|
1672
|
+
activeObject.controls.tr = disabledControl;
|
|
1673
|
+
activeObject.controls.mtr = rotateControl;
|
|
1674
|
+
canvasInstance.requestRenderAll();
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1508
1677
|
const buildCanvas = ({
|
|
1509
1678
|
videoSize,
|
|
1510
1679
|
canvasSize,
|
|
@@ -1524,6 +1693,9 @@ const useTwickCanvas = ({
|
|
|
1524
1693
|
if (twickCanvasRef.current) {
|
|
1525
1694
|
twickCanvasRef.current.off("mouse:up", handleMouseUp);
|
|
1526
1695
|
twickCanvasRef.current.off("text:editing:exited", onTextEdit);
|
|
1696
|
+
twickCanvasRef.current.off("object:moving", handleObjectMoving);
|
|
1697
|
+
twickCanvasRef.current.off("selection:created", applyMarqueeSelectionControls);
|
|
1698
|
+
twickCanvasRef.current.off("selection:updated", applyMarqueeSelectionControls);
|
|
1527
1699
|
twickCanvasRef.current.dispose();
|
|
1528
1700
|
}
|
|
1529
1701
|
const { canvas, canvasMetadata } = createCanvas({
|
|
@@ -1541,6 +1713,9 @@ const useTwickCanvas = ({
|
|
|
1541
1713
|
videoSizeRef.current = videoSize;
|
|
1542
1714
|
canvas == null ? void 0 : canvas.on("mouse:up", handleMouseUp);
|
|
1543
1715
|
canvas == null ? void 0 : canvas.on("text:editing:exited", onTextEdit);
|
|
1716
|
+
canvas == null ? void 0 : canvas.on("object:moving", handleObjectMoving);
|
|
1717
|
+
canvas == null ? void 0 : canvas.on("selection:created", applyMarqueeSelectionControls);
|
|
1718
|
+
canvas == null ? void 0 : canvas.on("selection:updated", applyMarqueeSelectionControls);
|
|
1544
1719
|
canvasResolutionRef.current = canvasSize;
|
|
1545
1720
|
setTwickCanvas(canvas);
|
|
1546
1721
|
twickCanvasRef.current = canvas;
|
|
@@ -1570,7 +1745,8 @@ const useTwickCanvas = ({
|
|
|
1570
1745
|
if (event.target) {
|
|
1571
1746
|
const object = event.target;
|
|
1572
1747
|
const elementId = object.get("id");
|
|
1573
|
-
|
|
1748
|
+
const action = (_a = event.transform) == null ? void 0 : _a.action;
|
|
1749
|
+
if (action === "drag") {
|
|
1574
1750
|
const original = event.transform.original;
|
|
1575
1751
|
if (object.left === original.left && object.top === original.top) {
|
|
1576
1752
|
onCanvasOperation == null ? void 0 : onCanvasOperation(
|
|
@@ -1580,7 +1756,38 @@ const useTwickCanvas = ({
|
|
|
1580
1756
|
return;
|
|
1581
1757
|
}
|
|
1582
1758
|
}
|
|
1583
|
-
|
|
1759
|
+
const context = {
|
|
1760
|
+
canvasMetadata: canvasMetadataRef.current,
|
|
1761
|
+
videoSize: videoSizeRef.current,
|
|
1762
|
+
elementFrameMapRef: elementFrameMap,
|
|
1763
|
+
captionPropsRef,
|
|
1764
|
+
watermarkPropsRef
|
|
1765
|
+
};
|
|
1766
|
+
if (object instanceof fabric.ActiveSelection && (action === "drag" || action === "rotate")) {
|
|
1767
|
+
const objects = object.getObjects();
|
|
1768
|
+
for (const fabricObj of objects) {
|
|
1769
|
+
const id = fabricObj.get("id");
|
|
1770
|
+
if (!id || id === "e-watermark") continue;
|
|
1771
|
+
const currentElement = elementMap.current[id];
|
|
1772
|
+
if (!currentElement) continue;
|
|
1773
|
+
const handler = elementController.get(currentElement.type);
|
|
1774
|
+
const result = (_b = handler == null ? void 0 : handler.updateFromFabricObject) == null ? void 0 : _b.call(
|
|
1775
|
+
handler,
|
|
1776
|
+
fabricObj,
|
|
1777
|
+
currentElement,
|
|
1778
|
+
context
|
|
1779
|
+
);
|
|
1780
|
+
if (result) {
|
|
1781
|
+
elementMap.current[id] = result.element;
|
|
1782
|
+
onCanvasOperation == null ? void 0 : onCanvasOperation(
|
|
1783
|
+
result.operation ?? CANVAS_OPERATIONS.ITEM_UPDATED,
|
|
1784
|
+
result.payload ?? result.element
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
switch (action) {
|
|
1584
1791
|
case "drag":
|
|
1585
1792
|
case "scale":
|
|
1586
1793
|
case "scaleX":
|
|
@@ -1590,13 +1797,7 @@ const useTwickCanvas = ({
|
|
|
1590
1797
|
const handler = elementController.get(
|
|
1591
1798
|
elementId === "e-watermark" ? "watermark" : currentElement == null ? void 0 : currentElement.type
|
|
1592
1799
|
);
|
|
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
|
-
});
|
|
1800
|
+
const result = (_c = handler == null ? void 0 : handler.updateFromFabricObject) == null ? void 0 : _c.call(handler, object, currentElement ?? { id: elementId, type: "text", props: {} }, context);
|
|
1600
1801
|
if (result) {
|
|
1601
1802
|
elementMap.current[elementId] = result.element;
|
|
1602
1803
|
onCanvasOperation == null ? void 0 : onCanvasOperation(
|
|
@@ -1614,7 +1815,8 @@ const useTwickCanvas = ({
|
|
|
1614
1815
|
watermark,
|
|
1615
1816
|
seekTime = 0,
|
|
1616
1817
|
captionProps,
|
|
1617
|
-
cleanAndAdd = false
|
|
1818
|
+
cleanAndAdd = false,
|
|
1819
|
+
lockAspectRatio
|
|
1618
1820
|
}) => {
|
|
1619
1821
|
if (!twickCanvas || !getCanvasContext(twickCanvas)) return;
|
|
1620
1822
|
try {
|
|
@@ -1627,16 +1829,26 @@ const useTwickCanvas = ({
|
|
|
1627
1829
|
}
|
|
1628
1830
|
}
|
|
1629
1831
|
captionPropsRef.current = captionProps;
|
|
1832
|
+
const uniqueElements = [];
|
|
1833
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
1834
|
+
for (const el of elements) {
|
|
1835
|
+
if (!el || !el.id) continue;
|
|
1836
|
+
if (seenIds.has(el.id)) continue;
|
|
1837
|
+
seenIds.add(el.id);
|
|
1838
|
+
uniqueElements.push(el);
|
|
1839
|
+
}
|
|
1630
1840
|
await Promise.all(
|
|
1631
|
-
|
|
1841
|
+
uniqueElements.map(async (element, index) => {
|
|
1632
1842
|
try {
|
|
1633
1843
|
if (!element) return;
|
|
1844
|
+
const zOrder = element.zIndex ?? index;
|
|
1634
1845
|
await addElementToCanvas({
|
|
1635
1846
|
element,
|
|
1636
|
-
index,
|
|
1847
|
+
index: zOrder,
|
|
1637
1848
|
reorder: false,
|
|
1638
1849
|
seekTime,
|
|
1639
|
-
captionProps
|
|
1850
|
+
captionProps,
|
|
1851
|
+
lockAspectRatio
|
|
1640
1852
|
});
|
|
1641
1853
|
} catch {
|
|
1642
1854
|
}
|
|
@@ -1656,8 +1868,10 @@ const useTwickCanvas = ({
|
|
|
1656
1868
|
index,
|
|
1657
1869
|
reorder = true,
|
|
1658
1870
|
seekTime,
|
|
1659
|
-
captionProps
|
|
1871
|
+
captionProps,
|
|
1872
|
+
lockAspectRatio
|
|
1660
1873
|
}) => {
|
|
1874
|
+
var _a;
|
|
1661
1875
|
if (!twickCanvas) return;
|
|
1662
1876
|
const handler = elementController.get(element.type);
|
|
1663
1877
|
if (handler) {
|
|
@@ -1669,10 +1883,11 @@ const useTwickCanvas = ({
|
|
|
1669
1883
|
seekTime,
|
|
1670
1884
|
captionProps: captionProps ?? null,
|
|
1671
1885
|
elementFrameMapRef: elementFrameMap,
|
|
1672
|
-
getCurrentFrameEffect
|
|
1886
|
+
getCurrentFrameEffect,
|
|
1887
|
+
lockAspectRatio: lockAspectRatio ?? ((_a = element.props) == null ? void 0 : _a.lockAspectRatio)
|
|
1673
1888
|
});
|
|
1674
1889
|
}
|
|
1675
|
-
elementMap.current[element.id] = element;
|
|
1890
|
+
elementMap.current[element.id] = { ...element, zIndex: element.zIndex ?? index };
|
|
1676
1891
|
if (reorder) {
|
|
1677
1892
|
reorderElementsByZIndex(twickCanvas);
|
|
1678
1893
|
}
|
|
@@ -1693,13 +1908,30 @@ const useTwickCanvas = ({
|
|
|
1693
1908
|
elementMap.current[element.id] = element;
|
|
1694
1909
|
}
|
|
1695
1910
|
};
|
|
1911
|
+
const applyZOrder = (elementId, direction) => {
|
|
1912
|
+
if (!twickCanvas) return false;
|
|
1913
|
+
const newZIndex = changeZOrder(twickCanvas, elementId, direction);
|
|
1914
|
+
if (newZIndex == null) return false;
|
|
1915
|
+
const element = elementMap.current[elementId];
|
|
1916
|
+
if (element) elementMap.current[elementId] = { ...element, zIndex: newZIndex };
|
|
1917
|
+
onCanvasOperation == null ? void 0 : onCanvasOperation(CANVAS_OPERATIONS.Z_ORDER_CHANGED, { elementId, direction });
|
|
1918
|
+
return true;
|
|
1919
|
+
};
|
|
1920
|
+
const bringToFront = (elementId) => applyZOrder(elementId, "front");
|
|
1921
|
+
const sendToBack = (elementId) => applyZOrder(elementId, "back");
|
|
1922
|
+
const bringForward = (elementId) => applyZOrder(elementId, "forward");
|
|
1923
|
+
const sendBackward = (elementId) => applyZOrder(elementId, "backward");
|
|
1696
1924
|
return {
|
|
1697
1925
|
twickCanvas,
|
|
1698
1926
|
buildCanvas,
|
|
1699
1927
|
onVideoSizeChange,
|
|
1700
1928
|
addWatermarkToCanvas,
|
|
1701
1929
|
addElementToCanvas,
|
|
1702
|
-
setCanvasElements
|
|
1930
|
+
setCanvasElements,
|
|
1931
|
+
bringToFront,
|
|
1932
|
+
sendToBack,
|
|
1933
|
+
bringForward,
|
|
1934
|
+
sendBackward
|
|
1703
1935
|
};
|
|
1704
1936
|
};
|
|
1705
1937
|
exports.CANVAS_OPERATIONS = CANVAS_OPERATIONS;
|