@twick/studio 0.15.14 → 0.15.16

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.
Files changed (33) hide show
  1. package/README.md +2 -2
  2. package/dist/components/container/captions-panel-container.d.ts +1 -0
  3. package/dist/components/container/element-panel-container.d.ts +1 -1
  4. package/dist/components/container/properties-panel-container.d.ts +8 -7
  5. package/dist/components/panel/captions-panel.d.ts +46 -0
  6. package/dist/components/panel/circle-panel.d.ts +1 -1
  7. package/dist/components/panel/rect-panel.d.ts +1 -1
  8. package/dist/components/properties/{subtitlte-prop.d.ts → caption-prop.d.ts} +4 -4
  9. package/dist/components/properties/generate-captions.d.ts +14 -0
  10. package/dist/components/properties/property-row.d.ts +10 -0
  11. package/dist/components/properties/text-props.d.ts +3 -0
  12. package/dist/helpers/generate-captions.service.d.ts +21 -0
  13. package/dist/helpers/volume-db.d.ts +22 -0
  14. package/dist/hooks/use-captions-panel.d.ts +13 -0
  15. package/dist/hooks/use-generate-captions.d.ts +10 -0
  16. package/dist/hooks/use-studio-operation.d.ts +4 -4
  17. package/dist/index.d.ts +10 -13
  18. package/dist/index.js +1135 -1051
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +1142 -1061
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/studio.css +521 -162
  23. package/dist/types/index.d.ts +20 -16
  24. package/package.json +14 -12
  25. package/dist/components/container/icon-panel-container.d.ts +0 -3
  26. package/dist/components/container/subtitles-panel-container.d.ts +0 -1
  27. package/dist/components/panel/icon-panel.d.ts +0 -4
  28. package/dist/components/panel/subtitles-panel.d.ts +0 -46
  29. package/dist/components/properties/generate-subtitles.d.ts +0 -13
  30. package/dist/helpers/generate-subtitles.service.d.ts +0 -21
  31. package/dist/hooks/use-generate-subtitles.d.ts +0 -9
  32. package/dist/hooks/use-icon-panel.d.ts +0 -24
  33. 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$u = [
117
- ["rect", { width: "18", height: "14", x: "3", y: "5", rx: "2", ry: "2", key: "12ruh7" }],
118
- ["path", { d: "M7 15h4M15 15h2M7 11h2M13 11h4", key: "1ueiar" }]
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 Captions = createLucideIcon("captions", __iconNode$u);
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$t = [
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$t);
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$s = [
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$s);
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$r = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
151
- const Circle = createLucideIcon("circle", __iconNode$r);
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$q = [
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$q);
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$p = [
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$p);
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$o = [
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$o);
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$n = [
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$n);
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$m = [
210
- ["path", { d: "M6 16c5 0 7-8 12-8a4 4 0 0 1 0 8c-5 0-7-8-12-8a4 4 0 1 0 0 8", key: "18ogeb" }]
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 Infinity = createLucideIcon("infinity", __iconNode$m);
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$l = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
220
- const LoaderCircle = createLucideIcon("loader-circle", __iconNode$l);
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$k = [
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$k);
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$j = [
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$j);
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$i = [
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$i);
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$h = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
261
- const Play = createLucideIcon("play", __iconNode$h);
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$g = [
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$g);
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$f = [
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$f);
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$e = [
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$e);
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$d = [
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$d);
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$c = [
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$c);
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$9 = [
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$9);
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$8 = [
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$8);
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$7 = [
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$7);
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$6 = [
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$6);
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$5 = [
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$5);
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$4 = [
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$4);
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$3 = [
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$3);
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$2 = [
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$2);
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$1 = [
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$1);
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: "Video" },
515
- { id: "image", name: "Image", icon: "Image", description: "Image" },
516
- { id: "audio", name: "Audio", icon: "Audio", description: "Audio" },
517
- { id: "text", name: "Text", icon: "Type", description: "Add text elements", shortcut: "T" },
518
- { id: "icon", name: "Icons", icon: "Icon", description: "Icon Element", shortcut: "I" },
519
- { id: "circle", name: "Circle", icon: "Circle", description: "Circle Element", shortcut: "C" },
520
- { id: "rect", name: "Rect", icon: "Rect", description: "Rect Element" },
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$1 = (iconName) => {
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$1(tool.icon);
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: `${tool.name}${tool.shortcut ? ` (${tool.shortcut})` : ""}`,
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
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", style: { gap: "0.5rem" }, children: [
613
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm opacity-80", children: "Orientation" }),
614
- /* @__PURE__ */ jsxRuntime.jsx(
615
- "button",
616
- {
617
- className: `btn-ghost ${orientation === "vertical" ? "btn-primary" : ""}`,
618
- title: "Portrait (720×1280)",
619
- onClick: () => handleOrientationChange("vertical"),
620
- children: /* @__PURE__ */ jsxRuntime.jsx(RectangleVertical, { className: "icon-sm" })
621
- }
622
- ),
623
- /* @__PURE__ */ jsxRuntime.jsx(
624
- "button",
625
- {
626
- className: `btn-ghost ${orientation === "horizontal" ? "btn-primary" : ""}`,
627
- title: "Landscape (1280×720)",
628
- onClick: () => handleOrientationChange("horizontal"),
629
- children: /* @__PURE__ */ jsxRuntime.jsx(RectangleHorizontal, { className: "icon-sm" })
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
- className: "media-list-item",
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
- className: "media-item",
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 media-action-btn-primary",
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
- className: "media-item",
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.jsx("div", { className: "media-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
1434
- "button",
1435
- {
1436
- onClick: (e) => {
1437
- e.stopPropagation();
1438
- onItemSelect(item, true);
1439
- },
1440
- className: "media-action-btn media-action-btn-primary",
1441
- children: /* @__PURE__ */ jsxRuntime.jsx(Plus, { className: "icon-sm" })
1442
- }
1443
- ) }),
1444
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "media-actions media-actions-corner", children: /* @__PURE__ */ jsxRuntime.jsx(
1445
- "button",
1446
- {
1447
- onClick: (e) => {
1448
- var _a, _b;
1449
- e.stopPropagation();
1450
- const videoEl = (_b = (_a = e.currentTarget.parentElement) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.querySelector("video");
1451
- if (videoEl) {
1452
- togglePlayPause(item, videoEl);
1453
- }
1454
- },
1455
- className: "media-action-btn",
1456
- children: playingVideo === item.id ? /* @__PURE__ */ jsxRuntime.jsx(Pause, { className: "icon-sm" }) : /* @__PURE__ */ jsxRuntime.jsx(Play, { className: "icon-sm" })
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
@@ -1775,7 +1844,7 @@ function TextPanel({
1775
1844
  ] })
1776
1845
  ] })
1777
1846
  ] }),
1778
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleApplyChanges, className: "btn-primary w-full", children: operation }) })
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 }) })
1779
1848
  ] });
1780
1849
  }
