@twick/studio 0.14.5

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 (56) hide show
  1. package/README.md +122 -0
  2. package/dist/components/container/audio-panel-container.d.ts +3 -0
  3. package/dist/components/container/circle-panel-container.d.ts +3 -0
  4. package/dist/components/container/element-panel-container.d.ts +12 -0
  5. package/dist/components/container/icon-panel-container.d.ts +3 -0
  6. package/dist/components/container/image-panel-container.d.ts +3 -0
  7. package/dist/components/container/properties-panel-container.d.ts +9 -0
  8. package/dist/components/container/rect-panel-container.d.ts +3 -0
  9. package/dist/components/container/text-panel-container.d.ts +9 -0
  10. package/dist/components/container/video-panel-container.d.ts +3 -0
  11. package/dist/components/header.d.ts +10 -0
  12. package/dist/components/panel/audio-panel.d.ts +3 -0
  13. package/dist/components/panel/circle-panel.d.ts +4 -0
  14. package/dist/components/panel/icon-panel.d.ts +4 -0
  15. package/dist/components/panel/image-panel.d.ts +3 -0
  16. package/dist/components/panel/rect-panel.d.ts +4 -0
  17. package/dist/components/panel/subtitles-panel.d.ts +27 -0
  18. package/dist/components/panel/text-panel.d.ts +4 -0
  19. package/dist/components/panel/video-panel.d.ts +3 -0
  20. package/dist/components/properties/animation.d.ts +3 -0
  21. package/dist/components/properties/element-props.d.ts +3 -0
  22. package/dist/components/properties/playback-props.d.ts +3 -0
  23. package/dist/components/properties/subtitlte-prop.d.ts +35 -0
  24. package/dist/components/properties/text-effects.d.ts +3 -0
  25. package/dist/components/props-toolbar.d.ts +7 -0
  26. package/dist/components/shared/accordion-item.d.ts +9 -0
  27. package/dist/components/shared/color-input.d.ts +5 -0
  28. package/dist/components/shared/file-input.d.ts +7 -0
  29. package/dist/components/shared/index.d.ts +3 -0
  30. package/dist/components/shared/media-manager.d.ts +3 -0
  31. package/dist/components/shared/prop-container.d.ts +7 -0
  32. package/dist/components/shared/search-input.d.ts +5 -0
  33. package/dist/components/toolbar.d.ts +23 -0
  34. package/dist/components/twick-studio.d.ts +5 -0
  35. package/dist/context/media-context.d.ts +15 -0
  36. package/dist/context/video-panel-context.d.ts +15 -0
  37. package/dist/hooks/use-audio-preview.d.ts +11 -0
  38. package/dist/hooks/use-circle-panel.d.ts +29 -0
  39. package/dist/hooks/use-icon-panel.d.ts +24 -0
  40. package/dist/hooks/use-media-panel.d.ts +23 -0
  41. package/dist/hooks/use-rect-panel.d.ts +29 -0
  42. package/dist/hooks/use-studio-manager.d.ts +11 -0
  43. package/dist/hooks/use-studio-operation.d.ts +8 -0
  44. package/dist/hooks/use-text-panel.d.ts +50 -0
  45. package/dist/hooks/use-video-preview.d.ts +11 -0
  46. package/dist/index.d.ts +41 -0
  47. package/dist/index.js +3254 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/index.mjs +3254 -0
  50. package/dist/index.mjs.map +1 -0
  51. package/dist/studio.css +284 -0
  52. package/dist/styles/input-styles.d.ts +39 -0
  53. package/dist/twick.svg +3 -0
  54. package/dist/types/index.d.ts +134 -0
  55. package/dist/types/media-panel.d.ts +21 -0
  56. package/package.json +50 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,3254 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
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, CaptionElement, ElementTextEffect, ElementAnimation } from "@twick/timeline";
7
+ import VideoEditor, { BrowserMediaManager, AVAILABLE_TEXT_FONTS, TEXT_EFFECTS, ANIMATIONS } from "@twick/video-editor";
8
+ /**
9
+ * @license lucide-react v0.511.0 - ISC
10
+ *
11
+ * This source code is licensed under the ISC license.
12
+ * See the LICENSE file in the root directory of this source tree.
13
+ */
14
+ const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
15
+ const toCamelCase = (string) => string.replace(
16
+ /^([A-Z])|[\s-_]+(\w)/g,
17
+ (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()
18
+ );
19
+ const toPascalCase = (string) => {
20
+ const camelCase = toCamelCase(string);
21
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
22
+ };
23
+ const mergeClasses = (...classes) => classes.filter((className, index, array) => {
24
+ return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
25
+ }).join(" ").trim();
26
+ const hasA11yProp = (props) => {
27
+ for (const prop in props) {
28
+ if (prop.startsWith("aria-") || prop === "role" || prop === "title") {
29
+ return true;
30
+ }
31
+ }
32
+ };
33
+ /**
34
+ * @license lucide-react v0.511.0 - ISC
35
+ *
36
+ * This source code is licensed under the ISC license.
37
+ * See the LICENSE file in the root directory of this source tree.
38
+ */
39
+ var defaultAttributes = {
40
+ xmlns: "http://www.w3.org/2000/svg",
41
+ width: 24,
42
+ height: 24,
43
+ viewBox: "0 0 24 24",
44
+ fill: "none",
45
+ stroke: "currentColor",
46
+ strokeWidth: 2,
47
+ strokeLinecap: "round",
48
+ strokeLinejoin: "round"
49
+ };
50
+ /**
51
+ * @license lucide-react v0.511.0 - ISC
52
+ *
53
+ * This source code is licensed under the ISC license.
54
+ * See the LICENSE file in the root directory of this source tree.
55
+ */
56
+ const Icon = forwardRef(
57
+ ({
58
+ color = "currentColor",
59
+ size = 24,
60
+ strokeWidth = 2,
61
+ absoluteStrokeWidth,
62
+ className = "",
63
+ children,
64
+ iconNode,
65
+ ...rest
66
+ }, ref) => createElement(
67
+ "svg",
68
+ {
69
+ ref,
70
+ ...defaultAttributes,
71
+ width: size,
72
+ height: size,
73
+ stroke: color,
74
+ strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
75
+ className: mergeClasses("lucide", className),
76
+ ...!children && !hasA11yProp(rest) && { "aria-hidden": "true" },
77
+ ...rest
78
+ },
79
+ [
80
+ ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
81
+ ...Array.isArray(children) ? children : [children]
82
+ ]
83
+ )
84
+ );
85
+ /**
86
+ * @license lucide-react v0.511.0 - ISC
87
+ *
88
+ * This source code is licensed under the ISC license.
89
+ * See the LICENSE file in the root directory of this source tree.
90
+ */
91
+ const createLucideIcon = (iconName, iconNode) => {
92
+ const Component = forwardRef(
93
+ ({ className, ...props }, ref) => createElement(Icon, {
94
+ ref,
95
+ iconNode,
96
+ className: mergeClasses(
97
+ `lucide-${toKebabCase(toPascalCase(iconName))}`,
98
+ `lucide-${iconName}`,
99
+ className
100
+ ),
101
+ ...props
102
+ })
103
+ );
104
+ Component.displayName = toPascalCase(iconName);
105
+ return Component;
106
+ };
107
+ /**
108
+ * @license lucide-react v0.511.0 - ISC
109
+ *
110
+ * This source code is licensed under the ISC license.
111
+ * See the LICENSE file in the root directory of this source tree.
112
+ */
113
+ const __iconNode$o = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
114
+ const Check = createLucideIcon("check", __iconNode$o);
115
+ /**
116
+ * @license lucide-react v0.511.0 - ISC
117
+ *
118
+ * This source code is licensed under the ISC license.
119
+ * See the LICENSE file in the root directory of this source tree.
120
+ */
121
+ const __iconNode$n = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
122
+ const Circle = createLucideIcon("circle", __iconNode$n);
123
+ /**
124
+ * @license lucide-react v0.511.0 - ISC
125
+ *
126
+ * This source code is licensed under the ISC license.
127
+ * See the LICENSE file in the root directory of this source tree.
128
+ */
129
+ const __iconNode$m = [
130
+ [
131
+ "path",
132
+ { 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" }
133
+ ],
134
+ ["path", { d: "m6.2 5.3 3.1 3.9", key: "iuk76l" }],
135
+ ["path", { d: "m12.4 3.4 3.1 4", key: "6hsd6n" }],
136
+ ["path", { d: "M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z", key: "ltgou9" }]
137
+ ];
138
+ const Clapperboard = createLucideIcon("clapperboard", __iconNode$m);
139
+ /**
140
+ * @license lucide-react v0.511.0 - ISC
141
+ *
142
+ * This source code is licensed under the ISC license.
143
+ * See the LICENSE file in the root directory of this source tree.
144
+ */
145
+ const __iconNode$l = [
146
+ ["path", { d: "M12 15V3", key: "m9g1x1" }],
147
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
148
+ ["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]
149
+ ];
150
+ const Download = createLucideIcon("download", __iconNode$l);
151
+ /**
152
+ * @license lucide-react v0.511.0 - ISC
153
+ *
154
+ * This source code is licensed under the ISC license.
155
+ * See the LICENSE file in the root directory of this source tree.
156
+ */
157
+ const __iconNode$k = [
158
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
159
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }]
160
+ ];
161
+ const File = createLucideIcon("file", __iconNode$k);
162
+ /**
163
+ * @license lucide-react v0.511.0 - ISC
164
+ *
165
+ * This source code is licensed under the ISC license.
166
+ * See the LICENSE file in the root directory of this source tree.
167
+ */
168
+ const __iconNode$j = [
169
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
170
+ ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
171
+ ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
172
+ ];
173
+ const Image = createLucideIcon("image", __iconNode$j);
174
+ /**
175
+ * @license lucide-react v0.511.0 - ISC
176
+ *
177
+ * This source code is licensed under the ISC license.
178
+ * See the LICENSE file in the root directory of this source tree.
179
+ */
180
+ const __iconNode$i = [
181
+ ["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" }]
182
+ ];
183
+ const Infinity = createLucideIcon("infinity", __iconNode$i);
184
+ /**
185
+ * @license lucide-react v0.511.0 - ISC
186
+ *
187
+ * This source code is licensed under the ISC license.
188
+ * See the LICENSE file in the root directory of this source tree.
189
+ */
190
+ const __iconNode$h = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
191
+ const LoaderCircle = createLucideIcon("loader-circle", __iconNode$h);
192
+ /**
193
+ * @license lucide-react v0.511.0 - ISC
194
+ *
195
+ * This source code is licensed under the ISC license.
196
+ * See the LICENSE file in the root directory of this source tree.
197
+ */
198
+ const __iconNode$g = [
199
+ ["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" }]
200
+ ];
201
+ const MessageSquare = createLucideIcon("message-square", __iconNode$g);
202
+ /**
203
+ * @license lucide-react v0.511.0 - ISC
204
+ *
205
+ * This source code is licensed under the ISC license.
206
+ * See the LICENSE file in the root directory of this source tree.
207
+ */
208
+ const __iconNode$f = [
209
+ ["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
210
+ ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
211
+ ["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
212
+ ];
213
+ const Music = createLucideIcon("music", __iconNode$f);
214
+ /**
215
+ * @license lucide-react v0.511.0 - ISC
216
+ *
217
+ * This source code is licensed under the ISC license.
218
+ * See the LICENSE file in the root directory of this source tree.
219
+ */
220
+ const __iconNode$e = [
221
+ ["rect", { x: "14", y: "4", width: "4", height: "16", rx: "1", key: "zuxfzm" }],
222
+ ["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
223
+ ];
224
+ const Pause = createLucideIcon("pause", __iconNode$e);
225
+ /**
226
+ * @license lucide-react v0.511.0 - ISC
227
+ *
228
+ * This source code is licensed under the ISC license.
229
+ * See the LICENSE file in the root directory of this source tree.
230
+ */
231
+ const __iconNode$d = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
232
+ const Play = createLucideIcon("play", __iconNode$d);
233
+ /**
234
+ * @license lucide-react v0.511.0 - ISC
235
+ *
236
+ * This source code is licensed under the ISC license.
237
+ * See the LICENSE file in the root directory of this source tree.
238
+ */
239
+ const __iconNode$c = [
240
+ ["path", { d: "M5 12h14", key: "1ays0h" }],
241
+ ["path", { d: "M12 5v14", key: "s699le" }]
242
+ ];
243
+ const Plus = createLucideIcon("plus", __iconNode$c);
244
+ /**
245
+ * @license lucide-react v0.511.0 - ISC
246
+ *
247
+ * This source code is licensed under the ISC license.
248
+ * See the LICENSE file in the root directory of this source tree.
249
+ */
250
+ const __iconNode$b = [
251
+ [
252
+ "path",
253
+ {
254
+ d: "M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z",
255
+ key: "1c8476"
256
+ }
257
+ ],
258
+ ["path", { d: "M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7", key: "1ydtos" }],
259
+ ["path", { d: "M7 3v4a1 1 0 0 0 1 1h7", key: "t51u73" }]
260
+ ];
261
+ const Save = createLucideIcon("save", __iconNode$b);
262
+ /**
263
+ * @license lucide-react v0.511.0 - ISC
264
+ *
265
+ * This source code is licensed under the ISC license.
266
+ * See the LICENSE file in the root directory of this source tree.
267
+ */
268
+ const __iconNode$a = [
269
+ ["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
270
+ ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
271
+ ];
272
+ const Search = createLucideIcon("search", __iconNode$a);
273
+ /**
274
+ * @license lucide-react v0.511.0 - ISC
275
+ *
276
+ * This source code is licensed under the ISC license.
277
+ * See the LICENSE file in the root directory of this source tree.
278
+ */
279
+ const __iconNode$9 = [
280
+ [
281
+ "path",
282
+ {
283
+ 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",
284
+ key: "1qme2f"
285
+ }
286
+ ],
287
+ ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
288
+ ];
289
+ const Settings = createLucideIcon("settings", __iconNode$9);
290
+ /**
291
+ * @license lucide-react v0.511.0 - ISC
292
+ *
293
+ * This source code is licensed under the ISC license.
294
+ * See the LICENSE file in the root directory of this source tree.
295
+ */
296
+ const __iconNode$8 = [
297
+ [
298
+ "path",
299
+ {
300
+ d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z",
301
+ key: "4pj2yx"
302
+ }
303
+ ],
304
+ ["path", { d: "M20 3v4", key: "1olli1" }],
305
+ ["path", { d: "M22 5h-4", key: "1gvqau" }],
306
+ ["path", { d: "M4 17v2", key: "vumght" }],
307
+ ["path", { d: "M5 18H3", key: "zchphs" }]
308
+ ];
309
+ const Sparkles = createLucideIcon("sparkles", __iconNode$8);
310
+ /**
311
+ * @license lucide-react v0.511.0 - ISC
312
+ *
313
+ * This source code is licensed under the ISC license.
314
+ * See the LICENSE file in the root directory of this source tree.
315
+ */
316
+ const __iconNode$7 = [
317
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
318
+ ];
319
+ const Square = createLucideIcon("square", __iconNode$7);
320
+ /**
321
+ * @license lucide-react v0.511.0 - ISC
322
+ *
323
+ * This source code is licensed under the ISC license.
324
+ * See the LICENSE file in the root directory of this source tree.
325
+ */
326
+ const __iconNode$6 = [
327
+ ["path", { d: "M3 6h18", key: "d0wm0j" }],
328
+ ["path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6", key: "4alrt4" }],
329
+ ["path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2", key: "v07s0e" }],
330
+ ["line", { x1: "10", x2: "10", y1: "11", y2: "17", key: "1uufr5" }],
331
+ ["line", { x1: "14", x2: "14", y1: "11", y2: "17", key: "xtxkd" }]
332
+ ];
333
+ const Trash2 = createLucideIcon("trash-2", __iconNode$6);
334
+ /**
335
+ * @license lucide-react v0.511.0 - ISC
336
+ *
337
+ * This source code is licensed under the ISC license.
338
+ * See the LICENSE file in the root directory of this source tree.
339
+ */
340
+ const __iconNode$5 = [
341
+ ["path", { d: "M12 4v16", key: "1654pz" }],
342
+ ["path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2", key: "e0r10z" }],
343
+ ["path", { d: "M9 20h6", key: "s66wpe" }]
344
+ ];
345
+ const Type = createLucideIcon("type", __iconNode$5);
346
+ /**
347
+ * @license lucide-react v0.511.0 - ISC
348
+ *
349
+ * This source code is licensed under the ISC license.
350
+ * See the LICENSE file in the root directory of this source tree.
351
+ */
352
+ const __iconNode$4 = [
353
+ ["path", { d: "M12 3v12", key: "1x0j5s" }],
354
+ ["path", { d: "m17 8-5-5-5 5", key: "7q97r8" }],
355
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }]
356
+ ];
357
+ const Upload = createLucideIcon("upload", __iconNode$4);
358
+ /**
359
+ * @license lucide-react v0.511.0 - ISC
360
+ *
361
+ * This source code is licensed under the ISC license.
362
+ * See the LICENSE file in the root directory of this source tree.
363
+ */
364
+ const __iconNode$3 = [
365
+ [
366
+ "path",
367
+ {
368
+ d: "m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5",
369
+ key: "ftymec"
370
+ }
371
+ ],
372
+ ["rect", { x: "2", y: "6", width: "14", height: "12", rx: "2", key: "158x01" }]
373
+ ];
374
+ const Video = createLucideIcon("video", __iconNode$3);
375
+ /**
376
+ * @license lucide-react v0.511.0 - ISC
377
+ *
378
+ * This source code is licensed under the ISC license.
379
+ * See the LICENSE file in the root directory of this source tree.
380
+ */
381
+ const __iconNode$2 = [
382
+ [
383
+ "path",
384
+ {
385
+ d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z",
386
+ key: "uqj9uw"
387
+ }
388
+ ],
389
+ ["path", { d: "M16 9a5 5 0 0 1 0 6", key: "1q6k2b" }],
390
+ ["path", { d: "M19.364 18.364a9 9 0 0 0 0-12.728", key: "ijwkga" }]
391
+ ];
392
+ const Volume2 = createLucideIcon("volume-2", __iconNode$2);
393
+ /**
394
+ * @license lucide-react v0.511.0 - ISC
395
+ *
396
+ * This source code is licensed under the ISC license.
397
+ * See the LICENSE file in the root directory of this source tree.
398
+ */
399
+ const __iconNode$1 = [
400
+ [
401
+ "path",
402
+ {
403
+ d: "m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72",
404
+ key: "ul74o6"
405
+ }
406
+ ],
407
+ ["path", { d: "m14 7 3 3", key: "1r5n42" }],
408
+ ["path", { d: "M5 6v4", key: "ilb8ba" }],
409
+ ["path", { d: "M19 14v4", key: "blhpug" }],
410
+ ["path", { d: "M10 2v2", key: "7u0qdc" }],
411
+ ["path", { d: "M7 8H3", key: "zfb6yr" }],
412
+ ["path", { d: "M21 16h-4", key: "1cnmox" }],
413
+ ["path", { d: "M11 3H9", key: "1obp7u" }]
414
+ ];
415
+ const WandSparkles = createLucideIcon("wand-sparkles", __iconNode$1);
416
+ /**
417
+ * @license lucide-react v0.511.0 - ISC
418
+ *
419
+ * This source code is licensed under the ISC license.
420
+ * See the LICENSE file in the root directory of this source tree.
421
+ */
422
+ const __iconNode = [
423
+ [
424
+ "path",
425
+ {
426
+ 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",
427
+ key: "1xq2db"
428
+ }
429
+ ]
430
+ ];
431
+ const Zap = createLucideIcon("zap", __iconNode);
432
+ const toolCategories = [
433
+ { id: "video", name: "Video", icon: "Video", description: "Video" },
434
+ { id: "image", name: "Image", icon: "Image", description: "Image" },
435
+ { id: "audio", name: "Audio", icon: "Audio", description: "Audio" },
436
+ { id: "text", name: "Text", icon: "Type", description: "Add text elements", shortcut: "T" },
437
+ { id: "icon", name: "Icons", icon: "Icon", description: "Icon Element", shortcut: "I" },
438
+ { id: "circle", name: "Circle", icon: "Circle", description: "Circle Element", shortcut: "C" },
439
+ { id: "rect", name: "Rect", icon: "Rect", description: "Rect Element" }
440
+ // { id: 'subtitle', name: 'Subtitles', icon: 'MessageSquare', description: 'Manage subtitles', shortcut: 'S' },
441
+ ];
442
+ const getIcon$1 = (iconName) => {
443
+ switch (iconName) {
444
+ case "Plus":
445
+ return Plus;
446
+ case "Type":
447
+ return Type;
448
+ case "Icon":
449
+ return Infinity;
450
+ case "Upload":
451
+ return Upload;
452
+ case "Square":
453
+ return Square;
454
+ case "Image":
455
+ return Image;
456
+ case "Video":
457
+ return Video;
458
+ case "Audio":
459
+ return Music;
460
+ case "Circle":
461
+ return Circle;
462
+ case "Rect":
463
+ return Square;
464
+ case "MessageSquare":
465
+ return MessageSquare;
466
+ default:
467
+ return Plus;
468
+ }
469
+ };
470
+ function Toolbar({ selectedTool, setSelectedTool }) {
471
+ const handleToolSelect = (toolId) => {
472
+ setSelectedTool(toolId);
473
+ };
474
+ return /* @__PURE__ */ jsx("div", { className: "w-16 bg-neutral/80 border-r border-gray-300/50 flex flex-col items-center py-4 space-y-3 justify-start backdrop-blur-md shadow-lg", children: toolCategories.map((tool) => {
475
+ const Icon2 = getIcon$1(tool.icon);
476
+ const isSelected = selectedTool === tool.id;
477
+ return /* @__PURE__ */ jsxs(
478
+ "button",
479
+ {
480
+ onClick: () => handleToolSelect(tool.id),
481
+ className: `
482
+ toolbar-btn group ${isSelected ? "active" : ""}
483
+ `,
484
+ title: `${tool.name}${tool.shortcut ? ` (${tool.shortcut})` : ""}`,
485
+ children: [
486
+ /* @__PURE__ */ jsx(Icon2, { className: "w-4 h-4" }),
487
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] mt-1 transition-opacity duration-200", children: tool.name })
488
+ ]
489
+ },
490
+ tool.id
491
+ );
492
+ }) });
493
+ }
494
+ const StudioHeader = ({
495
+ setVideoResolution,
496
+ onLoadProject,
497
+ onSaveProject,
498
+ onExportVideo
499
+ }) => {
500
+ const [orientation, setOrientation] = useState(
501
+ "vertical"
502
+ );
503
+ useEffect(() => {
504
+ const orientation2 = localStorage.getItem("orientation");
505
+ if (orientation2) {
506
+ setOrientation(orientation2);
507
+ }
508
+ }, []);
509
+ useEffect(() => {
510
+ if (orientation === "horizontal") {
511
+ localStorage.setItem("orientation", "horizontal");
512
+ setVideoResolution({ width: 1280, height: 720 });
513
+ } else {
514
+ localStorage.setItem("orientation", "vertical");
515
+ setVideoResolution({ width: 720, height: 1280 });
516
+ }
517
+ }, [orientation]);
518
+ return /* @__PURE__ */ jsxs("header", { className: "h-14 bg-neutral-800/90 border-b border-gray-600/50 flex items-center justify-between px-4 backdrop-blur-md shadow-lg", children: [
519
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
520
+ /* @__PURE__ */ jsx(Clapperboard, { className: "w-8 h-8 text-purple-400" }),
521
+ /* @__PURE__ */ jsx("h1", { className: "text-lg font-bold text-gradient-purple", children: "Twick Studio" })
522
+ ] }) }),
523
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
524
+ /* @__PURE__ */ jsxs(
525
+ "button",
526
+ {
527
+ className: "btn btn-ghost w-32",
528
+ title: "Load Project",
529
+ onClick: onLoadProject,
530
+ children: [
531
+ /* @__PURE__ */ jsx(File, { className: "w-4 h-4 mr-2" }),
532
+ "Load Project"
533
+ ]
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsxs(
537
+ "button",
538
+ {
539
+ className: "btn btn-ghost w-32",
540
+ title: "Save Draft",
541
+ onClick: onSaveProject,
542
+ children: [
543
+ /* @__PURE__ */ jsx(Save, { className: "w-4 h-4 mr-2" }),
544
+ "Save Draft"
545
+ ]
546
+ }
547
+ ),
548
+ /* @__PURE__ */ jsxs(
549
+ "button",
550
+ {
551
+ className: "btn btn-primary w-32",
552
+ title: "Export",
553
+ onClick: onExportVideo,
554
+ children: [
555
+ /* @__PURE__ */ jsx(Download, { className: "w-4 h-4 mr-2" }),
556
+ "Export"
557
+ ]
558
+ }
559
+ )
560
+ ] })
561
+ ] });
562
+ };
563
+ const useStudioManager = () => {
564
+ const [selectedProp, setSelectedProp] = useState("element-props");
565
+ const { editor, selectedItem, setSelectedItem } = useTimelineContext();
566
+ const selectedElement = selectedItem instanceof TrackElement ? selectedItem : null;
567
+ const [selectedTool, setSelectedTool] = useState("none");
568
+ const isToolChanged = useRef(false);
569
+ const addElement = (element) => {
570
+ if (selectedItem instanceof Track) {
571
+ editor.addElementToTrack(selectedItem, element);
572
+ } else {
573
+ const newTrack = editor.addTrack("Track");
574
+ editor.addElementToTrack(newTrack, element);
575
+ }
576
+ };
577
+ const updateElement = (element) => {
578
+ const updatedElement = editor.updateElement(element);
579
+ editor.refresh();
580
+ setSelectedItem(updatedElement);
581
+ };
582
+ useEffect(() => {
583
+ if (selectedItem instanceof TrackElement) {
584
+ setSelectedTool(selectedItem.getType());
585
+ isToolChanged.current = true;
586
+ } else if (selectedItem instanceof Track) ;
587
+ else {
588
+ if (isToolChanged.current) {
589
+ setSelectedTool("none");
590
+ } else {
591
+ setSelectedTool("video");
592
+ }
593
+ }
594
+ }, [selectedItem]);
595
+ return {
596
+ selectedProp,
597
+ setSelectedProp,
598
+ selectedTool,
599
+ setSelectedTool,
600
+ selectedElement,
601
+ addElement,
602
+ updateElement
603
+ };
604
+ };
605
+ function SubtitlesPanel() {
606
+ const [subtitles, setSubtitles] = useState([]);
607
+ const handleGenerate = () => {
608
+ console.log("Generating subtitles...");
609
+ };
610
+ const handleAdd = () => {
611
+ const newId = (subtitles.length + 1).toString();
612
+ const lastEnd = subtitles.length > 0 ? subtitles[subtitles.length - 1].end : 0;
613
+ const newSubtitle = {
614
+ id: newId,
615
+ start: lastEnd,
616
+ end: lastEnd + 1,
617
+ text: ""
618
+ };
619
+ setSubtitles([...subtitles, newSubtitle]);
620
+ };
621
+ const handleDelete = (id) => {
622
+ setSubtitles(subtitles.filter((sub) => sub.id !== id));
623
+ };
624
+ const handleSave = (id) => {
625
+ console.log("Saving subtitle:", id);
626
+ };
627
+ const handleUpdateSubtitle = (id, field, value) => {
628
+ setSubtitles(subtitles.map(
629
+ (sub) => sub.id === id ? { ...sub, [field]: value } : sub
630
+ ));
631
+ };
632
+ return /* @__PURE__ */ jsxs("div", { className: "w-72 h-full bg-neutral-800/80 border-l border-gray-600/50 p-3 overflow-y-auto overflow-x-hidden backdrop-blur-md shadow-lg", children: [
633
+ /* @__PURE__ */ jsx("h3", { className: "text-xl font-bold text-white mb-6", children: "Subtitles" }),
634
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3 mb-6", children: [
635
+ /* @__PURE__ */ jsx(
636
+ "button",
637
+ {
638
+ onClick: handleGenerate,
639
+ className: "flex-1 bg-neutral-700 hover:bg-neutral-600 text-white font-medium py-2 px-4 rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500/20",
640
+ children: "Generate"
641
+ }
642
+ ),
643
+ /* @__PURE__ */ jsx(
644
+ "button",
645
+ {
646
+ onClick: handleAdd,
647
+ className: "flex-1 bg-neutral-700 hover:bg-neutral-600 text-white font-medium py-2 px-4 rounded-lg border-2 border-purple-500 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500/20",
648
+ children: "Add"
649
+ }
650
+ )
651
+ ] }),
652
+ /* @__PURE__ */ jsx("div", { className: "space-y-4", children: subtitles.map((subtitle) => /* @__PURE__ */ jsxs(
653
+ "div",
654
+ {
655
+ className: "bg-neutral-700/50 border border-gray-600 rounded-lg p-3",
656
+ children: [
657
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3 mb-3", children: [
658
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
659
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "Start" }),
660
+ /* @__PURE__ */ jsx(
661
+ "input",
662
+ {
663
+ type: "number",
664
+ min: "0",
665
+ step: "0.1",
666
+ value: subtitle.start,
667
+ onChange: (e) => handleUpdateSubtitle(subtitle.id, "start", Number(e.target.value)),
668
+ className: "w-full bg-neutral-800/80 border border-gray-600 rounded-lg text-white text-sm px-3 py-2 focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-200 backdrop-blur-sm"
669
+ }
670
+ )
671
+ ] }),
672
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
673
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "End" }),
674
+ /* @__PURE__ */ jsx(
675
+ "input",
676
+ {
677
+ type: "number",
678
+ min: "0",
679
+ step: "0.1",
680
+ value: subtitle.end,
681
+ onChange: (e) => handleUpdateSubtitle(subtitle.id, "end", Number(e.target.value)),
682
+ className: "w-full bg-neutral-800/80 border border-gray-600 rounded-lg text-white text-sm px-3 py-2 focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-200 backdrop-blur-sm"
683
+ }
684
+ )
685
+ ] })
686
+ ] }),
687
+ /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsx(
688
+ "input",
689
+ {
690
+ type: "text",
691
+ placeholder: "subtitle",
692
+ value: subtitle.text,
693
+ onChange: (e) => handleUpdateSubtitle(subtitle.id, "text", e.target.value),
694
+ className: "w-full bg-neutral-800/80 border border-gray-600 rounded-lg text-white text-sm px-3 py-2 placeholder-gray-400 focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-200 backdrop-blur-sm"
695
+ }
696
+ ) }),
697
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
698
+ /* @__PURE__ */ jsx(
699
+ "button",
700
+ {
701
+ onClick: () => handleDelete(subtitle.id),
702
+ className: "p-2 text-red-400 hover:text-red-300 hover:bg-red-500/20 rounded-lg transition-all duration-200",
703
+ title: "Delete subtitle",
704
+ children: /* @__PURE__ */ jsx(Trash2, { className: "w-4 h-4" })
705
+ }
706
+ ),
707
+ /* @__PURE__ */ jsx(
708
+ "button",
709
+ {
710
+ onClick: () => handleSave(subtitle.id),
711
+ className: "p-2 text-green-400 hover:text-green-300 hover:bg-green-500/20 rounded-lg transition-all duration-200",
712
+ title: "Save subtitle",
713
+ children: /* @__PURE__ */ jsx(Check, { className: "w-4 h-4" })
714
+ }
715
+ )
716
+ ] })
717
+ ]
718
+ },
719
+ subtitle.id
720
+ )) }),
721
+ subtitles.length === 0 && /* @__PURE__ */ jsxs("div", { className: "text-center py-8 text-gray-400", children: [
722
+ /* @__PURE__ */ jsx("p", { children: "No subtitles yet" }),
723
+ /* @__PURE__ */ jsx("p", { className: "text-sm mt-2", children: 'Click "Add" to create your first subtitle' })
724
+ ] })
725
+ ] });
726
+ }
727
+ const FileInput = ({
728
+ acceptFileTypes,
729
+ onFileLoad,
730
+ buttonText,
731
+ id
732
+ }) => {
733
+ const onFileChange = (e) => {
734
+ var _a;
735
+ const file = (_a = e.target.files) == null ? void 0 : _a[0];
736
+ if (file) {
737
+ const reader = new FileReader();
738
+ reader.onload = (event) => {
739
+ var _a2;
740
+ try {
741
+ onFileLoad({
742
+ content: file.type === "application/json" ? (_a2 = event.target) == null ? void 0 : _a2.result : void 0,
743
+ type: file.type,
744
+ name: file.name,
745
+ file,
746
+ blobUrl: URL.createObjectURL(file)
747
+ });
748
+ } catch (error) {
749
+ console.error("Error parsing file:", error);
750
+ }
751
+ };
752
+ console.log("file", file);
753
+ if (file.type === "application/json") {
754
+ reader.readAsText(file);
755
+ } else {
756
+ reader.readAsDataURL(file);
757
+ }
758
+ }
759
+ };
760
+ return /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
761
+ /* @__PURE__ */ jsx(
762
+ "input",
763
+ {
764
+ type: "file",
765
+ accept: acceptFileTypes.join(","),
766
+ className: "absolute w-0.1 h-0.1 opacity-0 overflow-hidden -z-10",
767
+ id,
768
+ onChange: onFileChange
769
+ }
770
+ ),
771
+ /* @__PURE__ */ jsxs(
772
+ "label",
773
+ {
774
+ htmlFor: id,
775
+ className: "w-full btn btn-primary flex items-center justify-center gap-2 py-2",
776
+ children: [
777
+ /* @__PURE__ */ jsx(Upload, { className: "w-4 h-4" }),
778
+ buttonText ?? "Upload"
779
+ ]
780
+ }
781
+ )
782
+ ] });
783
+ };
784
+ const _MediaManagerSingleton = class _MediaManagerSingleton {
785
+ constructor() {
786
+ }
787
+ static getInstance() {
788
+ if (!_MediaManagerSingleton.instance) {
789
+ _MediaManagerSingleton.instance = new BrowserMediaManager();
790
+ }
791
+ return _MediaManagerSingleton.instance;
792
+ }
793
+ };
794
+ __publicField(_MediaManagerSingleton, "instance", null);
795
+ let MediaManagerSingleton = _MediaManagerSingleton;
796
+ const getMediaManager = () => MediaManagerSingleton.getInstance();
797
+ const initialMediaState = {
798
+ items: [],
799
+ searchQuery: "",
800
+ isLoading: false
801
+ };
802
+ const MediaContext = createContext(null);
803
+ function MediaProvider({ children }) {
804
+ const [videoState, setVideoState] = useState(initialMediaState);
805
+ const [audioState, setAudioState] = useState(initialMediaState);
806
+ const [imageState, setImageState] = useState(initialMediaState);
807
+ const mediaManager = getMediaManager();
808
+ const getStateAndSetter = (type) => {
809
+ switch (type) {
810
+ case "video":
811
+ return [videoState, setVideoState];
812
+ case "audio":
813
+ return [audioState, setAudioState];
814
+ case "image":
815
+ return [imageState, setImageState];
816
+ }
817
+ };
818
+ const loadItems = async (type, query) => {
819
+ const [state, setState] = getStateAndSetter(type);
820
+ setState({ ...state, isLoading: true });
821
+ try {
822
+ const results = await mediaManager.search({
823
+ query,
824
+ type
825
+ });
826
+ setState({
827
+ items: results,
828
+ searchQuery: query,
829
+ isLoading: false
830
+ });
831
+ } catch (error) {
832
+ console.error(`Error loading ${type} items:`, error);
833
+ setState({
834
+ ...state,
835
+ isLoading: false
836
+ });
837
+ }
838
+ };
839
+ useEffect(() => {
840
+ loadItems("video", "");
841
+ loadItems("audio", "");
842
+ loadItems("image", "");
843
+ }, []);
844
+ const setSearchQuery = (type, query) => {
845
+ const [state, setState] = getStateAndSetter(type);
846
+ setState({ ...state, searchQuery: query });
847
+ loadItems(type, query);
848
+ };
849
+ const addItem = (type, newItem) => {
850
+ const [state, setState] = getStateAndSetter(type);
851
+ setState({
852
+ ...state,
853
+ items: [...state.items, newItem]
854
+ });
855
+ };
856
+ return /* @__PURE__ */ jsx(
857
+ MediaContext.Provider,
858
+ {
859
+ value: {
860
+ videoState,
861
+ audioState,
862
+ imageState,
863
+ setSearchQuery,
864
+ addItem
865
+ },
866
+ children
867
+ }
868
+ );
869
+ }
870
+ function useMedia(type) {
871
+ const context = useContext(MediaContext);
872
+ if (!context) {
873
+ throw new Error("useMedia must be used within a MediaProvider");
874
+ }
875
+ const state = context[`${type}State`];
876
+ return {
877
+ items: state.items,
878
+ searchQuery: state.searchQuery,
879
+ isLoading: state.isLoading,
880
+ setSearchQuery: (query) => context.setSearchQuery(type, query),
881
+ addItem: (item) => context.addItem(type, item)
882
+ };
883
+ }
884
+ const mediaConfigs = {
885
+ video: {
886
+ acceptFileTypes: ["video/*"],
887
+ createElement: (url, parentSize) => new VideoElement(url, parentSize),
888
+ updateElement: async (element, url) => {
889
+ if (element instanceof VideoElement) {
890
+ element.setSrc(url);
891
+ await element.updateVideoMeta();
892
+ }
893
+ }
894
+ },
895
+ audio: {
896
+ acceptFileTypes: ["audio/*"],
897
+ createElement: (url, _parentSize) => new AudioElement(url),
898
+ updateElement: async (element, url) => {
899
+ if (element instanceof AudioElement) {
900
+ element.setSrc(url);
901
+ await element.updateAudioMeta();
902
+ }
903
+ }
904
+ },
905
+ image: {
906
+ acceptFileTypes: ["image/*"],
907
+ createElement: (url, parentSize) => new ImageElement(url, parentSize),
908
+ updateElement: async (element, url) => {
909
+ if (element instanceof ImageElement) {
910
+ element.setSrc(url);
911
+ await element.updateImageMeta();
912
+ }
913
+ }
914
+ }
915
+ };
916
+ const useMediaPanel = (type, {
917
+ selectedElement,
918
+ addElement,
919
+ updateElement
920
+ }, videoResolution) => {
921
+ const { items, searchQuery, setSearchQuery, addItem, isLoading } = useMedia(type);
922
+ const mediaManager = getMediaManager();
923
+ const handleSelection = async (item, forceAdd) => {
924
+ const config2 = mediaConfigs[type];
925
+ if (forceAdd) {
926
+ const element = config2.createElement(item.url, videoResolution);
927
+ addElement(element);
928
+ } else {
929
+ if (selectedElement) {
930
+ await config2.updateElement(selectedElement, item.url);
931
+ updateElement(selectedElement);
932
+ } else {
933
+ const element = config2.createElement(item.url, videoResolution);
934
+ addElement(element);
935
+ }
936
+ }
937
+ };
938
+ const handleFileUpload = async (fileData) => {
939
+ const arrayBuffer = await fileData.file.arrayBuffer();
940
+ const newItem = await mediaManager.addItem({
941
+ name: fileData.file.name,
942
+ url: fileData.blobUrl,
943
+ type,
944
+ arrayBuffer,
945
+ metadata: {
946
+ name: fileData.file.name,
947
+ size: fileData.file.size,
948
+ type: fileData.file.type
949
+ }
950
+ });
951
+ addItem(newItem);
952
+ };
953
+ const config = mediaConfigs[type];
954
+ return {
955
+ items,
956
+ searchQuery,
957
+ setSearchQuery,
958
+ handleSelection,
959
+ handleFileUpload,
960
+ isLoading,
961
+ acceptFileTypes: config.acceptFileTypes
962
+ };
963
+ };
964
+ const SearchInput = ({
965
+ searchQuery,
966
+ setSearchQuery
967
+ }) => {
968
+ return /* @__PURE__ */ jsxs("div", { className: "relative mb-3", children: [
969
+ /* @__PURE__ */ jsx(
970
+ "input",
971
+ {
972
+ type: "text",
973
+ placeholder: "Search media...",
974
+ value: searchQuery,
975
+ onChange: (e) => setSearchQuery(e.target.value),
976
+ className: "w-full pl-8 pr-3 py-2 bg-neutral-700/80 border border-gray-600 rounded-lg text-gray-100 text-sm placeholder-gray-400 focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-200 backdrop-blur-sm shadow-sm"
977
+ }
978
+ ),
979
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400 hover:text-gray-300 z-10 pointer-events-none" })
980
+ ] });
981
+ };
982
+ const inputStyles = {
983
+ // Base container styles for form groups
984
+ container: "mb-6",
985
+ // Label styles
986
+ label: {
987
+ base: "block text-sm font-semibold text-gray-300 mb-2",
988
+ small: "block text-xs text-gray-400 mb-1"
989
+ },
990
+ // Text input and select styles
991
+ input: {
992
+ base: "w-full bg-neutral-700/50 border border-gray-600 rounded-lg text-white text-sm px-3 py-2 focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-200 backdrop-blur-sm",
993
+ small: "bg-neutral-700/50 border border-gray-600 rounded-lg text-white text-xs px-2 py-1 focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-200 backdrop-blur-sm"
994
+ },
995
+ // Range input styles
996
+ range: {
997
+ base: "flex-1 h-2 bg-neutral-600 rounded-full appearance-none cursor-pointer slider-thumb",
998
+ gradient: "flex-1 h-2 bg-gradient-to-r from-purple-500 to-neutral-600 rounded-full appearance-none cursor-pointer slider-thumb",
999
+ container: "flex items-center gap-3",
1000
+ value: "text-white text-sm font-medium min-w-[50px]"
1001
+ },
1002
+ // Color input styles
1003
+ color: {
1004
+ container: "flex items-center gap-2",
1005
+ picker: "w-6 h-6 rounded border border-gray-600 cursor-pointer",
1006
+ preview: "flex-1 h-8 rounded-lg border border-gray-600"
1007
+ },
1008
+ // Toggle button styles
1009
+ toggle: {
1010
+ base: "w-10 h-10 rounded-lg border-2 transition-all duration-200 hover:bg-purple-400 hover:text-white",
1011
+ active: "bg-purple-600 border-purple-500 text-white",
1012
+ inactive: "bg-transparent border-gray-600 text-gray-400 hover:border-gray-500"
1013
+ },
1014
+ // Radio button styles
1015
+ radio: {
1016
+ base: "w-4 h-4 text-purple-600 bg-neutral-700 border-gray-600 focus:ring-purple-500 focus:ring-2",
1017
+ label: "text-sm text-gray-300",
1018
+ container: "flex items-center gap-2"
1019
+ },
1020
+ // Action button styles
1021
+ button: {
1022
+ primary: "w-full bg-purple-600 hover:bg-purple-700 text-white font-medium py-3 px-4 rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500/20"
1023
+ },
1024
+ // Panel container styles
1025
+ panel: {
1026
+ container: "w-72 h-full bg-neutral-800/80 border-l border-gray-600/50 p-4 overflow-y-auto overflow-x-hidden backdrop-blur-md shadow-lg",
1027
+ title: "text-xl font-bold text-white mb-6"
1028
+ }
1029
+ };
1030
+ const useAudioPreview = () => {
1031
+ const [playingAudio, setPlayingAudio] = useState(null);
1032
+ const audioRef = useRef(null);
1033
+ useEffect(() => {
1034
+ return () => {
1035
+ if (audioRef.current) {
1036
+ audioRef.current.pause();
1037
+ audioRef.current = null;
1038
+ }
1039
+ };
1040
+ }, []);
1041
+ const stopPlayback = useCallback(() => {
1042
+ if (audioRef.current) {
1043
+ audioRef.current.pause();
1044
+ audioRef.current = null;
1045
+ }
1046
+ setPlayingAudio(null);
1047
+ }, []);
1048
+ const togglePlayPause = useCallback((item) => {
1049
+ if (playingAudio === item.id) {
1050
+ stopPlayback();
1051
+ return;
1052
+ }
1053
+ stopPlayback();
1054
+ const audio = new Audio(item.url);
1055
+ audio.addEventListener("ended", stopPlayback);
1056
+ audio.play();
1057
+ audioRef.current = audio;
1058
+ setPlayingAudio(item.id);
1059
+ }, [playingAudio, stopPlayback]);
1060
+ return {
1061
+ playingAudio,
1062
+ audioElement: audioRef.current,
1063
+ togglePlayPause,
1064
+ stopPlayback
1065
+ };
1066
+ };
1067
+ const AudioPanel = ({
1068
+ items,
1069
+ searchQuery,
1070
+ onSearchChange,
1071
+ onItemSelect,
1072
+ onFileUpload,
1073
+ acceptFileTypes
1074
+ }) => {
1075
+ const { playingAudio, togglePlayPause } = useAudioPreview();
1076
+ return /* @__PURE__ */ jsxs("div", { className: inputStyles.panel.container, children: [
1077
+ /* @__PURE__ */ jsx("h3", { className: inputStyles.panel.title, children: "Audio Library" }),
1078
+ /* @__PURE__ */ jsx("div", { className: inputStyles.container, children: /* @__PURE__ */ jsx(
1079
+ SearchInput,
1080
+ {
1081
+ searchQuery,
1082
+ setSearchQuery: onSearchChange
1083
+ }
1084
+ ) }),
1085
+ /* @__PURE__ */ jsx("div", { className: `${inputStyles.container} mb-8`, children: /* @__PURE__ */ jsx(
1086
+ FileInput,
1087
+ {
1088
+ id: "audio-upload",
1089
+ acceptFileTypes,
1090
+ onFileLoad: onFileUpload,
1091
+ buttonText: "Upload"
1092
+ }
1093
+ ) }),
1094
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
1095
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: (items || []).map((item) => {
1096
+ var _a;
1097
+ return /* @__PURE__ */ jsx(
1098
+ "div",
1099
+ {
1100
+ onDoubleClick: () => onItemSelect(item),
1101
+ className: "audio-item group relative cursor-pointer p-3 bg-neutral-700/50 rounded-lg hover:bg-neutral-700/80 transition-all duration-200 border border-transparent hover:border-purple-500/30",
1102
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1103
+ /* @__PURE__ */ jsx(
1104
+ "button",
1105
+ {
1106
+ onClick: (e) => {
1107
+ e.stopPropagation();
1108
+ togglePlayPause(item);
1109
+ },
1110
+ className: "w-8 h-8 rounded-full bg-purple-500/80 hover:bg-purple-500 flex items-center justify-center text-white transition-all duration-200 flex-shrink-0",
1111
+ children: playingAudio === item.id ? /* @__PURE__ */ jsx(Pause, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Play, { className: "w-4 h-4" })
1112
+ }
1113
+ ),
1114
+ /* @__PURE__ */ jsx("div", { className: `w-10 h-10 rounded-lg ${playingAudio === item.id ? "bg-purple-500/40" : "bg-purple-500/20"} flex items-center justify-center flex-shrink-0 transition-colors duration-200`, children: /* @__PURE__ */ jsx(Volume2, { className: `w-5 h-5 ${playingAudio === item.id ? "text-purple-300" : "text-purple-400"}` }) }),
1115
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx("h4", { className: "text-sm font-medium text-gray-100 truncate", children: (_a = item.metadata) == null ? void 0 : _a.title }) }),
1116
+ /* @__PURE__ */ jsx(
1117
+ "button",
1118
+ {
1119
+ onClick: (e) => {
1120
+ e.stopPropagation();
1121
+ onItemSelect(item, true);
1122
+ },
1123
+ className: "w-6 h-6 rounded-full bg-purple-500/60 hover:bg-purple-500 flex items-center justify-center text-white text-xs opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex-shrink-0",
1124
+ children: /* @__PURE__ */ jsx(Plus, { className: "w-3 h-3" })
1125
+ }
1126
+ )
1127
+ ] })
1128
+ },
1129
+ item.id
1130
+ );
1131
+ }) }),
1132
+ items.length === 0 && /* @__PURE__ */ jsx("div", { className: `${inputStyles.container} flex items-center justify-center h-24`, children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
1133
+ /* @__PURE__ */ jsx(WandSparkles, { className: "w-10 h-10 mx-auto mb-2 text-purple-500/50" }),
1134
+ /* @__PURE__ */ jsx("p", { className: inputStyles.label.base, children: "No audio files found" }),
1135
+ searchQuery && /* @__PURE__ */ jsx("p", { className: inputStyles.label.small, children: "Try adjusting your search" })
1136
+ ] }) })
1137
+ ] })
1138
+ ] });
1139
+ };
1140
+ const AudioPanelContainer = (props) => {
1141
+ const {
1142
+ items,
1143
+ searchQuery,
1144
+ setSearchQuery,
1145
+ handleSelection,
1146
+ handleFileUpload,
1147
+ isLoading,
1148
+ acceptFileTypes
1149
+ } = useMediaPanel(
1150
+ "audio",
1151
+ {
1152
+ selectedElement: props.selectedElement ?? null,
1153
+ addElement: props.addElement,
1154
+ updateElement: props.updateElement
1155
+ },
1156
+ props.videoResolution
1157
+ );
1158
+ return /* @__PURE__ */ jsx(
1159
+ AudioPanel,
1160
+ {
1161
+ items,
1162
+ searchQuery,
1163
+ onSearchChange: setSearchQuery,
1164
+ onItemSelect: handleSelection,
1165
+ onFileUpload: handleFileUpload,
1166
+ isLoading,
1167
+ acceptFileTypes
1168
+ }
1169
+ );
1170
+ };
1171
+ function ImagePanel({
1172
+ items,
1173
+ searchQuery,
1174
+ onSearchChange,
1175
+ onItemSelect,
1176
+ onFileUpload,
1177
+ acceptFileTypes
1178
+ }) {
1179
+ return /* @__PURE__ */ jsxs("div", { className: inputStyles.panel.container, children: [
1180
+ /* @__PURE__ */ jsx("h3", { className: inputStyles.panel.title, children: "Image Library" }),
1181
+ /* @__PURE__ */ jsx("div", { className: inputStyles.container, children: /* @__PURE__ */ jsx(
1182
+ SearchInput,
1183
+ {
1184
+ searchQuery,
1185
+ setSearchQuery: onSearchChange
1186
+ }
1187
+ ) }),
1188
+ /* @__PURE__ */ jsx("div", { className: `${inputStyles.container} mb-8`, children: /* @__PURE__ */ jsx(
1189
+ FileInput,
1190
+ {
1191
+ id: "image-upload",
1192
+ acceptFileTypes,
1193
+ onFileLoad: onFileUpload,
1194
+ buttonText: "Upload"
1195
+ }
1196
+ ) }),
1197
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
1198
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 auto-rows-fr", children: (items || []).map((item) => /* @__PURE__ */ jsxs(
1199
+ "div",
1200
+ {
1201
+ onDoubleClick: () => onItemSelect(item),
1202
+ className: "media-item-compact group relative cursor-pointer overflow-hidden hover:shadow-lg hover:shadow-purple-500/20 transition-all duration-200",
1203
+ children: [
1204
+ /* @__PURE__ */ jsx(
1205
+ "img",
1206
+ {
1207
+ src: item.url,
1208
+ alt: "",
1209
+ className: "h-full w-full object-cover transition-transform group-hover:scale-105"
1210
+ }
1211
+ ),
1212
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black bg-opacity-0 transition-opacity group-hover:bg-opacity-20" }),
1213
+ /* @__PURE__ */ jsx("div", { className: "absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200", children: /* @__PURE__ */ jsx(
1214
+ "button",
1215
+ {
1216
+ onClick: (e) => {
1217
+ e.stopPropagation();
1218
+ onItemSelect(item, true);
1219
+ },
1220
+ className: "w-5 h-5 rounded-full bg-purple-500/80 hover:bg-purple-500 flex items-center justify-center text-white text-xs",
1221
+ children: /* @__PURE__ */ jsx(Plus, { className: "w-3 h-3" })
1222
+ }
1223
+ ) })
1224
+ ]
1225
+ },
1226
+ item.id
1227
+ )) }),
1228
+ items.length === 0 && /* @__PURE__ */ jsx("div", { className: `${inputStyles.container} flex items-center justify-center h-24`, children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
1229
+ /* @__PURE__ */ jsx(WandSparkles, { className: "w-10 h-10 mx-auto mb-2 text-purple-500/50" }),
1230
+ /* @__PURE__ */ jsx("p", { className: inputStyles.label.base, children: "No images found" }),
1231
+ searchQuery && /* @__PURE__ */ jsx("p", { className: inputStyles.label.small, children: "Try adjusting your search" })
1232
+ ] }) })
1233
+ ] })
1234
+ ] });
1235
+ }
1236
+ function ImagePanelContainer(props) {
1237
+ const {
1238
+ items,
1239
+ searchQuery,
1240
+ setSearchQuery,
1241
+ handleSelection,
1242
+ handleFileUpload,
1243
+ isLoading,
1244
+ acceptFileTypes
1245
+ } = useMediaPanel(
1246
+ "image",
1247
+ {
1248
+ selectedElement: props.selectedElement ?? null,
1249
+ addElement: props.addElement,
1250
+ updateElement: props.updateElement
1251
+ },
1252
+ props.videoResolution
1253
+ );
1254
+ return /* @__PURE__ */ jsx(
1255
+ ImagePanel,
1256
+ {
1257
+ items,
1258
+ searchQuery,
1259
+ onSearchChange: setSearchQuery,
1260
+ onItemSelect: handleSelection,
1261
+ onFileUpload: handleFileUpload,
1262
+ isLoading,
1263
+ acceptFileTypes
1264
+ }
1265
+ );
1266
+ }
1267
+ const useVideoPreview = () => {
1268
+ const [playingVideo, setPlayingVideo] = useState(null);
1269
+ const videoRef = useRef(null);
1270
+ useEffect(() => {
1271
+ return () => {
1272
+ if (videoRef.current) {
1273
+ videoRef.current.pause();
1274
+ videoRef.current = null;
1275
+ }
1276
+ };
1277
+ }, []);
1278
+ const stopPlayback = useCallback(() => {
1279
+ if (videoRef.current) {
1280
+ videoRef.current.pause();
1281
+ videoRef.current = null;
1282
+ }
1283
+ setPlayingVideo(null);
1284
+ }, []);
1285
+ const togglePlayPause = useCallback((item, videoElement) => {
1286
+ if (playingVideo === item.id) {
1287
+ videoElement.pause();
1288
+ stopPlayback();
1289
+ return;
1290
+ }
1291
+ stopPlayback();
1292
+ videoElement.currentTime = 0;
1293
+ videoElement.play();
1294
+ videoRef.current = videoElement;
1295
+ setPlayingVideo(item.id);
1296
+ videoElement.addEventListener("ended", stopPlayback, { once: true });
1297
+ }, [playingVideo, stopPlayback]);
1298
+ return {
1299
+ playingVideo,
1300
+ videoElement: videoRef.current,
1301
+ togglePlayPause,
1302
+ stopPlayback
1303
+ };
1304
+ };
1305
+ function VideoPanel({
1306
+ items,
1307
+ searchQuery,
1308
+ onSearchChange,
1309
+ onItemSelect,
1310
+ onFileUpload,
1311
+ acceptFileTypes
1312
+ }) {
1313
+ const { playingVideo, togglePlayPause } = useVideoPreview();
1314
+ return /* @__PURE__ */ jsxs("div", { className: inputStyles.panel.container, children: [
1315
+ /* @__PURE__ */ jsx("h3", { className: inputStyles.panel.title, children: "Video Library" }),
1316
+ /* @__PURE__ */ jsx("div", { className: inputStyles.container, children: /* @__PURE__ */ jsx(
1317
+ SearchInput,
1318
+ {
1319
+ searchQuery,
1320
+ setSearchQuery: onSearchChange
1321
+ }
1322
+ ) }),
1323
+ /* @__PURE__ */ jsx("div", { className: `${inputStyles.container} mb-8`, children: /* @__PURE__ */ jsx(
1324
+ FileInput,
1325
+ {
1326
+ id: "video-upload",
1327
+ acceptFileTypes,
1328
+ onFileLoad: onFileUpload,
1329
+ buttonText: "Upload"
1330
+ }
1331
+ ) }),
1332
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
1333
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 auto-rows-fr", children: (items || []).map((item) => /* @__PURE__ */ jsxs(
1334
+ "div",
1335
+ {
1336
+ onDoubleClick: () => onItemSelect(item),
1337
+ className: "media-item-compact group relative cursor-pointer overflow-hidden hover:shadow-lg hover:shadow-purple-500/20 transition-all duration-200",
1338
+ children: [
1339
+ /* @__PURE__ */ jsx(
1340
+ "video",
1341
+ {
1342
+ src: item.url,
1343
+ poster: item.thumbnail,
1344
+ className: `h-full w-full object-cover transition-transform ${playingVideo === item.id ? "scale-105" : "group-hover:scale-105"}`,
1345
+ ref: (el) => {
1346
+ if (el) {
1347
+ el.addEventListener("ended", () => {
1348
+ el.currentTime = 0;
1349
+ }, { once: true });
1350
+ }
1351
+ }
1352
+ }
1353
+ ),
1354
+ /* @__PURE__ */ jsx("div", { className: `absolute inset-0 bg-black transition-opacity ${playingVideo === item.id ? "bg-opacity-30" : "bg-opacity-0 group-hover:bg-opacity-20"}` }),
1355
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-1 right-1 flex gap-2", children: [
1356
+ /* @__PURE__ */ jsx(
1357
+ "button",
1358
+ {
1359
+ onClick: (e) => {
1360
+ var _a, _b;
1361
+ e.stopPropagation();
1362
+ const videoEl = (_b = (_a = e.currentTarget.parentElement) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.querySelector("video");
1363
+ if (videoEl) {
1364
+ togglePlayPause(item, videoEl);
1365
+ }
1366
+ },
1367
+ className: `w-6 h-6 rounded-full bg-purple-500/80 hover:bg-purple-500 flex items-center justify-center text-white text-xs ${playingVideo === item.id ? "opacity-100" : "opacity-0 group-hover:opacity-100"} transition-opacity duration-200`,
1368
+ children: playingVideo === item.id ? /* @__PURE__ */ jsx(Pause, { className: "w-3 h-3" }) : /* @__PURE__ */ jsx(Play, { className: "w-3 h-3" })
1369
+ }
1370
+ ),
1371
+ /* @__PURE__ */ jsx(
1372
+ "button",
1373
+ {
1374
+ onClick: (e) => {
1375
+ e.stopPropagation();
1376
+ onItemSelect(item, true);
1377
+ },
1378
+ className: "w-6 h-6 rounded-full bg-purple-500/80 hover:bg-purple-500 flex items-center justify-center text-white text-xs opacity-0 group-hover:opacity-100 transition-opacity duration-200",
1379
+ children: /* @__PURE__ */ jsx(Plus, { className: "w-3 h-3" })
1380
+ }
1381
+ )
1382
+ ] })
1383
+ ]
1384
+ },
1385
+ item.id
1386
+ )) }),
1387
+ items.length === 0 && /* @__PURE__ */ jsx("div", { className: `${inputStyles.container} flex items-center justify-center h-24`, children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
1388
+ /* @__PURE__ */ jsx(WandSparkles, { className: "w-10 h-10 mx-auto mb-2 text-purple-500/50" }),
1389
+ /* @__PURE__ */ jsx("p", { className: inputStyles.label.base, children: "No videos found" }),
1390
+ searchQuery && /* @__PURE__ */ jsx("p", { className: inputStyles.label.small, children: "Try adjusting your search" })
1391
+ ] }) })
1392
+ ] })
1393
+ ] });
1394
+ }
1395
+ function VideoPanelContainer(props) {
1396
+ const {
1397
+ items,
1398
+ searchQuery,
1399
+ setSearchQuery,
1400
+ handleSelection,
1401
+ handleFileUpload,
1402
+ isLoading,
1403
+ acceptFileTypes
1404
+ } = useMediaPanel(
1405
+ "video",
1406
+ {
1407
+ selectedElement: props.selectedElement ?? null,
1408
+ addElement: props.addElement,
1409
+ updateElement: props.updateElement
1410
+ },
1411
+ props.videoResolution
1412
+ );
1413
+ return /* @__PURE__ */ jsx(
1414
+ VideoPanel,
1415
+ {
1416
+ items,
1417
+ searchQuery,
1418
+ onSearchChange: setSearchQuery,
1419
+ onItemSelect: handleSelection,
1420
+ onFileUpload: handleFileUpload,
1421
+ isLoading,
1422
+ acceptFileTypes
1423
+ }
1424
+ );
1425
+ }
1426
+ function TextPanel({
1427
+ textContent,
1428
+ fontSize,
1429
+ selectedFont,
1430
+ isBold,
1431
+ isItalic,
1432
+ textColor,
1433
+ strokeColor,
1434
+ applyShadow,
1435
+ shadowColor,
1436
+ strokeWidth,
1437
+ fonts,
1438
+ operation,
1439
+ setTextContent,
1440
+ setFontSize,
1441
+ setSelectedFont,
1442
+ setIsBold,
1443
+ setIsItalic,
1444
+ setTextColor,
1445
+ setStrokeColor,
1446
+ setApplyShadow,
1447
+ setShadowColor,
1448
+ setStrokeWidth,
1449
+ handleApplyChanges
1450
+ }) {
1451
+ return /* @__PURE__ */ jsxs("div", { className: inputStyles.panel.container, children: [
1452
+ /* @__PURE__ */ jsx("h3", { className: inputStyles.panel.title, children: "Text Element" }),
1453
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
1454
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Text Content" }),
1455
+ /* @__PURE__ */ jsx(
1456
+ "input",
1457
+ {
1458
+ type: "text",
1459
+ value: textContent,
1460
+ onChange: (e) => setTextContent(e.target.value),
1461
+ className: inputStyles.input.base
1462
+ }
1463
+ )
1464
+ ] }),
1465
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
1466
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Font Size" }),
1467
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.range.container, children: [
1468
+ /* @__PURE__ */ jsx(
1469
+ "input",
1470
+ {
1471
+ type: "range",
1472
+ min: "8",
1473
+ max: "120",
1474
+ value: fontSize,
1475
+ onChange: (e) => setFontSize(Number(e.target.value)),
1476
+ className: inputStyles.range.gradient
1477
+ }
1478
+ ),
1479
+ /* @__PURE__ */ jsxs("span", { className: inputStyles.range.value, children: [
1480
+ fontSize,
1481
+ "px"
1482
+ ] })
1483
+ ] })
1484
+ ] }),
1485
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
1486
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Font" }),
1487
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1488
+ /* @__PURE__ */ jsx(
1489
+ "select",
1490
+ {
1491
+ value: selectedFont,
1492
+ onChange: (e) => setSelectedFont(e.target.value),
1493
+ className: inputStyles.input.base,
1494
+ children: fonts.map((font) => /* @__PURE__ */ jsx("option", { value: font, children: font }, font))
1495
+ }
1496
+ ),
1497
+ /* @__PURE__ */ jsx(
1498
+ "button",
1499
+ {
1500
+ onClick: () => setIsBold(!isBold),
1501
+ className: `${inputStyles.toggle.base} ${isBold ? inputStyles.toggle.active : inputStyles.toggle.inactive} min-w-6`,
1502
+ children: /* @__PURE__ */ jsx("span", { className: "font-bold", children: "B" })
1503
+ }
1504
+ ),
1505
+ /* @__PURE__ */ jsx(
1506
+ "button",
1507
+ {
1508
+ onClick: () => setIsItalic(!isItalic),
1509
+ className: `${inputStyles.toggle.base} ${isItalic ? inputStyles.toggle.active : inputStyles.toggle.inactive} min-w-6`,
1510
+ children: /* @__PURE__ */ jsx("span", { className: "italic", children: "I" })
1511
+ }
1512
+ )
1513
+ ] })
1514
+ ] }),
1515
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
1516
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Colors" }),
1517
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1518
+ /* @__PURE__ */ jsxs("div", { children: [
1519
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.small, children: "Text Color" }),
1520
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.color.container, children: [
1521
+ /* @__PURE__ */ jsx(
1522
+ "input",
1523
+ {
1524
+ type: "color",
1525
+ value: textColor,
1526
+ onChange: (e) => setTextColor(e.target.value),
1527
+ className: inputStyles.color.picker
1528
+ }
1529
+ ),
1530
+ /* @__PURE__ */ jsx(
1531
+ "input",
1532
+ {
1533
+ type: "text",
1534
+ value: textColor,
1535
+ onChange: (e) => setTextColor(e.target.value),
1536
+ className: inputStyles.input.small
1537
+ }
1538
+ )
1539
+ ] })
1540
+ ] }),
1541
+ /* @__PURE__ */ jsxs("div", { children: [
1542
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.small, children: "Stroke Color" }),
1543
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.color.container, children: [
1544
+ /* @__PURE__ */ jsx(
1545
+ "input",
1546
+ {
1547
+ type: "color",
1548
+ value: strokeColor,
1549
+ onChange: (e) => setStrokeColor(e.target.value),
1550
+ className: inputStyles.color.picker
1551
+ }
1552
+ ),
1553
+ /* @__PURE__ */ jsx(
1554
+ "input",
1555
+ {
1556
+ type: "text",
1557
+ value: strokeColor,
1558
+ onChange: (e) => setStrokeColor(e.target.value),
1559
+ className: inputStyles.input.small
1560
+ }
1561
+ )
1562
+ ] })
1563
+ ] }),
1564
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.radio.container, children: [
1565
+ /* @__PURE__ */ jsx(
1566
+ "input",
1567
+ {
1568
+ type: "checkbox",
1569
+ id: "applyShadow",
1570
+ checked: applyShadow,
1571
+ onChange: (e) => setApplyShadow(e.target.checked),
1572
+ className: inputStyles.radio.base
1573
+ }
1574
+ ),
1575
+ /* @__PURE__ */ jsx("label", { htmlFor: "applyShadow", className: inputStyles.radio.label, children: "Apply Shadow" })
1576
+ ] }),
1577
+ applyShadow && /* @__PURE__ */ jsxs("div", { children: [
1578
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.small, children: "Shadow Color" }),
1579
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.color.container, children: [
1580
+ /* @__PURE__ */ jsx(
1581
+ "input",
1582
+ {
1583
+ type: "color",
1584
+ value: shadowColor,
1585
+ onChange: (e) => setShadowColor(e.target.value),
1586
+ className: inputStyles.color.picker
1587
+ }
1588
+ ),
1589
+ /* @__PURE__ */ jsx(
1590
+ "input",
1591
+ {
1592
+ type: "text",
1593
+ value: shadowColor,
1594
+ onChange: (e) => setShadowColor(e.target.value),
1595
+ className: inputStyles.input.small
1596
+ }
1597
+ )
1598
+ ] })
1599
+ ] })
1600
+ ] })
1601
+ ] }),
1602
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
1603
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Stroke Width" }),
1604
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.range.container, children: [
1605
+ /* @__PURE__ */ jsx(
1606
+ "input",
1607
+ {
1608
+ type: "range",
1609
+ min: "0",
1610
+ max: "2",
1611
+ step: 0.1,
1612
+ value: strokeWidth,
1613
+ onChange: (e) => setStrokeWidth(Number(e.target.value)),
1614
+ className: inputStyles.range.base
1615
+ }
1616
+ ),
1617
+ /* @__PURE__ */ jsx("span", { className: inputStyles.range.value, children: strokeWidth })
1618
+ ] })
1619
+ ] }),
1620
+ /* @__PURE__ */ jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsx("button", { onClick: handleApplyChanges, className: inputStyles.button.primary, children: operation }) })
1621
+ ] });
1622
+ }
1623
+ const DEFAULT_TEXT_PROPS = {
1624
+ text: "Sample",
1625
+ fontSize: 48,
1626
+ fontFamily: "Poppins",
1627
+ fontWeight: 400,
1628
+ fontStyle: "normal",
1629
+ textColor: "#ffffff",
1630
+ strokeColor: "#4d4d4d",
1631
+ strokeWidth: 0,
1632
+ applyShadow: false,
1633
+ shadowColor: "#000000",
1634
+ textAlign: "center",
1635
+ shadowOffset: [0, 0],
1636
+ shadowBlur: 2,
1637
+ shadowOpacity: 1
1638
+ };
1639
+ const useTextPanel = ({
1640
+ selectedElement,
1641
+ addElement,
1642
+ updateElement
1643
+ }) => {
1644
+ const [textContent, setTextContent] = useState(DEFAULT_TEXT_PROPS.text);
1645
+ const [fontSize, setFontSize] = useState(DEFAULT_TEXT_PROPS.fontSize);
1646
+ const [selectedFont, setSelectedFont] = useState(DEFAULT_TEXT_PROPS.fontFamily);
1647
+ const [isBold, setIsBold] = useState(DEFAULT_TEXT_PROPS.fontWeight === 700);
1648
+ const [isItalic, setIsItalic] = useState(DEFAULT_TEXT_PROPS.fontStyle === "italic");
1649
+ const [textColor, setTextColor] = useState(DEFAULT_TEXT_PROPS.textColor);
1650
+ const [strokeColor, setStrokeColor] = useState(DEFAULT_TEXT_PROPS.strokeColor);
1651
+ const [applyShadow, setApplyShadow] = useState(DEFAULT_TEXT_PROPS.applyShadow);
1652
+ const [shadowColor, setShadowColor] = useState(DEFAULT_TEXT_PROPS.shadowColor);
1653
+ const [strokeWidth, setStrokeWidth] = useState(DEFAULT_TEXT_PROPS.strokeWidth);
1654
+ const fonts = Object.values(AVAILABLE_TEXT_FONTS);
1655
+ const handleApplyChanges = async () => {
1656
+ let textElement;
1657
+ if (selectedElement instanceof TextElement) {
1658
+ textElement = selectedElement;
1659
+ textElement.setText(textContent);
1660
+ textElement.setFontSize(fontSize);
1661
+ textElement.setFontFamily(selectedFont);
1662
+ textElement.setFontWeight(isBold ? 700 : 400);
1663
+ textElement.setFontStyle(isItalic ? "italic" : "normal");
1664
+ textElement.setFill(textColor);
1665
+ textElement.setStrokeColor(strokeColor);
1666
+ textElement.setLineWidth(strokeWidth);
1667
+ textElement.setTextAlign(DEFAULT_TEXT_PROPS.textAlign);
1668
+ if (applyShadow) {
1669
+ textElement.setProps({
1670
+ ...textElement.getProps(),
1671
+ shadowColor,
1672
+ shadowOffset: DEFAULT_TEXT_PROPS.shadowOffset,
1673
+ shadowBlur: DEFAULT_TEXT_PROPS.shadowBlur,
1674
+ shadowOpacity: DEFAULT_TEXT_PROPS.shadowOpacity
1675
+ });
1676
+ } else {
1677
+ textElement.setProps({
1678
+ ...textElement.getProps(),
1679
+ shadowColor: void 0,
1680
+ shadowOffset: void 0,
1681
+ shadowBlur: void 0,
1682
+ shadowOpacity: void 0
1683
+ });
1684
+ }
1685
+ updateElement(textElement);
1686
+ } else {
1687
+ textElement = new TextElement(textContent).setFontSize(fontSize).setFontFamily(selectedFont).setFontWeight(isBold ? 700 : 400).setFontStyle(isItalic ? "italic" : "normal").setFill(textColor).setStrokeColor(strokeColor).setLineWidth(strokeWidth).setTextAlign("center");
1688
+ if (applyShadow) {
1689
+ textElement.setProps({
1690
+ ...textElement.getProps(),
1691
+ shadowColor,
1692
+ shadowOffset: DEFAULT_TEXT_PROPS.shadowOffset,
1693
+ shadowBlur: DEFAULT_TEXT_PROPS.shadowBlur,
1694
+ shadowOpacity: DEFAULT_TEXT_PROPS.shadowOpacity
1695
+ });
1696
+ }
1697
+ await addElement(textElement);
1698
+ }
1699
+ };
1700
+ useEffect(() => {
1701
+ if (selectedElement instanceof TextElement) {
1702
+ setTextContent(selectedElement.getText());
1703
+ const textProps = selectedElement.getProps();
1704
+ setSelectedFont(textProps.fontFamily ?? DEFAULT_TEXT_PROPS.fontFamily);
1705
+ setFontSize(textProps.fontSize ?? DEFAULT_TEXT_PROPS.fontSize);
1706
+ setIsBold(textProps.fontWeight === 700);
1707
+ setIsItalic(textProps.fontStyle === "italic");
1708
+ setTextColor(textProps.fill ?? DEFAULT_TEXT_PROPS.textColor);
1709
+ setStrokeColor(textProps.stroke ?? DEFAULT_TEXT_PROPS.strokeColor);
1710
+ setStrokeWidth(textProps.lineWidth ?? DEFAULT_TEXT_PROPS.strokeWidth);
1711
+ const hasShadow = textProps.shadowColor !== void 0;
1712
+ setApplyShadow(hasShadow);
1713
+ if (hasShadow) {
1714
+ setShadowColor(textProps.shadowColor ?? DEFAULT_TEXT_PROPS.shadowColor);
1715
+ }
1716
+ } else {
1717
+ setTextContent(DEFAULT_TEXT_PROPS.text);
1718
+ setFontSize(DEFAULT_TEXT_PROPS.fontSize);
1719
+ setSelectedFont(DEFAULT_TEXT_PROPS.fontFamily);
1720
+ setIsBold(DEFAULT_TEXT_PROPS.fontWeight === 700);
1721
+ setIsItalic(DEFAULT_TEXT_PROPS.fontStyle === "italic");
1722
+ setTextColor(DEFAULT_TEXT_PROPS.textColor);
1723
+ setStrokeColor(DEFAULT_TEXT_PROPS.strokeColor);
1724
+ setStrokeWidth(DEFAULT_TEXT_PROPS.strokeWidth);
1725
+ setApplyShadow(DEFAULT_TEXT_PROPS.applyShadow);
1726
+ setShadowColor(DEFAULT_TEXT_PROPS.shadowColor);
1727
+ }
1728
+ }, [selectedElement]);
1729
+ return {
1730
+ textContent,
1731
+ fontSize,
1732
+ selectedFont,
1733
+ isBold,
1734
+ isItalic,
1735
+ textColor,
1736
+ strokeColor,
1737
+ applyShadow,
1738
+ shadowColor,
1739
+ strokeWidth,
1740
+ fonts,
1741
+ operation: selectedElement instanceof TextElement ? "Apply Changes" : "Add Text",
1742
+ setTextContent,
1743
+ setFontSize,
1744
+ setSelectedFont,
1745
+ setIsBold,
1746
+ setIsItalic,
1747
+ setTextColor,
1748
+ setStrokeColor,
1749
+ setApplyShadow,
1750
+ setShadowColor,
1751
+ setStrokeWidth,
1752
+ handleApplyChanges
1753
+ };
1754
+ };
1755
+ function TextPanelContainer(props) {
1756
+ const textPanelProps = useTextPanel(props);
1757
+ return /* @__PURE__ */ jsx(TextPanel, { ...textPanelProps });
1758
+ }
1759
+ function IconPanel({
1760
+ icons,
1761
+ loading,
1762
+ hasMore,
1763
+ totalIcons,
1764
+ searchQuery,
1765
+ handleSearch,
1766
+ handleSelection,
1767
+ handleDownloadIcon,
1768
+ handleLoadMore
1769
+ }) {
1770
+ return /* @__PURE__ */ jsxs("div", { className: inputStyles.panel.container, children: [
1771
+ /* @__PURE__ */ jsx("h3", { className: inputStyles.panel.title, children: "Icon Library" }),
1772
+ /* @__PURE__ */ jsx("div", { className: inputStyles.container, children: /* @__PURE__ */ jsxs("div", { className: "relative mb-3", children: [
1773
+ /* @__PURE__ */ jsx(
1774
+ Search,
1775
+ {
1776
+ className: "absolute left-2.5 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400 z-10 pointer-events-none"
1777
+ }
1778
+ ),
1779
+ /* @__PURE__ */ jsx(
1780
+ "input",
1781
+ {
1782
+ type: "text",
1783
+ placeholder: "Search icons...",
1784
+ value: searchQuery,
1785
+ onChange: (e) => handleSearch(e.target.value),
1786
+ className: `${inputStyles.input.base} pl-8`
1787
+ }
1788
+ )
1789
+ ] }) }),
1790
+ loading && icons.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx(LoaderCircle, { className: "h-8 w-8 animate-spin text-purple-500" }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1791
+ totalIcons > 0 && /* @__PURE__ */ jsxs("div", { className: "mb-4 text-sm text-gray-400", children: [
1792
+ "Showing ",
1793
+ icons.length,
1794
+ " of ",
1795
+ totalIcons,
1796
+ " icons"
1797
+ ] }),
1798
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-3 mb-2 p-2", children: (icons || []).map((icon, index) => /* @__PURE__ */ jsxs("div", { className: "group relative cursor-pointer", children: [
1799
+ /* @__PURE__ */ jsx(
1800
+ "div",
1801
+ {
1802
+ onClick: () => handleSelection(icon),
1803
+ className: "w-16 h-16 flex items-center justify-center bg-neutral-700/50 border border-gray-600 rounded-lg hover:border-purple-500 hover:bg-neutral-700/70 transition-all duration-200 p-2",
1804
+ dangerouslySetInnerHTML: { __html: icon.svg }
1805
+ }
1806
+ ),
1807
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity duration-200 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1808
+ /* @__PURE__ */ jsx(
1809
+ "button",
1810
+ {
1811
+ onClick: (e) => {
1812
+ e.stopPropagation();
1813
+ handleSelection(icon);
1814
+ },
1815
+ className: "p-1.5 bg-purple-600 hover:bg-purple-700 rounded transition-colors duration-200",
1816
+ title: "Add to timeline",
1817
+ children: /* @__PURE__ */ jsx("span", { className: "text-white text-xs", children: "+" })
1818
+ }
1819
+ ),
1820
+ /* @__PURE__ */ jsx(
1821
+ "button",
1822
+ {
1823
+ onClick: (e) => {
1824
+ e.stopPropagation();
1825
+ handleDownloadIcon(icon);
1826
+ },
1827
+ className: "p-1.5 bg-purple-600 hover:bg-purple-700 rounded transition-colors duration-200",
1828
+ title: "Download SVG",
1829
+ children: /* @__PURE__ */ jsx(Download, { className: "w-3 h-3 text-white" })
1830
+ }
1831
+ )
1832
+ ] }) }),
1833
+ /* @__PURE__ */ jsx("div", { className: "absolute -bottom-8 left-1/2 transform -translate-x-1/2 bg-black/80 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-10", children: icon.name })
1834
+ ] }, index)) }),
1835
+ hasMore && /* @__PURE__ */ jsx(
1836
+ "button",
1837
+ {
1838
+ onClick: handleLoadMore,
1839
+ disabled: loading,
1840
+ className: `${inputStyles.button.primary} disabled:opacity-50 disabled:cursor-not-allowed`,
1841
+ children: loading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
1842
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }),
1843
+ "Loading..."
1844
+ ] }) : "Load More"
1845
+ }
1846
+ ),
1847
+ !loading && icons.length === 0 && searchQuery && /* @__PURE__ */ jsxs("div", { className: "text-center py-8 text-gray-400", children: [
1848
+ /* @__PURE__ */ jsx("p", { children: "No icons found" }),
1849
+ /* @__PURE__ */ jsx("p", { className: "text-sm mt-2", children: "Try a different search term" })
1850
+ ] })
1851
+ ] })
1852
+ ] });
1853
+ }
1854
+ const ICONS_PER_PAGE = 20;
1855
+ const useIconPanel = ({
1856
+ selectedElement,
1857
+ addElement,
1858
+ updateElement
1859
+ }) => {
1860
+ const [icons, setIcons] = useState([]);
1861
+ const [loading, setLoading] = useState(false);
1862
+ const [page, setPage] = useState(1);
1863
+ const [hasMore, setHasMore] = useState(true);
1864
+ const [totalIcons, setTotalIcons] = useState(0);
1865
+ const [searchQuery, setSearchQuery] = useState("");
1866
+ const currentQuery = useRef("");
1867
+ const fetchIcons = async (query, reset = false) => {
1868
+ try {
1869
+ setLoading(true);
1870
+ const newPage = reset ? 1 : page;
1871
+ const start = (newPage - 1) * ICONS_PER_PAGE;
1872
+ const url = `https://api.iconify.design/search?query=${query}&limit=${ICONS_PER_PAGE}&offset=${start}`;
1873
+ const response = await fetch(url);
1874
+ const data = await response.json();
1875
+ const iconData = data.icons || [];
1876
+ const total = data.total || 0;
1877
+ setTotalIcons(total);
1878
+ const formattedIcons = await Promise.all(
1879
+ iconData.map(async (icon) => {
1880
+ const svgUrl = `https://api.iconify.design/${icon}.svg`;
1881
+ try {
1882
+ const svgResponse = await fetch(svgUrl);
1883
+ const svg = await svgResponse.text();
1884
+ return { name: icon, svg };
1885
+ } catch (e) {
1886
+ console.error(`Error fetching SVG for ${icon}:`, e);
1887
+ return null;
1888
+ }
1889
+ })
1890
+ );
1891
+ const validIcons = formattedIcons.filter((icon) => icon !== null);
1892
+ if (reset) {
1893
+ setIcons(validIcons);
1894
+ } else {
1895
+ setIcons([...icons, ...validIcons]);
1896
+ }
1897
+ setHasMore(start + validIcons.length < total);
1898
+ if (!reset) {
1899
+ setPage(newPage + 1);
1900
+ } else {
1901
+ setPage(2);
1902
+ }
1903
+ } catch (error) {
1904
+ console.error("Error fetching icons:", error);
1905
+ } finally {
1906
+ setLoading(false);
1907
+ }
1908
+ };
1909
+ useEffect(() => {
1910
+ fetchIcons("media", true);
1911
+ }, []);
1912
+ const handleSearch = (query) => {
1913
+ currentQuery.current = query;
1914
+ setSearchQuery(query);
1915
+ fetchIcons(query, true);
1916
+ };
1917
+ const handleSelection = (icon) => {
1918
+ const svgBlob = new Blob([icon.svg], { type: "image/svg+xml" });
1919
+ const url = URL.createObjectURL(svgBlob);
1920
+ let iconElement;
1921
+ if (selectedElement instanceof IconElement) {
1922
+ iconElement = selectedElement;
1923
+ iconElement.setSrc(url);
1924
+ iconElement.setName(icon.name);
1925
+ updateElement == null ? void 0 : updateElement(iconElement);
1926
+ } else {
1927
+ iconElement = new IconElement(url, {
1928
+ width: 100,
1929
+ height: 100
1930
+ });
1931
+ iconElement.setName(icon.name);
1932
+ addElement == null ? void 0 : addElement(iconElement);
1933
+ }
1934
+ URL.revokeObjectURL(url);
1935
+ };
1936
+ const handleDownloadIcon = (icon) => {
1937
+ const blob = new Blob([icon.svg], { type: "image/svg+xml" });
1938
+ const url = URL.createObjectURL(blob);
1939
+ const a = document.createElement("a");
1940
+ a.href = url;
1941
+ a.download = `${icon.name}.svg`;
1942
+ document.body.appendChild(a);
1943
+ a.click();
1944
+ document.body.removeChild(a);
1945
+ URL.revokeObjectURL(url);
1946
+ };
1947
+ const handleLoadMore = () => {
1948
+ fetchIcons(currentQuery.current, false);
1949
+ };
1950
+ return {
1951
+ icons,
1952
+ loading,
1953
+ hasMore,
1954
+ totalIcons,
1955
+ searchQuery,
1956
+ handleSearch,
1957
+ handleSelection,
1958
+ handleDownloadIcon,
1959
+ handleLoadMore
1960
+ };
1961
+ };
1962
+ function IconPanelContainer(props) {
1963
+ const iconPanelProps = useIconPanel({
1964
+ selectedElement: props.selectedElement ?? null,
1965
+ addElement: props.addElement,
1966
+ updateElement: props.updateElement
1967
+ });
1968
+ return /* @__PURE__ */ jsx(IconPanel, { ...iconPanelProps });
1969
+ }
1970
+ function RectPanel({
1971
+ cornerRadius,
1972
+ fillColor,
1973
+ opacity,
1974
+ strokeColor,
1975
+ lineWidth,
1976
+ setCornerRadius,
1977
+ setFillColor,
1978
+ setOpacity,
1979
+ setStrokeColor,
1980
+ setLineWidth,
1981
+ handleApplyChanges
1982
+ }) {
1983
+ return /* @__PURE__ */ jsxs("div", { className: inputStyles.panel.container, children: [
1984
+ /* @__PURE__ */ jsx("h3", { className: inputStyles.panel.title, children: "Rectangle" }),
1985
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
1986
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Corner Radius" }),
1987
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.range.container, children: [
1988
+ /* @__PURE__ */ jsx(
1989
+ "input",
1990
+ {
1991
+ type: "range",
1992
+ min: "0",
1993
+ max: "100",
1994
+ value: cornerRadius,
1995
+ onChange: (e) => setCornerRadius(Number(e.target.value)),
1996
+ className: inputStyles.range.base
1997
+ }
1998
+ ),
1999
+ /* @__PURE__ */ jsxs("span", { className: inputStyles.range.value, children: [
2000
+ cornerRadius,
2001
+ "px"
2002
+ ] })
2003
+ ] })
2004
+ ] }),
2005
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
2006
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Fill Color" }),
2007
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.color.container, children: [
2008
+ /* @__PURE__ */ jsx(
2009
+ "input",
2010
+ {
2011
+ type: "color",
2012
+ value: fillColor,
2013
+ onChange: (e) => setFillColor(e.target.value),
2014
+ className: inputStyles.color.picker
2015
+ }
2016
+ ),
2017
+ /* @__PURE__ */ jsx(
2018
+ "div",
2019
+ {
2020
+ className: inputStyles.color.preview,
2021
+ style: { backgroundColor: fillColor }
2022
+ }
2023
+ )
2024
+ ] })
2025
+ ] }),
2026
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
2027
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Opacity" }),
2028
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.range.container, children: [
2029
+ /* @__PURE__ */ jsx(
2030
+ "input",
2031
+ {
2032
+ type: "range",
2033
+ min: "0",
2034
+ max: "100",
2035
+ value: opacity,
2036
+ onChange: (e) => setOpacity(Number(e.target.value)),
2037
+ className: inputStyles.range.gradient
2038
+ }
2039
+ ),
2040
+ /* @__PURE__ */ jsxs("span", { className: inputStyles.range.value, children: [
2041
+ opacity,
2042
+ "%"
2043
+ ] })
2044
+ ] })
2045
+ ] }),
2046
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
2047
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Stroke Color" }),
2048
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.color.container, children: [
2049
+ /* @__PURE__ */ jsx(
2050
+ "input",
2051
+ {
2052
+ type: "color",
2053
+ value: strokeColor,
2054
+ onChange: (e) => setStrokeColor(e.target.value),
2055
+ className: inputStyles.color.picker
2056
+ }
2057
+ ),
2058
+ /* @__PURE__ */ jsx(
2059
+ "div",
2060
+ {
2061
+ className: inputStyles.color.preview,
2062
+ style: { backgroundColor: strokeColor }
2063
+ }
2064
+ )
2065
+ ] })
2066
+ ] }),
2067
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
2068
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Line Width" }),
2069
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.range.container, children: [
2070
+ /* @__PURE__ */ jsx(
2071
+ "input",
2072
+ {
2073
+ type: "range",
2074
+ min: "0",
2075
+ max: "20",
2076
+ value: lineWidth,
2077
+ onChange: (e) => setLineWidth(Number(e.target.value)),
2078
+ className: inputStyles.range.base
2079
+ }
2080
+ ),
2081
+ /* @__PURE__ */ jsxs("span", { className: inputStyles.range.value, children: [
2082
+ lineWidth,
2083
+ "px"
2084
+ ] })
2085
+ ] })
2086
+ ] }),
2087
+ /* @__PURE__ */ jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsx(
2088
+ "button",
2089
+ {
2090
+ onClick: handleApplyChanges,
2091
+ className: inputStyles.button.primary,
2092
+ children: "Apply Changes"
2093
+ }
2094
+ ) })
2095
+ ] });
2096
+ }
2097
+ const DEFAULT_RECT_PROPS = {
2098
+ cornerRadius: 0,
2099
+ fillColor: "#3b82f6",
2100
+ opacity: 100,
2101
+ strokeColor: "#000000",
2102
+ lineWidth: 0
2103
+ };
2104
+ const useRectPanel = ({
2105
+ selectedElement,
2106
+ addElement,
2107
+ updateElement
2108
+ }) => {
2109
+ const [cornerRadius, setCornerRadius] = useState(DEFAULT_RECT_PROPS.cornerRadius);
2110
+ const [fillColor, setFillColor] = useState(DEFAULT_RECT_PROPS.fillColor);
2111
+ const [opacity, setOpacity] = useState(DEFAULT_RECT_PROPS.opacity);
2112
+ const [strokeColor, setStrokeColor] = useState(DEFAULT_RECT_PROPS.strokeColor);
2113
+ const [lineWidth, setLineWidth] = useState(DEFAULT_RECT_PROPS.lineWidth);
2114
+ const handleApplyChanges = () => {
2115
+ let rectElement;
2116
+ if (selectedElement instanceof RectElement) {
2117
+ rectElement = selectedElement;
2118
+ rectElement.setCornerRadius(cornerRadius);
2119
+ rectElement.setOpacity(opacity);
2120
+ rectElement.setStrokeColor(strokeColor);
2121
+ rectElement.setLineWidth(lineWidth);
2122
+ updateElement == null ? void 0 : updateElement(rectElement);
2123
+ } else {
2124
+ rectElement = new RectElement(fillColor, { width: 200, height: 200 }).setCornerRadius(cornerRadius).setOpacity(opacity).setStrokeColor(strokeColor).setLineWidth(lineWidth);
2125
+ addElement == null ? void 0 : addElement(rectElement);
2126
+ }
2127
+ };
2128
+ useEffect(() => {
2129
+ if (selectedElement instanceof RectElement) {
2130
+ setCornerRadius(selectedElement.getCornerRadius() ?? DEFAULT_RECT_PROPS.cornerRadius);
2131
+ setFillColor(selectedElement.getFill() ?? DEFAULT_RECT_PROPS.fillColor);
2132
+ setOpacity(selectedElement.getOpacity() ?? DEFAULT_RECT_PROPS.opacity);
2133
+ setStrokeColor(selectedElement.getStrokeColor() ?? DEFAULT_RECT_PROPS.strokeColor);
2134
+ setLineWidth(selectedElement.getLineWidth() ?? DEFAULT_RECT_PROPS.lineWidth);
2135
+ }
2136
+ }, [selectedElement]);
2137
+ return {
2138
+ cornerRadius,
2139
+ fillColor,
2140
+ opacity,
2141
+ strokeColor,
2142
+ lineWidth,
2143
+ setCornerRadius,
2144
+ setFillColor,
2145
+ setOpacity,
2146
+ setStrokeColor,
2147
+ setLineWidth,
2148
+ handleApplyChanges
2149
+ };
2150
+ };
2151
+ function RectPanelContainer(props) {
2152
+ const rectPanelProps = useRectPanel({
2153
+ selectedElement: props.selectedElement ?? null,
2154
+ addElement: props.addElement,
2155
+ updateElement: props.updateElement
2156
+ });
2157
+ return /* @__PURE__ */ jsx(RectPanel, { ...rectPanelProps });
2158
+ }
2159
+ function CirclePanel({
2160
+ radius,
2161
+ fillColor,
2162
+ opacity,
2163
+ strokeColor,
2164
+ lineWidth,
2165
+ setRadius,
2166
+ setFillColor,
2167
+ setOpacity,
2168
+ setStrokeColor,
2169
+ setLineWidth,
2170
+ handleApplyChanges
2171
+ }) {
2172
+ return /* @__PURE__ */ jsxs("div", { className: inputStyles.panel.container, children: [
2173
+ /* @__PURE__ */ jsx("h3", { className: inputStyles.panel.title, children: "Circle" }),
2174
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
2175
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Radius" }),
2176
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.range.container, children: [
2177
+ /* @__PURE__ */ jsx(
2178
+ "input",
2179
+ {
2180
+ type: "range",
2181
+ min: "10",
2182
+ max: "300",
2183
+ value: radius,
2184
+ onChange: (e) => setRadius(Number(e.target.value)),
2185
+ className: inputStyles.range.base
2186
+ }
2187
+ ),
2188
+ /* @__PURE__ */ jsxs("span", { className: inputStyles.range.value, children: [
2189
+ radius,
2190
+ "px"
2191
+ ] })
2192
+ ] })
2193
+ ] }),
2194
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
2195
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Fill Color" }),
2196
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.color.container, children: [
2197
+ /* @__PURE__ */ jsx(
2198
+ "input",
2199
+ {
2200
+ type: "color",
2201
+ value: fillColor,
2202
+ onChange: (e) => setFillColor(e.target.value),
2203
+ className: inputStyles.color.picker
2204
+ }
2205
+ ),
2206
+ /* @__PURE__ */ jsx(
2207
+ "div",
2208
+ {
2209
+ className: inputStyles.color.preview,
2210
+ style: { backgroundColor: fillColor }
2211
+ }
2212
+ )
2213
+ ] })
2214
+ ] }),
2215
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
2216
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Opacity" }),
2217
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.range.container, children: [
2218
+ /* @__PURE__ */ jsx(
2219
+ "input",
2220
+ {
2221
+ type: "range",
2222
+ min: "0",
2223
+ max: "100",
2224
+ value: opacity,
2225
+ onChange: (e) => setOpacity(Number(e.target.value)),
2226
+ className: inputStyles.range.gradient
2227
+ }
2228
+ ),
2229
+ /* @__PURE__ */ jsxs("span", { className: inputStyles.range.value, children: [
2230
+ opacity,
2231
+ "%"
2232
+ ] })
2233
+ ] })
2234
+ ] }),
2235
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
2236
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Stroke Color" }),
2237
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.color.container, children: [
2238
+ /* @__PURE__ */ jsx(
2239
+ "input",
2240
+ {
2241
+ type: "color",
2242
+ value: strokeColor,
2243
+ onChange: (e) => setStrokeColor(e.target.value),
2244
+ className: inputStyles.color.picker
2245
+ }
2246
+ ),
2247
+ /* @__PURE__ */ jsx(
2248
+ "div",
2249
+ {
2250
+ className: inputStyles.color.preview,
2251
+ style: { backgroundColor: strokeColor }
2252
+ }
2253
+ )
2254
+ ] })
2255
+ ] }),
2256
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.container, children: [
2257
+ /* @__PURE__ */ jsx("label", { className: inputStyles.label.base, children: "Line Width" }),
2258
+ /* @__PURE__ */ jsxs("div", { className: inputStyles.range.container, children: [
2259
+ /* @__PURE__ */ jsx(
2260
+ "input",
2261
+ {
2262
+ type: "range",
2263
+ min: "0",
2264
+ max: "20",
2265
+ value: lineWidth,
2266
+ onChange: (e) => setLineWidth(Number(e.target.value)),
2267
+ className: inputStyles.range.base
2268
+ }
2269
+ ),
2270
+ /* @__PURE__ */ jsxs("span", { className: inputStyles.range.value, children: [
2271
+ lineWidth,
2272
+ "px"
2273
+ ] })
2274
+ ] })
2275
+ ] }),
2276
+ /* @__PURE__ */ jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsx(
2277
+ "button",
2278
+ {
2279
+ onClick: handleApplyChanges,
2280
+ className: inputStyles.button.primary,
2281
+ children: "Apply Changes"
2282
+ }
2283
+ ) })
2284
+ ] });
2285
+ }
2286
+ const DEFAULT_CIRCLE_PROPS = {
2287
+ radius: 50,
2288
+ fillColor: "#3b82f6",
2289
+ opacity: 100,
2290
+ strokeColor: "#000000",
2291
+ lineWidth: 0
2292
+ };
2293
+ const useCirclePanel = ({
2294
+ selectedElement,
2295
+ addElement,
2296
+ updateElement
2297
+ }) => {
2298
+ const [radius, setRadius] = useState(DEFAULT_CIRCLE_PROPS.radius);
2299
+ const [fillColor, setFillColor] = useState(DEFAULT_CIRCLE_PROPS.fillColor);
2300
+ const [opacity, setOpacity] = useState(DEFAULT_CIRCLE_PROPS.opacity);
2301
+ const [strokeColor, setStrokeColor] = useState(DEFAULT_CIRCLE_PROPS.strokeColor);
2302
+ const [lineWidth, setLineWidth] = useState(DEFAULT_CIRCLE_PROPS.lineWidth);
2303
+ const handleApplyChanges = () => {
2304
+ let circleElement;
2305
+ if (selectedElement instanceof CircleElement) {
2306
+ circleElement = selectedElement;
2307
+ circleElement.setRadius(radius);
2308
+ circleElement.setFill(fillColor);
2309
+ circleElement.setOpacity(opacity);
2310
+ circleElement.setStrokeColor(strokeColor);
2311
+ circleElement.setLineWidth(lineWidth);
2312
+ updateElement == null ? void 0 : updateElement(circleElement);
2313
+ } else {
2314
+ circleElement = new CircleElement(fillColor, radius).setOpacity(opacity).setStrokeColor(strokeColor).setLineWidth(lineWidth);
2315
+ addElement == null ? void 0 : addElement(circleElement);
2316
+ }
2317
+ };
2318
+ useEffect(() => {
2319
+ if (selectedElement instanceof CircleElement) {
2320
+ setRadius(selectedElement.getRadius() ?? DEFAULT_CIRCLE_PROPS.radius);
2321
+ setFillColor(selectedElement.getFill() ?? DEFAULT_CIRCLE_PROPS.fillColor);
2322
+ setOpacity(selectedElement.getOpacity() ?? DEFAULT_CIRCLE_PROPS.opacity);
2323
+ setStrokeColor(selectedElement.getStrokeColor() ?? DEFAULT_CIRCLE_PROPS.strokeColor);
2324
+ setLineWidth(selectedElement.getLineWidth() ?? DEFAULT_CIRCLE_PROPS.lineWidth);
2325
+ }
2326
+ }, [selectedElement]);
2327
+ return {
2328
+ radius,
2329
+ fillColor,
2330
+ opacity,
2331
+ strokeColor,
2332
+ lineWidth,
2333
+ setRadius,
2334
+ setFillColor,
2335
+ setOpacity,
2336
+ setStrokeColor,
2337
+ setLineWidth,
2338
+ handleApplyChanges
2339
+ };
2340
+ };
2341
+ function CirclePanelContainer(props) {
2342
+ const circlePanelProps = useCirclePanel({
2343
+ selectedElement: props.selectedElement ?? null,
2344
+ addElement: props.addElement,
2345
+ updateElement: props.updateElement
2346
+ });
2347
+ return /* @__PURE__ */ jsx(CirclePanel, { ...circlePanelProps });
2348
+ }
2349
+ const ElementPanelContainer = ({
2350
+ selectedTool,
2351
+ setSelectedTool,
2352
+ videoResolution,
2353
+ selectedElement,
2354
+ addElement,
2355
+ updateElement
2356
+ }) => {
2357
+ const addNewElement = async (element) => {
2358
+ await addElement(element);
2359
+ if (!["image", "video", "audio"].includes(selectedTool)) {
2360
+ setSelectedTool("none");
2361
+ }
2362
+ };
2363
+ const renderLibrary = () => {
2364
+ switch (selectedTool) {
2365
+ case "image":
2366
+ return /* @__PURE__ */ jsx(
2367
+ ImagePanelContainer,
2368
+ {
2369
+ videoResolution,
2370
+ selectedElement,
2371
+ addElement: addNewElement,
2372
+ updateElement
2373
+ }
2374
+ );
2375
+ case "audio":
2376
+ return /* @__PURE__ */ jsx(
2377
+ AudioPanelContainer,
2378
+ {
2379
+ videoResolution,
2380
+ selectedElement,
2381
+ addElement: addNewElement,
2382
+ updateElement
2383
+ }
2384
+ );
2385
+ case "video":
2386
+ return /* @__PURE__ */ jsx(
2387
+ VideoPanelContainer,
2388
+ {
2389
+ videoResolution,
2390
+ selectedElement,
2391
+ addElement: addNewElement,
2392
+ updateElement
2393
+ }
2394
+ );
2395
+ case "text":
2396
+ return /* @__PURE__ */ jsx(
2397
+ TextPanelContainer,
2398
+ {
2399
+ selectedElement,
2400
+ addElement: addNewElement,
2401
+ updateElement
2402
+ }
2403
+ );
2404
+ case "icon":
2405
+ return /* @__PURE__ */ jsx(
2406
+ IconPanelContainer,
2407
+ {
2408
+ videoResolution,
2409
+ selectedElement,
2410
+ addElement: addNewElement,
2411
+ updateElement
2412
+ }
2413
+ );
2414
+ case "rect":
2415
+ return /* @__PURE__ */ jsx(
2416
+ RectPanelContainer,
2417
+ {
2418
+ videoResolution,
2419
+ selectedElement,
2420
+ addElement: addNewElement,
2421
+ updateElement
2422
+ }
2423
+ );
2424
+ case "circle":
2425
+ return /* @__PURE__ */ jsx(
2426
+ CirclePanelContainer,
2427
+ {
2428
+ videoResolution,
2429
+ selectedElement,
2430
+ addElement: addNewElement,
2431
+ updateElement
2432
+ }
2433
+ );
2434
+ case "subtitle":
2435
+ return /* @__PURE__ */ jsx(SubtitlesPanel, {});
2436
+ default:
2437
+ return /* @__PURE__ */ jsx("div", { className: inputStyles.panel.container, children: /* @__PURE__ */ jsx("div", { className: "flex-1 flex h-full items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2438
+ /* @__PURE__ */ jsx(WandSparkles, { className: "w-12 h-12 mx-auto mb-4 text-purple-500/50" }),
2439
+ /* @__PURE__ */ jsx("p", { className: inputStyles.label.base, children: "Select a Tool to Begin" })
2440
+ ] }) }) });
2441
+ }
2442
+ };
2443
+ return renderLibrary();
2444
+ };
2445
+ const propsCategories = /* @__PURE__ */ new Map([
2446
+ [
2447
+ "element-props",
2448
+ {
2449
+ id: "element-props",
2450
+ name: "Properties",
2451
+ icon: "Settings",
2452
+ description: "Element Properties"
2453
+ }
2454
+ ],
2455
+ [
2456
+ "animations",
2457
+ {
2458
+ id: "animations",
2459
+ name: "Animations",
2460
+ icon: "Zap",
2461
+ description: "Animations"
2462
+ }
2463
+ ],
2464
+ [
2465
+ "text-effects",
2466
+ {
2467
+ id: "text-effects",
2468
+ name: "Text Effects",
2469
+ icon: "SparklesIcon",
2470
+ description: "Text Effects"
2471
+ }
2472
+ ],
2473
+ [
2474
+ "color-effects",
2475
+ {
2476
+ id: "color-effects",
2477
+ name: "Color Effects",
2478
+ icon: "Image",
2479
+ description: "Color Effects"
2480
+ }
2481
+ ],
2482
+ [
2483
+ "playback-props",
2484
+ {
2485
+ id: "playback-props",
2486
+ name: "Playback Props",
2487
+ icon: "Music",
2488
+ description: "Playback Properties"
2489
+ }
2490
+ ],
2491
+ [
2492
+ "subtitle-style",
2493
+ {
2494
+ id: "subtitle-style",
2495
+ name: "Subtitle Style",
2496
+ icon: "MessageSquare",
2497
+ description: "Subtitle Style"
2498
+ }
2499
+ ]
2500
+ ]);
2501
+ const getIcon = (iconName) => {
2502
+ switch (iconName) {
2503
+ case "Type":
2504
+ return Type;
2505
+ case "Infinity":
2506
+ return Infinity;
2507
+ case "Image":
2508
+ return Image;
2509
+ case "Music":
2510
+ return Music;
2511
+ case "MessageSquare":
2512
+ return MessageSquare;
2513
+ case "Settings":
2514
+ return Settings;
2515
+ case "SparklesIcon":
2516
+ return Sparkles;
2517
+ case "Zap":
2518
+ return Zap;
2519
+ default:
2520
+ return Plus;
2521
+ }
2522
+ };
2523
+ function PropsToolbar({
2524
+ selectedElement,
2525
+ selectedProp,
2526
+ setSelectedProp
2527
+ }) {
2528
+ const availableSections = useMemo(() => {
2529
+ const sections = [];
2530
+ if (selectedElement instanceof TextElement) {
2531
+ sections.push(propsCategories.get("element-props"));
2532
+ sections.push(propsCategories.get("animations"));
2533
+ sections.push(propsCategories.get("text-effects"));
2534
+ } else if (selectedElement instanceof ImageElement) {
2535
+ sections.push(propsCategories.get("element-props"));
2536
+ sections.push(propsCategories.get("animations"));
2537
+ sections.push(propsCategories.get("color-effects"));
2538
+ } else if (selectedElement instanceof VideoElement) {
2539
+ sections.push(propsCategories.get("element-props"));
2540
+ sections.push(propsCategories.get("animations"));
2541
+ sections.push(propsCategories.get("color-effects"));
2542
+ sections.push(propsCategories.get("playback-props"));
2543
+ } else if (selectedElement instanceof AudioElement) {
2544
+ sections.push(propsCategories.get("element-props"));
2545
+ sections.push(propsCategories.get("playback-props"));
2546
+ } else if (selectedElement instanceof CircleElement) {
2547
+ sections.push(propsCategories.get("element-props"));
2548
+ sections.push(propsCategories.get("animations"));
2549
+ } else if (selectedElement instanceof RectElement) {
2550
+ sections.push(propsCategories.get("element-props"));
2551
+ sections.push(propsCategories.get("animations"));
2552
+ } else if (selectedElement instanceof IconElement) {
2553
+ sections.push(propsCategories.get("element-props"));
2554
+ sections.push(propsCategories.get("animations"));
2555
+ } else if (selectedElement instanceof CaptionElement) {
2556
+ sections.push(propsCategories.get("element-props"));
2557
+ sections.push(propsCategories.get("animations"));
2558
+ sections.push(propsCategories.get("subtitle-style"));
2559
+ }
2560
+ return sections;
2561
+ }, [selectedElement]);
2562
+ if (!selectedElement) {
2563
+ return null;
2564
+ }
2565
+ return /* @__PURE__ */ jsx("div", { className: "w-16 bg-neutral/80 border-l border-gray-300/50 flex flex-col items-center py-4 space-y-3 justify-start backdrop-blur-md shadow-lg", children: availableSections.map((tool) => {
2566
+ const Icon2 = getIcon(tool.icon);
2567
+ const isSelected = selectedProp === tool.id;
2568
+ return /* @__PURE__ */ jsxs(
2569
+ "button",
2570
+ {
2571
+ onClick: () => setSelectedProp(tool.id),
2572
+ className: `
2573
+ props-toolbar-btn group ${isSelected ? "active" : ""}
2574
+ `,
2575
+ title: `${tool.name}${tool.shortcut ? ` (${tool.shortcut})` : ""}`,
2576
+ children: [
2577
+ /* @__PURE__ */ jsx(Icon2, { className: "w-4 h-4" }),
2578
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] mt-1 transition-opacity duration-200", children: tool.name })
2579
+ ]
2580
+ },
2581
+ tool.id
2582
+ );
2583
+ }) });
2584
+ }
2585
+ function ElementProps({ selectedElement, updateElement }) {
2586
+ const elementProps = (selectedElement == null ? void 0 : selectedElement.getProps()) || {};
2587
+ const { x, y, opacity, rotation } = elementProps;
2588
+ const handleUpdateElement = (props) => {
2589
+ if (selectedElement) {
2590
+ updateElement == null ? void 0 : updateElement(selectedElement == null ? void 0 : selectedElement.setProps({ ...elementProps, ...props }));
2591
+ }
2592
+ };
2593
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
2594
+ /* @__PURE__ */ jsxs("div", { className: "bg-neutral-800/40 rounded-lg p-2.5 border border-gray-600/20", children: [
2595
+ /* @__PURE__ */ jsxs("h5", { className: "text-xs font-semibold text-gray-200 mb-2 flex items-center gap-1.5", children: [
2596
+ /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-purple-400 rounded-full" }),
2597
+ "Position & Size"
2598
+ ] }),
2599
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
2600
+ /* @__PURE__ */ jsxs("div", { children: [
2601
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "X" }),
2602
+ /* @__PURE__ */ jsx(
2603
+ "input",
2604
+ {
2605
+ type: "number",
2606
+ value: x ?? 0,
2607
+ onChange: (e) => handleUpdateElement({ x: Number(e.target.value) }),
2608
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200"
2609
+ }
2610
+ )
2611
+ ] }),
2612
+ /* @__PURE__ */ jsxs("div", { children: [
2613
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "Y" }),
2614
+ /* @__PURE__ */ jsx(
2615
+ "input",
2616
+ {
2617
+ type: "number",
2618
+ value: y ?? 0,
2619
+ onChange: (e) => handleUpdateElement({ y: Number(e.target.value) }),
2620
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200"
2621
+ }
2622
+ )
2623
+ ] })
2624
+ ] })
2625
+ ] }),
2626
+ /* @__PURE__ */ jsxs("div", { className: "bg-neutral-800/40 rounded-lg p-2.5 border border-gray-600/20", children: [
2627
+ /* @__PURE__ */ jsxs("h5", { className: "text-xs font-semibold text-gray-200 mb-2 flex items-center gap-1.5", children: [
2628
+ /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-purple-400 rounded-full" }),
2629
+ "Opacity"
2630
+ ] }),
2631
+ /* @__PURE__ */ jsx(
2632
+ "input",
2633
+ {
2634
+ type: "range",
2635
+ min: "0",
2636
+ max: "100",
2637
+ value: (opacity ?? 1) * 100,
2638
+ onChange: (e) => handleUpdateElement({ opacity: Number(e.target.value) / 100 }),
2639
+ className: "w-full h-1.5 bg-gradient-to-r from-purple-500/30 to-neutral-600/50 rounded-full appearance-none cursor-pointer slider-thumb"
2640
+ }
2641
+ ),
2642
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-xs text-gray-400 mt-1", children: [
2643
+ /* @__PURE__ */ jsx("span", { children: "0%" }),
2644
+ /* @__PURE__ */ jsx("span", { children: "100%" })
2645
+ ] })
2646
+ ] }),
2647
+ /* @__PURE__ */ jsxs("div", { className: "bg-neutral-800/40 rounded-lg p-2.5 border border-gray-600/20", children: [
2648
+ /* @__PURE__ */ jsxs("h5", { className: "text-xs font-semibold text-gray-200 mb-2 flex items-center gap-1.5", children: [
2649
+ /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-purple-400 rounded-full" }),
2650
+ "Rotation"
2651
+ ] }),
2652
+ /* @__PURE__ */ jsx(
2653
+ "input",
2654
+ {
2655
+ type: "range",
2656
+ min: "0",
2657
+ max: "360",
2658
+ value: rotation ?? 0,
2659
+ onChange: (e) => handleUpdateElement({ rotation: Number(e.target.value) }),
2660
+ className: "w-full h-1.5 bg-gradient-to-r from-purple-500/30 to-neutral-600/50 rounded-full appearance-none cursor-pointer slider-thumb"
2661
+ }
2662
+ ),
2663
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-xs text-gray-400 mt-1", children: [
2664
+ /* @__PURE__ */ jsx("span", { children: "0°" }),
2665
+ /* @__PURE__ */ jsx("span", { children: "360°" })
2666
+ ] })
2667
+ ] })
2668
+ ] });
2669
+ }
2670
+ function TextEffects({
2671
+ selectedElement,
2672
+ updateElement
2673
+ }) {
2674
+ if (!(selectedElement instanceof TextElement)) return null;
2675
+ const currentEffect = selectedElement.getTextEffect();
2676
+ const handleUpdateEffect = (props) => {
2677
+ if (!selectedElement || !(selectedElement instanceof TextElement)) return;
2678
+ let effect = currentEffect;
2679
+ if (props.name === "") {
2680
+ selectedElement.setTextEffect(void 0);
2681
+ updateElement == null ? void 0 : updateElement(selectedElement);
2682
+ return;
2683
+ }
2684
+ if (!effect || props.name && props.name !== effect.getName()) {
2685
+ effect = new ElementTextEffect(
2686
+ props.name || (currentEffect == null ? void 0 : currentEffect.getName()) || TEXT_EFFECTS[0].name
2687
+ );
2688
+ effect.setDelay(0);
2689
+ effect.setDuration(1);
2690
+ effect.setBufferTime(0.1);
2691
+ }
2692
+ if (props.delay !== void 0) effect.setDelay(props.delay);
2693
+ if (props.duration !== void 0) effect.setDuration(props.duration);
2694
+ if (props.bufferTime !== void 0) effect.setBufferTime(props.bufferTime);
2695
+ selectedElement.setTextEffect(effect);
2696
+ updateElement == null ? void 0 : updateElement(selectedElement);
2697
+ };
2698
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
2699
+ /* @__PURE__ */ jsxs("div", { className: "bg-neutral-800/40 rounded-lg p-2.5 border border-gray-600/20", children: [
2700
+ /* @__PURE__ */ jsxs("h5", { className: "text-xs font-semibold text-gray-200 mb-2 flex items-center gap-1.5", children: [
2701
+ /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-purple-400 rounded-full" }),
2702
+ "Text Effect Type"
2703
+ ] }),
2704
+ /* @__PURE__ */ jsxs(
2705
+ "select",
2706
+ {
2707
+ value: (currentEffect == null ? void 0 : currentEffect.getName()) || "",
2708
+ onChange: (e) => handleUpdateEffect({ name: e.target.value }),
2709
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200",
2710
+ children: [
2711
+ /* @__PURE__ */ jsx("option", { value: "", children: "No Effect" }),
2712
+ TEXT_EFFECTS.map((effect) => /* @__PURE__ */ jsx("option", { value: effect.name, children: effect.name.charAt(0).toUpperCase() + effect.name.slice(1) }, effect.name))
2713
+ ]
2714
+ }
2715
+ )
2716
+ ] }),
2717
+ currentEffect && /* @__PURE__ */ jsxs("div", { className: "bg-neutral-800/40 rounded-lg p-2.5 border border-gray-600/20", children: [
2718
+ /* @__PURE__ */ jsxs("h5", { className: "text-xs font-semibold text-gray-200 mb-2 flex items-center gap-1.5", children: [
2719
+ /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-purple-400 rounded-full" }),
2720
+ "Effect Options"
2721
+ ] }),
2722
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2723
+ /* @__PURE__ */ jsxs("div", { children: [
2724
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "Delay (seconds)" }),
2725
+ /* @__PURE__ */ jsx(
2726
+ "input",
2727
+ {
2728
+ type: "number",
2729
+ min: "0",
2730
+ max: "5",
2731
+ step: "0.1",
2732
+ value: currentEffect.getDelay() ?? 0,
2733
+ onChange: (e) => handleUpdateEffect({ delay: Number(e.target.value) }),
2734
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200"
2735
+ }
2736
+ )
2737
+ ] }),
2738
+ /* @__PURE__ */ jsxs("div", { children: [
2739
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "Duration (seconds)" }),
2740
+ /* @__PURE__ */ jsx(
2741
+ "input",
2742
+ {
2743
+ type: "number",
2744
+ min: "0.1",
2745
+ max: "10",
2746
+ step: "0.1",
2747
+ value: currentEffect.getDuration() ?? 1,
2748
+ onChange: (e) => handleUpdateEffect({ duration: Number(e.target.value) }),
2749
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200"
2750
+ }
2751
+ )
2752
+ ] }),
2753
+ /* @__PURE__ */ jsxs("div", { children: [
2754
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "Buffer Time (seconds)" }),
2755
+ /* @__PURE__ */ jsx(
2756
+ "input",
2757
+ {
2758
+ type: "number",
2759
+ min: "0.05",
2760
+ max: "1",
2761
+ step: "0.05",
2762
+ value: currentEffect.getBufferTime() ?? 0.1,
2763
+ onChange: (e) => handleUpdateEffect({ bufferTime: Number(e.target.value) }),
2764
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200"
2765
+ }
2766
+ )
2767
+ ] })
2768
+ ] })
2769
+ ] })
2770
+ ] });
2771
+ }
2772
+ function Animation({
2773
+ selectedElement,
2774
+ updateElement
2775
+ }) {
2776
+ if (!(selectedElement instanceof TrackElement)) return null;
2777
+ const currentAnimation = selectedElement == null ? void 0 : selectedElement.getAnimation();
2778
+ const handleUpdateAnimation = (props) => {
2779
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2780
+ if (!selectedElement) return;
2781
+ let animation = currentAnimation;
2782
+ if (props.name === "") {
2783
+ selectedElement.setAnimation(void 0);
2784
+ updateElement == null ? void 0 : updateElement(selectedElement);
2785
+ return;
2786
+ }
2787
+ const animationDef = ANIMATIONS.find(
2788
+ (a) => a.name === (props.name || (currentAnimation == null ? void 0 : currentAnimation.getName()))
2789
+ );
2790
+ if (!animationDef) return;
2791
+ if (!animation || props.name && props.name !== animation.getName()) {
2792
+ animation = new ElementAnimation(
2793
+ props.name || (currentAnimation == null ? void 0 : currentAnimation.getName()) || ANIMATIONS[0].name
2794
+ );
2795
+ animation.setInterval(animationDef.interval || 1);
2796
+ animation.setDuration(animationDef.duration || 1);
2797
+ animation.setIntensity(animationDef.intensity || 1);
2798
+ animation.setAnimate(animationDef.animate || "enter");
2799
+ if (animationDef.mode) animation.setMode(animationDef.mode);
2800
+ if (animationDef.direction)
2801
+ animation.setDirection(animationDef.direction);
2802
+ }
2803
+ if (props.interval !== void 0) {
2804
+ const [min, max] = ((_a = animationDef.options) == null ? void 0 : _a.interval) || [0.1, 5];
2805
+ animation.setInterval(Math.min(Math.max(props.interval, min), max));
2806
+ }
2807
+ if (props.duration !== void 0) {
2808
+ const [min, max] = ((_b = animationDef.options) == null ? void 0 : _b.duration) || [0.1, 5];
2809
+ animation.setDuration(Math.min(Math.max(props.duration, min), max));
2810
+ }
2811
+ if (props.intensity !== void 0) {
2812
+ const [min, max] = ((_c = animationDef.options) == null ? void 0 : _c.intensity) || [0.1, 2];
2813
+ animation.setIntensity(Math.min(Math.max(props.intensity, min), max));
2814
+ }
2815
+ if (props.animate && ((_e = (_d = animationDef.options) == null ? void 0 : _d.animate) == null ? void 0 : _e.includes(props.animate))) {
2816
+ animation.setAnimate(props.animate);
2817
+ }
2818
+ if (props.mode && ((_g = (_f = animationDef.options) == null ? void 0 : _f.mode) == null ? void 0 : _g.includes(props.mode))) {
2819
+ animation.setMode(props.mode);
2820
+ }
2821
+ if (props.direction && ((_i = (_h = animationDef.options) == null ? void 0 : _h.direction) == null ? void 0 : _i.includes(props.direction))) {
2822
+ animation.setDirection(props.direction);
2823
+ }
2824
+ selectedElement.setAnimation(animation);
2825
+ updateElement == null ? void 0 : updateElement(selectedElement);
2826
+ };
2827
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
2828
+ /* @__PURE__ */ jsxs("div", { className: "bg-neutral-800/40 rounded-lg p-2.5 border border-gray-600/20", children: [
2829
+ /* @__PURE__ */ jsxs("h5", { className: "text-xs font-semibold text-gray-200 mb-2 flex items-center gap-1.5", children: [
2830
+ /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-purple-400 rounded-full" }),
2831
+ "Animation Type"
2832
+ ] }),
2833
+ /* @__PURE__ */ jsxs(
2834
+ "select",
2835
+ {
2836
+ value: (currentAnimation == null ? void 0 : currentAnimation.getName()) || "",
2837
+ onChange: (e) => handleUpdateAnimation({ name: e.target.value }),
2838
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200",
2839
+ children: [
2840
+ /* @__PURE__ */ jsx("option", { value: "", children: "No Animation" }),
2841
+ ANIMATIONS.map((animation) => /* @__PURE__ */ jsx("option", { value: animation.name, children: animation.name.charAt(0).toUpperCase() + animation.name.slice(1) }, animation.name))
2842
+ ]
2843
+ }
2844
+ )
2845
+ ] }),
2846
+ currentAnimation && /* @__PURE__ */ jsxs("div", { className: "bg-neutral-800/40 rounded-lg p-2.5 border border-gray-600/20", children: [
2847
+ /* @__PURE__ */ jsxs("h5", { className: "text-xs font-semibold text-gray-200 mb-2 flex items-center gap-1.5", children: [
2848
+ /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-purple-400 rounded-full" }),
2849
+ "Animation Options"
2850
+ ] }),
2851
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: (() => {
2852
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
2853
+ const animationDef = ANIMATIONS.find(
2854
+ (a) => a.name === currentAnimation.getName()
2855
+ );
2856
+ if (!animationDef || !animationDef.options) return null;
2857
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2858
+ ((_a = animationDef.options) == null ? void 0 : _a.animate) && /* @__PURE__ */ jsxs("div", { children: [
2859
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "When to Animate" }),
2860
+ /* @__PURE__ */ jsx(
2861
+ "select",
2862
+ {
2863
+ value: currentAnimation.getAnimate(),
2864
+ onChange: (e) => handleUpdateAnimation({
2865
+ animate: e.target.value
2866
+ }),
2867
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200",
2868
+ children: (_b = animationDef.options) == null ? void 0 : _b.animate.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option.charAt(0).toUpperCase() + option.slice(1) }, option))
2869
+ }
2870
+ )
2871
+ ] }),
2872
+ ((_c = animationDef.options) == null ? void 0 : _c.direction) && /* @__PURE__ */ jsxs("div", { children: [
2873
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "Direction" }),
2874
+ /* @__PURE__ */ jsx(
2875
+ "select",
2876
+ {
2877
+ value: currentAnimation.getDirection(),
2878
+ onChange: (e) => handleUpdateAnimation({
2879
+ direction: e.target.value
2880
+ }),
2881
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200",
2882
+ children: (_d = animationDef.options) == null ? void 0 : _d.direction.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option.charAt(0).toUpperCase() + option.slice(1) }, option))
2883
+ }
2884
+ )
2885
+ ] }),
2886
+ ((_e = animationDef.options) == null ? void 0 : _e.mode) && /* @__PURE__ */ jsxs("div", { children: [
2887
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "Mode" }),
2888
+ /* @__PURE__ */ jsx(
2889
+ "select",
2890
+ {
2891
+ value: currentAnimation.getMode(),
2892
+ onChange: (e) => handleUpdateAnimation({
2893
+ mode: e.target.value
2894
+ }),
2895
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200",
2896
+ children: (_f = animationDef.options) == null ? void 0 : _f.mode.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option.charAt(0).toUpperCase() + option.slice(1) }, option))
2897
+ }
2898
+ )
2899
+ ] }),
2900
+ ((_g = animationDef.options) == null ? void 0 : _g.duration) && /* @__PURE__ */ jsxs("div", { children: [
2901
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "Duration (seconds)" }),
2902
+ /* @__PURE__ */ jsx(
2903
+ "input",
2904
+ {
2905
+ type: "number",
2906
+ min: (_h = animationDef.options) == null ? void 0 : _h.duration[0],
2907
+ max: (_i = animationDef.options) == null ? void 0 : _i.duration[1],
2908
+ step: "0.1",
2909
+ value: currentAnimation.getDuration(),
2910
+ onChange: (e) => handleUpdateAnimation({
2911
+ duration: Number(e.target.value)
2912
+ }),
2913
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200"
2914
+ }
2915
+ )
2916
+ ] }),
2917
+ ((_j = animationDef.options) == null ? void 0 : _j.interval) && /* @__PURE__ */ jsxs("div", { children: [
2918
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "Interval (seconds)" }),
2919
+ /* @__PURE__ */ jsx(
2920
+ "input",
2921
+ {
2922
+ type: "number",
2923
+ min: (_k = animationDef.options) == null ? void 0 : _k.interval[0],
2924
+ max: (_l = animationDef.options) == null ? void 0 : _l.interval[1],
2925
+ step: "0.1",
2926
+ value: currentAnimation.getInterval(),
2927
+ onChange: (e) => handleUpdateAnimation({
2928
+ interval: Number(e.target.value)
2929
+ }),
2930
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200"
2931
+ }
2932
+ )
2933
+ ] }),
2934
+ ((_m = animationDef.options) == null ? void 0 : _m.intensity) && /* @__PURE__ */ jsxs("div", { children: [
2935
+ /* @__PURE__ */ jsx("label", { className: "block text-xs text-gray-400 mb-1", children: "Intensity" }),
2936
+ /* @__PURE__ */ jsx(
2937
+ "input",
2938
+ {
2939
+ type: "number",
2940
+ min: (_n = animationDef.options) == null ? void 0 : _n.intensity[0],
2941
+ max: (_o = animationDef.options) == null ? void 0 : _o.intensity[1],
2942
+ step: "0.1",
2943
+ value: currentAnimation.getIntensity(),
2944
+ onChange: (e) => handleUpdateAnimation({
2945
+ intensity: Number(e.target.value)
2946
+ }),
2947
+ className: "w-full bg-neutral-700/60 border border-gray-600/40 rounded-md text-white text-xs px-2 py-1.5 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500/30 transition-all duration-200"
2948
+ }
2949
+ )
2950
+ ] })
2951
+ ] });
2952
+ })() })
2953
+ ] })
2954
+ ] });
2955
+ }
2956
+ function PlaybackPropsPanel({
2957
+ selectedElement,
2958
+ updateElement
2959
+ }) {
2960
+ const elementProps = (selectedElement == null ? void 0 : selectedElement.getProps()) || {};
2961
+ const { volume } = elementProps;
2962
+ const handleUpdateElement = (props) => {
2963
+ if (selectedElement) {
2964
+ updateElement == null ? void 0 : updateElement(selectedElement == null ? void 0 : selectedElement.setProps({ ...elementProps, ...props }));
2965
+ }
2966
+ };
2967
+ return /* @__PURE__ */ jsx("div", { className: "space-y-3", children: /* @__PURE__ */ jsxs("div", { className: "bg-neutral-800/40 rounded-lg p-2.5 border border-gray-600/20", children: [
2968
+ /* @__PURE__ */ jsxs("h5", { className: "text-xs font-semibold text-gray-200 mb-2 flex items-center gap-1.5", children: [
2969
+ /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-purple-400 rounded-full" }),
2970
+ "Volume"
2971
+ ] }),
2972
+ /* @__PURE__ */ jsx(
2973
+ "input",
2974
+ {
2975
+ type: "range",
2976
+ min: "0",
2977
+ max: "3",
2978
+ step: 0.1,
2979
+ value: volume ?? 0,
2980
+ onChange: (e) => handleUpdateElement({ volume: Number(e.target.value) }),
2981
+ className: "w-full h-1.5 bg-gradient-to-r from-purple-500/30 to-neutral-600/50 rounded-full appearance-none cursor-pointer slider-thumb"
2982
+ }
2983
+ ),
2984
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-xs text-gray-400 mt-1", children: [
2985
+ /* @__PURE__ */ jsx("span", { children: "0" }),
2986
+ /* @__PURE__ */ jsx("span", { children: "3" })
2987
+ ] })
2988
+ ] }) });
2989
+ }
2990
+ function PropContainer({ title, icon, children }) {
2991
+ return /* @__PURE__ */ jsxs("div", { className: "border-b border-gray-600/30 last:border-b-0 bg-gradient-to-b from-neutral-800/40 to-neutral-800/20 backdrop-blur-sm", children: [
2992
+ /* @__PURE__ */ jsx("div", { className: "w-full flex items-center justify-between px-3 py-2.5 text-left text-gray-200 hover:text-white hover:bg-gradient-to-r hover:from-purple-600/20 hover:to-purple-500/10 transition-all duration-200 rounded-none border-l-2 border-transparent hover:border-purple-500/50", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
2993
+ /* @__PURE__ */ jsx("div", { className: "text-purple-400/80", children: icon }),
2994
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-sm", children: title })
2995
+ ] }) }),
2996
+ /* @__PURE__ */ jsx(
2997
+ "div",
2998
+ {
2999
+ className: `overflow-x-hidden overflow-y-auto p-1 transition-all duration-300 ease-in-out`,
3000
+ children
3001
+ }
3002
+ )
3003
+ ] });
3004
+ }
3005
+ function PropertiesPanelContainer({
3006
+ selectedProp,
3007
+ selectedElement,
3008
+ updateElement
3009
+ }) {
3010
+ if (!selectedElement) {
3011
+ return /* @__PURE__ */ jsx("div", { className: "w-72 h-full bg-gradient-to-b from-neutral-800/90 to-neutral-900/80 border-l border-gray-600/40 overflow-y-auto overflow-x-hidden backdrop-blur-xl shadow-2xl", children: /* @__PURE__ */ jsx("div", { className: "px-3 py-3 border-b border-gray-600/30 bg-gradient-to-r from-purple-600/10 to-transparent", children: /* @__PURE__ */ jsx("h3", { className: "text-lg font-bold text-white", children: "Select Element to see properties" }) }) });
3012
+ }
3013
+ return /* @__PURE__ */ jsxs("div", { className: "w-72 h-full bg-gradient-to-b from-neutral-800/90 to-neutral-900/80 border-l border-gray-600/40 overflow-y-hidden overflow-x-hidden backdrop-blur-xl shadow-2xl", children: [
3014
+ selectedProp === "element-props" && /* @__PURE__ */ jsx(
3015
+ PropContainer,
3016
+ {
3017
+ title: "All Properties",
3018
+ icon: /* @__PURE__ */ jsx(Settings, { className: "w-4 h-4" }),
3019
+ children: /* @__PURE__ */ jsx(
3020
+ ElementProps,
3021
+ {
3022
+ selectedElement,
3023
+ updateElement
3024
+ }
3025
+ )
3026
+ }
3027
+ ),
3028
+ selectedProp === "playback-props" && /* @__PURE__ */ jsx(
3029
+ PropContainer,
3030
+ {
3031
+ title: "Playback Properties",
3032
+ icon: /* @__PURE__ */ jsx(Music, { className: "w-4 h-4" }),
3033
+ children: /* @__PURE__ */ jsx(
3034
+ PlaybackPropsPanel,
3035
+ {
3036
+ selectedElement,
3037
+ updateElement
3038
+ }
3039
+ )
3040
+ }
3041
+ ),
3042
+ selectedProp === "text-effects" && /* @__PURE__ */ jsx(
3043
+ PropContainer,
3044
+ {
3045
+ title: "Text Effects",
3046
+ icon: /* @__PURE__ */ jsx(Sparkles, { className: "w-4 h-4" }),
3047
+ children: /* @__PURE__ */ jsx(
3048
+ TextEffects,
3049
+ {
3050
+ selectedElement,
3051
+ updateElement
3052
+ }
3053
+ )
3054
+ }
3055
+ ),
3056
+ selectedProp === "animations" && /* @__PURE__ */ jsx(PropContainer, { title: "Animations", icon: /* @__PURE__ */ jsx(Zap, { className: "w-4 h-4" }), children: /* @__PURE__ */ jsx(
3057
+ Animation,
3058
+ {
3059
+ selectedElement,
3060
+ updateElement
3061
+ }
3062
+ ) })
3063
+ ] });
3064
+ }
3065
+ const loadFile = (accept) => {
3066
+ return new Promise((resolve, reject) => {
3067
+ try {
3068
+ const input = document.createElement("input");
3069
+ input.type = "file";
3070
+ input.accept = accept;
3071
+ input.style.display = "none";
3072
+ document.body.appendChild(input);
3073
+ const cleanup = () => {
3074
+ input.value = "";
3075
+ document.body.removeChild(input);
3076
+ };
3077
+ input.onchange = () => {
3078
+ const file = input.files && input.files[0];
3079
+ cleanup();
3080
+ if (!file) {
3081
+ reject(new Error("No file selected"));
3082
+ return;
3083
+ }
3084
+ resolve(file);
3085
+ };
3086
+ input.click();
3087
+ } catch (error) {
3088
+ reject(error);
3089
+ }
3090
+ });
3091
+ };
3092
+ const saveAsFile = (content, type, name) => {
3093
+ const blob = typeof content === "string" ? new Blob([content], { type }) : content;
3094
+ const url = URL.createObjectURL(blob);
3095
+ const a = document.createElement("a");
3096
+ a.href = url;
3097
+ a.download = name;
3098
+ a.click();
3099
+ URL.revokeObjectURL(url);
3100
+ };
3101
+ const useStudioOperation = (studioConfig) => {
3102
+ const { editor, present } = useTimelineContext();
3103
+ const [projectName, setProjectName] = useState("");
3104
+ const onLoadProject = async () => {
3105
+ let project;
3106
+ if (studioConfig == null ? void 0 : studioConfig.loadProject) {
3107
+ project = await studioConfig.loadProject();
3108
+ } else {
3109
+ const file = await loadFile("application/json");
3110
+ const text = await file.text();
3111
+ setProjectName(file.name);
3112
+ project = JSON.parse(text);
3113
+ }
3114
+ console.log("Editor", editor);
3115
+ editor.loadProject(project);
3116
+ };
3117
+ const onSaveProject = async () => {
3118
+ let fileName;
3119
+ if (projectName) {
3120
+ fileName = projectName;
3121
+ } else {
3122
+ fileName = prompt("Enter the name of the project") || "untitled-project";
3123
+ fileName = fileName + ".json";
3124
+ setProjectName(fileName);
3125
+ }
3126
+ if ((studioConfig == null ? void 0 : studioConfig.saveProject) && present) {
3127
+ await studioConfig.saveProject(present, fileName);
3128
+ } else {
3129
+ const file = await saveAsFile(
3130
+ JSON.stringify(present),
3131
+ "application/json",
3132
+ fileName
3133
+ );
3134
+ if (file) {
3135
+ console.log("File saved", file);
3136
+ }
3137
+ }
3138
+ };
3139
+ const onExportVideo = async () => {
3140
+ var _a, _b;
3141
+ if ((studioConfig == null ? void 0 : studioConfig.exportVideo) && present) {
3142
+ await studioConfig.exportVideo(present, {
3143
+ outFile: "output.mp4",
3144
+ fps: 30,
3145
+ resolution: {
3146
+ width: (_a = studioConfig.videoProps) == null ? void 0 : _a.width,
3147
+ height: (_b = studioConfig.videoProps) == null ? void 0 : _b.height
3148
+ }
3149
+ });
3150
+ } else {
3151
+ alert("Export video not supported in demo mode");
3152
+ }
3153
+ };
3154
+ return { onLoadProject, onSaveProject, onExportVideo };
3155
+ };
3156
+ function TwickStudio({ studioConfig }) {
3157
+ var _a;
3158
+ const {
3159
+ selectedTool,
3160
+ setSelectedTool,
3161
+ selectedProp,
3162
+ setSelectedProp,
3163
+ selectedElement,
3164
+ addElement,
3165
+ updateElement
3166
+ } = useStudioManager();
3167
+ const { videoResolution, setVideoResolution } = useTimelineContext();
3168
+ const { onLoadProject, onSaveProject, onExportVideo } = useStudioOperation(studioConfig);
3169
+ const twickStudiConfig = useMemo(
3170
+ () => ({
3171
+ canvasMode: true,
3172
+ ...studioConfig || {},
3173
+ videoProps: {
3174
+ ...(studioConfig == null ? void 0 : studioConfig.videoProps) || {},
3175
+ width: videoResolution.width,
3176
+ height: videoResolution.height
3177
+ }
3178
+ }),
3179
+ [videoResolution, studioConfig]
3180
+ );
3181
+ return /* @__PURE__ */ jsx(MediaProvider, { children: /* @__PURE__ */ jsxs("div", { className: "h-screen w-screen overflow-hidden bg-neutral-900 text-gray-100", children: [
3182
+ /* @__PURE__ */ jsx(
3183
+ StudioHeader,
3184
+ {
3185
+ setVideoResolution,
3186
+ onLoadProject,
3187
+ onSaveProject,
3188
+ onExportVideo
3189
+ }
3190
+ ),
3191
+ /* @__PURE__ */ jsxs("div", { className: "flex h-[calc(100vh-56px)]", children: [
3192
+ /* @__PURE__ */ jsx(
3193
+ Toolbar,
3194
+ {
3195
+ selectedTool,
3196
+ setSelectedTool
3197
+ }
3198
+ ),
3199
+ /* @__PURE__ */ jsx("aside", { className: "border-r border-gray-600/50 backdrop-blur-md shadow-lg", children: /* @__PURE__ */ jsx(
3200
+ ElementPanelContainer,
3201
+ {
3202
+ videoResolution,
3203
+ selectedTool,
3204
+ setSelectedTool,
3205
+ selectedElement,
3206
+ addElement,
3207
+ updateElement
3208
+ }
3209
+ ) }),
3210
+ /* @__PURE__ */ jsx("main", { className: "flex-1 flex flex-col bg-neutral-700 main-container", children: /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-1", children: /* @__PURE__ */ jsx(
3211
+ "div",
3212
+ {
3213
+ className: "canvas-container",
3214
+ style: {
3215
+ maxWidth: ((_a = twickStudiConfig.playerProps) == null ? void 0 : _a.maxWidth) ?? 960
3216
+ },
3217
+ children: /* @__PURE__ */ jsx(VideoEditor, { editorConfig: twickStudiConfig })
3218
+ }
3219
+ ) }) }),
3220
+ /* @__PURE__ */ jsx("aside", { className: "border-r border-gray-600/50 backdrop-blur-md shadow-lg", children: /* @__PURE__ */ jsx(
3221
+ PropertiesPanelContainer,
3222
+ {
3223
+ selectedProp,
3224
+ selectedElement,
3225
+ updateElement
3226
+ }
3227
+ ) }),
3228
+ /* @__PURE__ */ jsx(
3229
+ PropsToolbar,
3230
+ {
3231
+ selectedElement,
3232
+ selectedProp,
3233
+ setSelectedProp
3234
+ }
3235
+ )
3236
+ ] })
3237
+ ] }) });
3238
+ }
3239
+ export {
3240
+ AudioPanel,
3241
+ CirclePanel,
3242
+ IconPanel,
3243
+ ImagePanel,
3244
+ RectPanel,
3245
+ StudioHeader,
3246
+ SubtitlesPanel,
3247
+ TextPanel,
3248
+ Toolbar,
3249
+ TwickStudio,
3250
+ VideoPanel,
3251
+ TwickStudio as default,
3252
+ useStudioManager
3253
+ };
3254
+ //# sourceMappingURL=index.mjs.map