@twick/studio 0.15.13 → 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/README.md +2 -2
- package/dist/components/container/captions-panel-container.d.ts +1 -0
- package/dist/components/container/element-panel-container.d.ts +1 -1
- package/dist/components/container/properties-panel-container.d.ts +7 -7
- package/dist/components/panel/captions-panel.d.ts +46 -0
- package/dist/components/panel/circle-panel.d.ts +1 -1
- package/dist/components/panel/rect-panel.d.ts +1 -1
- package/dist/components/panel/text-panel.d.ts +1 -1
- package/dist/components/properties/{subtitlte-prop.d.ts → caption-prop.d.ts} +4 -4
- package/dist/components/properties/generate-captions.d.ts +13 -0
- package/dist/components/properties/property-row.d.ts +10 -0
- package/dist/components/properties/text-props.d.ts +3 -0
- package/dist/helpers/generate-captions.service.d.ts +21 -0
- package/dist/helpers/volume-db.d.ts +22 -0
- package/dist/hooks/use-captions-panel.d.ts +13 -0
- package/dist/hooks/use-generate-captions.d.ts +9 -0
- package/dist/hooks/use-studio-operation.d.ts +4 -4
- package/dist/hooks/use-text-panel.d.ts +6 -0
- package/dist/index.d.ts +10 -13
- package/dist/index.js +1209 -1046
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1194 -1034
- package/dist/index.mjs.map +1 -1
- package/dist/studio.css +521 -162
- package/dist/types/index.d.ts +18 -16
- package/package.json +14 -12
- package/dist/components/container/icon-panel-container.d.ts +0 -3
- package/dist/components/container/subtitles-panel-container.d.ts +0 -1
- package/dist/components/panel/icon-panel.d.ts +0 -4
- package/dist/components/panel/subtitles-panel.d.ts +0 -46
- package/dist/components/properties/generate-subtitles.d.ts +0 -13
- package/dist/helpers/generate-subtitles.service.d.ts +0 -21
- package/dist/hooks/use-generate-subtitles.d.ts +0 -9
- package/dist/hooks/use-icon-panel.d.ts +0 -24
- package/dist/hooks/use-subtitles-panel.d.ts +0 -13
package/dist/index.js
CHANGED
|
@@ -113,49 +113,103 @@ const createLucideIcon = (iconName, iconNode) => {
|
|
|
113
113
|
* This source code is licensed under the ISC license.
|
|
114
114
|
* See the LICENSE file in the root directory of this source tree.
|
|
115
115
|
*/
|
|
116
|
-
const __iconNode$
|
|
117
|
-
["
|
|
118
|
-
["path", { d: "
|
|
116
|
+
const __iconNode$y = [
|
|
117
|
+
["path", { d: "M17 12H7", key: "16if0g" }],
|
|
118
|
+
["path", { d: "M19 18H5", key: "18s9l3" }],
|
|
119
|
+
["path", { d: "M21 6H3", key: "1jwq7v" }]
|
|
119
120
|
];
|
|
120
|
-
const
|
|
121
|
+
const AlignCenter = createLucideIcon("align-center", __iconNode$y);
|
|
121
122
|
/**
|
|
122
123
|
* @license lucide-react v0.511.0 - ISC
|
|
123
124
|
*
|
|
124
125
|
* This source code is licensed under the ISC license.
|
|
125
126
|
* See the LICENSE file in the root directory of this source tree.
|
|
126
127
|
*/
|
|
127
|
-
const __iconNode$
|
|
128
|
+
const __iconNode$x = [
|
|
129
|
+
["path", { d: "M15 12H3", key: "6jk70r" }],
|
|
130
|
+
["path", { d: "M17 18H3", key: "1amg6g" }],
|
|
131
|
+
["path", { d: "M21 6H3", key: "1jwq7v" }]
|
|
132
|
+
];
|
|
133
|
+
const AlignLeft = createLucideIcon("align-left", __iconNode$x);
|
|
134
|
+
/**
|
|
135
|
+
* @license lucide-react v0.511.0 - ISC
|
|
136
|
+
*
|
|
137
|
+
* This source code is licensed under the ISC license.
|
|
138
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
139
|
+
*/
|
|
140
|
+
const __iconNode$w = [
|
|
141
|
+
["path", { d: "M21 12H9", key: "dn1m92" }],
|
|
142
|
+
["path", { d: "M21 18H7", key: "1ygte8" }],
|
|
143
|
+
["path", { d: "M21 6H3", key: "1jwq7v" }]
|
|
144
|
+
];
|
|
145
|
+
const AlignRight = createLucideIcon("align-right", __iconNode$w);
|
|
146
|
+
/**
|
|
147
|
+
* @license lucide-react v0.511.0 - ISC
|
|
148
|
+
*
|
|
149
|
+
* This source code is licensed under the ISC license.
|
|
150
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
151
|
+
*/
|
|
152
|
+
const __iconNode$v = [
|
|
153
|
+
[
|
|
154
|
+
"path",
|
|
155
|
+
{ d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8", key: "mg9rjx" }
|
|
156
|
+
]
|
|
157
|
+
];
|
|
158
|
+
const Bold = createLucideIcon("bold", __iconNode$v);
|
|
159
|
+
/**
|
|
160
|
+
* @license lucide-react v0.511.0 - ISC
|
|
161
|
+
*
|
|
162
|
+
* This source code is licensed under the ISC license.
|
|
163
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
164
|
+
*/
|
|
165
|
+
const __iconNode$u = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
|
|
166
|
+
const ChevronDown = createLucideIcon("chevron-down", __iconNode$u);
|
|
167
|
+
/**
|
|
168
|
+
* @license lucide-react v0.511.0 - ISC
|
|
169
|
+
*
|
|
170
|
+
* This source code is licensed under the ISC license.
|
|
171
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
172
|
+
*/
|
|
173
|
+
const __iconNode$t = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
|
|
174
|
+
const ChevronRight = createLucideIcon("chevron-right", __iconNode$t);
|
|
175
|
+
/**
|
|
176
|
+
* @license lucide-react v0.511.0 - ISC
|
|
177
|
+
*
|
|
178
|
+
* This source code is licensed under the ISC license.
|
|
179
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
180
|
+
*/
|
|
181
|
+
const __iconNode$s = [
|
|
128
182
|
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
129
183
|
["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }]
|
|
130
184
|
];
|
|
131
|
-
const CircleCheck = createLucideIcon("circle-check", __iconNode$
|
|
185
|
+
const CircleCheck = createLucideIcon("circle-check", __iconNode$s);
|
|
132
186
|
/**
|
|
133
187
|
* @license lucide-react v0.511.0 - ISC
|
|
134
188
|
*
|
|
135
189
|
* This source code is licensed under the ISC license.
|
|
136
190
|
* See the LICENSE file in the root directory of this source tree.
|
|
137
191
|
*/
|
|
138
|
-
const __iconNode$
|
|
192
|
+
const __iconNode$r = [
|
|
139
193
|
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
140
194
|
["path", { d: "m15 9-6 6", key: "1uzhvr" }],
|
|
141
195
|
["path", { d: "m9 9 6 6", key: "z0biqf" }]
|
|
142
196
|
];
|
|
143
|
-
const CircleX = createLucideIcon("circle-x", __iconNode$
|
|
197
|
+
const CircleX = createLucideIcon("circle-x", __iconNode$r);
|
|
144
198
|
/**
|
|
145
199
|
* @license lucide-react v0.511.0 - ISC
|
|
146
200
|
*
|
|
147
201
|
* This source code is licensed under the ISC license.
|
|
148
202
|
* See the LICENSE file in the root directory of this source tree.
|
|
149
203
|
*/
|
|
150
|
-
const __iconNode$
|
|
151
|
-
const Circle = createLucideIcon("circle", __iconNode$
|
|
204
|
+
const __iconNode$q = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
|
|
205
|
+
const Circle = createLucideIcon("circle", __iconNode$q);
|
|
152
206
|
/**
|
|
153
207
|
* @license lucide-react v0.511.0 - ISC
|
|
154
208
|
*
|
|
155
209
|
* This source code is licensed under the ISC license.
|
|
156
210
|
* See the LICENSE file in the root directory of this source tree.
|
|
157
211
|
*/
|
|
158
|
-
const __iconNode$
|
|
212
|
+
const __iconNode$p = [
|
|
159
213
|
[
|
|
160
214
|
"path",
|
|
161
215
|
{ d: "M20.2 6 3 11l-.9-2.4c-.3-1.1.3-2.2 1.3-2.5l13.5-4c1.1-.3 2.2.3 2.5 1.3Z", key: "1tn4o7" }
|
|
@@ -164,139 +218,172 @@ const __iconNode$q = [
|
|
|
164
218
|
["path", { d: "m12.4 3.4 3.1 4", key: "6hsd6n" }],
|
|
165
219
|
["path", { d: "M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z", key: "ltgou9" }]
|
|
166
220
|
];
|
|
167
|
-
const Clapperboard = createLucideIcon("clapperboard", __iconNode$
|
|
221
|
+
const Clapperboard = createLucideIcon("clapperboard", __iconNode$p);
|
|
168
222
|
/**
|
|
169
223
|
* @license lucide-react v0.511.0 - ISC
|
|
170
224
|
*
|
|
171
225
|
* This source code is licensed under the ISC license.
|
|
172
226
|
* See the LICENSE file in the root directory of this source tree.
|
|
173
227
|
*/
|
|
174
|
-
const __iconNode$
|
|
228
|
+
const __iconNode$o = [
|
|
175
229
|
["path", { d: "M12 15V3", key: "m9g1x1" }],
|
|
176
230
|
["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
|
|
177
231
|
["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]
|
|
178
232
|
];
|
|
179
|
-
const Download = createLucideIcon("download", __iconNode$
|
|
233
|
+
const Download = createLucideIcon("download", __iconNode$o);
|
|
180
234
|
/**
|
|
181
235
|
* @license lucide-react v0.511.0 - ISC
|
|
182
236
|
*
|
|
183
237
|
* This source code is licensed under the ISC license.
|
|
184
238
|
* See the LICENSE file in the root directory of this source tree.
|
|
185
239
|
*/
|
|
186
|
-
const __iconNode$
|
|
240
|
+
const __iconNode$n = [
|
|
187
241
|
["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
|
|
188
242
|
["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }]
|
|
189
243
|
];
|
|
190
|
-
const File = createLucideIcon("file", __iconNode$
|
|
244
|
+
const File = createLucideIcon("file", __iconNode$n);
|
|
191
245
|
/**
|
|
192
246
|
* @license lucide-react v0.511.0 - ISC
|
|
193
247
|
*
|
|
194
248
|
* This source code is licensed under the ISC license.
|
|
195
249
|
* See the LICENSE file in the root directory of this source tree.
|
|
196
250
|
*/
|
|
197
|
-
const __iconNode$
|
|
251
|
+
const __iconNode$m = [
|
|
198
252
|
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
|
|
199
253
|
["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
|
|
200
254
|
["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
|
|
201
255
|
];
|
|
202
|
-
const Image = createLucideIcon("image", __iconNode$
|
|
256
|
+
const Image = createLucideIcon("image", __iconNode$m);
|
|
203
257
|
/**
|
|
204
258
|
* @license lucide-react v0.511.0 - ISC
|
|
205
259
|
*
|
|
206
260
|
* This source code is licensed under the ISC license.
|
|
207
261
|
* See the LICENSE file in the root directory of this source tree.
|
|
208
262
|
*/
|
|
209
|
-
const __iconNode$
|
|
210
|
-
["
|
|
263
|
+
const __iconNode$l = [
|
|
264
|
+
["line", { x1: "19", x2: "10", y1: "4", y2: "4", key: "15jd3p" }],
|
|
265
|
+
["line", { x1: "14", x2: "5", y1: "20", y2: "20", key: "bu0au3" }],
|
|
266
|
+
["line", { x1: "15", x2: "9", y1: "4", y2: "20", key: "uljnxc" }]
|
|
211
267
|
];
|
|
212
|
-
const
|
|
268
|
+
const Italic = createLucideIcon("italic", __iconNode$l);
|
|
213
269
|
/**
|
|
214
270
|
* @license lucide-react v0.511.0 - ISC
|
|
215
271
|
*
|
|
216
272
|
* This source code is licensed under the ISC license.
|
|
217
273
|
* See the LICENSE file in the root directory of this source tree.
|
|
218
274
|
*/
|
|
219
|
-
const __iconNode$
|
|
220
|
-
const LoaderCircle = createLucideIcon("loader-circle", __iconNode$
|
|
275
|
+
const __iconNode$k = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
|
|
276
|
+
const LoaderCircle = createLucideIcon("loader-circle", __iconNode$k);
|
|
221
277
|
/**
|
|
222
278
|
* @license lucide-react v0.511.0 - ISC
|
|
223
279
|
*
|
|
224
280
|
* This source code is licensed under the ISC license.
|
|
225
281
|
* See the LICENSE file in the root directory of this source tree.
|
|
226
282
|
*/
|
|
227
|
-
const __iconNode$
|
|
283
|
+
const __iconNode$j = [
|
|
228
284
|
["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", key: "1lielz" }]
|
|
229
285
|
];
|
|
230
|
-
const MessageSquare = createLucideIcon("message-square", __iconNode$
|
|
286
|
+
const MessageSquare = createLucideIcon("message-square", __iconNode$j);
|
|
231
287
|
/**
|
|
232
288
|
* @license lucide-react v0.511.0 - ISC
|
|
233
289
|
*
|
|
234
290
|
* This source code is licensed under the ISC license.
|
|
235
291
|
* See the LICENSE file in the root directory of this source tree.
|
|
236
292
|
*/
|
|
237
|
-
const __iconNode$
|
|
293
|
+
const __iconNode$i = [
|
|
294
|
+
["circle", { cx: "8", cy: "18", r: "4", key: "1fc0mg" }],
|
|
295
|
+
["path", { d: "M12 18V2l7 4", key: "g04rme" }]
|
|
296
|
+
];
|
|
297
|
+
const Music2 = createLucideIcon("music-2", __iconNode$i);
|
|
298
|
+
/**
|
|
299
|
+
* @license lucide-react v0.511.0 - ISC
|
|
300
|
+
*
|
|
301
|
+
* This source code is licensed under the ISC license.
|
|
302
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
303
|
+
*/
|
|
304
|
+
const __iconNode$h = [
|
|
238
305
|
["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
|
|
239
306
|
["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
|
|
240
307
|
["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
|
|
241
308
|
];
|
|
242
|
-
const Music = createLucideIcon("music", __iconNode$
|
|
309
|
+
const Music = createLucideIcon("music", __iconNode$h);
|
|
243
310
|
/**
|
|
244
311
|
* @license lucide-react v0.511.0 - ISC
|
|
245
312
|
*
|
|
246
313
|
* This source code is licensed under the ISC license.
|
|
247
314
|
* See the LICENSE file in the root directory of this source tree.
|
|
248
315
|
*/
|
|
249
|
-
const __iconNode$
|
|
316
|
+
const __iconNode$g = [
|
|
250
317
|
["rect", { x: "14", y: "4", width: "4", height: "16", rx: "1", key: "zuxfzm" }],
|
|
251
318
|
["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
|
|
252
319
|
];
|
|
253
|
-
const Pause = createLucideIcon("pause", __iconNode$
|
|
320
|
+
const Pause = createLucideIcon("pause", __iconNode$g);
|
|
254
321
|
/**
|
|
255
322
|
* @license lucide-react v0.511.0 - ISC
|
|
256
323
|
*
|
|
257
324
|
* This source code is licensed under the ISC license.
|
|
258
325
|
* See the LICENSE file in the root directory of this source tree.
|
|
259
326
|
*/
|
|
260
|
-
const __iconNode$
|
|
261
|
-
const Play = createLucideIcon("play", __iconNode$
|
|
327
|
+
const __iconNode$f = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
|
|
328
|
+
const Play = createLucideIcon("play", __iconNode$f);
|
|
262
329
|
/**
|
|
263
330
|
* @license lucide-react v0.511.0 - ISC
|
|
264
331
|
*
|
|
265
332
|
* This source code is licensed under the ISC license.
|
|
266
333
|
* See the LICENSE file in the root directory of this source tree.
|
|
267
334
|
*/
|
|
268
|
-
const __iconNode$
|
|
335
|
+
const __iconNode$e = [
|
|
269
336
|
["path", { d: "M5 12h14", key: "1ays0h" }],
|
|
270
337
|
["path", { d: "M12 5v14", key: "s699le" }]
|
|
271
338
|
];
|
|
272
|
-
const Plus = createLucideIcon("plus", __iconNode$
|
|
339
|
+
const Plus = createLucideIcon("plus", __iconNode$e);
|
|
273
340
|
/**
|
|
274
341
|
* @license lucide-react v0.511.0 - ISC
|
|
275
342
|
*
|
|
276
343
|
* This source code is licensed under the ISC license.
|
|
277
344
|
* See the LICENSE file in the root directory of this source tree.
|
|
278
345
|
*/
|
|
279
|
-
const __iconNode$
|
|
346
|
+
const __iconNode$d = [
|
|
280
347
|
["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2", key: "9lu3g6" }]
|
|
281
348
|
];
|
|
282
|
-
const RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode$
|
|
349
|
+
const RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode$d);
|
|
283
350
|
/**
|
|
284
351
|
* @license lucide-react v0.511.0 - ISC
|
|
285
352
|
*
|
|
286
353
|
* This source code is licensed under the ISC license.
|
|
287
354
|
* See the LICENSE file in the root directory of this source tree.
|
|
288
355
|
*/
|
|
289
|
-
const __iconNode$
|
|
356
|
+
const __iconNode$c = [
|
|
290
357
|
["rect", { width: "12", height: "20", x: "6", y: "2", rx: "2", key: "1oxtiu" }]
|
|
291
358
|
];
|
|
292
|
-
const RectangleVertical = createLucideIcon("rectangle-vertical", __iconNode$
|
|
359
|
+
const RectangleVertical = createLucideIcon("rectangle-vertical", __iconNode$c);
|
|
293
360
|
/**
|
|
294
361
|
* @license lucide-react v0.511.0 - ISC
|
|
295
362
|
*
|
|
296
363
|
* This source code is licensed under the ISC license.
|
|
297
364
|
* See the LICENSE file in the root directory of this source tree.
|
|
298
365
|
*/
|
|
299
|
-
const __iconNode$
|
|
366
|
+
const __iconNode$b = [
|
|
367
|
+
[
|
|
368
|
+
"path",
|
|
369
|
+
{
|
|
370
|
+
d: "M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z",
|
|
371
|
+
key: "icamh8"
|
|
372
|
+
}
|
|
373
|
+
],
|
|
374
|
+
["path", { d: "m14.5 12.5 2-2", key: "inckbg" }],
|
|
375
|
+
["path", { d: "m11.5 9.5 2-2", key: "fmmyf7" }],
|
|
376
|
+
["path", { d: "m8.5 6.5 2-2", key: "vc6u1g" }],
|
|
377
|
+
["path", { d: "m17.5 15.5 2-2", key: "wo5hmg" }]
|
|
378
|
+
];
|
|
379
|
+
const Ruler = createLucideIcon("ruler", __iconNode$b);
|
|
380
|
+
/**
|
|
381
|
+
* @license lucide-react v0.511.0 - ISC
|
|
382
|
+
*
|
|
383
|
+
* This source code is licensed under the ISC license.
|
|
384
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
385
|
+
*/
|
|
386
|
+
const __iconNode$a = [
|
|
300
387
|
[
|
|
301
388
|
"path",
|
|
302
389
|
{
|
|
@@ -307,56 +394,28 @@ const __iconNode$d = [
|
|
|
307
394
|
["path", { d: "M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7", key: "1ydtos" }],
|
|
308
395
|
["path", { d: "M7 3v4a1 1 0 0 0 1 1h7", key: "t51u73" }]
|
|
309
396
|
];
|
|
310
|
-
const Save = createLucideIcon("save", __iconNode$
|
|
397
|
+
const Save = createLucideIcon("save", __iconNode$a);
|
|
311
398
|
/**
|
|
312
399
|
* @license lucide-react v0.511.0 - ISC
|
|
313
400
|
*
|
|
314
401
|
* This source code is licensed under the ISC license.
|
|
315
402
|
* See the LICENSE file in the root directory of this source tree.
|
|
316
403
|
*/
|
|
317
|
-
const __iconNode$
|
|
404
|
+
const __iconNode$9 = [
|
|
318
405
|
["circle", { cx: "6", cy: "6", r: "3", key: "1lh9wr" }],
|
|
319
406
|
["path", { d: "M8.12 8.12 12 12", key: "1alkpv" }],
|
|
320
407
|
["path", { d: "M20 4 8.12 15.88", key: "xgtan2" }],
|
|
321
408
|
["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
|
|
322
409
|
["path", { d: "M14.8 14.8 20 20", key: "ptml3r" }]
|
|
323
410
|
];
|
|
324
|
-
const Scissors = createLucideIcon("scissors", __iconNode$
|
|
325
|
-
/**
|
|
326
|
-
* @license lucide-react v0.511.0 - ISC
|
|
327
|
-
*
|
|
328
|
-
* This source code is licensed under the ISC license.
|
|
329
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
330
|
-
*/
|
|
331
|
-
const __iconNode$b = [
|
|
332
|
-
["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
|
|
333
|
-
["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
|
|
334
|
-
];
|
|
335
|
-
const Search = createLucideIcon("search", __iconNode$b);
|
|
336
|
-
/**
|
|
337
|
-
* @license lucide-react v0.511.0 - ISC
|
|
338
|
-
*
|
|
339
|
-
* This source code is licensed under the ISC license.
|
|
340
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
341
|
-
*/
|
|
342
|
-
const __iconNode$a = [
|
|
343
|
-
[
|
|
344
|
-
"path",
|
|
345
|
-
{
|
|
346
|
-
d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",
|
|
347
|
-
key: "1qme2f"
|
|
348
|
-
}
|
|
349
|
-
],
|
|
350
|
-
["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
|
|
351
|
-
];
|
|
352
|
-
const Settings = createLucideIcon("settings", __iconNode$a);
|
|
411
|
+
const Scissors = createLucideIcon("scissors", __iconNode$9);
|
|
353
412
|
/**
|
|
354
413
|
* @license lucide-react v0.511.0 - ISC
|
|
355
414
|
*
|
|
356
415
|
* This source code is licensed under the ISC license.
|
|
357
416
|
* See the LICENSE file in the root directory of this source tree.
|
|
358
417
|
*/
|
|
359
|
-
const __iconNode$
|
|
418
|
+
const __iconNode$8 = [
|
|
360
419
|
[
|
|
361
420
|
"path",
|
|
362
421
|
{
|
|
@@ -369,62 +428,62 @@ const __iconNode$9 = [
|
|
|
369
428
|
["path", { d: "M4 17v2", key: "vumght" }],
|
|
370
429
|
["path", { d: "M5 18H3", key: "zchphs" }]
|
|
371
430
|
];
|
|
372
|
-
const Sparkles = createLucideIcon("sparkles", __iconNode$
|
|
431
|
+
const Sparkles = createLucideIcon("sparkles", __iconNode$8);
|
|
373
432
|
/**
|
|
374
433
|
* @license lucide-react v0.511.0 - ISC
|
|
375
434
|
*
|
|
376
435
|
* This source code is licensed under the ISC license.
|
|
377
436
|
* See the LICENSE file in the root directory of this source tree.
|
|
378
437
|
*/
|
|
379
|
-
const __iconNode$
|
|
438
|
+
const __iconNode$7 = [
|
|
380
439
|
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
|
|
381
440
|
];
|
|
382
|
-
const Square = createLucideIcon("square", __iconNode$
|
|
441
|
+
const Square = createLucideIcon("square", __iconNode$7);
|
|
383
442
|
/**
|
|
384
443
|
* @license lucide-react v0.511.0 - ISC
|
|
385
444
|
*
|
|
386
445
|
* This source code is licensed under the ISC license.
|
|
387
446
|
* See the LICENSE file in the root directory of this source tree.
|
|
388
447
|
*/
|
|
389
|
-
const __iconNode$
|
|
448
|
+
const __iconNode$6 = [
|
|
390
449
|
["path", { d: "M3 6h18", key: "d0wm0j" }],
|
|
391
450
|
["path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6", key: "4alrt4" }],
|
|
392
451
|
["path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2", key: "v07s0e" }],
|
|
393
452
|
["line", { x1: "10", x2: "10", y1: "11", y2: "17", key: "1uufr5" }],
|
|
394
453
|
["line", { x1: "14", x2: "14", y1: "11", y2: "17", key: "xtxkd" }]
|
|
395
454
|
];
|
|
396
|
-
const Trash2 = createLucideIcon("trash-2", __iconNode$
|
|
455
|
+
const Trash2 = createLucideIcon("trash-2", __iconNode$6);
|
|
397
456
|
/**
|
|
398
457
|
* @license lucide-react v0.511.0 - ISC
|
|
399
458
|
*
|
|
400
459
|
* This source code is licensed under the ISC license.
|
|
401
460
|
* See the LICENSE file in the root directory of this source tree.
|
|
402
461
|
*/
|
|
403
|
-
const __iconNode$
|
|
462
|
+
const __iconNode$5 = [
|
|
404
463
|
["path", { d: "M12 4v16", key: "1654pz" }],
|
|
405
464
|
["path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2", key: "e0r10z" }],
|
|
406
465
|
["path", { d: "M9 20h6", key: "s66wpe" }]
|
|
407
466
|
];
|
|
408
|
-
const Type = createLucideIcon("type", __iconNode$
|
|
467
|
+
const Type = createLucideIcon("type", __iconNode$5);
|
|
409
468
|
/**
|
|
410
469
|
* @license lucide-react v0.511.0 - ISC
|
|
411
470
|
*
|
|
412
471
|
* This source code is licensed under the ISC license.
|
|
413
472
|
* See the LICENSE file in the root directory of this source tree.
|
|
414
473
|
*/
|
|
415
|
-
const __iconNode$
|
|
474
|
+
const __iconNode$4 = [
|
|
416
475
|
["path", { d: "M12 3v12", key: "1x0j5s" }],
|
|
417
476
|
["path", { d: "m17 8-5-5-5 5", key: "7q97r8" }],
|
|
418
477
|
["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }]
|
|
419
478
|
];
|
|
420
|
-
const Upload = createLucideIcon("upload", __iconNode$
|
|
479
|
+
const Upload = createLucideIcon("upload", __iconNode$4);
|
|
421
480
|
/**
|
|
422
481
|
* @license lucide-react v0.511.0 - ISC
|
|
423
482
|
*
|
|
424
483
|
* This source code is licensed under the ISC license.
|
|
425
484
|
* See the LICENSE file in the root directory of this source tree.
|
|
426
485
|
*/
|
|
427
|
-
const __iconNode$
|
|
486
|
+
const __iconNode$3 = [
|
|
428
487
|
[
|
|
429
488
|
"path",
|
|
430
489
|
{
|
|
@@ -434,14 +493,14 @@ const __iconNode$4 = [
|
|
|
434
493
|
],
|
|
435
494
|
["rect", { x: "2", y: "6", width: "14", height: "12", rx: "2", key: "158x01" }]
|
|
436
495
|
];
|
|
437
|
-
const Video = createLucideIcon("video", __iconNode$
|
|
496
|
+
const Video = createLucideIcon("video", __iconNode$3);
|
|
438
497
|
/**
|
|
439
498
|
* @license lucide-react v0.511.0 - ISC
|
|
440
499
|
*
|
|
441
500
|
* This source code is licensed under the ISC license.
|
|
442
501
|
* See the LICENSE file in the root directory of this source tree.
|
|
443
502
|
*/
|
|
444
|
-
const __iconNode$
|
|
503
|
+
const __iconNode$2 = [
|
|
445
504
|
[
|
|
446
505
|
"path",
|
|
447
506
|
{
|
|
@@ -452,14 +511,14 @@ const __iconNode$3 = [
|
|
|
452
511
|
["path", { d: "M16 9a5 5 0 0 1 0 6", key: "1q6k2b" }],
|
|
453
512
|
["path", { d: "M19.364 18.364a9 9 0 0 0 0-12.728", key: "ijwkga" }]
|
|
454
513
|
];
|
|
455
|
-
const Volume2 = createLucideIcon("volume-2", __iconNode$
|
|
514
|
+
const Volume2 = createLucideIcon("volume-2", __iconNode$2);
|
|
456
515
|
/**
|
|
457
516
|
* @license lucide-react v0.511.0 - ISC
|
|
458
517
|
*
|
|
459
518
|
* This source code is licensed under the ISC license.
|
|
460
519
|
* See the LICENSE file in the root directory of this source tree.
|
|
461
520
|
*/
|
|
462
|
-
const __iconNode$
|
|
521
|
+
const __iconNode$1 = [
|
|
463
522
|
[
|
|
464
523
|
"path",
|
|
465
524
|
{
|
|
@@ -470,14 +529,14 @@ const __iconNode$2 = [
|
|
|
470
529
|
["line", { x1: "22", x2: "16", y1: "9", y2: "15", key: "1ewh16" }],
|
|
471
530
|
["line", { x1: "16", x2: "22", y1: "9", y2: "15", key: "5ykzw1" }]
|
|
472
531
|
];
|
|
473
|
-
const VolumeX = createLucideIcon("volume-x", __iconNode$
|
|
532
|
+
const VolumeX = createLucideIcon("volume-x", __iconNode$1);
|
|
474
533
|
/**
|
|
475
534
|
* @license lucide-react v0.511.0 - ISC
|
|
476
535
|
*
|
|
477
536
|
* This source code is licensed under the ISC license.
|
|
478
537
|
* See the LICENSE file in the root directory of this source tree.
|
|
479
538
|
*/
|
|
480
|
-
const __iconNode
|
|
539
|
+
const __iconNode = [
|
|
481
540
|
[
|
|
482
541
|
"path",
|
|
483
542
|
{
|
|
@@ -493,41 +552,22 @@ const __iconNode$1 = [
|
|
|
493
552
|
["path", { d: "M21 16h-4", key: "1cnmox" }],
|
|
494
553
|
["path", { d: "M11 3H9", key: "1obp7u" }]
|
|
495
554
|
];
|
|
496
|
-
const WandSparkles = createLucideIcon("wand-sparkles", __iconNode
|
|
497
|
-
/**
|
|
498
|
-
* @license lucide-react v0.511.0 - ISC
|
|
499
|
-
*
|
|
500
|
-
* This source code is licensed under the ISC license.
|
|
501
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
502
|
-
*/
|
|
503
|
-
const __iconNode = [
|
|
504
|
-
[
|
|
505
|
-
"path",
|
|
506
|
-
{
|
|
507
|
-
d: "M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",
|
|
508
|
-
key: "1xq2db"
|
|
509
|
-
}
|
|
510
|
-
]
|
|
511
|
-
];
|
|
512
|
-
const Zap = createLucideIcon("zap", __iconNode);
|
|
555
|
+
const WandSparkles = createLucideIcon("wand-sparkles", __iconNode);
|
|
513
556
|
const toolCategories = [
|
|
514
|
-
{ id: "video", name: "Video", icon: "Video", description: "
|
|
515
|
-
{ id: "image", name: "Image", icon: "Image", description: "
|
|
516
|
-
{ id: "audio", name: "Audio", icon: "Audio", description: "
|
|
517
|
-
{ id: "text", name: "Text", icon: "Type", description: "Add text elements"
|
|
518
|
-
{ id: "
|
|
519
|
-
{ id: "
|
|
520
|
-
{ id: "
|
|
521
|
-
{ id: "subtitle", name: "Subtitles", icon: "MessageSquare", description: "Manage subtitles", shortcut: "S" }
|
|
557
|
+
{ id: "video", name: "Video", icon: "Video", description: "Add a video element" },
|
|
558
|
+
{ id: "image", name: "Image", icon: "Image", description: "Add an image element" },
|
|
559
|
+
{ id: "audio", name: "Audio", icon: "Audio", description: "Add an audio element" },
|
|
560
|
+
{ id: "text", name: "Text", icon: "Type", description: "Add text elements" },
|
|
561
|
+
{ id: "circle", name: "Circle", icon: "Circle", description: "Add a circle element" },
|
|
562
|
+
{ id: "rect", name: "Rect", icon: "Rect", description: "Add a rectangle element" },
|
|
563
|
+
{ id: "caption", name: "Caption", icon: "MessageSquare", description: "Manage captions" }
|
|
522
564
|
];
|
|
523
|
-
const getIcon
|
|
565
|
+
const getIcon = (iconName) => {
|
|
524
566
|
switch (iconName) {
|
|
525
567
|
case "Plus":
|
|
526
568
|
return Plus;
|
|
527
569
|
case "Type":
|
|
528
570
|
return Type;
|
|
529
|
-
case "Icon":
|
|
530
|
-
return Infinity;
|
|
531
571
|
case "Upload":
|
|
532
572
|
return Upload;
|
|
533
573
|
case "Square":
|
|
@@ -553,14 +593,16 @@ function Toolbar({ selectedTool, setSelectedTool }) {
|
|
|
553
593
|
setSelectedTool(toolId);
|
|
554
594
|
};
|
|
555
595
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sidebar", children: toolCategories.map((tool) => {
|
|
556
|
-
const Icon2 = getIcon
|
|
596
|
+
const Icon2 = getIcon(tool.icon);
|
|
557
597
|
const isSelected = selectedTool === tool.id;
|
|
598
|
+
const tooltipText = `${tool.name}${tool.shortcut ? ` (${tool.shortcut})` : ""}`;
|
|
558
599
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
559
600
|
"div",
|
|
560
601
|
{
|
|
561
602
|
onClick: () => handleToolSelect(tool.id),
|
|
562
603
|
className: `toolbar-btn ${isSelected ? "active" : ""}`,
|
|
563
|
-
title:
|
|
604
|
+
title: tooltipText,
|
|
605
|
+
"data-tooltip": tooltipText,
|
|
564
606
|
children: [
|
|
565
607
|
/* @__PURE__ */ jsxRuntime.jsx(Icon2, { className: "icon-sm" }),
|
|
566
608
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "toolbar-label", children: tool.name })
|
|
@@ -607,28 +649,29 @@ const StudioHeader = ({
|
|
|
607
649
|
return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "header", children: [
|
|
608
650
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", children: [
|
|
609
651
|
/* @__PURE__ */ jsxRuntime.jsx(Clapperboard, { className: "icon-lg accent-purple" }),
|
|
610
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-gradient", children: "Twick Studio" })
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
652
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-gradient", children: "Twick Studio" }),
|
|
653
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "header-separator" }),
|
|
654
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", style: { gap: "0.5rem" }, children: [
|
|
655
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm opacity-80", children: "Orientation" }),
|
|
656
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
657
|
+
"button",
|
|
658
|
+
{
|
|
659
|
+
className: `btn-ghost ${orientation === "vertical" ? "btn-primary" : ""}`,
|
|
660
|
+
title: "Portrait (720×1280)",
|
|
661
|
+
onClick: () => handleOrientationChange("vertical"),
|
|
662
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(RectangleVertical, { className: "icon-sm" })
|
|
663
|
+
}
|
|
664
|
+
),
|
|
665
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
666
|
+
"button",
|
|
667
|
+
{
|
|
668
|
+
className: `btn-ghost ${orientation === "horizontal" ? "btn-primary" : ""}`,
|
|
669
|
+
title: "Landscape (1280×720)",
|
|
670
|
+
onClick: () => handleOrientationChange("horizontal"),
|
|
671
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(RectangleHorizontal, { className: "icon-sm" })
|
|
672
|
+
}
|
|
673
|
+
)
|
|
674
|
+
] })
|
|
632
675
|
] }),
|
|
633
676
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", children: [
|
|
634
677
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -1180,8 +1223,16 @@ const AudioPanel = ({
|
|
|
1180
1223
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1181
1224
|
"div",
|
|
1182
1225
|
{
|
|
1226
|
+
draggable: true,
|
|
1183
1227
|
onDoubleClick: () => onItemSelect(item),
|
|
1184
|
-
|
|
1228
|
+
onDragStart: (e) => {
|
|
1229
|
+
e.dataTransfer.setData(
|
|
1230
|
+
VideoEditor.TIMELINE_DROP_MEDIA_TYPE,
|
|
1231
|
+
JSON.stringify({ type: "audio", url: item.url })
|
|
1232
|
+
);
|
|
1233
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
1234
|
+
},
|
|
1235
|
+
className: "media-list-item media-item-draggable",
|
|
1185
1236
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "media-list-content", children: [
|
|
1186
1237
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1187
1238
|
"button",
|
|
@@ -1283,18 +1334,26 @@ function ImagePanel({
|
|
|
1283
1334
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "media-grid", children: (items || []).map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1284
1335
|
"div",
|
|
1285
1336
|
{
|
|
1337
|
+
draggable: true,
|
|
1286
1338
|
onDoubleClick: () => onItemSelect(item),
|
|
1287
|
-
|
|
1339
|
+
onDragStart: (e) => {
|
|
1340
|
+
e.dataTransfer.setData(
|
|
1341
|
+
VideoEditor.TIMELINE_DROP_MEDIA_TYPE,
|
|
1342
|
+
JSON.stringify({ type: "image", url: item.url })
|
|
1343
|
+
);
|
|
1344
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
1345
|
+
},
|
|
1346
|
+
className: "media-item media-item-draggable",
|
|
1288
1347
|
children: [
|
|
1289
1348
|
/* @__PURE__ */ jsxRuntime.jsx("img", { src: item.url, alt: "", className: "media-item-content" }),
|
|
1290
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "media-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1349
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "media-actions media-actions-corner", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1291
1350
|
"button",
|
|
1292
1351
|
{
|
|
1293
1352
|
onClick: (e) => {
|
|
1294
1353
|
e.stopPropagation();
|
|
1295
1354
|
onItemSelect(item, true);
|
|
1296
1355
|
},
|
|
1297
|
-
className: "media-action-btn
|
|
1356
|
+
className: "media-action-btn",
|
|
1298
1357
|
children: /* @__PURE__ */ jsxRuntime.jsx(Plus, { className: "icon-sm" })
|
|
1299
1358
|
}
|
|
1300
1359
|
) })
|
|
@@ -1412,8 +1471,16 @@ function VideoPanel({
|
|
|
1412
1471
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "media-grid", children: (items || []).map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1413
1472
|
"div",
|
|
1414
1473
|
{
|
|
1474
|
+
draggable: true,
|
|
1415
1475
|
onDoubleClick: () => onItemSelect(item),
|
|
1416
|
-
|
|
1476
|
+
onDragStart: (e) => {
|
|
1477
|
+
e.dataTransfer.setData(
|
|
1478
|
+
VideoEditor.TIMELINE_DROP_MEDIA_TYPE,
|
|
1479
|
+
JSON.stringify({ type: "video", url: item.url })
|
|
1480
|
+
);
|
|
1481
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
1482
|
+
},
|
|
1483
|
+
className: "media-item media-item-draggable",
|
|
1417
1484
|
children: [
|
|
1418
1485
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1419
1486
|
"video",
|
|
@@ -1430,32 +1497,34 @@ function VideoPanel({
|
|
|
1430
1497
|
}
|
|
1431
1498
|
}
|
|
1432
1499
|
),
|
|
1433
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
e
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1500
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "media-actions media-actions-corner", children: [
|
|
1501
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1502
|
+
"button",
|
|
1503
|
+
{
|
|
1504
|
+
onClick: (e) => {
|
|
1505
|
+
var _a, _b;
|
|
1506
|
+
e.stopPropagation();
|
|
1507
|
+
const videoEl = (_b = (_a = e.currentTarget.parentElement) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.querySelector("video");
|
|
1508
|
+
if (videoEl) {
|
|
1509
|
+
togglePlayPause(item, videoEl);
|
|
1510
|
+
}
|
|
1511
|
+
},
|
|
1512
|
+
className: "media-action-btn",
|
|
1513
|
+
children: playingVideo === item.id ? /* @__PURE__ */ jsxRuntime.jsx(Pause, { className: "icon-sm" }) : /* @__PURE__ */ jsxRuntime.jsx(Play, { className: "icon-sm" })
|
|
1514
|
+
}
|
|
1515
|
+
),
|
|
1516
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1517
|
+
"button",
|
|
1518
|
+
{
|
|
1519
|
+
onClick: (e) => {
|
|
1520
|
+
e.stopPropagation();
|
|
1521
|
+
onItemSelect(item, true);
|
|
1522
|
+
},
|
|
1523
|
+
className: "media-action-btn",
|
|
1524
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Plus, { className: "icon-sm" })
|
|
1525
|
+
}
|
|
1526
|
+
)
|
|
1527
|
+
] })
|
|
1459
1528
|
]
|
|
1460
1529
|
},
|
|
1461
1530
|
item.id
|
|
@@ -1526,6 +1595,9 @@ function TextPanel({
|
|
|
1526
1595
|
applyShadow,
|
|
1527
1596
|
shadowColor,
|
|
1528
1597
|
strokeWidth,
|
|
1598
|
+
applyBackground,
|
|
1599
|
+
backgroundColor,
|
|
1600
|
+
backgroundOpacity,
|
|
1529
1601
|
fonts,
|
|
1530
1602
|
operation,
|
|
1531
1603
|
setTextContent,
|
|
@@ -1538,6 +1610,9 @@ function TextPanel({
|
|
|
1538
1610
|
setApplyShadow,
|
|
1539
1611
|
setShadowColor,
|
|
1540
1612
|
setStrokeWidth,
|
|
1613
|
+
setApplyBackground,
|
|
1614
|
+
setBackgroundColor,
|
|
1615
|
+
setBackgroundOpacity,
|
|
1541
1616
|
handleApplyChanges
|
|
1542
1617
|
}) {
|
|
1543
1618
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
@@ -1706,7 +1781,70 @@ function TextPanel({
|
|
|
1706
1781
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "slider-value", children: strokeWidth })
|
|
1707
1782
|
] })
|
|
1708
1783
|
] }),
|
|
1709
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1784
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
|
|
1785
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Background" }),
|
|
1786
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "color-section", children: [
|
|
1787
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "checkbox-control", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "checkbox-label", children: [
|
|
1788
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1789
|
+
"input",
|
|
1790
|
+
{
|
|
1791
|
+
type: "checkbox",
|
|
1792
|
+
checked: applyBackground,
|
|
1793
|
+
onChange: (e) => setApplyBackground(e.target.checked),
|
|
1794
|
+
className: "checkbox-purple"
|
|
1795
|
+
}
|
|
1796
|
+
),
|
|
1797
|
+
"Apply Background"
|
|
1798
|
+
] }) }),
|
|
1799
|
+
applyBackground && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1800
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "color-control", children: [
|
|
1801
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-small", children: "Background Color" }),
|
|
1802
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "color-inputs", children: [
|
|
1803
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1804
|
+
"input",
|
|
1805
|
+
{
|
|
1806
|
+
type: "color",
|
|
1807
|
+
value: backgroundColor,
|
|
1808
|
+
onChange: (e) => setBackgroundColor(e.target.value),
|
|
1809
|
+
className: "color-picker"
|
|
1810
|
+
}
|
|
1811
|
+
),
|
|
1812
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1813
|
+
"input",
|
|
1814
|
+
{
|
|
1815
|
+
type: "text",
|
|
1816
|
+
value: backgroundColor,
|
|
1817
|
+
onChange: (e) => setBackgroundColor(e.target.value),
|
|
1818
|
+
className: "color-text"
|
|
1819
|
+
}
|
|
1820
|
+
)
|
|
1821
|
+
] })
|
|
1822
|
+
] }),
|
|
1823
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
|
|
1824
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-small", children: "Background Opacity" }),
|
|
1825
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "slider-container", children: [
|
|
1826
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1827
|
+
"input",
|
|
1828
|
+
{
|
|
1829
|
+
type: "range",
|
|
1830
|
+
min: "0",
|
|
1831
|
+
max: "1",
|
|
1832
|
+
step: 0.1,
|
|
1833
|
+
value: backgroundOpacity,
|
|
1834
|
+
onChange: (e) => setBackgroundOpacity(Number(e.target.value)),
|
|
1835
|
+
className: "slider-purple"
|
|
1836
|
+
}
|
|
1837
|
+
),
|
|
1838
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "slider-value", children: [
|
|
1839
|
+
Math.round(backgroundOpacity * 100),
|
|
1840
|
+
"%"
|
|
1841
|
+
] })
|
|
1842
|
+
] })
|
|
1843
|
+
] })
|
|
1844
|
+
] })
|
|
1845
|
+
] })
|
|
1846
|
+
] }),
|
|
1847
|
+
operation !== "Apply Changes" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleApplyChanges, className: "btn-primary w-full", children: operation }) })
|
|
1710
1848
|
] });
|
|
1711
1849
|
}
|
|
1712
1850
|
const DEFAULT_TEXT_PROPS = {
|
|
@@ -1740,68 +1878,155 @@ const useTextPanel = ({
|
|
|
1740
1878
|
const [applyShadow, setApplyShadow] = react.useState(DEFAULT_TEXT_PROPS.applyShadow);
|
|
1741
1879
|
const [shadowColor, setShadowColor] = react.useState(DEFAULT_TEXT_PROPS.shadowColor);
|
|
1742
1880
|
const [strokeWidth, setStrokeWidth] = react.useState(DEFAULT_TEXT_PROPS.strokeWidth);
|
|
1881
|
+
const [applyBackground, setApplyBackground] = react.useState(false);
|
|
1882
|
+
const [backgroundColor, setBackgroundColor] = react.useState("#FACC15");
|
|
1883
|
+
const [backgroundOpacity, setBackgroundOpacity] = react.useState(1);
|
|
1743
1884
|
const fonts = Object.values(VideoEditor.AVAILABLE_TEXT_FONTS);
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1885
|
+
const applyLiveChangesToExistingText = (overrides = {}) => {
|
|
1886
|
+
if (!(selectedElement instanceof timeline.TextElement)) {
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
const textElement = selectedElement;
|
|
1890
|
+
const nextState = {
|
|
1891
|
+
textContent,
|
|
1892
|
+
fontSize,
|
|
1893
|
+
selectedFont,
|
|
1894
|
+
isBold,
|
|
1895
|
+
isItalic,
|
|
1896
|
+
textColor,
|
|
1897
|
+
strokeColor,
|
|
1898
|
+
strokeWidth,
|
|
1899
|
+
applyShadow,
|
|
1900
|
+
shadowColor,
|
|
1901
|
+
applyBackground,
|
|
1902
|
+
backgroundColor,
|
|
1903
|
+
backgroundOpacity,
|
|
1904
|
+
...overrides
|
|
1905
|
+
};
|
|
1906
|
+
textElement.setText(nextState.textContent);
|
|
1907
|
+
textElement.setFontSize(nextState.fontSize);
|
|
1908
|
+
textElement.setFontFamily(nextState.selectedFont);
|
|
1909
|
+
textElement.setFontWeight(nextState.isBold ? 700 : 400);
|
|
1910
|
+
textElement.setFontStyle(nextState.isItalic ? "italic" : "normal");
|
|
1911
|
+
textElement.setFill(nextState.textColor);
|
|
1912
|
+
textElement.setStrokeColor(nextState.strokeColor);
|
|
1913
|
+
textElement.setLineWidth(nextState.strokeWidth);
|
|
1914
|
+
textElement.setTextAlign(DEFAULT_TEXT_PROPS.textAlign);
|
|
1915
|
+
const nextProps = { ...textElement.getProps() };
|
|
1916
|
+
if (nextState.applyShadow) {
|
|
1917
|
+
nextProps.shadowColor = nextState.shadowColor;
|
|
1918
|
+
nextProps.shadowOffset = DEFAULT_TEXT_PROPS.shadowOffset;
|
|
1919
|
+
nextProps.shadowBlur = DEFAULT_TEXT_PROPS.shadowBlur;
|
|
1920
|
+
nextProps.shadowOpacity = DEFAULT_TEXT_PROPS.shadowOpacity;
|
|
1775
1921
|
} else {
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
shadowColor,
|
|
1781
|
-
shadowOffset: DEFAULT_TEXT_PROPS.shadowOffset,
|
|
1782
|
-
shadowBlur: DEFAULT_TEXT_PROPS.shadowBlur,
|
|
1783
|
-
shadowOpacity: DEFAULT_TEXT_PROPS.shadowOpacity
|
|
1784
|
-
});
|
|
1785
|
-
}
|
|
1786
|
-
await addElement(textElement);
|
|
1922
|
+
nextProps.shadowColor = void 0;
|
|
1923
|
+
nextProps.shadowOffset = void 0;
|
|
1924
|
+
nextProps.shadowBlur = void 0;
|
|
1925
|
+
nextProps.shadowOpacity = void 0;
|
|
1787
1926
|
}
|
|
1927
|
+
if (nextState.applyBackground) {
|
|
1928
|
+
nextProps.backgroundColor = nextState.backgroundColor;
|
|
1929
|
+
nextProps.backgroundOpacity = nextState.backgroundOpacity;
|
|
1930
|
+
} else {
|
|
1931
|
+
nextProps.backgroundColor = void 0;
|
|
1932
|
+
nextProps.backgroundOpacity = void 0;
|
|
1933
|
+
}
|
|
1934
|
+
textElement.setProps(nextProps);
|
|
1935
|
+
updateElement(textElement);
|
|
1788
1936
|
};
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1937
|
+
const handleTextContentChange = (text) => {
|
|
1938
|
+
setTextContent(text);
|
|
1939
|
+
applyLiveChangesToExistingText({ textContent: text });
|
|
1940
|
+
};
|
|
1941
|
+
const handleFontSizeChange = (size) => {
|
|
1942
|
+
setFontSize(size);
|
|
1943
|
+
applyLiveChangesToExistingText({ fontSize: size });
|
|
1944
|
+
};
|
|
1945
|
+
const handleSelectedFontChange = (font) => {
|
|
1946
|
+
setSelectedFont(font);
|
|
1947
|
+
applyLiveChangesToExistingText({ selectedFont: font });
|
|
1948
|
+
};
|
|
1949
|
+
const handleIsBoldChange = (bold) => {
|
|
1950
|
+
setIsBold(bold);
|
|
1951
|
+
applyLiveChangesToExistingText({ isBold: bold });
|
|
1952
|
+
};
|
|
1953
|
+
const handleIsItalicChange = (italic) => {
|
|
1954
|
+
setIsItalic(italic);
|
|
1955
|
+
applyLiveChangesToExistingText({ isItalic: italic });
|
|
1956
|
+
};
|
|
1957
|
+
const handleTextColorChange = (color) => {
|
|
1958
|
+
setTextColor(color);
|
|
1959
|
+
applyLiveChangesToExistingText({ textColor: color });
|
|
1960
|
+
};
|
|
1961
|
+
const handleStrokeColorChange = (color) => {
|
|
1962
|
+
setStrokeColor(color);
|
|
1963
|
+
applyLiveChangesToExistingText({ strokeColor: color });
|
|
1964
|
+
};
|
|
1965
|
+
const handleStrokeWidthChange = (width) => {
|
|
1966
|
+
setStrokeWidth(width);
|
|
1967
|
+
applyLiveChangesToExistingText({ strokeWidth: width });
|
|
1968
|
+
};
|
|
1969
|
+
const handleApplyShadowChange = (shadow) => {
|
|
1970
|
+
setApplyShadow(shadow);
|
|
1971
|
+
applyLiveChangesToExistingText({ applyShadow: shadow });
|
|
1972
|
+
};
|
|
1973
|
+
const handleShadowColorChange = (color) => {
|
|
1974
|
+
setShadowColor(color);
|
|
1975
|
+
applyLiveChangesToExistingText({ shadowColor: color });
|
|
1976
|
+
};
|
|
1977
|
+
const handleApplyBackgroundChange = (apply) => {
|
|
1978
|
+
setApplyBackground(apply);
|
|
1979
|
+
applyLiveChangesToExistingText({ applyBackground: apply });
|
|
1980
|
+
};
|
|
1981
|
+
const handleBackgroundColorChange = (color) => {
|
|
1982
|
+
setBackgroundColor(color);
|
|
1983
|
+
applyLiveChangesToExistingText({ backgroundColor: color });
|
|
1984
|
+
};
|
|
1985
|
+
const handleBackgroundOpacityChange = (opacity) => {
|
|
1986
|
+
setBackgroundOpacity(opacity);
|
|
1987
|
+
applyLiveChangesToExistingText({ backgroundOpacity: opacity });
|
|
1988
|
+
};
|
|
1989
|
+
const handleApplyChanges = async () => {
|
|
1990
|
+
if (selectedElement instanceof timeline.TextElement) {
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
const textElement = new timeline.TextElement(textContent).setFontSize(fontSize).setFontFamily(selectedFont).setFontWeight(isBold ? 700 : 400).setFontStyle(isItalic ? "italic" : "normal").setFill(textColor).setStrokeColor(strokeColor).setLineWidth(strokeWidth).setTextAlign("center");
|
|
1994
|
+
const nextProps = { ...textElement.getProps() };
|
|
1995
|
+
if (applyShadow) {
|
|
1996
|
+
nextProps.shadowColor = shadowColor;
|
|
1997
|
+
nextProps.shadowOffset = DEFAULT_TEXT_PROPS.shadowOffset;
|
|
1998
|
+
nextProps.shadowBlur = DEFAULT_TEXT_PROPS.shadowBlur;
|
|
1999
|
+
nextProps.shadowOpacity = DEFAULT_TEXT_PROPS.shadowOpacity;
|
|
2000
|
+
}
|
|
2001
|
+
if (applyBackground) {
|
|
2002
|
+
nextProps.backgroundColor = backgroundColor;
|
|
2003
|
+
nextProps.backgroundOpacity = backgroundOpacity;
|
|
2004
|
+
}
|
|
2005
|
+
textElement.setProps(nextProps);
|
|
2006
|
+
await addElement(textElement);
|
|
2007
|
+
};
|
|
2008
|
+
react.useEffect(() => {
|
|
2009
|
+
if (selectedElement instanceof timeline.TextElement) {
|
|
2010
|
+
setTextContent(selectedElement.getText());
|
|
2011
|
+
const textProps = selectedElement.getProps();
|
|
2012
|
+
setSelectedFont(textProps.fontFamily ?? DEFAULT_TEXT_PROPS.fontFamily);
|
|
2013
|
+
setFontSize(textProps.fontSize ?? DEFAULT_TEXT_PROPS.fontSize);
|
|
2014
|
+
setIsBold(textProps.fontWeight === 700);
|
|
2015
|
+
setIsItalic(textProps.fontStyle === "italic");
|
|
2016
|
+
setTextColor(textProps.fill ?? DEFAULT_TEXT_PROPS.textColor);
|
|
2017
|
+
setStrokeColor(textProps.stroke ?? DEFAULT_TEXT_PROPS.strokeColor);
|
|
2018
|
+
setStrokeWidth(textProps.lineWidth ?? DEFAULT_TEXT_PROPS.strokeWidth);
|
|
2019
|
+
const hasShadow = textProps.shadowColor !== void 0;
|
|
2020
|
+
setApplyShadow(hasShadow);
|
|
2021
|
+
if (hasShadow) {
|
|
1803
2022
|
setShadowColor(textProps.shadowColor ?? DEFAULT_TEXT_PROPS.shadowColor);
|
|
1804
2023
|
}
|
|
2024
|
+
const hasBackground = textProps.backgroundColor != null && textProps.backgroundColor !== "";
|
|
2025
|
+
setApplyBackground(hasBackground);
|
|
2026
|
+
if (hasBackground) {
|
|
2027
|
+
setBackgroundColor(textProps.backgroundColor ?? "#FACC15");
|
|
2028
|
+
setBackgroundOpacity(textProps.backgroundOpacity ?? 1);
|
|
2029
|
+
}
|
|
1805
2030
|
} else {
|
|
1806
2031
|
setTextContent(DEFAULT_TEXT_PROPS.text);
|
|
1807
2032
|
setFontSize(DEFAULT_TEXT_PROPS.fontSize);
|
|
@@ -1813,6 +2038,9 @@ const useTextPanel = ({
|
|
|
1813
2038
|
setStrokeWidth(DEFAULT_TEXT_PROPS.strokeWidth);
|
|
1814
2039
|
setApplyShadow(DEFAULT_TEXT_PROPS.applyShadow);
|
|
1815
2040
|
setShadowColor(DEFAULT_TEXT_PROPS.shadowColor);
|
|
2041
|
+
setApplyBackground(false);
|
|
2042
|
+
setBackgroundColor("#FACC15");
|
|
2043
|
+
setBackgroundOpacity(1);
|
|
1816
2044
|
}
|
|
1817
2045
|
}, [selectedElement]);
|
|
1818
2046
|
return {
|
|
@@ -1828,16 +2056,22 @@ const useTextPanel = ({
|
|
|
1828
2056
|
strokeWidth,
|
|
1829
2057
|
fonts,
|
|
1830
2058
|
operation: selectedElement instanceof timeline.TextElement ? "Apply Changes" : "Add Text",
|
|
1831
|
-
setTextContent,
|
|
1832
|
-
setFontSize,
|
|
1833
|
-
setSelectedFont,
|
|
1834
|
-
setIsBold,
|
|
1835
|
-
setIsItalic,
|
|
1836
|
-
setTextColor,
|
|
1837
|
-
setStrokeColor,
|
|
1838
|
-
setApplyShadow,
|
|
1839
|
-
setShadowColor,
|
|
1840
|
-
setStrokeWidth,
|
|
2059
|
+
setTextContent: handleTextContentChange,
|
|
2060
|
+
setFontSize: handleFontSizeChange,
|
|
2061
|
+
setSelectedFont: handleSelectedFontChange,
|
|
2062
|
+
setIsBold: handleIsBoldChange,
|
|
2063
|
+
setIsItalic: handleIsItalicChange,
|
|
2064
|
+
setTextColor: handleTextColorChange,
|
|
2065
|
+
setStrokeColor: handleStrokeColorChange,
|
|
2066
|
+
setApplyShadow: handleApplyShadowChange,
|
|
2067
|
+
setShadowColor: handleShadowColorChange,
|
|
2068
|
+
setStrokeWidth: handleStrokeWidthChange,
|
|
2069
|
+
applyBackground,
|
|
2070
|
+
backgroundColor,
|
|
2071
|
+
backgroundOpacity,
|
|
2072
|
+
setApplyBackground: handleApplyBackgroundChange,
|
|
2073
|
+
setBackgroundColor: handleBackgroundColorChange,
|
|
2074
|
+
setBackgroundOpacity: handleBackgroundOpacityChange,
|
|
1841
2075
|
handleApplyChanges
|
|
1842
2076
|
};
|
|
1843
2077
|
};
|
|
@@ -1845,227 +2079,14 @@ function TextPanelContainer(props) {
|
|
|
1845
2079
|
const textPanelProps = useTextPanel(props);
|
|
1846
2080
|
return /* @__PURE__ */ jsxRuntime.jsx(TextPanel, { ...textPanelProps });
|
|
1847
2081
|
}
|
|
1848
|
-
const SearchInput = ({
|
|
1849
|
-
searchQuery,
|
|
1850
|
-
setSearchQuery
|
|
1851
|
-
}) => {
|
|
1852
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "search-container", children: [
|
|
1853
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1854
|
-
"input",
|
|
1855
|
-
{
|
|
1856
|
-
type: "text",
|
|
1857
|
-
placeholder: "Search media...",
|
|
1858
|
-
value: searchQuery,
|
|
1859
|
-
onChange: (e) => setSearchQuery(e.target.value),
|
|
1860
|
-
className: "input search-input w-full"
|
|
1861
|
-
}
|
|
1862
|
-
),
|
|
1863
|
-
/* @__PURE__ */ jsxRuntime.jsx(Search, { className: "search-icon" })
|
|
1864
|
-
] });
|
|
1865
|
-
};
|
|
1866
|
-
function IconPanel({
|
|
1867
|
-
icons,
|
|
1868
|
-
loading,
|
|
1869
|
-
totalIcons,
|
|
1870
|
-
searchQuery,
|
|
1871
|
-
handleSearch,
|
|
1872
|
-
handleSelection,
|
|
1873
|
-
handleDownloadIcon,
|
|
1874
|
-
handleLoadMore
|
|
1875
|
-
}) {
|
|
1876
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
1877
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Icon Library" }),
|
|
1878
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { searchQuery, setSearchQuery: handleSearch }) }),
|
|
1879
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "media-content", children: [
|
|
1880
|
-
totalIcons > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "media-count", children: [
|
|
1881
|
-
"Showing ",
|
|
1882
|
-
icons.length,
|
|
1883
|
-
" of ",
|
|
1884
|
-
totalIcons,
|
|
1885
|
-
" icons"
|
|
1886
|
-
] }),
|
|
1887
|
-
loading && icons.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
1888
|
-
/* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
|
|
1889
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Loading icons..." })
|
|
1890
|
-
] }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "icon-grid", children: (icons || []).map((icon, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "icon-item", children: [
|
|
1891
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1892
|
-
"div",
|
|
1893
|
-
{
|
|
1894
|
-
onClick: () => handleSelection(icon),
|
|
1895
|
-
className: "icon-content",
|
|
1896
|
-
dangerouslySetInnerHTML: { __html: icon.svg }
|
|
1897
|
-
}
|
|
1898
|
-
),
|
|
1899
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "icon-actions", children: [
|
|
1900
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1901
|
-
"button",
|
|
1902
|
-
{
|
|
1903
|
-
onClick: (e) => {
|
|
1904
|
-
e.stopPropagation();
|
|
1905
|
-
handleSelection(icon);
|
|
1906
|
-
},
|
|
1907
|
-
className: "icon-action-btn",
|
|
1908
|
-
title: "Add to timeline",
|
|
1909
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(Plus, { className: "icon-sm" })
|
|
1910
|
-
}
|
|
1911
|
-
),
|
|
1912
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1913
|
-
"button",
|
|
1914
|
-
{
|
|
1915
|
-
onClick: (e) => {
|
|
1916
|
-
e.stopPropagation();
|
|
1917
|
-
handleDownloadIcon(icon);
|
|
1918
|
-
},
|
|
1919
|
-
className: "icon-action-btn",
|
|
1920
|
-
title: "Download SVG",
|
|
1921
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(Download, { className: "icon-sm" })
|
|
1922
|
-
}
|
|
1923
|
-
)
|
|
1924
|
-
] }),
|
|
1925
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "icon-name", children: icon.name })
|
|
1926
|
-
] }, index)) }),
|
|
1927
|
-
!loading && icons.length === 0 && searchQuery && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
1928
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No icons found" }),
|
|
1929
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-subtext", children: "Try a different search term" })
|
|
1930
|
-
] }) }),
|
|
1931
|
-
!loading && totalIcons && icons.length < totalIcons && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1932
|
-
"button",
|
|
1933
|
-
{
|
|
1934
|
-
onClick: handleLoadMore,
|
|
1935
|
-
disabled: loading,
|
|
1936
|
-
className: "btn-primary",
|
|
1937
|
-
children: "Load More Icons"
|
|
1938
|
-
}
|
|
1939
|
-
) })
|
|
1940
|
-
] })
|
|
1941
|
-
] });
|
|
1942
|
-
}
|
|
1943
|
-
const ICONS_PER_PAGE = 20;
|
|
1944
|
-
const useIconPanel = ({
|
|
1945
|
-
selectedElement,
|
|
1946
|
-
addElement,
|
|
1947
|
-
updateElement
|
|
1948
|
-
}) => {
|
|
1949
|
-
const [icons, setIcons] = react.useState([]);
|
|
1950
|
-
const [loading, setLoading] = react.useState(false);
|
|
1951
|
-
const [page, setPage] = react.useState(1);
|
|
1952
|
-
const [hasMore, setHasMore] = react.useState(true);
|
|
1953
|
-
const [totalIcons, setTotalIcons] = react.useState(0);
|
|
1954
|
-
const [searchQuery, setSearchQuery] = react.useState("");
|
|
1955
|
-
const currentQuery = react.useRef("");
|
|
1956
|
-
const fetchIcons = async (query, reset = false) => {
|
|
1957
|
-
try {
|
|
1958
|
-
setLoading(true);
|
|
1959
|
-
const newPage = reset ? 1 : page;
|
|
1960
|
-
const start = (newPage - 1) * ICONS_PER_PAGE;
|
|
1961
|
-
const url = `https://api.iconify.design/search?query=${query}&limit=${ICONS_PER_PAGE}&offset=${start}`;
|
|
1962
|
-
const response = await fetch(url);
|
|
1963
|
-
const data = await response.json();
|
|
1964
|
-
const iconData = data.icons || [];
|
|
1965
|
-
const total = data.total || 0;
|
|
1966
|
-
setTotalIcons(total);
|
|
1967
|
-
const formattedIcons = await Promise.all(
|
|
1968
|
-
iconData.map(async (icon) => {
|
|
1969
|
-
const svgUrl = `https://api.iconify.design/${icon}.svg`;
|
|
1970
|
-
try {
|
|
1971
|
-
const svgResponse = await fetch(svgUrl);
|
|
1972
|
-
const svg = await svgResponse.text();
|
|
1973
|
-
return { name: icon, svg };
|
|
1974
|
-
} catch (e) {
|
|
1975
|
-
console.error(`Error fetching SVG for ${icon}:`, e);
|
|
1976
|
-
return null;
|
|
1977
|
-
}
|
|
1978
|
-
})
|
|
1979
|
-
);
|
|
1980
|
-
const validIcons = formattedIcons.filter((icon) => icon !== null);
|
|
1981
|
-
if (reset) {
|
|
1982
|
-
setIcons(validIcons);
|
|
1983
|
-
} else {
|
|
1984
|
-
setIcons([...icons, ...validIcons]);
|
|
1985
|
-
}
|
|
1986
|
-
setHasMore(start + validIcons.length < total);
|
|
1987
|
-
if (!reset) {
|
|
1988
|
-
setPage(newPage + 1);
|
|
1989
|
-
} else {
|
|
1990
|
-
setPage(2);
|
|
1991
|
-
}
|
|
1992
|
-
} catch (error) {
|
|
1993
|
-
console.error("Error fetching icons:", error);
|
|
1994
|
-
} finally {
|
|
1995
|
-
setLoading(false);
|
|
1996
|
-
}
|
|
1997
|
-
};
|
|
1998
|
-
react.useEffect(() => {
|
|
1999
|
-
fetchIcons("media", true);
|
|
2000
|
-
}, []);
|
|
2001
|
-
const handleSearch = (query) => {
|
|
2002
|
-
currentQuery.current = query;
|
|
2003
|
-
setSearchQuery(query);
|
|
2004
|
-
fetchIcons(query, true);
|
|
2005
|
-
};
|
|
2006
|
-
const handleSelection = (icon) => {
|
|
2007
|
-
const svgBlob = new Blob([icon.svg], { type: "image/svg+xml" });
|
|
2008
|
-
const url = URL.createObjectURL(svgBlob);
|
|
2009
|
-
let iconElement;
|
|
2010
|
-
if (selectedElement instanceof timeline.IconElement) {
|
|
2011
|
-
iconElement = selectedElement;
|
|
2012
|
-
iconElement.setSrc(url);
|
|
2013
|
-
iconElement.setName(icon.name);
|
|
2014
|
-
updateElement == null ? void 0 : updateElement(iconElement);
|
|
2015
|
-
} else {
|
|
2016
|
-
iconElement = new timeline.IconElement(url, {
|
|
2017
|
-
width: 100,
|
|
2018
|
-
height: 100
|
|
2019
|
-
});
|
|
2020
|
-
iconElement.setName(icon.name);
|
|
2021
|
-
addElement == null ? void 0 : addElement(iconElement);
|
|
2022
|
-
}
|
|
2023
|
-
URL.revokeObjectURL(url);
|
|
2024
|
-
};
|
|
2025
|
-
const handleDownloadIcon = (icon) => {
|
|
2026
|
-
const blob = new Blob([icon.svg], { type: "image/svg+xml" });
|
|
2027
|
-
const url = URL.createObjectURL(blob);
|
|
2028
|
-
const a = document.createElement("a");
|
|
2029
|
-
a.href = url;
|
|
2030
|
-
a.download = `${icon.name}.svg`;
|
|
2031
|
-
document.body.appendChild(a);
|
|
2032
|
-
a.click();
|
|
2033
|
-
document.body.removeChild(a);
|
|
2034
|
-
URL.revokeObjectURL(url);
|
|
2035
|
-
};
|
|
2036
|
-
const handleLoadMore = () => {
|
|
2037
|
-
fetchIcons(currentQuery.current, false);
|
|
2038
|
-
};
|
|
2039
|
-
return {
|
|
2040
|
-
icons,
|
|
2041
|
-
loading,
|
|
2042
|
-
hasMore,
|
|
2043
|
-
totalIcons,
|
|
2044
|
-
searchQuery,
|
|
2045
|
-
handleSearch,
|
|
2046
|
-
handleSelection,
|
|
2047
|
-
handleDownloadIcon,
|
|
2048
|
-
handleLoadMore
|
|
2049
|
-
};
|
|
2050
|
-
};
|
|
2051
|
-
function IconPanelContainer(props) {
|
|
2052
|
-
const iconPanelProps = useIconPanel({
|
|
2053
|
-
selectedElement: props.selectedElement ?? null,
|
|
2054
|
-
addElement: props.addElement,
|
|
2055
|
-
updateElement: props.updateElement
|
|
2056
|
-
});
|
|
2057
|
-
return /* @__PURE__ */ jsxRuntime.jsx(IconPanel, { ...iconPanelProps });
|
|
2058
|
-
}
|
|
2059
2082
|
function RectPanel({
|
|
2060
2083
|
cornerRadius,
|
|
2061
2084
|
fillColor,
|
|
2062
|
-
opacity,
|
|
2063
2085
|
strokeColor,
|
|
2064
2086
|
lineWidth,
|
|
2065
2087
|
operation,
|
|
2066
2088
|
setCornerRadius,
|
|
2067
2089
|
setFillColor,
|
|
2068
|
-
setOpacity,
|
|
2069
2090
|
setStrokeColor,
|
|
2070
2091
|
setLineWidth,
|
|
2071
2092
|
handleApplyChanges
|
|
@@ -2115,26 +2136,6 @@ function RectPanel({
|
|
|
2115
2136
|
)
|
|
2116
2137
|
] })
|
|
2117
2138
|
] }),
|
|
2118
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
|
|
2119
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Opacity" }),
|
|
2120
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "slider-container", children: [
|
|
2121
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2122
|
-
"input",
|
|
2123
|
-
{
|
|
2124
|
-
type: "range",
|
|
2125
|
-
min: "0",
|
|
2126
|
-
max: "100",
|
|
2127
|
-
value: opacity,
|
|
2128
|
-
onChange: (e) => setOpacity(Number(e.target.value)),
|
|
2129
|
-
className: "slider-purple"
|
|
2130
|
-
}
|
|
2131
|
-
),
|
|
2132
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "slider-value", children: [
|
|
2133
|
-
opacity,
|
|
2134
|
-
"%"
|
|
2135
|
-
] })
|
|
2136
|
-
] })
|
|
2137
|
-
] }),
|
|
2138
2139
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
|
|
2139
2140
|
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Stroke Color" }),
|
|
2140
2141
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "color-inputs", children: [
|
|
@@ -2178,7 +2179,7 @@ function RectPanel({
|
|
|
2178
2179
|
] })
|
|
2179
2180
|
] })
|
|
2180
2181
|
] }),
|
|
2181
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2182
|
+
operation !== "Apply Changes" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2182
2183
|
"button",
|
|
2183
2184
|
{
|
|
2184
2185
|
onClick: handleApplyChanges,
|
|
@@ -2191,6 +2192,7 @@ function RectPanel({
|
|
|
2191
2192
|
const DEFAULT_RECT_PROPS = {
|
|
2192
2193
|
cornerRadius: 0,
|
|
2193
2194
|
fillColor: "#3b82f6",
|
|
2195
|
+
// UI uses 0–100%; element opacity is 0–1
|
|
2194
2196
|
opacity: 100,
|
|
2195
2197
|
strokeColor: "#000000",
|
|
2196
2198
|
lineWidth: 0
|
|
@@ -2205,25 +2207,56 @@ const useRectPanel = ({
|
|
|
2205
2207
|
const [opacity, setOpacity] = react.useState(DEFAULT_RECT_PROPS.opacity);
|
|
2206
2208
|
const [strokeColor, setStrokeColor] = react.useState(DEFAULT_RECT_PROPS.strokeColor);
|
|
2207
2209
|
const [lineWidth, setLineWidth] = react.useState(DEFAULT_RECT_PROPS.lineWidth);
|
|
2210
|
+
const applyLiveChangesToExistingRect = (overrides = {}) => {
|
|
2211
|
+
if (!(selectedElement instanceof timeline.RectElement)) {
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
const rectElement = selectedElement;
|
|
2215
|
+
const nextCornerRadius = overrides.cornerRadius ?? cornerRadius;
|
|
2216
|
+
const nextFillColor = overrides.fillColor ?? fillColor;
|
|
2217
|
+
const nextOpacityPercent = overrides.opacity ?? opacity;
|
|
2218
|
+
const nextStrokeColor = overrides.strokeColor ?? strokeColor;
|
|
2219
|
+
const nextLineWidth = overrides.lineWidth ?? lineWidth;
|
|
2220
|
+
rectElement.setCornerRadius(nextCornerRadius);
|
|
2221
|
+
rectElement.setFill(nextFillColor);
|
|
2222
|
+
rectElement.setOpacity(nextOpacityPercent / 100);
|
|
2223
|
+
rectElement.setStrokeColor(nextStrokeColor);
|
|
2224
|
+
rectElement.setLineWidth(nextLineWidth);
|
|
2225
|
+
updateElement == null ? void 0 : updateElement(rectElement);
|
|
2226
|
+
};
|
|
2227
|
+
const handleCornerRadiusChange = (radius) => {
|
|
2228
|
+
setCornerRadius(radius);
|
|
2229
|
+
applyLiveChangesToExistingRect({ cornerRadius: radius });
|
|
2230
|
+
};
|
|
2231
|
+
const handleFillColorChange = (color) => {
|
|
2232
|
+
setFillColor(color);
|
|
2233
|
+
applyLiveChangesToExistingRect({ fillColor: color });
|
|
2234
|
+
};
|
|
2235
|
+
const handleOpacityChange = (nextOpacity) => {
|
|
2236
|
+
setOpacity(nextOpacity);
|
|
2237
|
+
applyLiveChangesToExistingRect({ opacity: nextOpacity });
|
|
2238
|
+
};
|
|
2239
|
+
const handleStrokeColorChange = (color) => {
|
|
2240
|
+
setStrokeColor(color);
|
|
2241
|
+
applyLiveChangesToExistingRect({ strokeColor: color });
|
|
2242
|
+
};
|
|
2243
|
+
const handleLineWidthChange = (width) => {
|
|
2244
|
+
setLineWidth(width);
|
|
2245
|
+
applyLiveChangesToExistingRect({ lineWidth: width });
|
|
2246
|
+
};
|
|
2208
2247
|
const handleApplyChanges = () => {
|
|
2209
|
-
let rectElement;
|
|
2210
2248
|
if (selectedElement instanceof timeline.RectElement) {
|
|
2211
|
-
|
|
2212
|
-
rectElement.setCornerRadius(cornerRadius);
|
|
2213
|
-
rectElement.setOpacity(opacity);
|
|
2214
|
-
rectElement.setStrokeColor(strokeColor);
|
|
2215
|
-
rectElement.setLineWidth(lineWidth);
|
|
2216
|
-
updateElement == null ? void 0 : updateElement(rectElement);
|
|
2217
|
-
} else {
|
|
2218
|
-
rectElement = new timeline.RectElement(fillColor, { width: 200, height: 200 }).setCornerRadius(cornerRadius).setOpacity(opacity).setStrokeColor(strokeColor).setLineWidth(lineWidth);
|
|
2219
|
-
addElement == null ? void 0 : addElement(rectElement);
|
|
2249
|
+
return;
|
|
2220
2250
|
}
|
|
2251
|
+
const rectElement = new timeline.RectElement(fillColor, { width: 200, height: 200 }).setCornerRadius(cornerRadius).setOpacity(opacity / 100).setStrokeColor(strokeColor).setLineWidth(lineWidth);
|
|
2252
|
+
addElement == null ? void 0 : addElement(rectElement);
|
|
2221
2253
|
};
|
|
2222
2254
|
react.useEffect(() => {
|
|
2223
2255
|
if (selectedElement instanceof timeline.RectElement) {
|
|
2224
2256
|
setCornerRadius(selectedElement.getCornerRadius() ?? DEFAULT_RECT_PROPS.cornerRadius);
|
|
2225
2257
|
setFillColor(selectedElement.getFill() ?? DEFAULT_RECT_PROPS.fillColor);
|
|
2226
|
-
|
|
2258
|
+
const elementOpacity = selectedElement.getOpacity();
|
|
2259
|
+
setOpacity(elementOpacity != null ? elementOpacity * 100 : DEFAULT_RECT_PROPS.opacity);
|
|
2227
2260
|
setStrokeColor(selectedElement.getStrokeColor() ?? DEFAULT_RECT_PROPS.strokeColor);
|
|
2228
2261
|
setLineWidth(selectedElement.getLineWidth() ?? DEFAULT_RECT_PROPS.lineWidth);
|
|
2229
2262
|
}
|
|
@@ -2235,11 +2268,11 @@ const useRectPanel = ({
|
|
|
2235
2268
|
strokeColor,
|
|
2236
2269
|
lineWidth,
|
|
2237
2270
|
operation: selectedElement instanceof timeline.RectElement ? "Apply Changes" : "Add Rectangle",
|
|
2238
|
-
setCornerRadius,
|
|
2239
|
-
setFillColor,
|
|
2240
|
-
setOpacity,
|
|
2241
|
-
setStrokeColor,
|
|
2242
|
-
setLineWidth,
|
|
2271
|
+
setCornerRadius: handleCornerRadiusChange,
|
|
2272
|
+
setFillColor: handleFillColorChange,
|
|
2273
|
+
setOpacity: handleOpacityChange,
|
|
2274
|
+
setStrokeColor: handleStrokeColorChange,
|
|
2275
|
+
setLineWidth: handleLineWidthChange,
|
|
2243
2276
|
handleApplyChanges
|
|
2244
2277
|
};
|
|
2245
2278
|
};
|
|
@@ -2254,13 +2287,11 @@ function RectPanelContainer(props) {
|
|
|
2254
2287
|
function CirclePanel({
|
|
2255
2288
|
radius,
|
|
2256
2289
|
fillColor,
|
|
2257
|
-
opacity,
|
|
2258
2290
|
strokeColor,
|
|
2259
2291
|
lineWidth,
|
|
2260
2292
|
operation,
|
|
2261
2293
|
setRadius,
|
|
2262
2294
|
setFillColor,
|
|
2263
|
-
setOpacity,
|
|
2264
2295
|
setStrokeColor,
|
|
2265
2296
|
setLineWidth,
|
|
2266
2297
|
handleApplyChanges
|
|
@@ -2310,26 +2341,6 @@ function CirclePanel({
|
|
|
2310
2341
|
)
|
|
2311
2342
|
] })
|
|
2312
2343
|
] }),
|
|
2313
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
|
|
2314
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Opacity" }),
|
|
2315
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "slider-container", children: [
|
|
2316
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2317
|
-
"input",
|
|
2318
|
-
{
|
|
2319
|
-
type: "range",
|
|
2320
|
-
min: "0",
|
|
2321
|
-
max: "100",
|
|
2322
|
-
value: opacity,
|
|
2323
|
-
onChange: (e) => setOpacity(Number(e.target.value)),
|
|
2324
|
-
className: "slider-purple"
|
|
2325
|
-
}
|
|
2326
|
-
),
|
|
2327
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "slider-value", children: [
|
|
2328
|
-
opacity,
|
|
2329
|
-
"%"
|
|
2330
|
-
] })
|
|
2331
|
-
] })
|
|
2332
|
-
] }),
|
|
2333
2344
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
|
|
2334
2345
|
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Stroke Color" }),
|
|
2335
2346
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "color-inputs", children: [
|
|
@@ -2373,12 +2384,13 @@ function CirclePanel({
|
|
|
2373
2384
|
] })
|
|
2374
2385
|
] })
|
|
2375
2386
|
] }),
|
|
2376
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleApplyChanges, className: "btn-primary w-full", children: operation }) })
|
|
2387
|
+
operation !== "Apply Changes" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleApplyChanges, className: "btn-primary w-full", children: operation }) })
|
|
2377
2388
|
] });
|
|
2378
2389
|
}
|
|
2379
2390
|
const DEFAULT_CIRCLE_PROPS = {
|
|
2380
2391
|
radius: 50,
|
|
2381
2392
|
fillColor: "#3b82f6",
|
|
2393
|
+
// UI uses 0–100%; element opacity is 0–1
|
|
2382
2394
|
opacity: 100,
|
|
2383
2395
|
strokeColor: "#000000",
|
|
2384
2396
|
lineWidth: 0
|
|
@@ -2393,26 +2405,56 @@ const useCirclePanel = ({
|
|
|
2393
2405
|
const [opacity, setOpacity] = react.useState(DEFAULT_CIRCLE_PROPS.opacity);
|
|
2394
2406
|
const [strokeColor, setStrokeColor] = react.useState(DEFAULT_CIRCLE_PROPS.strokeColor);
|
|
2395
2407
|
const [lineWidth, setLineWidth] = react.useState(DEFAULT_CIRCLE_PROPS.lineWidth);
|
|
2408
|
+
const applyLiveChangesToExistingCircle = (overrides = {}) => {
|
|
2409
|
+
if (!(selectedElement instanceof timeline.CircleElement)) {
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
const circleElement = selectedElement;
|
|
2413
|
+
const nextRadius = overrides.radius ?? radius;
|
|
2414
|
+
const nextFillColor = overrides.fillColor ?? fillColor;
|
|
2415
|
+
const nextOpacityPercent = overrides.opacity ?? opacity;
|
|
2416
|
+
const nextStrokeColor = overrides.strokeColor ?? strokeColor;
|
|
2417
|
+
const nextLineWidth = overrides.lineWidth ?? lineWidth;
|
|
2418
|
+
circleElement.setRadius(nextRadius);
|
|
2419
|
+
circleElement.setFill(nextFillColor);
|
|
2420
|
+
circleElement.setOpacity(nextOpacityPercent / 100);
|
|
2421
|
+
circleElement.setStrokeColor(nextStrokeColor);
|
|
2422
|
+
circleElement.setLineWidth(nextLineWidth);
|
|
2423
|
+
updateElement == null ? void 0 : updateElement(circleElement);
|
|
2424
|
+
};
|
|
2425
|
+
const handleRadiusChange = (nextRadius) => {
|
|
2426
|
+
setRadius(nextRadius);
|
|
2427
|
+
applyLiveChangesToExistingCircle({ radius: nextRadius });
|
|
2428
|
+
};
|
|
2429
|
+
const handleFillColorChange = (color) => {
|
|
2430
|
+
setFillColor(color);
|
|
2431
|
+
applyLiveChangesToExistingCircle({ fillColor: color });
|
|
2432
|
+
};
|
|
2433
|
+
const handleOpacityChange = (nextOpacity) => {
|
|
2434
|
+
setOpacity(nextOpacity);
|
|
2435
|
+
applyLiveChangesToExistingCircle({ opacity: nextOpacity });
|
|
2436
|
+
};
|
|
2437
|
+
const handleStrokeColorChange = (color) => {
|
|
2438
|
+
setStrokeColor(color);
|
|
2439
|
+
applyLiveChangesToExistingCircle({ strokeColor: color });
|
|
2440
|
+
};
|
|
2441
|
+
const handleLineWidthChange = (width) => {
|
|
2442
|
+
setLineWidth(width);
|
|
2443
|
+
applyLiveChangesToExistingCircle({ lineWidth: width });
|
|
2444
|
+
};
|
|
2396
2445
|
const handleApplyChanges = () => {
|
|
2397
|
-
let circleElement;
|
|
2398
2446
|
if (selectedElement instanceof timeline.CircleElement) {
|
|
2399
|
-
|
|
2400
|
-
circleElement.setRadius(radius);
|
|
2401
|
-
circleElement.setFill(fillColor);
|
|
2402
|
-
circleElement.setOpacity(opacity);
|
|
2403
|
-
circleElement.setStrokeColor(strokeColor);
|
|
2404
|
-
circleElement.setLineWidth(lineWidth);
|
|
2405
|
-
updateElement == null ? void 0 : updateElement(circleElement);
|
|
2406
|
-
} else {
|
|
2407
|
-
circleElement = new timeline.CircleElement(fillColor, radius).setOpacity(opacity).setStrokeColor(strokeColor).setLineWidth(lineWidth);
|
|
2408
|
-
addElement == null ? void 0 : addElement(circleElement);
|
|
2447
|
+
return;
|
|
2409
2448
|
}
|
|
2449
|
+
const circleElement = new timeline.CircleElement(fillColor, radius).setOpacity(opacity / 100).setStrokeColor(strokeColor).setLineWidth(lineWidth);
|
|
2450
|
+
addElement == null ? void 0 : addElement(circleElement);
|
|
2410
2451
|
};
|
|
2411
2452
|
react.useEffect(() => {
|
|
2412
2453
|
if (selectedElement instanceof timeline.CircleElement) {
|
|
2413
2454
|
setRadius(selectedElement.getRadius() ?? DEFAULT_CIRCLE_PROPS.radius);
|
|
2414
2455
|
setFillColor(selectedElement.getFill() ?? DEFAULT_CIRCLE_PROPS.fillColor);
|
|
2415
|
-
|
|
2456
|
+
const elementOpacity = selectedElement.getOpacity();
|
|
2457
|
+
setOpacity(elementOpacity != null ? elementOpacity * 100 : DEFAULT_CIRCLE_PROPS.opacity);
|
|
2416
2458
|
setStrokeColor(selectedElement.getStrokeColor() ?? DEFAULT_CIRCLE_PROPS.strokeColor);
|
|
2417
2459
|
setLineWidth(selectedElement.getLineWidth() ?? DEFAULT_CIRCLE_PROPS.lineWidth);
|
|
2418
2460
|
}
|
|
@@ -2424,11 +2466,11 @@ const useCirclePanel = ({
|
|
|
2424
2466
|
strokeColor,
|
|
2425
2467
|
lineWidth,
|
|
2426
2468
|
operation: selectedElement instanceof timeline.CircleElement ? "Apply Changes" : "Add Circle",
|
|
2427
|
-
setRadius,
|
|
2428
|
-
setFillColor,
|
|
2429
|
-
setOpacity,
|
|
2430
|
-
setStrokeColor,
|
|
2431
|
-
setLineWidth,
|
|
2469
|
+
setRadius: handleRadiusChange,
|
|
2470
|
+
setFillColor: handleFillColorChange,
|
|
2471
|
+
setOpacity: handleOpacityChange,
|
|
2472
|
+
setStrokeColor: handleStrokeColorChange,
|
|
2473
|
+
setLineWidth: handleLineWidthChange,
|
|
2432
2474
|
handleApplyChanges
|
|
2433
2475
|
};
|
|
2434
2476
|
};
|
|
@@ -2440,16 +2482,16 @@ function CirclePanelContainer(props) {
|
|
|
2440
2482
|
});
|
|
2441
2483
|
return /* @__PURE__ */ jsxRuntime.jsx(CirclePanel, { ...circlePanelProps });
|
|
2442
2484
|
}
|
|
2443
|
-
function
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2485
|
+
function CaptionsPanel({
|
|
2486
|
+
captions,
|
|
2487
|
+
addCaption,
|
|
2488
|
+
splitCaption,
|
|
2489
|
+
deleteCaption,
|
|
2490
|
+
updateCaption
|
|
2449
2491
|
}) {
|
|
2450
2492
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
2451
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "panel-title", children: "
|
|
2452
|
-
|
|
2493
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "panel-title", children: "Captions" }),
|
|
2494
|
+
captions.map((caption, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2453
2495
|
"div",
|
|
2454
2496
|
{
|
|
2455
2497
|
className: "panel-section gap-2",
|
|
@@ -2458,9 +2500,9 @@ function SubtitlesPanel({
|
|
|
2458
2500
|
"input",
|
|
2459
2501
|
{
|
|
2460
2502
|
type: "text",
|
|
2461
|
-
placeholder: "Enter
|
|
2462
|
-
value:
|
|
2463
|
-
onChange: (e) =>
|
|
2503
|
+
placeholder: "Enter caption text",
|
|
2504
|
+
value: caption.t,
|
|
2505
|
+
onChange: (e) => updateCaption(i, { ...caption, t: e.target.value }),
|
|
2464
2506
|
className: "input-dark"
|
|
2465
2507
|
}
|
|
2466
2508
|
) }),
|
|
@@ -2468,18 +2510,18 @@ function SubtitlesPanel({
|
|
|
2468
2510
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2469
2511
|
"button",
|
|
2470
2512
|
{
|
|
2471
|
-
onClick: () =>
|
|
2513
|
+
onClick: () => splitCaption(i),
|
|
2472
2514
|
className: "btn-ghost",
|
|
2473
|
-
title: "Split
|
|
2515
|
+
title: "Split caption",
|
|
2474
2516
|
children: /* @__PURE__ */ jsxRuntime.jsx(Scissors, { className: "icon-sm" })
|
|
2475
2517
|
}
|
|
2476
2518
|
),
|
|
2477
2519
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2478
2520
|
"button",
|
|
2479
2521
|
{
|
|
2480
|
-
onClick: () =>
|
|
2522
|
+
onClick: () => deleteCaption(i),
|
|
2481
2523
|
className: "btn-ghost",
|
|
2482
|
-
title: "Delete
|
|
2524
|
+
title: "Delete caption",
|
|
2483
2525
|
children: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { className: "icon-sm", color: "var(--color-red-500)" })
|
|
2484
2526
|
}
|
|
2485
2527
|
)
|
|
@@ -2488,7 +2530,7 @@ function SubtitlesPanel({
|
|
|
2488
2530
|
},
|
|
2489
2531
|
i
|
|
2490
2532
|
)),
|
|
2491
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick:
|
|
2533
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: addCaption, className: "btn-primary w-full", title: "Add caption", children: "Add" }) })
|
|
2492
2534
|
] });
|
|
2493
2535
|
}
|
|
2494
2536
|
const CAPTION_PROPS = {
|
|
@@ -2543,16 +2585,16 @@ const CAPTION_PROPS = {
|
|
|
2543
2585
|
shadowBlur: 5
|
|
2544
2586
|
}
|
|
2545
2587
|
};
|
|
2546
|
-
const
|
|
2547
|
-
const [
|
|
2548
|
-
const
|
|
2588
|
+
const useCaptionsPanel = () => {
|
|
2589
|
+
const [captions, setCaptions] = react.useState([]);
|
|
2590
|
+
const captionsTrack = react.useRef(null);
|
|
2549
2591
|
const { editor } = timeline.useTimelineContext();
|
|
2550
|
-
const
|
|
2551
|
-
const
|
|
2552
|
-
if (
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2592
|
+
const fetchCaptions = async () => {
|
|
2593
|
+
const editorCaptionsTrack = editor.getCaptionsTrack();
|
|
2594
|
+
if (editorCaptionsTrack) {
|
|
2595
|
+
captionsTrack.current = editorCaptionsTrack;
|
|
2596
|
+
setCaptions(
|
|
2597
|
+
editorCaptionsTrack.getElements().map((element) => ({
|
|
2556
2598
|
s: element.getStart(),
|
|
2557
2599
|
e: element.getEnd(),
|
|
2558
2600
|
t: element.getText()
|
|
@@ -2561,12 +2603,12 @@ const useSubtitlesPanel = () => {
|
|
|
2561
2603
|
}
|
|
2562
2604
|
};
|
|
2563
2605
|
react.useEffect(() => {
|
|
2564
|
-
|
|
2606
|
+
fetchCaptions();
|
|
2565
2607
|
}, []);
|
|
2566
|
-
const
|
|
2608
|
+
const checkCaptionsTrack = () => {
|
|
2567
2609
|
var _a;
|
|
2568
|
-
if (!
|
|
2569
|
-
|
|
2610
|
+
if (!captionsTrack.current) {
|
|
2611
|
+
captionsTrack.current = editor.addTrack("Caption", "caption");
|
|
2570
2612
|
const props = {
|
|
2571
2613
|
capStyle: timeline.CAPTION_STYLE.WORD_BG_HIGHLIGHT,
|
|
2572
2614
|
...CAPTION_PROPS[timeline.CAPTION_STYLE.WORD_BG_HIGHLIGHT],
|
|
@@ -2574,61 +2616,61 @@ const useSubtitlesPanel = () => {
|
|
|
2574
2616
|
y: 200,
|
|
2575
2617
|
applyToAll: true
|
|
2576
2618
|
};
|
|
2577
|
-
(_a =
|
|
2619
|
+
(_a = captionsTrack.current) == null ? void 0 : _a.setProps(props);
|
|
2578
2620
|
}
|
|
2579
2621
|
};
|
|
2580
|
-
const
|
|
2581
|
-
const
|
|
2582
|
-
if (
|
|
2583
|
-
|
|
2622
|
+
const addCaption = () => {
|
|
2623
|
+
const newCaption = { s: 0, e: 0, t: "New Caption" };
|
|
2624
|
+
if (captions.length > 0) {
|
|
2625
|
+
newCaption.s = captions[captions.length - 1].e;
|
|
2584
2626
|
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2627
|
+
newCaption.e = newCaption.s + 1;
|
|
2628
|
+
setCaptions([...captions, newCaption]);
|
|
2629
|
+
checkCaptionsTrack();
|
|
2588
2630
|
const captionElement = new timeline.CaptionElement(
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2631
|
+
newCaption.t,
|
|
2632
|
+
newCaption.s,
|
|
2633
|
+
newCaption.e
|
|
2592
2634
|
);
|
|
2593
|
-
editor.addElementToTrack(
|
|
2635
|
+
editor.addElementToTrack(captionsTrack.current, captionElement);
|
|
2594
2636
|
};
|
|
2595
|
-
const
|
|
2596
|
-
if (
|
|
2597
|
-
const element =
|
|
2637
|
+
const splitCaption = async (index) => {
|
|
2638
|
+
if (captionsTrack.current) {
|
|
2639
|
+
const element = captionsTrack.current.getElements()[index];
|
|
2598
2640
|
const splitResult = await editor.splitElement(
|
|
2599
2641
|
element,
|
|
2600
2642
|
element.getStart() + element.getDuration() / 2
|
|
2601
2643
|
);
|
|
2602
2644
|
if (splitResult.success) {
|
|
2603
|
-
|
|
2645
|
+
fetchCaptions();
|
|
2604
2646
|
}
|
|
2605
2647
|
}
|
|
2606
2648
|
};
|
|
2607
|
-
const
|
|
2608
|
-
|
|
2609
|
-
if (
|
|
2610
|
-
editor.removeElement(
|
|
2649
|
+
const deleteCaption = (index) => {
|
|
2650
|
+
setCaptions(captions.filter((_, i) => i !== index));
|
|
2651
|
+
if (captionsTrack.current) {
|
|
2652
|
+
editor.removeElement(captionsTrack.current.getElements()[index]);
|
|
2611
2653
|
}
|
|
2612
2654
|
};
|
|
2613
|
-
const
|
|
2614
|
-
|
|
2615
|
-
if (
|
|
2616
|
-
const element =
|
|
2617
|
-
element.setText(
|
|
2655
|
+
const updateCaption = (index, caption) => {
|
|
2656
|
+
setCaptions(captions.map((sub, i) => i === index ? caption : sub));
|
|
2657
|
+
if (captionsTrack.current) {
|
|
2658
|
+
const element = captionsTrack.current.getElements()[index];
|
|
2659
|
+
element.setText(caption.t);
|
|
2618
2660
|
editor.updateElement(element);
|
|
2619
2661
|
}
|
|
2620
2662
|
};
|
|
2621
2663
|
return {
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2664
|
+
captions,
|
|
2665
|
+
addCaption,
|
|
2666
|
+
splitCaption,
|
|
2667
|
+
deleteCaption,
|
|
2668
|
+
updateCaption
|
|
2627
2669
|
};
|
|
2628
2670
|
};
|
|
2629
|
-
function
|
|
2630
|
-
const
|
|
2631
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2671
|
+
function CaptionsPanelContainer() {
|
|
2672
|
+
const captionsPanelProps = useCaptionsPanel();
|
|
2673
|
+
return /* @__PURE__ */ jsxRuntime.jsx(CaptionsPanel, { ...captionsPanelProps });
|
|
2632
2674
|
}
|
|
2633
2675
|
const ElementPanelContainer = ({
|
|
2634
2676
|
selectedTool,
|
|
@@ -2681,16 +2723,6 @@ const ElementPanelContainer = ({
|
|
|
2681
2723
|
updateElement
|
|
2682
2724
|
}
|
|
2683
2725
|
);
|
|
2684
|
-
case "icon":
|
|
2685
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2686
|
-
IconPanelContainer,
|
|
2687
|
-
{
|
|
2688
|
-
videoResolution,
|
|
2689
|
-
selectedElement,
|
|
2690
|
-
addElement: addNewElement,
|
|
2691
|
-
updateElement
|
|
2692
|
-
}
|
|
2693
|
-
);
|
|
2694
2726
|
case "rect":
|
|
2695
2727
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2696
2728
|
RectPanelContainer,
|
|
@@ -2711,8 +2743,8 @@ const ElementPanelContainer = ({
|
|
|
2711
2743
|
updateElement
|
|
2712
2744
|
}
|
|
2713
2745
|
);
|
|
2714
|
-
case "
|
|
2715
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2746
|
+
case "caption":
|
|
2747
|
+
return /* @__PURE__ */ jsxRuntime.jsx(CaptionsPanelContainer, {});
|
|
2716
2748
|
default:
|
|
2717
2749
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
2718
2750
|
/* @__PURE__ */ jsxRuntime.jsx(WandSparkles, { className: "empty-state-icon" }),
|
|
@@ -2722,159 +2754,37 @@ const ElementPanelContainer = ({
|
|
|
2722
2754
|
};
|
|
2723
2755
|
return renderLibrary();
|
|
2724
2756
|
};
|
|
2725
|
-
|
|
2726
|
-
[
|
|
2727
|
-
"
|
|
2728
|
-
{
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
[
|
|
2736
|
-
"animations",
|
|
2737
|
-
{
|
|
2738
|
-
id: "animations",
|
|
2739
|
-
name: "Animations",
|
|
2740
|
-
icon: "Zap",
|
|
2741
|
-
description: "Animations"
|
|
2742
|
-
}
|
|
2743
|
-
],
|
|
2744
|
-
[
|
|
2745
|
-
"text-effects",
|
|
2746
|
-
{
|
|
2747
|
-
id: "text-effects",
|
|
2748
|
-
name: "Text Effects",
|
|
2749
|
-
icon: "SparklesIcon",
|
|
2750
|
-
description: "Text Effects"
|
|
2751
|
-
}
|
|
2752
|
-
],
|
|
2753
|
-
[
|
|
2754
|
-
"color-effects",
|
|
2755
|
-
{
|
|
2756
|
-
id: "color-effects",
|
|
2757
|
-
name: "Color Effects",
|
|
2758
|
-
icon: "Image",
|
|
2759
|
-
description: "Color Effects"
|
|
2760
|
-
}
|
|
2761
|
-
],
|
|
2762
|
-
[
|
|
2763
|
-
"playback-props",
|
|
2764
|
-
{
|
|
2765
|
-
id: "playback-props",
|
|
2766
|
-
name: "Playback Props",
|
|
2767
|
-
icon: "Music",
|
|
2768
|
-
description: "Playback Properties"
|
|
2769
|
-
}
|
|
2770
|
-
],
|
|
2771
|
-
[
|
|
2772
|
-
"subtitle-style",
|
|
2773
|
-
{
|
|
2774
|
-
id: "subtitle-style",
|
|
2775
|
-
name: "Subtitle Style",
|
|
2776
|
-
icon: "MessageSquare",
|
|
2777
|
-
description: "Subtitle Style"
|
|
2778
|
-
}
|
|
2779
|
-
],
|
|
2780
|
-
[
|
|
2781
|
-
"generate-subtitles",
|
|
2782
|
-
{
|
|
2783
|
-
id: "generate-subtitles",
|
|
2784
|
-
name: "Generate Subtitles",
|
|
2785
|
-
icon: "Subtitles",
|
|
2786
|
-
description: "Generate Subtitles"
|
|
2787
|
-
}
|
|
2788
|
-
]
|
|
2789
|
-
]);
|
|
2790
|
-
const getIcon = (iconName) => {
|
|
2791
|
-
switch (iconName) {
|
|
2792
|
-
case "Type":
|
|
2793
|
-
return Type;
|
|
2794
|
-
case "Infinity":
|
|
2795
|
-
return Infinity;
|
|
2796
|
-
case "Image":
|
|
2797
|
-
return Image;
|
|
2798
|
-
case "Music":
|
|
2799
|
-
return Music;
|
|
2800
|
-
case "Subtitles":
|
|
2801
|
-
return Captions;
|
|
2802
|
-
case "MessageSquare":
|
|
2803
|
-
return MessageSquare;
|
|
2804
|
-
case "Settings":
|
|
2805
|
-
return Settings;
|
|
2806
|
-
case "SparklesIcon":
|
|
2807
|
-
return Sparkles;
|
|
2808
|
-
case "Zap":
|
|
2809
|
-
return Zap;
|
|
2810
|
-
default:
|
|
2811
|
-
return Plus;
|
|
2812
|
-
}
|
|
2813
|
-
};
|
|
2814
|
-
function PropsToolbar({
|
|
2815
|
-
selectedElement,
|
|
2816
|
-
selectedProp,
|
|
2817
|
-
setSelectedProp
|
|
2818
|
-
}) {
|
|
2819
|
-
const availableSections = react.useMemo(() => {
|
|
2820
|
-
const sections = [];
|
|
2821
|
-
if (selectedElement instanceof timeline.TextElement) {
|
|
2822
|
-
sections.push(propsCategories.get("element-props"));
|
|
2823
|
-
sections.push(propsCategories.get("animations"));
|
|
2824
|
-
sections.push(propsCategories.get("text-effects"));
|
|
2825
|
-
} else if (selectedElement instanceof timeline.ImageElement) {
|
|
2826
|
-
sections.push(propsCategories.get("element-props"));
|
|
2827
|
-
sections.push(propsCategories.get("animations"));
|
|
2828
|
-
sections.push(propsCategories.get("color-effects"));
|
|
2829
|
-
} else if (selectedElement instanceof timeline.VideoElement) {
|
|
2830
|
-
sections.push(propsCategories.get("element-props"));
|
|
2831
|
-
sections.push(propsCategories.get("animations"));
|
|
2832
|
-
sections.push(propsCategories.get("color-effects"));
|
|
2833
|
-
sections.push(propsCategories.get("playback-props"));
|
|
2834
|
-
sections.push(propsCategories.get("generate-subtitles"));
|
|
2835
|
-
} else if (selectedElement instanceof timeline.AudioElement) {
|
|
2836
|
-
sections.push(propsCategories.get("element-props"));
|
|
2837
|
-
sections.push(propsCategories.get("playback-props"));
|
|
2838
|
-
} else if (selectedElement instanceof timeline.CircleElement) {
|
|
2839
|
-
sections.push(propsCategories.get("element-props"));
|
|
2840
|
-
sections.push(propsCategories.get("animations"));
|
|
2841
|
-
} else if (selectedElement instanceof timeline.RectElement) {
|
|
2842
|
-
sections.push(propsCategories.get("element-props"));
|
|
2843
|
-
sections.push(propsCategories.get("animations"));
|
|
2844
|
-
} else if (selectedElement instanceof timeline.IconElement) {
|
|
2845
|
-
sections.push(propsCategories.get("element-props"));
|
|
2846
|
-
sections.push(propsCategories.get("animations"));
|
|
2847
|
-
} else if (selectedElement instanceof timeline.CaptionElement) {
|
|
2848
|
-
sections.push(propsCategories.get("element-props"));
|
|
2849
|
-
sections.push(propsCategories.get("animations"));
|
|
2850
|
-
sections.push(propsCategories.get("subtitle-style"));
|
|
2851
|
-
}
|
|
2852
|
-
return sections;
|
|
2853
|
-
}, [selectedElement]);
|
|
2854
|
-
react.useEffect(() => {
|
|
2855
|
-
if (availableSections == null ? void 0 : availableSections.length) {
|
|
2856
|
-
if (availableSections.map((section) => section.id).indexOf(selectedProp) === -1) {
|
|
2857
|
-
setSelectedProp(availableSections[0].id);
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2860
|
-
}, [availableSections]);
|
|
2861
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sidebar", children: availableSections.map((tool) => {
|
|
2862
|
-
const Icon2 = getIcon(tool.icon);
|
|
2863
|
-
const isSelected = selectedProp === tool.id;
|
|
2864
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2757
|
+
function PropertyRow({ label, children, secondary }) {
|
|
2758
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "property-row", children: [
|
|
2759
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-row-label", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "property-label", children: label }) }),
|
|
2760
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-row-control", children }),
|
|
2761
|
+
secondary && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-row-secondary", children: secondary })
|
|
2762
|
+
] });
|
|
2763
|
+
}
|
|
2764
|
+
function AccordionItem({ title, icon, children, isOpen, onToggle }) {
|
|
2765
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "accordion-item", children: [
|
|
2766
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2865
2767
|
"div",
|
|
2866
2768
|
{
|
|
2867
|
-
onClick:
|
|
2868
|
-
className:
|
|
2869
|
-
title: `${tool.name}${tool.shortcut ? ` (${tool.shortcut})` : ""}`,
|
|
2769
|
+
onClick: onToggle,
|
|
2770
|
+
className: "accordion-header",
|
|
2870
2771
|
children: [
|
|
2871
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2872
|
-
|
|
2772
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", children: [
|
|
2773
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "accent-purple", children: icon }),
|
|
2774
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "property-title", children: title })
|
|
2775
|
+
] }),
|
|
2776
|
+
isOpen ? /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, { className: "icon-sm accent-purple" }) : /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, { className: "icon-sm accent-purple" })
|
|
2873
2777
|
]
|
|
2874
|
-
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2778
|
+
}
|
|
2779
|
+
),
|
|
2780
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2781
|
+
"div",
|
|
2782
|
+
{
|
|
2783
|
+
className: `accordion-content ${isOpen ? "expanded" : ""}`,
|
|
2784
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "accordion-panel", children })
|
|
2785
|
+
}
|
|
2786
|
+
)
|
|
2787
|
+
] });
|
|
2878
2788
|
}
|
|
2879
2789
|
function ElementProps({ selectedElement, updateElement }) {
|
|
2880
2790
|
const opacity = (selectedElement == null ? void 0 : selectedElement.getOpacity()) || 1;
|
|
@@ -2898,77 +2808,134 @@ function ElementProps({ selectedElement, updateElement }) {
|
|
|
2898
2808
|
updateElement == null ? void 0 : updateElement(selectedElement);
|
|
2899
2809
|
}
|
|
2900
2810
|
};
|
|
2811
|
+
const handleDimensionsChange = (width, height) => {
|
|
2812
|
+
if (!selectedElement) return;
|
|
2813
|
+
if (selectedElement instanceof timeline.RectElement) {
|
|
2814
|
+
const size = selectedElement.getSize();
|
|
2815
|
+
selectedElement.setSize({ width: width ?? size.width, height: height ?? size.height });
|
|
2816
|
+
updateElement == null ? void 0 : updateElement(selectedElement);
|
|
2817
|
+
} else if (selectedElement instanceof timeline.CircleElement) {
|
|
2818
|
+
const dims = {
|
|
2819
|
+
width: selectedElement.getRadius() * 2,
|
|
2820
|
+
height: selectedElement.getRadius() * 2
|
|
2821
|
+
};
|
|
2822
|
+
const newDiameter = width !== void 0 && width !== dims.width ? width : height ?? dims.height;
|
|
2823
|
+
selectedElement.setRadius(newDiameter / 2);
|
|
2824
|
+
updateElement == null ? void 0 : updateElement(selectedElement);
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
const hasShapeDimensions = selectedElement instanceof timeline.RectElement || selectedElement instanceof timeline.CircleElement;
|
|
2828
|
+
let dimensions = null;
|
|
2829
|
+
if (selectedElement instanceof timeline.RectElement) {
|
|
2830
|
+
dimensions = selectedElement.getSize();
|
|
2831
|
+
} else if (selectedElement instanceof timeline.CircleElement) {
|
|
2832
|
+
const r = selectedElement.getRadius();
|
|
2833
|
+
dimensions = { width: r * 2, height: r * 2 };
|
|
2834
|
+
}
|
|
2835
|
+
const [isTransformOpen, setIsTransformOpen] = react.useState(false);
|
|
2901
2836
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
2902
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "
|
|
2903
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
"
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2837
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Properties" }),
|
|
2838
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2839
|
+
AccordionItem,
|
|
2840
|
+
{
|
|
2841
|
+
title: "Transform",
|
|
2842
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(Ruler, { className: "icon-sm" }),
|
|
2843
|
+
isOpen: isTransformOpen,
|
|
2844
|
+
onToggle: () => setIsTransformOpen((open) => !open),
|
|
2845
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "properties-group", children: [
|
|
2846
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "property-section", children: [
|
|
2847
|
+
/* @__PURE__ */ jsxRuntime.jsx(PropertyRow, { label: "Position X", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2848
|
+
"input",
|
|
2849
|
+
{
|
|
2850
|
+
type: "number",
|
|
2851
|
+
value: position.x ?? 0,
|
|
2852
|
+
onChange: (e) => handlePositionChange({ x: Number(e.target.value) }),
|
|
2853
|
+
className: "input-dark"
|
|
2854
|
+
}
|
|
2855
|
+
) }),
|
|
2856
|
+
/* @__PURE__ */ jsxRuntime.jsx(PropertyRow, { label: "Position Y", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2857
|
+
"input",
|
|
2858
|
+
{
|
|
2859
|
+
type: "number",
|
|
2860
|
+
value: position.y ?? 0,
|
|
2861
|
+
onChange: (e) => handlePositionChange({ y: Number(e.target.value) }),
|
|
2862
|
+
className: "input-dark"
|
|
2863
|
+
}
|
|
2864
|
+
) })
|
|
2865
|
+
] }),
|
|
2866
|
+
hasShapeDimensions && dimensions && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "property-section", children: [
|
|
2867
|
+
/* @__PURE__ */ jsxRuntime.jsx(PropertyRow, { label: "Width", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2868
|
+
"input",
|
|
2869
|
+
{
|
|
2870
|
+
type: "number",
|
|
2871
|
+
min: 1,
|
|
2872
|
+
value: Math.round(dimensions.width),
|
|
2873
|
+
onChange: (e) => handleDimensionsChange(
|
|
2874
|
+
Number(e.target.value),
|
|
2875
|
+
dimensions.height
|
|
2876
|
+
),
|
|
2877
|
+
className: "input-dark"
|
|
2878
|
+
}
|
|
2879
|
+
) }),
|
|
2880
|
+
/* @__PURE__ */ jsxRuntime.jsx(PropertyRow, { label: "Height", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2881
|
+
"input",
|
|
2882
|
+
{
|
|
2883
|
+
type: "number",
|
|
2884
|
+
min: 1,
|
|
2885
|
+
value: Math.round(dimensions.height),
|
|
2886
|
+
onChange: (e) => handleDimensionsChange(
|
|
2887
|
+
dimensions.width,
|
|
2888
|
+
Number(e.target.value)
|
|
2889
|
+
),
|
|
2890
|
+
className: "input-dark"
|
|
2891
|
+
}
|
|
2892
|
+
) })
|
|
2893
|
+
] }),
|
|
2894
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2895
|
+
PropertyRow,
|
|
2896
|
+
{
|
|
2897
|
+
label: "Opacity",
|
|
2898
|
+
secondary: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
2899
|
+
Math.round((opacity ?? 1) * 100),
|
|
2900
|
+
"%"
|
|
2901
|
+
] }),
|
|
2902
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2903
|
+
"input",
|
|
2904
|
+
{
|
|
2905
|
+
type: "range",
|
|
2906
|
+
min: "0",
|
|
2907
|
+
max: "100",
|
|
2908
|
+
value: (opacity ?? 1) * 100,
|
|
2909
|
+
onChange: (e) => handleOpacityChange(Number(e.target.value) / 100),
|
|
2910
|
+
className: "slider-purple"
|
|
2911
|
+
}
|
|
2912
|
+
)
|
|
2913
|
+
}
|
|
2914
|
+
) }),
|
|
2915
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2916
|
+
PropertyRow,
|
|
2917
|
+
{
|
|
2918
|
+
label: "Rotation",
|
|
2919
|
+
secondary: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
2920
|
+
Math.round(rotation ?? 0),
|
|
2921
|
+
"°"
|
|
2922
|
+
] }),
|
|
2923
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2924
|
+
"input",
|
|
2925
|
+
{
|
|
2926
|
+
type: "range",
|
|
2927
|
+
min: "0",
|
|
2928
|
+
max: "360",
|
|
2929
|
+
value: rotation ?? 0,
|
|
2930
|
+
onChange: (e) => handleRotationChange(Number(e.target.value)),
|
|
2931
|
+
className: "slider-purple"
|
|
2932
|
+
}
|
|
2933
|
+
)
|
|
2934
|
+
}
|
|
2935
|
+
) })
|
|
2969
2936
|
] })
|
|
2970
|
-
|
|
2971
|
-
|
|
2937
|
+
}
|
|
2938
|
+
)
|
|
2972
2939
|
] });
|
|
2973
2940
|
}
|
|
2974
2941
|
function TextEffects({
|
|
@@ -2999,70 +2966,72 @@ function TextEffects({
|
|
|
2999
2966
|
selectedElement.setTextEffect(effect);
|
|
3000
2967
|
updateElement == null ? void 0 : updateElement(selectedElement);
|
|
3001
2968
|
};
|
|
2969
|
+
const [isEffectsOpen, setIsEffectsOpen] = react.useState(false);
|
|
3002
2970
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
3003
2971
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Text Effects" }),
|
|
3004
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
"
|
|
3008
|
-
{
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
children:
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
{
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
2972
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2973
|
+
AccordionItem,
|
|
2974
|
+
{
|
|
2975
|
+
title: "Effects",
|
|
2976
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(Sparkles, { className: "icon-sm" }),
|
|
2977
|
+
isOpen: isEffectsOpen,
|
|
2978
|
+
onToggle: () => setIsEffectsOpen((open) => !open),
|
|
2979
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "properties-group", children: [
|
|
2980
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(PropertyRow, { label: "Preset", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2981
|
+
"select",
|
|
2982
|
+
{
|
|
2983
|
+
value: (currentEffect == null ? void 0 : currentEffect.getName()) || "",
|
|
2984
|
+
onChange: (e) => handleUpdateEffect({ name: e.target.value }),
|
|
2985
|
+
className: "select-dark w-full",
|
|
2986
|
+
children: [
|
|
2987
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "No Effect" }),
|
|
2988
|
+
VideoEditor.TEXT_EFFECTS.map((effect) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: effect.name, children: effect.name.charAt(0).toUpperCase() + effect.name.slice(1) }, effect.name))
|
|
2989
|
+
]
|
|
2990
|
+
}
|
|
2991
|
+
) }) }),
|
|
2992
|
+
currentEffect && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2993
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(PropertyRow, { label: "Delay (s)", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2994
|
+
"input",
|
|
2995
|
+
{
|
|
2996
|
+
type: "number",
|
|
2997
|
+
min: "0",
|
|
2998
|
+
max: "5",
|
|
2999
|
+
step: "0.1",
|
|
3000
|
+
value: currentEffect.getDelay() ?? 0,
|
|
3001
|
+
onChange: (e) => handleUpdateEffect({ delay: Number(e.target.value) }),
|
|
3002
|
+
className: "input-dark"
|
|
3003
|
+
}
|
|
3004
|
+
) }) }),
|
|
3005
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(PropertyRow, { label: "Duration (s)", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3006
|
+
"input",
|
|
3007
|
+
{
|
|
3008
|
+
type: "number",
|
|
3009
|
+
min: "0.1",
|
|
3010
|
+
max: "10",
|
|
3011
|
+
step: "0.1",
|
|
3012
|
+
value: currentEffect.getDuration() ?? 1,
|
|
3013
|
+
onChange: (e) => handleUpdateEffect({ duration: Number(e.target.value) }),
|
|
3014
|
+
className: "input-dark"
|
|
3015
|
+
}
|
|
3016
|
+
) }) }),
|
|
3017
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(PropertyRow, { label: "Buffer (s)", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3018
|
+
"input",
|
|
3019
|
+
{
|
|
3020
|
+
type: "number",
|
|
3021
|
+
min: "0.05",
|
|
3022
|
+
max: "1",
|
|
3023
|
+
step: "0.05",
|
|
3024
|
+
value: currentEffect.getBufferTime() ?? 0.1,
|
|
3025
|
+
onChange: (e) => handleUpdateEffect({
|
|
3026
|
+
bufferTime: Number(e.target.value)
|
|
3027
|
+
}),
|
|
3028
|
+
className: "input-dark"
|
|
3029
|
+
}
|
|
3030
|
+
) }) })
|
|
3031
|
+
] })
|
|
3032
|
+
] })
|
|
3033
|
+
}
|
|
3034
|
+
)
|
|
3066
3035
|
] });
|
|
3067
3036
|
}
|
|
3068
3037
|
function Animation({
|
|
@@ -3250,37 +3219,95 @@ function Animation({
|
|
|
3250
3219
|
})() })
|
|
3251
3220
|
] });
|
|
3252
3221
|
}
|
|
3222
|
+
const MIN_DB = -60;
|
|
3223
|
+
const MAX_DB = 6;
|
|
3224
|
+
function linearToDb(linear) {
|
|
3225
|
+
if (linear <= 0) return MIN_DB;
|
|
3226
|
+
const db = 20 * Math.log10(linear);
|
|
3227
|
+
return Math.max(MIN_DB, Math.min(MAX_DB, db));
|
|
3228
|
+
}
|
|
3229
|
+
function dbToLinear(db) {
|
|
3230
|
+
if (db <= MIN_DB) return 0;
|
|
3231
|
+
const linear = Math.pow(10, db / 20);
|
|
3232
|
+
return Math.min(linear, Math.pow(10, MAX_DB / 20));
|
|
3233
|
+
}
|
|
3234
|
+
const PLAYBACK_RATE_MIN = 0.25;
|
|
3235
|
+
const PLAYBACK_RATE_MAX = 2;
|
|
3236
|
+
const PLAYBACK_RATE_STEP = 0.25;
|
|
3253
3237
|
function PlaybackPropsPanel({
|
|
3254
3238
|
selectedElement,
|
|
3255
3239
|
updateElement
|
|
3256
3240
|
}) {
|
|
3257
3241
|
const elementProps = (selectedElement == null ? void 0 : selectedElement.getProps()) || {};
|
|
3258
|
-
const
|
|
3242
|
+
const volumeLinear = elementProps.volume ?? 1;
|
|
3243
|
+
const volumeDb = linearToDb(volumeLinear);
|
|
3244
|
+
const playbackRate = elementProps.playbackRate ?? 1;
|
|
3259
3245
|
const handleUpdateElement = (props) => {
|
|
3260
3246
|
if (selectedElement) {
|
|
3261
3247
|
updateElement == null ? void 0 : updateElement(selectedElement == null ? void 0 : selectedElement.setProps({ ...elementProps, ...props }));
|
|
3262
3248
|
}
|
|
3263
3249
|
};
|
|
3250
|
+
const handleVolumeDbChange = (db) => {
|
|
3251
|
+
handleUpdateElement({ volume: dbToLinear(db) });
|
|
3252
|
+
};
|
|
3253
|
+
const handlePlaybackRateChange = (rate) => {
|
|
3254
|
+
handleUpdateElement({ playbackRate: rate });
|
|
3255
|
+
};
|
|
3256
|
+
const [isPlaybackOpen, setIsPlaybackOpen] = react.useState(false);
|
|
3264
3257
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
3265
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Playback
|
|
3266
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3258
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Playback" }),
|
|
3259
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3260
|
+
AccordionItem,
|
|
3261
|
+
{
|
|
3262
|
+
title: "Playback",
|
|
3263
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(Music2, { className: "icon-sm" }),
|
|
3264
|
+
isOpen: isPlaybackOpen,
|
|
3265
|
+
onToggle: () => setIsPlaybackOpen((open) => !open),
|
|
3266
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "properties-group", children: [
|
|
3267
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3268
|
+
PropertyRow,
|
|
3269
|
+
{
|
|
3270
|
+
label: "Playback rate",
|
|
3271
|
+
secondary: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
3272
|
+
playbackRate,
|
|
3273
|
+
"×"
|
|
3274
|
+
] }),
|
|
3275
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3276
|
+
"input",
|
|
3277
|
+
{
|
|
3278
|
+
type: "range",
|
|
3279
|
+
min: PLAYBACK_RATE_MIN,
|
|
3280
|
+
max: PLAYBACK_RATE_MAX,
|
|
3281
|
+
step: PLAYBACK_RATE_STEP,
|
|
3282
|
+
value: playbackRate,
|
|
3283
|
+
onChange: (e) => handlePlaybackRateChange(Number(e.target.value)),
|
|
3284
|
+
className: "slider-purple"
|
|
3285
|
+
}
|
|
3286
|
+
)
|
|
3287
|
+
}
|
|
3288
|
+
) }),
|
|
3289
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3290
|
+
PropertyRow,
|
|
3291
|
+
{
|
|
3292
|
+
label: "Volume",
|
|
3293
|
+
secondary: /* @__PURE__ */ jsxRuntime.jsx("span", { children: volumeDb <= MIN_DB ? "−∞" : `${Math.round(volumeDb)} dB` }),
|
|
3294
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3295
|
+
"input",
|
|
3296
|
+
{
|
|
3297
|
+
type: "range",
|
|
3298
|
+
min: MIN_DB,
|
|
3299
|
+
max: MAX_DB,
|
|
3300
|
+
step: 1,
|
|
3301
|
+
value: volumeDb,
|
|
3302
|
+
onChange: (e) => handleVolumeDbChange(Number(e.target.value)),
|
|
3303
|
+
className: "slider-purple"
|
|
3304
|
+
}
|
|
3305
|
+
)
|
|
3306
|
+
}
|
|
3307
|
+
) })
|
|
3308
|
+
] })
|
|
3309
|
+
}
|
|
3310
|
+
)
|
|
3284
3311
|
] });
|
|
3285
3312
|
}
|
|
3286
3313
|
const hasAudio = async (src) => {
|
|
@@ -3369,11 +3396,11 @@ const saveAsFile = (content, type, name) => {
|
|
|
3369
3396
|
a.click();
|
|
3370
3397
|
URL.revokeObjectURL(url);
|
|
3371
3398
|
};
|
|
3372
|
-
function
|
|
3399
|
+
function GenerateCaptionsPanel({
|
|
3373
3400
|
selectedElement,
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3401
|
+
addCaptionsToTimeline,
|
|
3402
|
+
onGenerateCaptions,
|
|
3403
|
+
getCaptionstatus
|
|
3377
3404
|
}) {
|
|
3378
3405
|
const [containsAudio, setContainsAudio] = react.useState(null);
|
|
3379
3406
|
const [isLoading, setIsLoading] = react.useState(true);
|
|
@@ -3396,7 +3423,7 @@ function GenerateSubtitlesPanel({
|
|
|
3396
3423
|
}
|
|
3397
3424
|
};
|
|
3398
3425
|
const startPolling = async (reqId) => {
|
|
3399
|
-
if (!
|
|
3426
|
+
if (!getCaptionstatus) {
|
|
3400
3427
|
return;
|
|
3401
3428
|
}
|
|
3402
3429
|
setPollingStatus("polling");
|
|
@@ -3404,12 +3431,12 @@ function GenerateSubtitlesPanel({
|
|
|
3404
3431
|
setErrorMessage(null);
|
|
3405
3432
|
const poll = async () => {
|
|
3406
3433
|
try {
|
|
3407
|
-
const response = await
|
|
3434
|
+
const response = await getCaptionstatus(reqId);
|
|
3408
3435
|
if (response.status === "completed") {
|
|
3409
3436
|
stopPolling();
|
|
3410
3437
|
setPollingStatus("success");
|
|
3411
3438
|
setIsGenerating(false);
|
|
3412
|
-
|
|
3439
|
+
addCaptionsToTimeline(response.captions || []);
|
|
3413
3440
|
setTimeout(() => {
|
|
3414
3441
|
setPollingStatus("idle");
|
|
3415
3442
|
}, 3e3);
|
|
@@ -3418,32 +3445,32 @@ function GenerateSubtitlesPanel({
|
|
|
3418
3445
|
stopPolling();
|
|
3419
3446
|
setPollingStatus("error");
|
|
3420
3447
|
setIsGenerating(false);
|
|
3421
|
-
setErrorMessage(response.error || "Failed to generate
|
|
3422
|
-
console.error("Error generating
|
|
3448
|
+
setErrorMessage(response.error || "Failed to generate captions");
|
|
3449
|
+
console.error("Error generating captions:", response.error);
|
|
3423
3450
|
}
|
|
3424
3451
|
} catch (error) {
|
|
3425
3452
|
stopPolling();
|
|
3426
3453
|
setPollingStatus("error");
|
|
3427
3454
|
setIsGenerating(false);
|
|
3428
|
-
setErrorMessage(error instanceof Error ? error.message : "Failed to get
|
|
3429
|
-
console.error("Error polling for
|
|
3455
|
+
setErrorMessage(error instanceof Error ? error.message : "Failed to get caption status");
|
|
3456
|
+
console.error("Error polling for captions:", error);
|
|
3430
3457
|
}
|
|
3431
3458
|
};
|
|
3432
3459
|
await poll();
|
|
3433
3460
|
pollingIntervalRef.current = setInterval(poll, 2e3);
|
|
3434
3461
|
};
|
|
3435
|
-
const
|
|
3462
|
+
const handleGenerateCaptions = async () => {
|
|
3436
3463
|
if (!(selectedElement instanceof timeline.VideoElement)) {
|
|
3437
3464
|
return;
|
|
3438
3465
|
}
|
|
3439
3466
|
const videoElement = selectedElement;
|
|
3440
3467
|
try {
|
|
3441
|
-
const reqId = await
|
|
3468
|
+
const reqId = await onGenerateCaptions(videoElement);
|
|
3442
3469
|
if (!reqId) {
|
|
3443
3470
|
setPollingStatus("error");
|
|
3444
3471
|
setIsGenerating(false);
|
|
3445
|
-
setErrorMessage("Failed to start
|
|
3446
|
-
console.error("Error generating
|
|
3472
|
+
setErrorMessage("Failed to start caption generation");
|
|
3473
|
+
console.error("Error generating captions: Failed to start caption generation");
|
|
3447
3474
|
return;
|
|
3448
3475
|
}
|
|
3449
3476
|
currentReqIdRef.current = reqId;
|
|
@@ -3451,8 +3478,8 @@ function GenerateSubtitlesPanel({
|
|
|
3451
3478
|
} catch (error) {
|
|
3452
3479
|
setPollingStatus("error");
|
|
3453
3480
|
setIsGenerating(false);
|
|
3454
|
-
setErrorMessage(error instanceof Error ? error.message : "Failed to start
|
|
3455
|
-
console.error("Error generating
|
|
3481
|
+
setErrorMessage(error instanceof Error ? error.message : "Failed to start caption generation");
|
|
3482
|
+
console.error("Error generating captions:", error);
|
|
3456
3483
|
}
|
|
3457
3484
|
};
|
|
3458
3485
|
const checkAudio = async () => {
|
|
@@ -3484,7 +3511,7 @@ function GenerateSubtitlesPanel({
|
|
|
3484
3511
|
setErrorMessage(null);
|
|
3485
3512
|
}, [selectedElement]);
|
|
3486
3513
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
3487
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Generate
|
|
3514
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Generate Captions Panel" }),
|
|
3488
3515
|
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
3489
3516
|
/* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
|
|
3490
3517
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Checking for audio..." })
|
|
@@ -3495,85 +3522,228 @@ function GenerateSubtitlesPanel({
|
|
|
3495
3522
|
] }) }) }),
|
|
3496
3523
|
!isLoading && containsAudio === true && pollingStatus === "idle" && !isGenerating && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
3497
3524
|
/* @__PURE__ */ jsxRuntime.jsx(Volume2, { className: "empty-state-icon" }),
|
|
3498
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Audio detected! You can now generate
|
|
3525
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Audio detected! You can now generate captions" })
|
|
3499
3526
|
] }) }) }),
|
|
3500
3527
|
!isLoading && isGenerating && pollingStatus === "polling" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
3501
3528
|
/* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
|
|
3502
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Generating
|
|
3529
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Generating captions... Please wait" })
|
|
3503
3530
|
] }) }) }),
|
|
3504
3531
|
!isLoading && pollingStatus === "success" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
3505
3532
|
/* @__PURE__ */ jsxRuntime.jsx(CircleCheck, { className: "empty-state-icon", color: "var(--color-green-500)" }),
|
|
3506
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "
|
|
3533
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Captions generated successfully!" })
|
|
3507
3534
|
] }) }) }),
|
|
3508
3535
|
!isLoading && pollingStatus === "error" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
3509
3536
|
/* @__PURE__ */ jsxRuntime.jsx(CircleX, { className: "empty-state-icon", color: "var(--color-red-500)" }),
|
|
3510
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: errorMessage || "Failed to generate
|
|
3537
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: errorMessage || "Failed to generate captions" })
|
|
3511
3538
|
] }) }) }),
|
|
3512
3539
|
!isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3513
3540
|
"button",
|
|
3514
3541
|
{
|
|
3515
|
-
onClick:
|
|
3542
|
+
onClick: handleGenerateCaptions,
|
|
3516
3543
|
disabled: !containsAudio || isGenerating,
|
|
3517
3544
|
className: "btn-primary w-full",
|
|
3518
|
-
children: isGenerating ? "Generating..." : "Generate
|
|
3545
|
+
children: isGenerating ? "Generating..." : "Generate Captions"
|
|
3519
3546
|
}
|
|
3520
3547
|
) })
|
|
3521
3548
|
] });
|
|
3522
3549
|
}
|
|
3523
|
-
function
|
|
3524
|
-
selectedProp,
|
|
3550
|
+
function TextPropsPanel({
|
|
3525
3551
|
selectedElement,
|
|
3526
|
-
updateElement
|
|
3527
|
-
addSubtitlesToTimeline,
|
|
3528
|
-
onGenerateSubtitles,
|
|
3529
|
-
getSubtitleStatus
|
|
3552
|
+
updateElement
|
|
3530
3553
|
}) {
|
|
3531
|
-
if (!selectedElement)
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
)
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
}
|
|
3558
|
-
),
|
|
3559
|
-
selectedProp === "animations" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3560
|
-
Animation,
|
|
3561
|
-
{
|
|
3562
|
-
selectedElement,
|
|
3563
|
-
updateElement
|
|
3564
|
-
}
|
|
3565
|
-
),
|
|
3566
|
-
selectedProp === "generate-subtitles" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3567
|
-
GenerateSubtitlesPanel,
|
|
3554
|
+
if (!(selectedElement instanceof timeline.TextElement)) return null;
|
|
3555
|
+
const textProps = selectedElement.getProps() || {};
|
|
3556
|
+
const [isTypographyOpen, setIsTypographyOpen] = react.useState(false);
|
|
3557
|
+
const currentAlign = textProps.textAlign ?? "center";
|
|
3558
|
+
const currentWeight = textProps.fontWeight ?? 400;
|
|
3559
|
+
const isBold = currentWeight >= 600;
|
|
3560
|
+
const isItalic = textProps.fontStyle === "italic";
|
|
3561
|
+
const handleUpdate = (patch) => {
|
|
3562
|
+
if (!selectedElement) return;
|
|
3563
|
+
const next = { ...textProps, ...patch };
|
|
3564
|
+
selectedElement.setProps(next);
|
|
3565
|
+
updateElement == null ? void 0 : updateElement(selectedElement);
|
|
3566
|
+
};
|
|
3567
|
+
const toggleBold = () => {
|
|
3568
|
+
handleUpdate({ fontWeight: isBold ? 400 : 700 });
|
|
3569
|
+
};
|
|
3570
|
+
const toggleItalic = () => {
|
|
3571
|
+
handleUpdate({ fontStyle: isItalic ? "normal" : "italic" });
|
|
3572
|
+
};
|
|
3573
|
+
const setAlign = (align) => {
|
|
3574
|
+
handleUpdate({ textAlign: align });
|
|
3575
|
+
};
|
|
3576
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
3577
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Typography" }),
|
|
3578
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3579
|
+
AccordionItem,
|
|
3568
3580
|
{
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3581
|
+
title: "Typography",
|
|
3582
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(Type, { className: "icon-sm" }),
|
|
3583
|
+
isOpen: isTypographyOpen,
|
|
3584
|
+
onToggle: () => setIsTypographyOpen((open) => !open),
|
|
3585
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "properties-group", children: [
|
|
3586
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3587
|
+
PropertyRow,
|
|
3588
|
+
{
|
|
3589
|
+
label: "Font size",
|
|
3590
|
+
secondary: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
3591
|
+
textProps.fontSize ?? 48,
|
|
3592
|
+
"px"
|
|
3593
|
+
] }),
|
|
3594
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3595
|
+
"input",
|
|
3596
|
+
{
|
|
3597
|
+
type: "range",
|
|
3598
|
+
min: 8,
|
|
3599
|
+
max: 160,
|
|
3600
|
+
value: textProps.fontSize ?? 48,
|
|
3601
|
+
onChange: (e) => handleUpdate({ fontSize: Number(e.target.value) }),
|
|
3602
|
+
className: "slider-purple"
|
|
3603
|
+
}
|
|
3604
|
+
)
|
|
3605
|
+
}
|
|
3606
|
+
) }),
|
|
3607
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsxs(PropertyRow, { label: "Style", children: [
|
|
3608
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3609
|
+
"button",
|
|
3610
|
+
{
|
|
3611
|
+
type: "button",
|
|
3612
|
+
className: `form-btn ${isBold ? "active" : ""}`,
|
|
3613
|
+
onClick: toggleBold,
|
|
3614
|
+
title: "Bold",
|
|
3615
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Bold, { className: "icon-sm" })
|
|
3616
|
+
}
|
|
3617
|
+
),
|
|
3618
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3619
|
+
"button",
|
|
3620
|
+
{
|
|
3621
|
+
type: "button",
|
|
3622
|
+
className: `form-btn ${isItalic ? "active" : ""}`,
|
|
3623
|
+
onClick: toggleItalic,
|
|
3624
|
+
title: "Italic",
|
|
3625
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Italic, { className: "icon-sm" })
|
|
3626
|
+
}
|
|
3627
|
+
)
|
|
3628
|
+
] }) }),
|
|
3629
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsxs(PropertyRow, { label: "Align", children: [
|
|
3630
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3631
|
+
"button",
|
|
3632
|
+
{
|
|
3633
|
+
type: "button",
|
|
3634
|
+
className: `form-btn ${currentAlign === "left" ? "active" : ""}`,
|
|
3635
|
+
onClick: () => setAlign("left"),
|
|
3636
|
+
title: "Align left",
|
|
3637
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AlignLeft, { className: "icon-sm" })
|
|
3638
|
+
}
|
|
3639
|
+
),
|
|
3640
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3641
|
+
"button",
|
|
3642
|
+
{
|
|
3643
|
+
type: "button",
|
|
3644
|
+
className: `form-btn ${currentAlign === "center" ? "active" : ""}`,
|
|
3645
|
+
onClick: () => setAlign("center"),
|
|
3646
|
+
title: "Align center",
|
|
3647
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AlignCenter, { className: "icon-sm" })
|
|
3648
|
+
}
|
|
3649
|
+
),
|
|
3650
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3651
|
+
"button",
|
|
3652
|
+
{
|
|
3653
|
+
type: "button",
|
|
3654
|
+
className: `form-btn ${currentAlign === "right" ? "active" : ""}`,
|
|
3655
|
+
onClick: () => setAlign("right"),
|
|
3656
|
+
title: "Align right",
|
|
3657
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AlignRight, { className: "icon-sm" })
|
|
3658
|
+
}
|
|
3659
|
+
)
|
|
3660
|
+
] }) })
|
|
3661
|
+
] })
|
|
3573
3662
|
}
|
|
3574
3663
|
)
|
|
3575
3664
|
] });
|
|
3576
3665
|
}
|
|
3666
|
+
function PropertiesPanelContainer({
|
|
3667
|
+
selectedElement,
|
|
3668
|
+
updateElement,
|
|
3669
|
+
addCaptionsToTimeline,
|
|
3670
|
+
onGenerateCaptions,
|
|
3671
|
+
getCaptionstatus,
|
|
3672
|
+
videoResolution
|
|
3673
|
+
}) {
|
|
3674
|
+
const title = selectedElement instanceof timeline.TextElement ? selectedElement.getText() : (selectedElement == null ? void 0 : selectedElement.getName()) || (selectedElement == null ? void 0 : selectedElement.getType()) || "Element";
|
|
3675
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "properties-panel", "aria-label": "Element properties inspector", children: [
|
|
3676
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "properties-header", children: [
|
|
3677
|
+
!selectedElement && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: "Composition" }),
|
|
3678
|
+
selectedElement && selectedElement.getType() === "caption" && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: "Subtitles are edited from the captions panel" }),
|
|
3679
|
+
selectedElement && selectedElement.getType() !== "caption" && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: title })
|
|
3680
|
+
] }),
|
|
3681
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "prop-content", children: [
|
|
3682
|
+
!selectedElement && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
3683
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Canvas & Render" }),
|
|
3684
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "properties-group", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "property-section", children: [
|
|
3685
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "property-label", children: "Size" }),
|
|
3686
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "properties-size-readonly", children: [
|
|
3687
|
+
videoResolution.width,
|
|
3688
|
+
" × ",
|
|
3689
|
+
videoResolution.height
|
|
3690
|
+
] })
|
|
3691
|
+
] }) })
|
|
3692
|
+
] }),
|
|
3693
|
+
selectedElement && selectedElement.getType() === "caption" ? null : selectedElement && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: (() => {
|
|
3694
|
+
const isText = selectedElement instanceof timeline.TextElement;
|
|
3695
|
+
const isVideo = selectedElement instanceof timeline.VideoElement;
|
|
3696
|
+
const isAudio = selectedElement instanceof timeline.AudioElement;
|
|
3697
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3698
|
+
isText && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3699
|
+
TextPropsPanel,
|
|
3700
|
+
{
|
|
3701
|
+
selectedElement,
|
|
3702
|
+
updateElement
|
|
3703
|
+
}
|
|
3704
|
+
),
|
|
3705
|
+
!isAudio && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3706
|
+
ElementProps,
|
|
3707
|
+
{
|
|
3708
|
+
selectedElement,
|
|
3709
|
+
updateElement
|
|
3710
|
+
}
|
|
3711
|
+
),
|
|
3712
|
+
(isVideo || isAudio) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3713
|
+
PlaybackPropsPanel,
|
|
3714
|
+
{
|
|
3715
|
+
selectedElement,
|
|
3716
|
+
updateElement
|
|
3717
|
+
}
|
|
3718
|
+
),
|
|
3719
|
+
isText && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3720
|
+
TextEffects,
|
|
3721
|
+
{
|
|
3722
|
+
selectedElement,
|
|
3723
|
+
updateElement
|
|
3724
|
+
}
|
|
3725
|
+
),
|
|
3726
|
+
!isAudio && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3727
|
+
Animation,
|
|
3728
|
+
{
|
|
3729
|
+
selectedElement,
|
|
3730
|
+
updateElement
|
|
3731
|
+
}
|
|
3732
|
+
),
|
|
3733
|
+
isVideo && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3734
|
+
GenerateCaptionsPanel,
|
|
3735
|
+
{
|
|
3736
|
+
selectedElement,
|
|
3737
|
+
addCaptionsToTimeline,
|
|
3738
|
+
onGenerateCaptions,
|
|
3739
|
+
getCaptionstatus
|
|
3740
|
+
}
|
|
3741
|
+
)
|
|
3742
|
+
] });
|
|
3743
|
+
})() })
|
|
3744
|
+
] })
|
|
3745
|
+
] });
|
|
3746
|
+
}
|
|
3577
3747
|
const useStudioOperation = (studioConfig) => {
|
|
3578
3748
|
const { editor, present, videoResolution } = timeline.useTimelineContext();
|
|
3579
3749
|
const { setSeekTime, setPlayerState } = livePlayer.useLivePlayerContext();
|
|
@@ -3636,30 +3806,30 @@ const useStudioOperation = (studioConfig) => {
|
|
|
3636
3806
|
alert("Export video not supported in demo mode");
|
|
3637
3807
|
}
|
|
3638
3808
|
};
|
|
3639
|
-
const
|
|
3640
|
-
if (studioConfig == null ? void 0 : studioConfig.
|
|
3641
|
-
const service = studioConfig.
|
|
3642
|
-
const reqId = await service.
|
|
3809
|
+
const onGenerateCaptions = async (videoElement) => {
|
|
3810
|
+
if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
|
|
3811
|
+
const service = studioConfig.captionGenerationService;
|
|
3812
|
+
const reqId = await service.generateCaptions(videoElement, present);
|
|
3643
3813
|
return reqId;
|
|
3644
3814
|
}
|
|
3645
|
-
alert("Generate
|
|
3815
|
+
alert("Generate captions not supported in demo mode");
|
|
3646
3816
|
return null;
|
|
3647
3817
|
};
|
|
3648
|
-
const
|
|
3818
|
+
const addCaptionsToTimeline = (captions) => {
|
|
3649
3819
|
var _a;
|
|
3650
|
-
const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.
|
|
3820
|
+
const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.captionGenerationService) == null ? void 0 : _a.updateProjectWithCaptions(captions);
|
|
3651
3821
|
if (updatedProjectJSON) {
|
|
3652
3822
|
editor.loadProject(updatedProjectJSON);
|
|
3653
3823
|
}
|
|
3654
3824
|
};
|
|
3655
|
-
const
|
|
3656
|
-
if (studioConfig == null ? void 0 : studioConfig.
|
|
3657
|
-
const service = studioConfig.
|
|
3825
|
+
const getCaptionstatus = async (reqId) => {
|
|
3826
|
+
if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
|
|
3827
|
+
const service = studioConfig.captionGenerationService;
|
|
3658
3828
|
return await service.getRequestStatus(reqId);
|
|
3659
3829
|
}
|
|
3660
3830
|
return {
|
|
3661
3831
|
status: "failed",
|
|
3662
|
-
error: "
|
|
3832
|
+
error: "Caption generation service not found"
|
|
3663
3833
|
};
|
|
3664
3834
|
};
|
|
3665
3835
|
return {
|
|
@@ -3667,48 +3837,9 @@ const useStudioOperation = (studioConfig) => {
|
|
|
3667
3837
|
onSaveProject,
|
|
3668
3838
|
onExportVideo,
|
|
3669
3839
|
onNewProject,
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
};
|
|
3674
|
-
};
|
|
3675
|
-
const useGenerateSubtitles = (studioConfig) => {
|
|
3676
|
-
const { editor, present } = timeline.useTimelineContext();
|
|
3677
|
-
const onGenerateSubtitles = async (videoElement) => {
|
|
3678
|
-
if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
|
|
3679
|
-
const service = studioConfig.subtitleGenerationService;
|
|
3680
|
-
const reqId = await service.generateSubtitles(
|
|
3681
|
-
videoElement,
|
|
3682
|
-
present
|
|
3683
|
-
);
|
|
3684
|
-
return reqId;
|
|
3685
|
-
}
|
|
3686
|
-
alert("Generate subtitles not supported in demo mode");
|
|
3687
|
-
return null;
|
|
3688
|
-
};
|
|
3689
|
-
const addSubtitlesToTimeline = (subtitles) => {
|
|
3690
|
-
var _a;
|
|
3691
|
-
const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) == null ? void 0 : _a.updateProjectWithSubtitles(
|
|
3692
|
-
subtitles
|
|
3693
|
-
);
|
|
3694
|
-
if (updatedProjectJSON) {
|
|
3695
|
-
editor.loadProject(updatedProjectJSON);
|
|
3696
|
-
}
|
|
3697
|
-
};
|
|
3698
|
-
const getSubtitleStatus = async (reqId) => {
|
|
3699
|
-
if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
|
|
3700
|
-
const service = studioConfig.subtitleGenerationService;
|
|
3701
|
-
return await service.getRequestStatus(reqId);
|
|
3702
|
-
}
|
|
3703
|
-
return {
|
|
3704
|
-
status: "failed",
|
|
3705
|
-
error: "Subtitle generation service not found"
|
|
3706
|
-
};
|
|
3707
|
-
};
|
|
3708
|
-
return {
|
|
3709
|
-
onGenerateSubtitles,
|
|
3710
|
-
addSubtitlesToTimeline,
|
|
3711
|
-
getSubtitleStatus
|
|
3840
|
+
onGenerateCaptions,
|
|
3841
|
+
addCaptionsToTimeline,
|
|
3842
|
+
getCaptionstatus
|
|
3712
3843
|
};
|
|
3713
3844
|
};
|
|
3714
3845
|
function TwickStudio({ studioConfig }) {
|
|
@@ -3716,8 +3847,6 @@ function TwickStudio({ studioConfig }) {
|
|
|
3716
3847
|
const {
|
|
3717
3848
|
selectedTool,
|
|
3718
3849
|
setSelectedTool,
|
|
3719
|
-
selectedProp,
|
|
3720
|
-
setSelectedProp,
|
|
3721
3850
|
selectedElement,
|
|
3722
3851
|
addElement,
|
|
3723
3852
|
updateElement
|
|
@@ -3729,7 +3858,7 @@ function TwickStudio({ studioConfig }) {
|
|
|
3729
3858
|
onSaveProject,
|
|
3730
3859
|
onExportVideo
|
|
3731
3860
|
} = useStudioOperation(studioConfig);
|
|
3732
|
-
const {
|
|
3861
|
+
const { onGenerateCaptions, addCaptionsToTimeline, getCaptionstatus } = useGenerateCaptions(studioConfig);
|
|
3733
3862
|
const twickStudiConfig = react.useMemo(
|
|
3734
3863
|
() => ({
|
|
3735
3864
|
canvasMode: true,
|
|
@@ -3761,7 +3890,7 @@ function TwickStudio({ studioConfig }) {
|
|
|
3761
3890
|
setSelectedTool
|
|
3762
3891
|
}
|
|
3763
3892
|
),
|
|
3764
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3893
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "studio-left-panel", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3765
3894
|
ElementPanelContainer,
|
|
3766
3895
|
{
|
|
3767
3896
|
videoResolution,
|
|
@@ -3771,7 +3900,7 @@ function TwickStudio({ studioConfig }) {
|
|
|
3771
3900
|
addElement,
|
|
3772
3901
|
updateElement
|
|
3773
3902
|
}
|
|
3774
|
-
),
|
|
3903
|
+
) }),
|
|
3775
3904
|
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "main-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-wrapper", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3776
3905
|
"div",
|
|
3777
3906
|
{
|
|
@@ -3782,28 +3911,59 @@ function TwickStudio({ studioConfig }) {
|
|
|
3782
3911
|
children: /* @__PURE__ */ jsxRuntime.jsx(VideoEditor, { editorConfig: twickStudiConfig })
|
|
3783
3912
|
}
|
|
3784
3913
|
) }) }),
|
|
3785
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3914
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "studio-right-panel", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3786
3915
|
PropertiesPanelContainer,
|
|
3787
3916
|
{
|
|
3788
|
-
selectedProp,
|
|
3789
3917
|
selectedElement,
|
|
3790
3918
|
updateElement,
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3919
|
+
addCaptionsToTimeline,
|
|
3920
|
+
onGenerateCaptions,
|
|
3921
|
+
getCaptionstatus,
|
|
3922
|
+
videoResolution
|
|
3794
3923
|
}
|
|
3795
|
-
)
|
|
3796
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3797
|
-
PropsToolbar,
|
|
3798
|
-
{
|
|
3799
|
-
selectedElement,
|
|
3800
|
-
selectedProp,
|
|
3801
|
-
setSelectedProp
|
|
3802
|
-
}
|
|
3803
|
-
)
|
|
3924
|
+
) })
|
|
3804
3925
|
] })
|
|
3805
3926
|
] }) });
|
|
3806
3927
|
}
|
|
3928
|
+
const useGenerateCaptions = (studioConfig) => {
|
|
3929
|
+
const { editor, present } = timeline.useTimelineContext();
|
|
3930
|
+
const onGenerateCaptions = async (videoElement) => {
|
|
3931
|
+
if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
|
|
3932
|
+
const service = studioConfig.captionGenerationService;
|
|
3933
|
+
const reqId = await service.generateCaptions(
|
|
3934
|
+
videoElement,
|
|
3935
|
+
present
|
|
3936
|
+
);
|
|
3937
|
+
return reqId;
|
|
3938
|
+
}
|
|
3939
|
+
alert("Generate captions not supported in demo mode");
|
|
3940
|
+
return null;
|
|
3941
|
+
};
|
|
3942
|
+
const addCaptionsToTimeline = (captions) => {
|
|
3943
|
+
var _a;
|
|
3944
|
+
const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.captionGenerationService) == null ? void 0 : _a.updateProjectWithCaptions(
|
|
3945
|
+
captions
|
|
3946
|
+
);
|
|
3947
|
+
if (updatedProjectJSON) {
|
|
3948
|
+
editor.loadProject(updatedProjectJSON);
|
|
3949
|
+
}
|
|
3950
|
+
};
|
|
3951
|
+
const getCaptionstatus = async (reqId) => {
|
|
3952
|
+
if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
|
|
3953
|
+
const service = studioConfig.captionGenerationService;
|
|
3954
|
+
return await service.getRequestStatus(reqId);
|
|
3955
|
+
}
|
|
3956
|
+
return {
|
|
3957
|
+
status: "failed",
|
|
3958
|
+
error: "Caption generation service not found"
|
|
3959
|
+
};
|
|
3960
|
+
};
|
|
3961
|
+
return {
|
|
3962
|
+
onGenerateCaptions,
|
|
3963
|
+
addCaptionsToTimeline,
|
|
3964
|
+
getCaptionstatus
|
|
3965
|
+
};
|
|
3966
|
+
};
|
|
3807
3967
|
Object.defineProperty(exports, "AudioElement", {
|
|
3808
3968
|
enumerable: true,
|
|
3809
3969
|
get: () => timeline.AudioElement
|
|
@@ -3952,6 +4112,10 @@ Object.defineProperty(exports, "isTrackId", {
|
|
|
3952
4112
|
enumerable: true,
|
|
3953
4113
|
get: () => timeline.isTrackId
|
|
3954
4114
|
});
|
|
4115
|
+
Object.defineProperty(exports, "useTimelineContext", {
|
|
4116
|
+
enumerable: true,
|
|
4117
|
+
get: () => timeline.useTimelineContext
|
|
4118
|
+
});
|
|
3955
4119
|
Object.defineProperty(exports, "ANIMATIONS", {
|
|
3956
4120
|
enumerable: true,
|
|
3957
4121
|
get: () => VideoEditor.ANIMATIONS
|
|
@@ -4027,17 +4191,16 @@ Object.defineProperty(exports, "useLivePlayerContext", {
|
|
|
4027
4191
|
});
|
|
4028
4192
|
exports.AudioPanel = AudioPanel;
|
|
4029
4193
|
exports.CAPTION_PROPS = CAPTION_PROPS;
|
|
4194
|
+
exports.CaptionsPanel = CaptionsPanel;
|
|
4030
4195
|
exports.CirclePanel = CirclePanel;
|
|
4031
|
-
exports.IconPanel = IconPanel;
|
|
4032
4196
|
exports.ImagePanel = ImagePanel;
|
|
4033
4197
|
exports.RectPanel = RectPanel;
|
|
4034
4198
|
exports.StudioHeader = StudioHeader;
|
|
4035
|
-
exports.SubtitlesPanel = SubtitlesPanel;
|
|
4036
4199
|
exports.TextPanel = TextPanel;
|
|
4037
4200
|
exports.Toolbar = Toolbar;
|
|
4038
4201
|
exports.TwickStudio = TwickStudio;
|
|
4039
4202
|
exports.VideoPanel = VideoPanel;
|
|
4040
4203
|
exports.default = TwickStudio;
|
|
4041
|
-
exports.
|
|
4204
|
+
exports.useGenerateCaptions = useGenerateCaptions;
|
|
4042
4205
|
exports.useStudioManager = useStudioManager;
|
|
4043
4206
|
//# sourceMappingURL=index.js.map
|