1781
1850
  const DEFAULT_TEXT_PROPS = {
@@ -1813,56 +1882,128 @@ const useTextPanel = ({
1813
1882
  const [backgroundColor, setBackgroundColor] = react.useState("#FACC15");
1814
1883
  const [backgroundOpacity, setBackgroundOpacity] = react.useState(1);
1815
1884
  const fonts = Object.values(VideoEditor.AVAILABLE_TEXT_FONTS);
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;
1921
+ } else {
1922
+ nextProps.shadowColor = void 0;
1923
+ nextProps.shadowOffset = void 0;
1924
+ nextProps.shadowBlur = void 0;
1925
+ nextProps.shadowOpacity = void 0;
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);
1936
+ };
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
+ };
1816
1989
  const handleApplyChanges = async () => {
1817
- let textElement;
1818
1990
  if (selectedElement instanceof timeline.TextElement) {
1819
- textElement = selectedElement;
1820
- textElement.setText(textContent);
1821
- textElement.setFontSize(fontSize);
1822
- textElement.setFontFamily(selectedFont);
1823
- textElement.setFontWeight(isBold ? 700 : 400);
1824
- textElement.setFontStyle(isItalic ? "italic" : "normal");
1825
- textElement.setFill(textColor);
1826
- textElement.setStrokeColor(strokeColor);
1827
- textElement.setLineWidth(strokeWidth);
1828
- textElement.setTextAlign(DEFAULT_TEXT_PROPS.textAlign);
1829
- const nextProps = { ...textElement.getProps() };
1830
- if (applyShadow) {
1831
- nextProps.shadowColor = shadowColor;
1832
- nextProps.shadowOffset = DEFAULT_TEXT_PROPS.shadowOffset;
1833
- nextProps.shadowBlur = DEFAULT_TEXT_PROPS.shadowBlur;
1834
- nextProps.shadowOpacity = DEFAULT_TEXT_PROPS.shadowOpacity;
1835
- } else {
1836
- nextProps.shadowColor = void 0;
1837
- nextProps.shadowOffset = void 0;
1838
- nextProps.shadowBlur = void 0;
1839
- nextProps.shadowOpacity = void 0;
1840
- }
1841
- if (applyBackground) {
1842
- nextProps.backgroundColor = backgroundColor;
1843
- nextProps.backgroundOpacity = backgroundOpacity;
1844
- } else {
1845
- nextProps.backgroundColor = void 0;
1846
- nextProps.backgroundOpacity = void 0;
1847
- }
1848
- textElement.setProps(nextProps);
1849
- updateElement(textElement);
1850
- } else {
1851
- 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");
1852
- const nextProps = { ...textElement.getProps() };
1853
- if (applyShadow) {
1854
- nextProps.shadowColor = shadowColor;
1855
- nextProps.shadowOffset = DEFAULT_TEXT_PROPS.shadowOffset;
1856
- nextProps.shadowBlur = DEFAULT_TEXT_PROPS.shadowBlur;
1857
- nextProps.shadowOpacity = DEFAULT_TEXT_PROPS.shadowOpacity;
1858
- }
1859
- if (applyBackground) {
1860
- nextProps.backgroundColor = backgroundColor;
1861
- nextProps.backgroundOpacity = backgroundOpacity;
1862
- }
1863
- textElement.setProps(nextProps);
1864
- await addElement(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;
1865
2000
  }
2001
+ if (applyBackground) {
2002
+ nextProps.backgroundColor = backgroundColor;
2003
+ nextProps.backgroundOpacity = backgroundOpacity;
2004
+ }
2005
+ textElement.setProps(nextProps);
2006
+ await addElement(textElement);
1866
2007
  };
1867
2008
  react.useEffect(() => {
1868
2009
  if (selectedElement instanceof timeline.TextElement) {
@@ -1915,22 +2056,22 @@ const useTextPanel = ({
1915
2056
  strokeWidth,
1916
2057
  fonts,
1917
2058
  operation: selectedElement instanceof timeline.TextElement ? "Apply Changes" : "Add Text",
1918
- setTextContent,
1919
- setFontSize,
1920
- setSelectedFont,
1921
- setIsBold,
1922
- setIsItalic,
1923
- setTextColor,
1924
- setStrokeColor,
1925
- setApplyShadow,
1926
- setShadowColor,
1927
- 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,
1928
2069
  applyBackground,
1929
2070
  backgroundColor,
1930
2071
  backgroundOpacity,
1931
- setApplyBackground,
1932
- setBackgroundColor,
1933
- setBackgroundOpacity,
2072
+ setApplyBackground: handleApplyBackgroundChange,
2073
+ setBackgroundColor: handleBackgroundColorChange,
2074
+ setBackgroundOpacity: handleBackgroundOpacityChange,
1934
2075
  handleApplyChanges
1935
2076
  };
1936
2077
  };
@@ -1938,230 +2079,17 @@ function TextPanelContainer(props) {
1938
2079
  const textPanelProps = useTextPanel(props);
1939
2080
  return /* @__PURE__ */ jsxRuntime.jsx(TextPanel, { ...textPanelProps });
1940
2081
  }
1941
- const SearchInput = ({
1942
- searchQuery,
1943
- setSearchQuery
1944
- }) => {
1945
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "search-container", children: [
1946
- /* @__PURE__ */ jsxRuntime.jsx(
1947
- "input",
1948
- {
1949
- type: "text",
1950
- placeholder: "Search media...",
1951
- value: searchQuery,
1952
- onChange: (e) => setSearchQuery(e.target.value),
1953
- className: "input search-input w-full"
1954
- }
1955
- ),
1956
- /* @__PURE__ */ jsxRuntime.jsx(Search, { className: "search-icon" })
1957
- ] });
1958
- };
1959
- function IconPanel({
1960
- icons,
1961
- loading,
1962
- totalIcons,
1963
- searchQuery,
1964
- handleSearch,
1965
- handleSelection,
1966
- handleDownloadIcon,
1967
- handleLoadMore
1968
- }) {
1969
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
1970
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Icon Library" }),
1971
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { searchQuery, setSearchQuery: handleSearch }) }),
1972
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "media-content", children: [
1973
- totalIcons > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "media-count", children: [
1974
- "Showing ",
1975
- icons.length,
1976
- " of ",
1977
- totalIcons,
1978
- " icons"
1979
- ] }),
1980
- loading && icons.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
1981
- /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
1982
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Loading icons..." })
1983
- ] }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "icon-grid", children: (icons || []).map((icon, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "icon-item", children: [
1984
- /* @__PURE__ */ jsxRuntime.jsx(
1985
- "div",
1986
- {
1987
- onClick: () => handleSelection(icon),
1988
- className: "icon-content",
1989
- dangerouslySetInnerHTML: { __html: icon.svg }
1990
- }
1991
- ),
1992
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "icon-actions", children: [
1993
- /* @__PURE__ */ jsxRuntime.jsx(
1994
- "button",
1995
- {
1996
- onClick: (e) => {
1997
- e.stopPropagation();
1998
- handleSelection(icon);
1999
- },
2000
- className: "icon-action-btn",
2001
- title: "Add to timeline",
2002
- children: /* @__PURE__ */ jsxRuntime.jsx(Plus, { className: "icon-sm" })
2003
- }
2004
- ),
2005
- /* @__PURE__ */ jsxRuntime.jsx(
2006
- "button",
2007
- {
2008
- onClick: (e) => {
2009
- e.stopPropagation();
2010
- handleDownloadIcon(icon);
2011
- },
2012
- className: "icon-action-btn",
2013
- title: "Download SVG",
2014
- children: /* @__PURE__ */ jsxRuntime.jsx(Download, { className: "icon-sm" })
2015
- }
2016
- )
2017
- ] }),
2018
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "icon-name", children: icon.name })
2019
- ] }, index)) }),
2020
- !loading && icons.length === 0 && searchQuery && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
2021
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No icons found" }),
2022
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-subtext", children: "Try a different search term" })
2023
- ] }) }),
2024
- !loading && totalIcons && icons.length < totalIcons && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
2025
- "button",
2026
- {
2027
- onClick: handleLoadMore,
2028
- disabled: loading,
2029
- className: "btn-primary",
2030
- children: "Load More Icons"
2031
- }
2032
- ) })
2033
- ] })
2034
- ] });
2035
- }
2036
- const ICONS_PER_PAGE = 20;
2037
- const useIconPanel = ({
2038
- selectedElement,
2039
- addElement,
2040
- updateElement
2041
- }) => {
2042
- const [icons, setIcons] = react.useState([]);
2043
- const [loading, setLoading] = react.useState(false);
2044
- const [page, setPage] = react.useState(1);
2045
- const [hasMore, setHasMore] = react.useState(true);
2046
- const [totalIcons, setTotalIcons] = react.useState(0);
2047
- const [searchQuery, setSearchQuery] = react.useState("");
2048
- const currentQuery = react.useRef("");
2049
- const fetchIcons = async (query, reset = false) => {
2050
- try {
2051
- setLoading(true);
2052
- const newPage = reset ? 1 : page;
2053
- const start = (newPage - 1) * ICONS_PER_PAGE;
2054
- const url = `https://api.iconify.design/search?query=${query}&limit=${ICONS_PER_PAGE}&offset=${start}`;
2055
- const response = await fetch(url);
2056
- const data = await response.json();
2057
- const iconData = data.icons || [];
2058
- const total = data.total || 0;
2059
- setTotalIcons(total);
2060
- const formattedIcons = await Promise.all(
2061
- iconData.map(async (icon) => {
2062
- const svgUrl = `https://api.iconify.design/${icon}.svg`;
2063
- try {
2064
- const svgResponse = await fetch(svgUrl);
2065
- const svg = await svgResponse.text();
2066
- return { name: icon, svg };
2067
- } catch (e) {
2068
- console.error(`Error fetching SVG for ${icon}:`, e);
2069
- return null;
2070
- }
2071
- })
2072
- );
2073
- const validIcons = formattedIcons.filter((icon) => icon !== null);
2074
- if (reset) {
2075
- setIcons(validIcons);
2076
- } else {
2077
- setIcons([...icons, ...validIcons]);
2078
- }
2079
- setHasMore(start + validIcons.length < total);
2080
- if (!reset) {
2081
- setPage(newPage + 1);
2082
- } else {
2083
- setPage(2);
2084
- }
2085
- } catch (error) {
2086
- console.error("Error fetching icons:", error);
2087
- } finally {
2088
- setLoading(false);
2089
- }
2090
- };
2091
- react.useEffect(() => {
2092
- fetchIcons("media", true);
2093
- }, []);
2094
- const handleSearch = (query) => {
2095
- currentQuery.current = query;
2096
- setSearchQuery(query);
2097
- fetchIcons(query, true);
2098
- };
2099
- const handleSelection = (icon) => {
2100
- const svgBlob = new Blob([icon.svg], { type: "image/svg+xml" });
2101
- const url = URL.createObjectURL(svgBlob);
2102
- let iconElement;
2103
- if (selectedElement instanceof timeline.IconElement) {
2104
- iconElement = selectedElement;
2105
- iconElement.setSrc(url);
2106
- iconElement.setName(icon.name);
2107
- updateElement == null ? void 0 : updateElement(iconElement);
2108
- } else {
2109
- iconElement = new timeline.IconElement(url, {
2110
- width: 100,
2111
- height: 100
2112
- });
2113
- iconElement.setName(icon.name);
2114
- addElement == null ? void 0 : addElement(iconElement);
2115
- }
2116
- URL.revokeObjectURL(url);
2117
- };
2118
- const handleDownloadIcon = (icon) => {
2119
- const blob = new Blob([icon.svg], { type: "image/svg+xml" });
2120
- const url = URL.createObjectURL(blob);
2121
- const a = document.createElement("a");
2122
- a.href = url;
2123
- a.download = `${icon.name}.svg`;
2124
- document.body.appendChild(a);
2125
- a.click();
2126
- document.body.removeChild(a);
2127
- URL.revokeObjectURL(url);
2128
- };
2129
- const handleLoadMore = () => {
2130
- fetchIcons(currentQuery.current, false);
2131
- };
2132
- return {
2133
- icons,
2134
- loading,
2135
- hasMore,
2136
- totalIcons,
2137
- searchQuery,
2138
- handleSearch,
2139
- handleSelection,
2140
- handleDownloadIcon,
2141
- handleLoadMore
2142
- };
2143
- };
2144
- function IconPanelContainer(props) {
2145
- const iconPanelProps = useIconPanel({
2146
- selectedElement: props.selectedElement ?? null,
2147
- addElement: props.addElement,
2148
- updateElement: props.updateElement
2149
- });
2150
- return /* @__PURE__ */ jsxRuntime.jsx(IconPanel, { ...iconPanelProps });
2151
- }
2152
- function RectPanel({
2153
- cornerRadius,
2154
- fillColor,
2155
- opacity,
2156
- strokeColor,
2157
- lineWidth,
2158
- operation,
2159
- setCornerRadius,
2160
- setFillColor,
2161
- setOpacity,
2162
- setStrokeColor,
2163
- setLineWidth,
2164
- handleApplyChanges
2082
+ function RectPanel({
2083
+ cornerRadius,
2084
+ fillColor,
2085
+ strokeColor,
2086
+ lineWidth,
2087
+ operation,
2088
+ setCornerRadius,
2089
+ setFillColor,
2090
+ setStrokeColor,
2091
+ setLineWidth,
2092
+ handleApplyChanges
2165
2093
  }) {
2166
2094
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
2167
2095
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Rectangle" }),
@@ -2208,26 +2136,6 @@ function RectPanel({
2208
2136
  )
2209
2137
  ] })
2210
2138
  ] }),
