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