2211
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
2212
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Opacity" }),
2213
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "slider-container", children: [
2214
- /* @__PURE__ */ jsxRuntime.jsx(
2215
- "input",
2216
- {
2217
- type: "range",
2218
- min: "0",
2219
- max: "100",
2220
- value: opacity,
2221
- onChange: (e) => setOpacity(Number(e.target.value)),
2222
- className: "slider-purple"
2223
- }
2224
- ),
2225
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "slider-value", children: [
2226
- opacity,
2227
- "%"
2228
- ] })
2229
- ] })
2230
- ] }),
2231
2139
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
2232
2140
  /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Stroke Color" }),
2233
2141
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "color-inputs", children: [
@@ -2271,7 +2179,7 @@ function RectPanel({
2271
2179
  ] })
2272
2180
  ] })
2273
2181
  ] }),
2274
- /* @__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(
2275
2183
  "button",
2276
2184
  {
2277
2185
  onClick: handleApplyChanges,
@@ -2284,6 +2192,7 @@ function RectPanel({
2284
2192
  const DEFAULT_RECT_PROPS = {
2285
2193
  cornerRadius: 0,
2286
2194
  fillColor: "#3b82f6",
2195
+ // UI uses 0–100%; element opacity is 0–1
2287
2196
  opacity: 100,
2288
2197
  strokeColor: "#000000",
2289
2198
  lineWidth: 0
@@ -2298,25 +2207,56 @@ const useRectPanel = ({
2298
2207
  const [opacity, setOpacity] = react.useState(DEFAULT_RECT_PROPS.opacity);
2299
2208
  const [strokeColor, setStrokeColor] = react.useState(DEFAULT_RECT_PROPS.strokeColor);
2300
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
+ };
2301
2247
  const handleApplyChanges = () => {
2302
- let rectElement;
2303
2248
  if (selectedElement instanceof timeline.RectElement) {
2304
- rectElement = selectedElement;
2305
- rectElement.setCornerRadius(cornerRadius);
2306
- rectElement.setOpacity(opacity);
2307
- rectElement.setStrokeColor(strokeColor);
2308
- rectElement.setLineWidth(lineWidth);
2309
- updateElement == null ? void 0 : updateElement(rectElement);
2310
- } else {
2311
- rectElement = new timeline.RectElement(fillColor, { width: 200, height: 200 }).setCornerRadius(cornerRadius).setOpacity(opacity).setStrokeColor(strokeColor).setLineWidth(lineWidth);
2312
- addElement == null ? void 0 : addElement(rectElement);
2249
+ return;
2313
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);
2314
2253
  };
2315
2254
  react.useEffect(() => {
2316
2255
  if (selectedElement instanceof timeline.RectElement) {
2317
2256
  setCornerRadius(selectedElement.getCornerRadius() ?? DEFAULT_RECT_PROPS.cornerRadius);
2318
2257
  setFillColor(selectedElement.getFill() ?? DEFAULT_RECT_PROPS.fillColor);
2319
- setOpacity(selectedElement.getOpacity() ?? DEFAULT_RECT_PROPS.opacity);
2258
+ const elementOpacity = selectedElement.getOpacity();
2259
+ setOpacity(elementOpacity != null ? elementOpacity * 100 : DEFAULT_RECT_PROPS.opacity);
2320
2260
  setStrokeColor(selectedElement.getStrokeColor() ?? DEFAULT_RECT_PROPS.strokeColor);
2321
2261
  setLineWidth(selectedElement.getLineWidth() ?? DEFAULT_RECT_PROPS.lineWidth);
2322
2262
  }
@@ -2328,11 +2268,11 @@ const useRectPanel = ({
2328
2268
  strokeColor,
2329
2269
  lineWidth,
2330
2270
  operation: selectedElement instanceof timeline.RectElement ? "Apply Changes" : "Add Rectangle",
2331
- setCornerRadius,
2332
- setFillColor,
2333
- setOpacity,
2334
- setStrokeColor,
2335
- setLineWidth,
2271
+ setCornerRadius: handleCornerRadiusChange,
2272
+ setFillColor: handleFillColorChange,
2273
+ setOpacity: handleOpacityChange,
2274
+ setStrokeColor: handleStrokeColorChange,
2275
+ setLineWidth: handleLineWidthChange,
2336
2276
  handleApplyChanges
2337
2277
  };
2338
2278
  };
@@ -2347,13 +2287,11 @@ function RectPanelContainer(props) {
2347
2287
  function CirclePanel({
2348
2288
  radius,
2349
2289
  fillColor,
2350
- opacity,
2351
2290
  strokeColor,
2352
2291
  lineWidth,
2353
2292
  operation,
2354
2293
  setRadius,
2355
2294
  setFillColor,
2356
- setOpacity,
2357
2295
  setStrokeColor,
2358
2296
  setLineWidth,
2359
2297
  handleApplyChanges
@@ -2403,26 +2341,6 @@ function CirclePanel({
2403
2341
  )
2404
2342
  ] })
2405
2343
  ] }),
2406
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
2407
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Opacity" }),
2408
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "slider-container", children: [
2409
- /* @__PURE__ */ jsxRuntime.jsx(
2410
- "input",
2411
- {
2412
- type: "range",
2413
- min: "0",
2414
- max: "100",
2415
- value: opacity,
2416
- onChange: (e) => setOpacity(Number(e.target.value)),
2417
- className: "slider-purple"
2418
- }
2419
- ),
2420
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "slider-value", children: [
2421
- opacity,
2422
- "%"
2423
- ] })
2424
- ] })
2425
- ] }),
2426
2344
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
2427
2345
  /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Stroke Color" }),
2428
2346
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "color-inputs", children: [
@@ -2466,12 +2384,13 @@ function CirclePanel({
2466
2384
  ] })
2467
2385
  ] })
2468
2386
  ] }),
2469
- /* @__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 }) })
2470
2388
  ] });
2471
2389
  }
2472
2390
  const DEFAULT_CIRCLE_PROPS = {
2473
2391
  radius: 50,
2474
2392
  fillColor: "#3b82f6",
2393
+ // UI uses 0–100%; element opacity is 0–1
2475
2394
  opacity: 100,
2476
2395
  strokeColor: "#000000",
2477
2396
  lineWidth: 0
@@ -2486,26 +2405,56 @@ const useCirclePanel = ({
2486
2405
  const [opacity, setOpacity] = react.useState(DEFAULT_CIRCLE_PROPS.opacity);
2487
2406
  const [strokeColor, setStrokeColor] = react.useState(DEFAULT_CIRCLE_PROPS.strokeColor);
2488
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
+ };
2489
2445
  const handleApplyChanges = () => {
2490
- let circleElement;
2491
2446
  if (selectedElement instanceof timeline.CircleElement) {
2492
- circleElement = selectedElement;
2493
- circleElement.setRadius(radius);
2494
- circleElement.setFill(fillColor);
2495
- circleElement.setOpacity(opacity);
2496
- circleElement.setStrokeColor(strokeColor);
2497
- circleElement.setLineWidth(lineWidth);
2498
- updateElement == null ? void 0 : updateElement(circleElement);
2499
- } else {
2500
- circleElement = new timeline.CircleElement(fillColor, radius).setOpacity(opacity).setStrokeColor(strokeColor).setLineWidth(lineWidth);
2501
- addElement == null ? void 0 : addElement(circleElement);
2447
+ return;
2502
2448
  }
2449
+ const circleElement = new timeline.CircleElement(fillColor, radius).setOpacity(opacity / 100).setStrokeColor(strokeColor).setLineWidth(lineWidth);
2450
+ addElement == null ? void 0 : addElement(circleElement);
2503
2451
  };
2504
2452
  react.useEffect(() => {
2505
2453
  if (selectedElement instanceof timeline.CircleElement) {
2506
2454
  setRadius(selectedElement.getRadius() ?? DEFAULT_CIRCLE_PROPS.radius);
2507
2455
  setFillColor(selectedElement.getFill() ?? DEFAULT_CIRCLE_PROPS.fillColor);
2508
- setOpacity(selectedElement.getOpacity() ?? DEFAULT_CIRCLE_PROPS.opacity);
2456
+ const elementOpacity = selectedElement.getOpacity();
2457
+ setOpacity(elementOpacity != null ? elementOpacity * 100 : DEFAULT_CIRCLE_PROPS.opacity);
2509
2458
  setStrokeColor(selectedElement.getStrokeColor() ?? DEFAULT_CIRCLE_PROPS.strokeColor);
2510
2459
  setLineWidth(selectedElement.getLineWidth() ?? DEFAULT_CIRCLE_PROPS.lineWidth);
2511
2460
  }
@@ -2517,11 +2466,11 @@ const useCirclePanel = ({
2517
2466
  strokeColor,
2518
2467
  lineWidth,
2519
2468
  operation: selectedElement instanceof timeline.CircleElement ? "Apply Changes" : "Add Circle",
2520
- setRadius,
2521
- setFillColor,
2522
- setOpacity,
2523
- setStrokeColor,
2524
- setLineWidth,
2469
+ setRadius: handleRadiusChange,
2470
+ setFillColor: handleFillColorChange,
2471
+ setOpacity: handleOpacityChange,
2472
+ setStrokeColor: handleStrokeColorChange,
2473
+ setLineWidth: handleLineWidthChange,
2525
2474
  handleApplyChanges
2526
2475
  };
2527
2476
  };
@@ -2533,16 +2482,16 @@ function CirclePanelContainer(props) {
2533
2482
  });
2534
2483
  return /* @__PURE__ */ jsxRuntime.jsx(CirclePanel, { ...circlePanelProps });
2535
2484
  }
2536
- function SubtitlesPanel({
2537
- subtitles,
2538
- addSubtitle,
2539
- splitSubtitle,
2540
- deleteSubtitle,
2541
- updateSubtitle
2485
+ function CaptionsPanel({
2486
+ captions,
2487
+ addCaption,
2488
+ splitCaption,
2489
+ deleteCaption,
2490
+ updateCaption
2542
2491
  }) {
2543
2492
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
2544
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "panel-title", children: "Subtitles" }),
2545
- subtitles.map((subtitle, i) => /* @__PURE__ */ jsxRuntime.jsxs(
2493
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "panel-title", children: "Captions" }),
2494
+ captions.map((caption, i) => /* @__PURE__ */ jsxRuntime.jsxs(
2546
2495
  "div",
2547
2496
  {
2548
2497
  className: "panel-section gap-2",
@@ -2551,9 +2500,9 @@ function SubtitlesPanel({
2551
2500
  "input",
2552
2501
  {
2553
2502
  type: "text",
2554
- placeholder: "Enter subtitle text",
2555
- value: subtitle.t,
2556
- onChange: (e) => updateSubtitle(i, { ...subtitle, t: e.target.value }),
2503
+ placeholder: "Enter caption text",
2504
+ value: caption.t,
2505
+ onChange: (e) => updateCaption(i, { ...caption, t: e.target.value }),
2557
2506
  className: "input-dark"
2558
2507
  }
2559
2508
  ) }),
@@ -2561,18 +2510,18 @@ function SubtitlesPanel({
2561
2510
  /* @__PURE__ */ jsxRuntime.jsx(
2562
2511
  "button",
2563
2512
  {
2564
- onClick: () => splitSubtitle(i),
2513
+ onClick: () => splitCaption(i),
2565
2514
  className: "btn-ghost",
2566
- title: "Split subtitle",
2515
+ title: "Split caption",
2567
2516
  children: /* @__PURE__ */ jsxRuntime.jsx(Scissors, { className: "icon-sm" })
2568
2517
  }
2569
2518
  ),
2570
2519
  /* @__PURE__ */ jsxRuntime.jsx(
2571
2520
  "button",
2572
2521
  {
2573
- onClick: () => deleteSubtitle(i),
2522
+ onClick: () => deleteCaption(i),
2574
2523
  className: "btn-ghost",
2575
- title: "Delete subtitle",
2524
+ title: "Delete caption",
2576
2525
  children: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { className: "icon-sm", color: "var(--color-red-500)" })
2577
2526
  }
2578
2527
  )
@@ -2581,7 +2530,7 @@ function SubtitlesPanel({
2581
2530
  },
2582
2531
  i
2583
2532
  )),
2584
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: addSubtitle, className: "btn-primary w-full", title: "Add subtitle", children: "Add" }) })
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" }) })
2585
2534
  ] });
2586
2535
  }
2587
2536
  const CAPTION_PROPS = {
@@ -2636,16 +2585,16 @@ const CAPTION_PROPS = {
2636
2585
  shadowBlur: 5
2637
2586
  }
2638
2587
  };
2639
- const useSubtitlesPanel = () => {
2640
- const [subtitles, setSubtitles] = react.useState([]);
2641
- const subtitlesTrack = react.useRef(null);
2588
+ const useCaptionsPanel = () => {
2589
+ const [captions, setCaptions] = react.useState([]);
2590
+ const captionsTrack = react.useRef(null);
2642
2591
  const { editor } = timeline.useTimelineContext();
2643
- const fetchSubtitles = async () => {
2644
- const editorSubtitlesTrack = editor.getSubtitlesTrack();
2645
- if (editorSubtitlesTrack) {
2646
- subtitlesTrack.current = editorSubtitlesTrack;
2647
- setSubtitles(
2648
- editorSubtitlesTrack.getElements().map((element) => ({
2592
+ const fetchCaptions = async () => {
2593
+ const editorCaptionsTrack = editor.getCaptionsTrack();
2594
+ if (editorCaptionsTrack) {
2595
+ captionsTrack.current = editorCaptionsTrack;
2596
+ setCaptions(
2597
+ editorCaptionsTrack.getElements().map((element) => ({
2649
2598
  s: element.getStart(),
2650
2599
  e: element.getEnd(),
2651
2600
  t: element.getText()
@@ -2654,12 +2603,12 @@ const useSubtitlesPanel = () => {
2654
2603
  }
2655
2604
  };
2656
2605
  react.useEffect(() => {
2657
- fetchSubtitles();
2606
+ fetchCaptions();
2658
2607
  }, []);
2659
- const checkSubtitlesTrack = () => {
2608
+ const checkCaptionsTrack = () => {
2660
2609
  var _a;
2661
- if (!subtitlesTrack.current) {
2662
- subtitlesTrack.current = editor.addTrack("Subtitles", "caption");
2610
+ if (!captionsTrack.current) {
2611
+ captionsTrack.current = editor.addTrack("Caption", "caption");
2663
2612
  const props = {
2664
2613
  capStyle: timeline.CAPTION_STYLE.WORD_BG_HIGHLIGHT,
2665
2614
  ...CAPTION_PROPS[timeline.CAPTION_STYLE.WORD_BG_HIGHLIGHT],
@@ -2667,61 +2616,61 @@ const useSubtitlesPanel = () => {
2667
2616
  y: 200,
2668
2617
  applyToAll: true
2669
2618
  };
2670
- (_a = subtitlesTrack.current) == null ? void 0 : _a.setProps(props);
2619
+ (_a = captionsTrack.current) == null ? void 0 : _a.setProps(props);
2671
2620
  }
2672
2621
  };
2673
- const addSubtitle = () => {
2674
- const newSubtitle = { s: 0, e: 0, t: "New Subtitle" };
2675
- if (subtitles.length > 0) {
2676
- newSubtitle.s = subtitles[subtitles.length - 1].e;
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;
2677
2626
  }
2678
- newSubtitle.e = newSubtitle.s + 1;
2679
- setSubtitles([...subtitles, newSubtitle]);
2680
- checkSubtitlesTrack();
2627
+ newCaption.e = newCaption.s + 1;
2628
+ setCaptions([...captions, newCaption]);
2629
+ checkCaptionsTrack();
2681
2630
  const captionElement = new timeline.CaptionElement(
2682
- newSubtitle.t,
2683
- newSubtitle.s,
2684
- newSubtitle.e
2631
+ newCaption.t,
2632
+ newCaption.s,
2633
+ newCaption.e
2685
2634
  );
2686
- editor.addElementToTrack(subtitlesTrack.current, captionElement);
2635
+ editor.addElementToTrack(captionsTrack.current, captionElement);
2687
2636
  };
2688
- const splitSubtitle = async (index) => {
2689
- if (subtitlesTrack.current) {
2690
- const element = subtitlesTrack.current.getElements()[index];
2637
+ const splitCaption = async (index) => {
2638
+ if (captionsTrack.current) {
2639
+ const element = captionsTrack.current.getElements()[index];
2691
2640
  const splitResult = await editor.splitElement(
2692
2641
  element,
2693
2642
  element.getStart() + element.getDuration() / 2
2694
2643
  );
2695
2644
  if (splitResult.success) {
2696
- fetchSubtitles();
2645
+ fetchCaptions();
2697
2646
  }
2698
2647
  }
2699
2648
  };
2700
- const deleteSubtitle = (index) => {
2701
- setSubtitles(subtitles.filter((_, i) => i !== index));
2702
- if (subtitlesTrack.current) {
2703
- editor.removeElement(subtitlesTrack.current.getElements()[index]);
2649
+ const deleteCaption = (index) => {
2650
+ setCaptions(captions.filter((_, i) => i !== index));
2651
+ if (captionsTrack.current) {
2652
+ editor.removeElement(captionsTrack.current.getElements()[index]);
2704
2653
  }
2705
2654
  };
2706
- const updateSubtitle = (index, subtitle) => {
2707
- setSubtitles(subtitles.map((sub, i) => i === index ? subtitle : sub));
2708
- if (subtitlesTrack.current) {
2709
- const element = subtitlesTrack.current.getElements()[index];
2710
- element.setText(subtitle.t);
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);
2711
2660
  editor.updateElement(element);
2712
2661
  }
2713
2662
  };
2714
2663
  return {
2715
- subtitles,
2716
- addSubtitle,
2717
- splitSubtitle,
2718
- deleteSubtitle,
2719
- updateSubtitle
2664
+ captions,
2665
+ addCaption,
2666
+ splitCaption,
2667
+ deleteCaption,
2668
+ updateCaption
2720
2669
  };
2721
2670
  };
2722
- function SubtitlesPanelContainer() {
2723
- const subtitlesPanelProps = useSubtitlesPanel();
2724
- return /* @__PURE__ */ jsxRuntime.jsx(SubtitlesPanel, { ...subtitlesPanelProps });
2671
+ function CaptionsPanelContainer() {
2672
+ const captionsPanelProps = useCaptionsPanel();
2673
+ return /* @__PURE__ */ jsxRuntime.jsx(CaptionsPanel, { ...captionsPanelProps });
2725
2674
  }
2726
2675
  const ElementPanelContainer = ({
2727
2676
  selectedTool,
@@ -2774,16 +2723,6 @@ const ElementPanelContainer = ({
2774
2723
  updateElement
2775
2724
  }
2776
2725
  );
2777
- case "icon":
2778
- return /* @__PURE__ */ jsxRuntime.jsx(
2779
- IconPanelContainer,
2780
- {
2781
- videoResolution,
2782
- selectedElement,
2783
- addElement: addNewElement,
2784
- updateElement
2785
- }
2786
- );
2787
2726
  case "rect":
2788
2727
  return /* @__PURE__ */ jsxRuntime.jsx(
2789
2728
  RectPanelContainer,
@@ -2804,8 +2743,8 @@ const ElementPanelContainer = ({
2804
2743
  updateElement
2805
2744
  }
2806
2745
  );
2807
- case "subtitle":
2808
- return /* @__PURE__ */ jsxRuntime.jsx(SubtitlesPanelContainer, {});
2746
+ case "caption":
2747
+ return /* @__PURE__ */ jsxRuntime.jsx(CaptionsPanelContainer, {});
2809
2748
  default:
2810
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: [
2811
2750
  /* @__PURE__ */ jsxRuntime.jsx(WandSparkles, { className: "empty-state-icon" }),
@@ -2815,159 +2754,37 @@ const ElementPanelContainer = ({
2815
2754
  };
2816
2755
  return renderLibrary();
2817
2756
  };
2818
- const propsCategories = /* @__PURE__ */ new Map([
2819
- [
2820
- "element-props",
2821
- {
2822
- id: "element-props",
2823
- name: "Properties",
2824
- icon: "Settings",
2825
- description: "Element Properties"
2826
- }
2827
- ],
2828
- [
2829
- "animations",
2830
- {
2831
- id: "animations",
2832
- name: "Animations",
2833
- icon: "Zap",
2834
- description: "Animations"
2835
- }
2836
- ],
2837
- [
2838
- "text-effects",
2839
- {
2840
- id: "text-effects",
2841
- name: "Text Effects",
2842
- icon: "SparklesIcon",
2843
- description: "Text Effects"
2844
- }
2845
- ],
2846
- [
2847
- "color-effects",
2848
- {
2849
- id: "color-effects",
2850
- name: "Color Effects",
2851
- icon: "Image",
2852
- description: "Color Effects"
2853
- }
2854
- ],
2855
- [
2856
- "playback-props",
2857
- {
2858
- id: "playback-props",
2859
- name: "Playback Props",
2860
- icon: "Music",
2861
- description: "Playback Properties"
2862
- }
2863
- ],
2864
- [
2865
- "subtitle-style",
2866
- {
2867
- id: "subtitle-style",
2868
- name: "Subtitle Style",
2869
- icon: "MessageSquare",
2870
- description: "Subtitle Style"
2871
- }
2872
- ],
2873
- [
2874
- "generate-subtitles",
2875
- {
2876
- id: "generate-subtitles",
2877
- name: "Generate Subtitles",
2878
- icon: "Subtitles",
2879
- description: "Generate Subtitles"
2880
- }
2881
- ]
2882
- ]);
2883
- const getIcon = (iconName) => {
2884
- switch (iconName) {
2885
- case "Type":
2886
- return Type;
2887
- case "Infinity":
2888
- return Infinity;
2889
- case "Image":
2890
- return Image;
2891
- case "Music":
2892
- return Music;
2893
- case "Subtitles":
2894
- return Captions;
2895
- case "MessageSquare":
2896
- return MessageSquare;
2897
- case "Settings":
2898
- return Settings;
2899
- case "SparklesIcon":
2900
- return Sparkles;
2901
- case "Zap":
2902
- return Zap;
2903
- default:
2904
- return Plus;
2905
- }
2906
- };
2907
- function PropsToolbar({
2908
- selectedElement,
2909
- selectedProp,
2910
- setSelectedProp
2911
- }) {
2912
- const availableSections = react.useMemo(() => {
2913
- const sections = [];
2914
- if (selectedElement instanceof timeline.TextElement) {
2915
- sections.push(propsCategories.get("element-props"));
2916
- sections.push(propsCategories.get("animations"));
2917
- sections.push(propsCategories.get("text-effects"));
2918
- } else if (selectedElement instanceof timeline.ImageElement) {
2919
- sections.push(propsCategories.get("element-props"));
2920
- sections.push(propsCategories.get("animations"));
2921
- sections.push(propsCategories.get("color-effects"));
2922
- } else if (selectedElement instanceof timeline.VideoElement) {
2923
- sections.push(propsCategories.get("element-props"));
2924
- sections.push(propsCategories.get("animations"));
2925
- sections.push(propsCategories.get("color-effects"));
2926
- sections.push(propsCategories.get("playback-props"));
2927
- sections.push(propsCategories.get("generate-subtitles"));
2928
- } else if (selectedElement instanceof timeline.AudioElement) {
2929
- sections.push(propsCategories.get("element-props"));
2930
- sections.push(propsCategories.get("playback-props"));
2931
- } else if (selectedElement instanceof timeline.CircleElement) {
2932
- sections.push(propsCategories.get("element-props"));
2933
- sections.push(propsCategories.get("animations"));
2934
- } else if (selectedElement instanceof timeline.RectElement) {
2935
- sections.push(propsCategories.get("element-props"));
2936
- sections.push(propsCategories.get("animations"));
2937
- } else if (selectedElement instanceof timeline.IconElement) {
2938
- sections.push(propsCategories.get("element-props"));
2939
- sections.push(propsCategories.get("animations"));
2940
- } else if (selectedElement instanceof timeline.CaptionElement) {
2941
- sections.push(propsCategories.get("element-props"));
2942
- sections.push(propsCategories.get("animations"));
2943
- sections.push(propsCategories.get("subtitle-style"));
2944
- }
2945
- return sections;
2946
- }, [selectedElement]);
2947
- react.useEffect(() => {
2948
- if (availableSections == null ? void 0 : availableSections.length) {
2949
- if (availableSections.map((section) => section.id).indexOf(selectedProp) === -1) {
2950
- setSelectedProp(availableSections[0].id);
2951
- }
2952
- }
2953
- }, [availableSections]);
2954
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sidebar", children: availableSections.map((tool) => {
2955
- const Icon2 = getIcon(tool.icon);
2956
- const isSelected = selectedProp === tool.id;
2957
- 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(
2958
2767
  "div",
2959
2768
  {
2960
- onClick: () => setSelectedProp(tool.id),
2961
- className: `toolbar-btn ${isSelected ? "active" : ""}`,
2962
- title: `${tool.name}${tool.shortcut ? ` (${tool.shortcut})` : ""}`,
2769
+ onClick: onToggle,
2770
+ className: "accordion-header",
2963
2771
  children: [
2964
- /* @__PURE__ */ jsxRuntime.jsx(Icon2, { className: "icon-sm" }),
2965
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "props-toolbar-label", children: tool.name })
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" })
2966
2777
  ]
2967
- },
2968
- tool.id
2969
- );
2970
- }) });
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
+ ] });
2971
2788
  }
2972
2789
  function ElementProps({ selectedElement, updateElement }) {
2973
2790
  const opacity = (selectedElement == null ? void 0 : selectedElement.getOpacity()) || 1;
@@ -2991,77 +2808,134 @@ function ElementProps({ selectedElement, updateElement }) {
2991
2808
  updateElement == null ? void 0 : updateElement(selectedElement);
2992
2809
  }
2993
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);
2994
2836
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
2995
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "All Properties" }),
2996
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
2997
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Position" }),
2998
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", children: [
2999
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3000
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-small", children: "X" }),
3001
- /* @__PURE__ */ jsxRuntime.jsx(
3002
- "input",
3003
- {
3004
- type: "number",
3005
- value: position.x ?? 0,
3006
- onChange: (e) => handlePositionChange({ x: Number(e.target.value) }),
3007
- className: "input-dark"
3008
- }
3009
- )
3010
- ] }),
3011
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3012
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-small", children: "Y" }),
3013
- /* @__PURE__ */ jsxRuntime.jsx(
3014
- "input",
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,
3015
2896
  {
3016
- type: "number",
3017
- value: position.y ?? 0,
3018
- onChange: (e) => handlePositionChange({ y: Number(e.target.value) }),
3019
- className: "input-dark"
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
+ )
3020
2913
  }
3021
- )
3022
- ] })
3023
- ] })
3024
- ] }),
3025
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
3026
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Opacity" }),
3027
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "slider-container", children: [
3028
- /* @__PURE__ */ jsxRuntime.jsx(
3029
- "input",
3030
- {
3031
- type: "range",
3032
- min: "0",
3033
- max: "100",
3034
- value: (opacity ?? 1) * 100,
3035
- onChange: (e) => handleOpacityChange(Number(e.target.value) / 100),
3036
- className: "slider-purple"
3037
- }
3038
- ),
3039
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "slider-value", children: [
3040
- Math.round((opacity ?? 1) * 100),
3041
- "%"
3042
- ] })
3043
- ] })
3044
- ] }),
3045
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
3046
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Rotation" }),
3047
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "slider-container", children: [
3048
- /* @__PURE__ */ jsxRuntime.jsx(
3049
- "input",
3050
- {
3051
- type: "range",
3052
- min: "0",
3053
- max: "360",
3054
- value: rotation ?? 0,
3055
- onChange: (e) => handleRotationChange(Number(e.target.value)),
3056
- className: "slider-purple"
3057
- }
3058
- ),
3059
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "slider-value", children: [
3060
- rotation ?? 0,
3061
- "°"
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
+ ) })
3062
2936
  ] })
3063
- ] })
3064
- ] })
2937
+ }
2938
+ )
3065
2939
  ] });
3066
2940
  }
3067
2941
  function TextEffects({
@@ -3092,70 +2966,72 @@ function TextEffects({
3092
2966
  selectedElement.setTextEffect(effect);
3093
2967
  updateElement == null ? void 0 : updateElement(selectedElement);
3094
2968
  };
2969
+ const [isEffectsOpen, setIsEffectsOpen] = react.useState(false);
3095
2970
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
3096
2971
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Text Effects" }),
3097
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
3098
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Text Effect Type" }),
3099
- /* @__PURE__ */ jsxRuntime.jsxs(
3100
- "select",
3101
- {
3102
- value: (currentEffect == null ? void 0 : currentEffect.getName()) || "",
3103
- onChange: (e) => handleUpdateEffect({ name: e.target.value }),
3104
- className: "select-dark w-full",
3105
- children: [
3106
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "No Effect" }),
3107
- VideoEditor.TEXT_EFFECTS.map((effect) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: effect.name, children: effect.name.charAt(0).toUpperCase() + effect.name.slice(1) }, effect.name))
3108
- ]
3109
- }
3110
- )
3111
- ] }),
3112
- currentEffect && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3113
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
3114
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Delay (seconds)" }),
3115
- /* @__PURE__ */ jsxRuntime.jsx(
3116
- "input",
3117
- {
3118
- type: "number",
3119
- min: "0",
3120
- max: "5",
3121
- step: "0.1",
3122
- value: currentEffect.getDelay() ?? 0,
3123
- onChange: (e) => handleUpdateEffect({ delay: Number(e.target.value) }),
3124
- className: "input-dark"
3125
- }
3126
- )
3127
- ] }),
3128
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
3129
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Duration (seconds)" }),
3130
- /* @__PURE__ */ jsxRuntime.jsx(
3131
- "input",
3132
- {
3133
- type: "number",
3134
- min: "0.1",
3135
- max: "10",
3136
- step: "0.1",
3137
- value: currentEffect.getDuration() ?? 1,
3138
- onChange: (e) => handleUpdateEffect({ duration: Number(e.target.value) }),
3139
- className: "input-dark"
3140
- }
3141
- )
3142
- ] }),
3143
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
3144
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Buffer Time (seconds)" }),
3145
- /* @__PURE__ */ jsxRuntime.jsx(
3146
- "input",
3147
- {
3148
- type: "number",
3149
- min: "0.05",
3150
- max: "1",
3151
- step: "0.05",
3152
- value: currentEffect.getBufferTime() ?? 0.1,
3153
- onChange: (e) => handleUpdateEffect({ bufferTime: Number(e.target.value) }),
3154
- className: "input-dark"
3155
- }
3156
- )
3157
- ] })
3158
- ] })
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
+ )
3159
3035
  ] });
3160
3036
  }
3161
3037
  function Animation({
@@ -3343,37 +3219,95 @@ function Animation({
3343
3219
  })() })
3344
3220
  ] });
3345
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;
3346
3237
  function PlaybackPropsPanel({
3347
3238
  selectedElement,
3348
3239
  updateElement
3349
3240
  }) {
3350
3241
  const elementProps = (selectedElement == null ? void 0 : selectedElement.getProps()) || {};
3351
- const { volume } = elementProps;
3242
+ const volumeLinear = elementProps.volume ?? 1;
3243
+ const volumeDb = linearToDb(volumeLinear);
3244
+ const playbackRate = elementProps.playbackRate ?? 1;
3352
3245
  const handleUpdateElement = (props) => {
3353
3246
  if (selectedElement) {
3354
3247
  updateElement == null ? void 0 : updateElement(selectedElement == null ? void 0 : selectedElement.setProps({ ...elementProps, ...props }));
3355
3248
  }
3356
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);
3357
3257
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
3358
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Playback Properties" }),
3359
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
3360
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Volume" }),
3361
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "slider-container", children: [
3362
- /* @__PURE__ */ jsxRuntime.jsx(
3363
- "input",
3364
- {
3365
- type: "range",
3366
- min: "0",
3367
- max: "3",
3368
- step: 0.1,
3369
- value: volume ?? 0,
3370
- onChange: (e) => handleUpdateElement({ volume: Number(e.target.value) }),
3371
- className: "slider-purple"
3372
- }
3373
- ),
3374
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "slider-value", children: volume ?? 0 })
3375
- ] })
3376
- ] })
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
+ )
3377
3311
  ] });
3378
3312
  }
3379
3313
  const hasAudio = async (src) => {
@@ -3462,11 +3396,12 @@ const saveAsFile = (content, type, name) => {
3462
3396
  a.click();
3463
3397
  URL.revokeObjectURL(url);
3464
3398
  };
3465
- function GenerateSubtitlesPanel({
3399
+ function GenerateCaptionsPanel({
3466
3400
  selectedElement,
3467
- addSubtitlesToTimeline,
3468
- onGenerateSubtitles,
3469
- getSubtitleStatus
3401
+ addCaptionsToTimeline,
3402
+ onGenerateCaptions,
3403
+ getCaptionstatus,
3404
+ pollingIntervalMs = 5e3
3470
3405
  }) {
3471
3406
  const [containsAudio, setContainsAudio] = react.useState(null);
3472
3407
  const [isLoading, setIsLoading] = react.useState(true);
@@ -3489,7 +3424,7 @@ function GenerateSubtitlesPanel({
3489
3424
  }
3490
3425
  };
3491
3426
  const startPolling = async (reqId) => {
3492
- if (!getSubtitleStatus) {
3427
+ if (!getCaptionstatus) {
3493
3428
  return;
3494
3429
  }
3495
3430
  setPollingStatus("polling");
@@ -3497,12 +3432,12 @@ function GenerateSubtitlesPanel({
3497
3432
  setErrorMessage(null);
3498
3433
  const poll = async () => {
3499
3434
  try {
3500
- const response = await getSubtitleStatus(reqId);
3435
+ const response = await getCaptionstatus(reqId);
3501
3436
  if (response.status === "completed") {
3502
3437
  stopPolling();
3503
3438
  setPollingStatus("success");
3504
3439
  setIsGenerating(false);
3505
- addSubtitlesToTimeline(response.subtitles || []);
3440
+ addCaptionsToTimeline(response.captions || []);
3506
3441
  setTimeout(() => {
3507
3442
  setPollingStatus("idle");
3508
3443
  }, 3e3);
@@ -3511,32 +3446,34 @@ function GenerateSubtitlesPanel({
3511
3446
  stopPolling();
3512
3447
  setPollingStatus("error");
3513
3448
  setIsGenerating(false);
3514
- setErrorMessage(response.error || "Failed to generate subtitles");
3515
- console.error("Error generating subtitles:", response.error);
3449
+ setErrorMessage(response.error || "Failed to generate captions");
3450
+ console.error("Error generating captions:", response.error);
3516
3451
  }
3517
3452
  } catch (error) {
3518
3453
  stopPolling();
3519
3454
  setPollingStatus("error");
3520
3455
  setIsGenerating(false);
3521
- setErrorMessage(error instanceof Error ? error.message : "Failed to get subtitle status");
3522
- console.error("Error polling for subtitles:", error);
3456
+ setErrorMessage(error instanceof Error ? error.message : "Failed to get caption status");
3457
+ console.error("Error polling for captions:", error);
3523
3458
  }
3524
3459
  };
3525
3460
  await poll();
3526
- pollingIntervalRef.current = setInterval(poll, 2e3);
3461
+ pollingIntervalRef.current = setInterval(poll, pollingIntervalMs);
3527
3462
  };
3528
- const handleGenerateSubtitles = async () => {
3463
+ const handleGenerateCaptions = async () => {
3529
3464
  if (!(selectedElement instanceof timeline.VideoElement)) {
3530
3465
  return;
3531
3466
  }
3467
+ setIsGenerating(true);
3468
+ setPollingStatus("polling");
3532
3469
  const videoElement = selectedElement;
3533
3470
  try {
3534
- const reqId = await onGenerateSubtitles(videoElement);
3471
+ const reqId = await onGenerateCaptions(videoElement);
3535
3472
  if (!reqId) {
3536
3473
  setPollingStatus("error");
3537
3474
  setIsGenerating(false);
3538
- setErrorMessage("Failed to start subtitle generation");
3539
- console.error("Error generating subtitles: Failed to start subtitle generation");
3475
+ setErrorMessage("Failed to start caption generation");
3476
+ console.error("Error generating captions: Failed to start caption generation");
3540
3477
  return;
3541
3478
  }
3542
3479
  currentReqIdRef.current = reqId;
@@ -3544,8 +3481,8 @@ function GenerateSubtitlesPanel({
3544
3481
  } catch (error) {
3545
3482
  setPollingStatus("error");
3546
3483
  setIsGenerating(false);
3547
- setErrorMessage(error instanceof Error ? error.message : "Failed to start subtitle generation");
3548
- console.error("Error generating subtitles:", error);
3484
+ setErrorMessage(error instanceof Error ? error.message : "Failed to start caption generation");
3485
+ console.error("Error generating captions:", error);
3549
3486
  }
3550
3487
  };
3551
3488
  const checkAudio = async () => {
@@ -3577,7 +3514,7 @@ function GenerateSubtitlesPanel({
3577
3514
  setErrorMessage(null);
3578
3515
  }, [selectedElement]);
3579
3516
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
3580
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Generate Subtitles Panel" }),
3517
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Generate Captions Panel" }),
3581
3518
  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: [
3582
3519
  /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
3583
3520
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Checking for audio..." })
@@ -3588,85 +3525,230 @@ function GenerateSubtitlesPanel({
3588
3525
  ] }) }) }),
3589
3526
  !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: [
3590
3527
  /* @__PURE__ */ jsxRuntime.jsx(Volume2, { className: "empty-state-icon" }),
3591
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Audio detected! You can now generate subtitles" })
3528
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Audio detected! You can now generate captions" })
3592
3529
  ] }) }) }),
3593
3530
  !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: [
3594
3531
  /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
3595
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Generating subtitles... Please wait" })
3532
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Generating captions... Please wait" })
3596
3533
  ] }) }) }),
3597
3534
  !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: [
3598
3535
  /* @__PURE__ */ jsxRuntime.jsx(CircleCheck, { className: "empty-state-icon", color: "var(--color-green-500)" }),
3599
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Subtitles generated successfully!" })
3536
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Captions generated successfully!" })
3600
3537
  ] }) }) }),
3601
3538
  !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: [
3602
3539
  /* @__PURE__ */ jsxRuntime.jsx(CircleX, { className: "empty-state-icon", color: "var(--color-red-500)" }),
3603
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: errorMessage || "Failed to generate subtitles" })
3540
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: errorMessage || "Failed to generate captions" })
3604
3541
  ] }) }) }),
3605
3542
  !isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
3606
3543
  "button",
3607
3544
  {
3608
- onClick: handleGenerateSubtitles,
3545
+ onClick: handleGenerateCaptions,
3609
3546
  disabled: !containsAudio || isGenerating,
3610
3547
  className: "btn-primary w-full",
3611
- children: isGenerating ? "Generating..." : "Generate Subtitles"
3548
+ children: isGenerating ? "Generating..." : "Generate Captions"
3612
3549
  }
3613
3550
  ) })
3614
3551
  ] });
3615
3552
  }
3616
- function PropertiesPanelContainer({
3617
- selectedProp,
3553
+ function TextPropsPanel({
3618
3554
  selectedElement,
3619
- updateElement,
3620
- addSubtitlesToTimeline,
3621
- onGenerateSubtitles,
3622
- getSubtitleStatus
3555
+ updateElement
3623
3556
  }) {
3624
- if (!selectedElement) {
3625
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "properties-header", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: "Select Element to see properties" }) }) });
3626
- }
3627
- if (selectedElement.getType() === "caption") {
3628
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "properties-header", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: "Not available for sub-title" }) }) });
3629
- }
3630
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3631
- selectedProp === "element-props" && /* @__PURE__ */ jsxRuntime.jsx(
3632
- ElementProps,
3633
- {
3634
- selectedElement,
3635
- updateElement
3636
- }
3637
- ),
3638
- selectedProp === "playback-props" && /* @__PURE__ */ jsxRuntime.jsx(
3639
- PlaybackPropsPanel,
3640
- {
3641
- selectedElement,
3642
- updateElement
3643
- }
3644
- ),
3645
- selectedProp === "text-effects" && /* @__PURE__ */ jsxRuntime.jsx(
3646
- TextEffects,
3647
- {
3648
- selectedElement,
3649
- updateElement
3650
- }
3651
- ),
3652
- selectedProp === "animations" && /* @__PURE__ */ jsxRuntime.jsx(
3653
- Animation,
3654
- {
3655
- selectedElement,
3656
- updateElement
3657
- }
3658
- ),
3659
- selectedProp === "generate-subtitles" && /* @__PURE__ */ jsxRuntime.jsx(
3660
- GenerateSubtitlesPanel,
3557
+ if (!(selectedElement instanceof timeline.TextElement)) return null;
3558
+ const textProps = selectedElement.getProps() || {};
3559
+ const [isTypographyOpen, setIsTypographyOpen] = react.useState(false);
3560
+ const currentAlign = textProps.textAlign ?? "center";
3561
+ const currentWeight = textProps.fontWeight ?? 400;
3562
+ const isBold = currentWeight >= 600;
3563
+ const isItalic = textProps.fontStyle === "italic";
3564
+ const handleUpdate = (patch) => {
3565
+ if (!selectedElement) return;
3566
+ const next = { ...textProps, ...patch };
3567
+ selectedElement.setProps(next);
3568
+ updateElement == null ? void 0 : updateElement(selectedElement);
3569
+ };
3570
+ const toggleBold = () => {
3571
+ handleUpdate({ fontWeight: isBold ? 400 : 700 });
3572
+ };
3573
+ const toggleItalic = () => {
3574
+ handleUpdate({ fontStyle: isItalic ? "normal" : "italic" });
3575
+ };
3576
+ const setAlign = (align) => {
3577
+ handleUpdate({ textAlign: align });
3578
+ };
3579
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
3580
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Typography" }),
3581
+ /* @__PURE__ */ jsxRuntime.jsx(
3582
+ AccordionItem,
3661
3583
  {
3662
- selectedElement,
3663
- addSubtitlesToTimeline,
3664
- onGenerateSubtitles,
3665
- getSubtitleStatus
3584
+ title: "Typography",
3585
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Type, { className: "icon-sm" }),
3586
+ isOpen: isTypographyOpen,
3587
+ onToggle: () => setIsTypographyOpen((open) => !open),
3588
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "properties-group", children: [
3589
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(
3590
+ PropertyRow,
3591
+ {
3592
+ label: "Font size",
3593
+ secondary: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3594
+ textProps.fontSize ?? 48,
3595
+ "px"
3596
+ ] }),
3597
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3598
+ "input",
3599
+ {
3600
+ type: "range",
3601
+ min: 8,
3602
+ max: 160,
3603
+ value: textProps.fontSize ?? 48,
3604
+ onChange: (e) => handleUpdate({ fontSize: Number(e.target.value) }),
3605
+ className: "slider-purple"
3606
+ }
3607
+ )
3608
+ }
3609
+ ) }),
3610
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsxs(PropertyRow, { label: "Style", children: [
3611
+ /* @__PURE__ */ jsxRuntime.jsx(
3612
+ "button",
3613
+ {
3614
+ type: "button",
3615
+ className: `form-btn ${isBold ? "active" : ""}`,
3616
+ onClick: toggleBold,
3617
+ title: "Bold",
3618
+ children: /* @__PURE__ */ jsxRuntime.jsx(Bold, { className: "icon-sm" })
3619
+ }
3620
+ ),
3621
+ /* @__PURE__ */ jsxRuntime.jsx(
3622
+ "button",
3623
+ {
3624
+ type: "button",
3625
+ className: `form-btn ${isItalic ? "active" : ""}`,
3626
+ onClick: toggleItalic,
3627
+ title: "Italic",
3628
+ children: /* @__PURE__ */ jsxRuntime.jsx(Italic, { className: "icon-sm" })
3629
+ }
3630
+ )
3631
+ ] }) }),
3632
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsxs(PropertyRow, { label: "Align", children: [
3633
+ /* @__PURE__ */ jsxRuntime.jsx(
3634
+ "button",
3635
+ {
3636
+ type: "button",
3637
+ className: `form-btn ${currentAlign === "left" ? "active" : ""}`,
3638
+ onClick: () => setAlign("left"),
3639
+ title: "Align left",
3640
+ children: /* @__PURE__ */ jsxRuntime.jsx(AlignLeft, { className: "icon-sm" })
3641
+ }
3642
+ ),
3643
+ /* @__PURE__ */ jsxRuntime.jsx(
3644
+ "button",
3645
+ {
3646
+ type: "button",
3647
+ className: `form-btn ${currentAlign === "center" ? "active" : ""}`,
3648
+ onClick: () => setAlign("center"),
3649
+ title: "Align center",
3650
+ children: /* @__PURE__ */ jsxRuntime.jsx(AlignCenter, { className: "icon-sm" })
3651
+ }
3652
+ ),
3653
+ /* @__PURE__ */ jsxRuntime.jsx(
3654
+ "button",
3655
+ {
3656
+ type: "button",
3657
+ className: `form-btn ${currentAlign === "right" ? "active" : ""}`,
3658
+ onClick: () => setAlign("right"),
3659
+ title: "Align right",
3660
+ children: /* @__PURE__ */ jsxRuntime.jsx(AlignRight, { className: "icon-sm" })
3661
+ }
3662
+ )
3663
+ ] }) })
3664
+ ] })
3666
3665
  }
3667
3666
  )
3668
3667
  ] });
3669
3668
  }
3669
+ function PropertiesPanelContainer({
3670
+ selectedElement,
3671
+ updateElement,
3672
+ addCaptionsToTimeline,
3673
+ onGenerateCaptions,
3674
+ getCaptionstatus,
3675
+ pollingIntervalMs,
3676
+ videoResolution
3677
+ }) {
3678
+ const title = selectedElement instanceof timeline.TextElement ? selectedElement.getText() : (selectedElement == null ? void 0 : selectedElement.getName()) || (selectedElement == null ? void 0 : selectedElement.getType()) || "Element";
3679
+ return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "properties-panel", "aria-label": "Element properties inspector", children: [
3680
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "properties-header", children: [
3681
+ !selectedElement && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: "Composition" }),
3682
+ selectedElement && selectedElement.getType() === "caption" && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: "Subtitles are edited from the captions panel" }),
3683
+ selectedElement && selectedElement.getType() !== "caption" && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: title })
3684
+ ] }),
3685
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "prop-content", children: [
3686
+ !selectedElement && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
3687
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Canvas & Render" }),
3688
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "properties-group", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "property-section", children: [
3689
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "property-label", children: "Size" }),
3690
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "properties-size-readonly", children: [
3691
+ videoResolution.width,
3692
+ " × ",
3693
+ videoResolution.height
3694
+ ] })
3695
+ ] }) })
3696
+ ] }),
3697
+ selectedElement && selectedElement.getType() === "caption" ? null : selectedElement && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: (() => {
3698
+ const isText = selectedElement instanceof timeline.TextElement;
3699
+ const isVideo = selectedElement instanceof timeline.VideoElement;
3700
+ const isAudio = selectedElement instanceof timeline.AudioElement;
3701
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3702
+ isText && /* @__PURE__ */ jsxRuntime.jsx(
3703
+ TextPropsPanel,
3704
+ {
3705
+ selectedElement,
3706
+ updateElement
3707
+ }
3708
+ ),
3709
+ !isAudio && /* @__PURE__ */ jsxRuntime.jsx(
3710
+ ElementProps,
3711
+ {
3712
+ selectedElement,
3713
+ updateElement
3714
+ }
3715
+ ),
3716
+ (isVideo || isAudio) && /* @__PURE__ */ jsxRuntime.jsx(
3717
+ PlaybackPropsPanel,
3718
+ {
3719
+ selectedElement,
3720
+ updateElement
3721
+ }
3722
+ ),
3723
+ isText && /* @__PURE__ */ jsxRuntime.jsx(
3724
+ TextEffects,
3725
+ {
3726
+ selectedElement,
3727
+ updateElement
3728
+ }
3729
+ ),
3730
+ !isAudio && /* @__PURE__ */ jsxRuntime.jsx(
3731
+ Animation,
3732
+ {
3733
+ selectedElement,
3734
+ updateElement
3735
+ }
3736
+ ),
3737
+ isVideo && /* @__PURE__ */ jsxRuntime.jsx(
3738
+ GenerateCaptionsPanel,
3739
+ {
3740
+ selectedElement,
3741
+ addCaptionsToTimeline,
3742
+ onGenerateCaptions,
3743
+ getCaptionstatus,
3744
+ pollingIntervalMs
3745
+ }
3746
+ )
3747
+ ] });
3748
+ })() })
3749
+ ] })
3750
+ ] });
3751
+ }
3670
3752
  const useStudioOperation = (studioConfig) => {
3671
3753
  const { editor, present, videoResolution } = timeline.useTimelineContext();
3672
3754
  const { setSeekTime, setPlayerState } = livePlayer.useLivePlayerContext();
@@ -3729,30 +3811,30 @@ const useStudioOperation = (studioConfig) => {
3729
3811
  alert("Export video not supported in demo mode");
3730
3812
  }
3731
3813
  };
3732
- const onGenerateSubtitles = async (videoElement) => {
3733
- if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3734
- const service = studioConfig.subtitleGenerationService;
3735
- const reqId = await service.generateSubtitles(videoElement, present);
3814
+ const onGenerateCaptions = async (videoElement) => {
3815
+ if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
3816
+ const service = studioConfig.captionGenerationService;
3817
+ const reqId = await service.generateCaptions(videoElement, present);
3736
3818
  return reqId;
3737
3819
  }
3738
- alert("Generate subtitles not supported in demo mode");
3820
+ alert("Generate captions not supported in demo mode");
3739
3821
  return null;
3740
3822
  };
3741
- const addSubtitlesToTimeline = (subtitles) => {
3823
+ const addCaptionsToTimeline = (captions) => {
3742
3824
  var _a;
3743
- const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) == null ? void 0 : _a.updateProjectWithSubtitles(subtitles);
3825
+ const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.captionGenerationService) == null ? void 0 : _a.updateProjectWithCaptions(captions);
3744
3826
  if (updatedProjectJSON) {
3745
3827
  editor.loadProject(updatedProjectJSON);
3746
3828
  }
3747
3829
  };
3748
- const getSubtitleStatus = async (reqId) => {
3749
- if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3750
- const service = studioConfig.subtitleGenerationService;
3830
+ const getCaptionstatus = async (reqId) => {
3831
+ if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
3832
+ const service = studioConfig.captionGenerationService;
3751
3833
  return await service.getRequestStatus(reqId);
3752
3834
  }
3753
3835
  return {
3754
3836
  status: "failed",
3755
- error: "Subtitle generation service not found"
3837
+ error: "Caption generation service not found"
3756
3838
  };
3757
3839
  };
3758
3840
  return {
@@ -3760,48 +3842,9 @@ const useStudioOperation = (studioConfig) => {
3760
3842
  onSaveProject,
3761
3843
  onExportVideo,
3762
3844
  onNewProject,
3763
- onGenerateSubtitles,
3764
- addSubtitlesToTimeline,
3765
- getSubtitleStatus
3766
- };
3767
- };
3768
- const useGenerateSubtitles = (studioConfig) => {
3769
- const { editor, present } = timeline.useTimelineContext();
3770
- const onGenerateSubtitles = async (videoElement) => {
3771
- if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3772
- const service = studioConfig.subtitleGenerationService;
3773
- const reqId = await service.generateSubtitles(
3774
- videoElement,
3775
- present
3776
- );
3777
- return reqId;
3778
- }
3779
- alert("Generate subtitles not supported in demo mode");
3780
- return null;
3781
- };
3782
- const addSubtitlesToTimeline = (subtitles) => {
3783
- var _a;
3784
- const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) == null ? void 0 : _a.updateProjectWithSubtitles(
3785
- subtitles
3786
- );
3787
- if (updatedProjectJSON) {
3788
- editor.loadProject(updatedProjectJSON);
3789
- }
3790
- };
3791
- const getSubtitleStatus = async (reqId) => {
3792
- if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3793
- const service = studioConfig.subtitleGenerationService;
3794
- return await service.getRequestStatus(reqId);
3795
- }
3796
- return {
3797
- status: "failed",
3798
- error: "Subtitle generation service not found"
3799
- };
3800
- };
3801
- return {
3802
- onGenerateSubtitles,
3803
- addSubtitlesToTimeline,
3804
- getSubtitleStatus
3845
+ onGenerateCaptions,
3846
+ addCaptionsToTimeline,
3847
+ getCaptionstatus
3805
3848
  };
3806
3849
  };
3807
3850
  function TwickStudio({ studioConfig }) {
@@ -3809,8 +3852,6 @@ function TwickStudio({ studioConfig }) {
3809
3852
  const {
3810
3853
  selectedTool,
3811
3854
  setSelectedTool,
3812
- selectedProp,
3813
- setSelectedProp,
3814
3855
  selectedElement,
3815
3856
  addElement,
3816
3857
  updateElement
@@ -3822,7 +3863,12 @@ function TwickStudio({ studioConfig }) {
3822
3863
  onSaveProject,
3823
3864
  onExportVideo
3824
3865
  } = useStudioOperation(studioConfig);
3825
- const { onGenerateSubtitles, addSubtitlesToTimeline, getSubtitleStatus } = useGenerateSubtitles(studioConfig);
3866
+ const {
3867
+ onGenerateCaptions,
3868
+ addCaptionsToTimeline,
3869
+ getCaptionstatus,
3870
+ pollingIntervalMs
3871
+ } = useGenerateCaptions(studioConfig);
3826
3872
  const twickStudiConfig = react.useMemo(
3827
3873
  () => ({
3828
3874
  canvasMode: true,
@@ -3854,7 +3900,7 @@ function TwickStudio({ studioConfig }) {
3854
3900
  setSelectedTool
3855
3901
  }
3856
3902
  ),
3857
- /* @__PURE__ */ jsxRuntime.jsx(
3903
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "studio-left-panel", children: /* @__PURE__ */ jsxRuntime.jsx(
3858
3904
  ElementPanelContainer,
3859
3905
  {
3860
3906
  videoResolution,
@@ -3864,39 +3910,74 @@ function TwickStudio({ studioConfig }) {
3864
3910
  addElement,
3865
3911
  updateElement
3866
3912
  }
3867
- ),
3913
+ ) }),
3868
3914
  /* @__PURE__ */ jsxRuntime.jsx("main", { className: "main-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-wrapper", children: /* @__PURE__ */ jsxRuntime.jsx(
3869
3915
  "div",
3870
3916
  {
3871
3917
  className: "canvas-container",
3872
3918
  style: {
3873
- maxWidth: ((_a = twickStudiConfig.playerProps) == null ? void 0 : _a.maxWidth) ?? 960
3919
+ maxWidth: ((_a = twickStudiConfig.playerProps) == null ? void 0 : _a.maxWidth) ?? "100%"
3874
3920
  },
3875
3921
  children: /* @__PURE__ */ jsxRuntime.jsx(VideoEditor, { editorConfig: twickStudiConfig })
3876
3922
  }
3877
3923
  ) }) }),
3878
- /* @__PURE__ */ jsxRuntime.jsx(
3924
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "studio-right-panel", children: /* @__PURE__ */ jsxRuntime.jsx(
3879
3925
  PropertiesPanelContainer,
3880
3926
  {
3881
- selectedProp,
3882
3927
  selectedElement,
3883
3928
  updateElement,
3884
- addSubtitlesToTimeline,
3885
- onGenerateSubtitles,
3886
- getSubtitleStatus
3929
+ addCaptionsToTimeline,
3930
+ onGenerateCaptions,
3931
+ getCaptionstatus,
3932
+ pollingIntervalMs,
3933
+ videoResolution
3887
3934
  }
3888
- ),
3889
- /* @__PURE__ */ jsxRuntime.jsx(
3890
- PropsToolbar,
3891
- {
3892
- selectedElement,
3893
- selectedProp,
3894
- setSelectedProp
3895
- }
3896
- )
3935
+ ) })
3897
3936
  ] })
3898
3937
  ] }) });
3899
3938
  }
3939
+ const useGenerateCaptions = (studioConfig) => {
3940
+ var _a;
3941
+ const { editor, present } = timeline.useTimelineContext();
3942
+ const onGenerateCaptions = async (videoElement) => {
3943
+ if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
3944
+ const service = studioConfig.captionGenerationService;
3945
+ const reqId = await service.generateCaptions(
3946
+ videoElement,
3947
+ present
3948
+ );
3949
+ return reqId;
3950
+ }
3951
+ alert("Generate captions not supported in demo mode");
3952
+ return null;
3953
+ };
3954
+ const addCaptionsToTimeline = (captions) => {
3955
+ var _a2;
3956
+ const updatedProjectJSON = (_a2 = studioConfig == null ? void 0 : studioConfig.captionGenerationService) == null ? void 0 : _a2.updateProjectWithCaptions(
3957
+ captions
3958
+ );
3959
+ if (updatedProjectJSON) {
3960
+ editor.loadProject(updatedProjectJSON);
3961
+ }
3962
+ };
3963
+ const getCaptionstatus = async (reqId) => {
3964
+ if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
3965
+ const service = studioConfig.captionGenerationService;
3966
+ return await service.getRequestStatus(reqId);
3967
+ }
3968
+ return {
3969
+ status: "failed",
3970
+ error: "Caption generation service not found"
3971
+ };
3972
+ };
3973
+ const pollingIntervalMs = ((_a = studioConfig == null ? void 0 : studioConfig.captionGenerationService) == null ? void 0 : _a.pollingIntervalMs) ?? 5e3;
3974
+ return {
3975
+ onGenerateCaptions,
3976
+ addCaptionsToTimeline,
3977
+ getCaptionstatus,
3978
+ pollingIntervalMs
3979
+ };
3980
+ };
3900
3981
  Object.defineProperty(exports, "AudioElement", {
3901
3982
  enumerable: true,
3902
3983
  get: () => timeline.AudioElement
@@ -4045,6 +4126,10 @@ Object.defineProperty(exports, "isTrackId", {
4045
4126
  enumerable: true,
4046
4127
  get: () => timeline.isTrackId
4047
4128
  });
4129
+ Object.defineProperty(exports, "useTimelineContext", {
4130
+ enumerable: true,
4131
+ get: () => timeline.useTimelineContext
4132
+ });
4048
4133
  Object.defineProperty(exports, "ANIMATIONS", {
4049
4134
  enumerable: true,
4050
4135
  get: () => VideoEditor.ANIMATIONS
@@ -4120,17 +4205,16 @@ Object.defineProperty(exports, "useLivePlayerContext", {
4120
4205
  });
4121
4206
  exports.AudioPanel = AudioPanel;
4122
4207
  exports.CAPTION_PROPS = CAPTION_PROPS;
4208
+ exports.CaptionsPanel = CaptionsPanel;
4123
4209
  exports.CirclePanel = CirclePanel;
4124
- exports.IconPanel = IconPanel;
4125
4210
  exports.ImagePanel = ImagePanel;
4126
4211
  exports.RectPanel = RectPanel;
4127
4212
  exports.StudioHeader = StudioHeader;
4128
- exports.SubtitlesPanel = SubtitlesPanel;
4129
4213
  exports.TextPanel = TextPanel;
4130
4214
  exports.Toolbar = Toolbar;
4131
4215
  exports.TwickStudio = TwickStudio;
4132
4216
  exports.VideoPanel = VideoPanel;
4133
4217
  exports.default = TwickStudio;
4134
- exports.useGenerateSubtitles = useGenerateSubtitles;
4218
+ exports.useGenerateCaptions = useGenerateCaptions;
4135
4219
  exports.useStudioManager = useStudioManager;
4136
4220
  //# sourceMappingURL=index.js.map