@twick/studio 0.14.6 → 0.14.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -6
- package/dist/components/container/element-panel-container.d.ts +26 -1
- package/dist/components/container/subtitles-panel-container.d.ts +1 -0
- package/dist/components/panel/audio-panel.d.ts +1 -1
- package/dist/components/panel/image-panel.d.ts +1 -1
- package/dist/components/panel/subtitles-panel.d.ts +37 -18
- package/dist/components/panel/video-panel.d.ts +1 -1
- package/dist/components/shared/index.d.ts +1 -0
- package/dist/components/shared/url-input.d.ts +6 -0
- package/dist/helpers/constant.d.ts +52 -0
- package/dist/hooks/use-studio-manager.d.ts +1 -1
- package/dist/hooks/use-subtitles-panel.d.ts +13 -0
- package/dist/index.js +413 -248
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +415 -250
- package/dist/index.mjs.map +1 -1
- package/dist/studio.css +17 -3
- package/dist/types/media-panel.d.ts +11 -6
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -110,23 +110,15 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
110
110
|
* This source code is licensed under the ISC license.
|
|
111
111
|
* See the LICENSE file in the root directory of this source tree.
|
|
112
112
|
*/
|
|
113
|
-
const __iconNode$o = [["
|
|
114
|
-
const
|
|
113
|
+
const __iconNode$o = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
|
|
114
|
+
const Circle = createLucideIcon("circle", __iconNode$o);
|
|
115
115
|
/**
|
|
116
116
|
* @license lucide-react v0.511.0 - ISC
|
|
117
117
|
*
|
|
118
118
|
* This source code is licensed under the ISC license.
|
|
119
119
|
* See the LICENSE file in the root directory of this source tree.
|
|
120
120
|
*/
|
|
121
|
-
const __iconNode$n = [
|
|
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 = [
|
|
121
|
+
const __iconNode$n = [
|
|
130
122
|
[
|
|
131
123
|
"path",
|
|
132
124
|
{ 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" }
|
|
@@ -135,119 +127,119 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
135
127
|
["path", { d: "m12.4 3.4 3.1 4", key: "6hsd6n" }],
|
|
136
128
|
["path", { d: "M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z", key: "ltgou9" }]
|
|
137
129
|
];
|
|
138
|
-
const Clapperboard = createLucideIcon("clapperboard", __iconNode$
|
|
130
|
+
const Clapperboard = createLucideIcon("clapperboard", __iconNode$n);
|
|
139
131
|
/**
|
|
140
132
|
* @license lucide-react v0.511.0 - ISC
|
|
141
133
|
*
|
|
142
134
|
* This source code is licensed under the ISC license.
|
|
143
135
|
* See the LICENSE file in the root directory of this source tree.
|
|
144
136
|
*/
|
|
145
|
-
const __iconNode$
|
|
137
|
+
const __iconNode$m = [
|
|
146
138
|
["path", { d: "M12 15V3", key: "m9g1x1" }],
|
|
147
139
|
["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
|
|
148
140
|
["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]
|
|
149
141
|
];
|
|
150
|
-
const Download = createLucideIcon("download", __iconNode$
|
|
142
|
+
const Download = createLucideIcon("download", __iconNode$m);
|
|
151
143
|
/**
|
|
152
144
|
* @license lucide-react v0.511.0 - ISC
|
|
153
145
|
*
|
|
154
146
|
* This source code is licensed under the ISC license.
|
|
155
147
|
* See the LICENSE file in the root directory of this source tree.
|
|
156
148
|
*/
|
|
157
|
-
const __iconNode$
|
|
149
|
+
const __iconNode$l = [
|
|
158
150
|
["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
151
|
["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }]
|
|
160
152
|
];
|
|
161
|
-
const File = createLucideIcon("file", __iconNode$
|
|
153
|
+
const File = createLucideIcon("file", __iconNode$l);
|
|
162
154
|
/**
|
|
163
155
|
* @license lucide-react v0.511.0 - ISC
|
|
164
156
|
*
|
|
165
157
|
* This source code is licensed under the ISC license.
|
|
166
158
|
* See the LICENSE file in the root directory of this source tree.
|
|
167
159
|
*/
|
|
168
|
-
const __iconNode$
|
|
160
|
+
const __iconNode$k = [
|
|
169
161
|
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
|
|
170
162
|
["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
|
|
171
163
|
["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
|
|
172
164
|
];
|
|
173
|
-
const Image = createLucideIcon("image", __iconNode$
|
|
165
|
+
const Image = createLucideIcon("image", __iconNode$k);
|
|
174
166
|
/**
|
|
175
167
|
* @license lucide-react v0.511.0 - ISC
|
|
176
168
|
*
|
|
177
169
|
* This source code is licensed under the ISC license.
|
|
178
170
|
* See the LICENSE file in the root directory of this source tree.
|
|
179
171
|
*/
|
|
180
|
-
const __iconNode$
|
|
172
|
+
const __iconNode$j = [
|
|
181
173
|
["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
174
|
];
|
|
183
|
-
const Infinity = createLucideIcon("infinity", __iconNode$
|
|
175
|
+
const Infinity = createLucideIcon("infinity", __iconNode$j);
|
|
184
176
|
/**
|
|
185
177
|
* @license lucide-react v0.511.0 - ISC
|
|
186
178
|
*
|
|
187
179
|
* This source code is licensed under the ISC license.
|
|
188
180
|
* See the LICENSE file in the root directory of this source tree.
|
|
189
181
|
*/
|
|
190
|
-
const __iconNode$
|
|
191
|
-
const LoaderCircle = createLucideIcon("loader-circle", __iconNode$
|
|
182
|
+
const __iconNode$i = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
|
|
183
|
+
const LoaderCircle = createLucideIcon("loader-circle", __iconNode$i);
|
|
192
184
|
/**
|
|
193
185
|
* @license lucide-react v0.511.0 - ISC
|
|
194
186
|
*
|
|
195
187
|
* This source code is licensed under the ISC license.
|
|
196
188
|
* See the LICENSE file in the root directory of this source tree.
|
|
197
189
|
*/
|
|
198
|
-
const __iconNode$
|
|
190
|
+
const __iconNode$h = [
|
|
199
191
|
["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
192
|
];
|
|
201
|
-
const MessageSquare = createLucideIcon("message-square", __iconNode$
|
|
193
|
+
const MessageSquare = createLucideIcon("message-square", __iconNode$h);
|
|
202
194
|
/**
|
|
203
195
|
* @license lucide-react v0.511.0 - ISC
|
|
204
196
|
*
|
|
205
197
|
* This source code is licensed under the ISC license.
|
|
206
198
|
* See the LICENSE file in the root directory of this source tree.
|
|
207
199
|
*/
|
|
208
|
-
const __iconNode$
|
|
200
|
+
const __iconNode$g = [
|
|
209
201
|
["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
|
|
210
202
|
["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
|
|
211
203
|
["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
|
|
212
204
|
];
|
|
213
|
-
const Music = createLucideIcon("music", __iconNode$
|
|
205
|
+
const Music = createLucideIcon("music", __iconNode$g);
|
|
214
206
|
/**
|
|
215
207
|
* @license lucide-react v0.511.0 - ISC
|
|
216
208
|
*
|
|
217
209
|
* This source code is licensed under the ISC license.
|
|
218
210
|
* See the LICENSE file in the root directory of this source tree.
|
|
219
211
|
*/
|
|
220
|
-
const __iconNode$
|
|
212
|
+
const __iconNode$f = [
|
|
221
213
|
["rect", { x: "14", y: "4", width: "4", height: "16", rx: "1", key: "zuxfzm" }],
|
|
222
214
|
["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
|
|
223
215
|
];
|
|
224
|
-
const Pause = createLucideIcon("pause", __iconNode$
|
|
216
|
+
const Pause = createLucideIcon("pause", __iconNode$f);
|
|
225
217
|
/**
|
|
226
218
|
* @license lucide-react v0.511.0 - ISC
|
|
227
219
|
*
|
|
228
220
|
* This source code is licensed under the ISC license.
|
|
229
221
|
* See the LICENSE file in the root directory of this source tree.
|
|
230
222
|
*/
|
|
231
|
-
const __iconNode$
|
|
232
|
-
const Play = createLucideIcon("play", __iconNode$
|
|
223
|
+
const __iconNode$e = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
|
|
224
|
+
const Play = createLucideIcon("play", __iconNode$e);
|
|
233
225
|
/**
|
|
234
226
|
* @license lucide-react v0.511.0 - ISC
|
|
235
227
|
*
|
|
236
228
|
* This source code is licensed under the ISC license.
|
|
237
229
|
* See the LICENSE file in the root directory of this source tree.
|
|
238
230
|
*/
|
|
239
|
-
const __iconNode$
|
|
231
|
+
const __iconNode$d = [
|
|
240
232
|
["path", { d: "M5 12h14", key: "1ays0h" }],
|
|
241
233
|
["path", { d: "M12 5v14", key: "s699le" }]
|
|
242
234
|
];
|
|
243
|
-
const Plus = createLucideIcon("plus", __iconNode$
|
|
235
|
+
const Plus = createLucideIcon("plus", __iconNode$d);
|
|
244
236
|
/**
|
|
245
237
|
* @license lucide-react v0.511.0 - ISC
|
|
246
238
|
*
|
|
247
239
|
* This source code is licensed under the ISC license.
|
|
248
240
|
* See the LICENSE file in the root directory of this source tree.
|
|
249
241
|
*/
|
|
250
|
-
const __iconNode$
|
|
242
|
+
const __iconNode$c = [
|
|
251
243
|
[
|
|
252
244
|
"path",
|
|
253
245
|
{
|
|
@@ -258,7 +250,21 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
258
250
|
["path", { d: "M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7", key: "1ydtos" }],
|
|
259
251
|
["path", { d: "M7 3v4a1 1 0 0 0 1 1h7", key: "t51u73" }]
|
|
260
252
|
];
|
|
261
|
-
const Save = createLucideIcon("save", __iconNode$
|
|
253
|
+
const Save = createLucideIcon("save", __iconNode$c);
|
|
254
|
+
/**
|
|
255
|
+
* @license lucide-react v0.511.0 - ISC
|
|
256
|
+
*
|
|
257
|
+
* This source code is licensed under the ISC license.
|
|
258
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
259
|
+
*/
|
|
260
|
+
const __iconNode$b = [
|
|
261
|
+
["circle", { cx: "6", cy: "6", r: "3", key: "1lh9wr" }],
|
|
262
|
+
["path", { d: "M8.12 8.12 12 12", key: "1alkpv" }],
|
|
263
|
+
["path", { d: "M20 4 8.12 15.88", key: "xgtan2" }],
|
|
264
|
+
["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
|
|
265
|
+
["path", { d: "M14.8 14.8 20 20", key: "ptml3r" }]
|
|
266
|
+
];
|
|
267
|
+
const Scissors = createLucideIcon("scissors", __iconNode$b);
|
|
262
268
|
/**
|
|
263
269
|
* @license lucide-react v0.511.0 - ISC
|
|
264
270
|
*
|
|
@@ -436,8 +442,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
436
442
|
{ id: "text", name: "Text", icon: "Type", description: "Add text elements", shortcut: "T" },
|
|
437
443
|
{ id: "icon", name: "Icons", icon: "Icon", description: "Icon Element", shortcut: "I" },
|
|
438
444
|
{ id: "circle", name: "Circle", icon: "Circle", description: "Circle Element", shortcut: "C" },
|
|
439
|
-
{ id: "rect", name: "Rect", icon: "Rect", description: "Rect Element" }
|
|
440
|
-
|
|
445
|
+
{ id: "rect", name: "Rect", icon: "Rect", description: "Rect Element" },
|
|
446
|
+
{ id: "subtitle", name: "Subtitles", icon: "MessageSquare", description: "Manage subtitles", shortcut: "S" }
|
|
441
447
|
];
|
|
442
448
|
const getIcon$1 = (iconName) => {
|
|
443
449
|
switch (iconName) {
|
|
@@ -560,34 +566,18 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
560
566
|
};
|
|
561
567
|
const useStudioManager = () => {
|
|
562
568
|
const [selectedProp, setSelectedProp] = react.useState("element-props");
|
|
563
|
-
const {
|
|
569
|
+
const { selectedItem } = timeline.useTimelineContext();
|
|
570
|
+
const { addElement, updateElement } = VideoEditor.useEditorManager();
|
|
564
571
|
const selectedElement = selectedItem instanceof timeline.TrackElement ? selectedItem : null;
|
|
565
572
|
const [selectedTool, setSelectedTool] = react.useState("none");
|
|
566
573
|
const isToolChanged = react.useRef(false);
|
|
567
|
-
const addElement = (element) => {
|
|
568
|
-
if (selectedItem instanceof timeline.Track) {
|
|
569
|
-
editor.addElementToTrack(selectedItem, element);
|
|
570
|
-
} else {
|
|
571
|
-
const newTrack = editor.addTrack("Track");
|
|
572
|
-
editor.addElementToTrack(newTrack, element);
|
|
573
|
-
}
|
|
574
|
-
};
|
|
575
|
-
const updateElement = (element) => {
|
|
576
|
-
const updatedElement = editor.updateElement(element);
|
|
577
|
-
editor.refresh();
|
|
578
|
-
setSelectedItem(updatedElement);
|
|
579
|
-
};
|
|
580
574
|
react.useEffect(() => {
|
|
581
575
|
if (selectedItem instanceof timeline.TrackElement) {
|
|
582
576
|
setSelectedTool(selectedItem.getType());
|
|
583
577
|
isToolChanged.current = true;
|
|
584
578
|
} else if (selectedItem instanceof timeline.Track) ;
|
|
585
579
|
else {
|
|
586
|
-
|
|
587
|
-
setSelectedTool("none");
|
|
588
|
-
} else {
|
|
589
|
-
setSelectedTool("video");
|
|
590
|
-
}
|
|
580
|
+
setSelectedTool("video");
|
|
591
581
|
}
|
|
592
582
|
}, [selectedItem]);
|
|
593
583
|
return {
|
|
@@ -600,131 +590,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
600
590
|
updateElement
|
|
601
591
|
};
|
|
602
592
|
};
|
|
603
|
-
function SubtitlesPanel() {
|
|
604
|
-
const [subtitles, setSubtitles] = react.useState([]);
|
|
605
|
-
const handleGenerate = () => {
|
|
606
|
-
console.log("Generating subtitles...");
|
|
607
|
-
};
|
|
608
|
-
const handleAdd = () => {
|
|
609
|
-
const newId = (subtitles.length + 1).toString();
|
|
610
|
-
const lastEnd = subtitles.length > 0 ? subtitles[subtitles.length - 1].end : 0;
|
|
611
|
-
const newSubtitle = {
|
|
612
|
-
id: newId,
|
|
613
|
-
start: lastEnd,
|
|
614
|
-
end: lastEnd + 1,
|
|
615
|
-
text: ""
|
|
616
|
-
};
|
|
617
|
-
setSubtitles([...subtitles, newSubtitle]);
|
|
618
|
-
};
|
|
619
|
-
const handleDelete = (id) => {
|
|
620
|
-
setSubtitles(subtitles.filter((sub) => sub.id !== id));
|
|
621
|
-
};
|
|
622
|
-
const handleSave = (id) => {
|
|
623
|
-
console.log("Saving subtitle:", id);
|
|
624
|
-
};
|
|
625
|
-
const handleUpdateSubtitle = (id, field, value) => {
|
|
626
|
-
setSubtitles(subtitles.map(
|
|
627
|
-
(sub) => sub.id === id ? { ...sub, [field]: value } : sub
|
|
628
|
-
));
|
|
629
|
-
};
|
|
630
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
631
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "panel-title", children: "Subtitles" }),
|
|
632
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", children: [
|
|
633
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
634
|
-
"button",
|
|
635
|
-
{
|
|
636
|
-
onClick: handleGenerate,
|
|
637
|
-
className: "btn-primary",
|
|
638
|
-
children: "Generate"
|
|
639
|
-
}
|
|
640
|
-
),
|
|
641
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
642
|
-
"button",
|
|
643
|
-
{
|
|
644
|
-
onClick: handleAdd,
|
|
645
|
-
className: "btn-primary",
|
|
646
|
-
children: "Add"
|
|
647
|
-
}
|
|
648
|
-
)
|
|
649
|
-
] }) }),
|
|
650
|
-
subtitles.map((subtitle) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
651
|
-
"div",
|
|
652
|
-
{
|
|
653
|
-
className: "panel-section",
|
|
654
|
-
children: [
|
|
655
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", children: [
|
|
656
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
657
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-small", children: "Start" }),
|
|
658
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
659
|
-
"input",
|
|
660
|
-
{
|
|
661
|
-
type: "number",
|
|
662
|
-
min: "0",
|
|
663
|
-
step: "0.1",
|
|
664
|
-
value: subtitle.start,
|
|
665
|
-
onChange: (e) => handleUpdateSubtitle(subtitle.id, "start", Number(e.target.value)),
|
|
666
|
-
className: "input-dark"
|
|
667
|
-
}
|
|
668
|
-
)
|
|
669
|
-
] }),
|
|
670
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
671
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-small", children: "End" }),
|
|
672
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
673
|
-
"input",
|
|
674
|
-
{
|
|
675
|
-
type: "number",
|
|
676
|
-
min: "0",
|
|
677
|
-
step: "0.1",
|
|
678
|
-
value: subtitle.end,
|
|
679
|
-
onChange: (e) => handleUpdateSubtitle(subtitle.id, "end", Number(e.target.value)),
|
|
680
|
-
className: "input-dark"
|
|
681
|
-
}
|
|
682
|
-
)
|
|
683
|
-
] })
|
|
684
|
-
] }),
|
|
685
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
686
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Subtitle Text" }),
|
|
687
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
688
|
-
"input",
|
|
689
|
-
{
|
|
690
|
-
type: "text",
|
|
691
|
-
placeholder: "Enter subtitle text",
|
|
692
|
-
value: subtitle.text,
|
|
693
|
-
onChange: (e) => handleUpdateSubtitle(subtitle.id, "text", e.target.value),
|
|
694
|
-
className: "input-dark"
|
|
695
|
-
}
|
|
696
|
-
)
|
|
697
|
-
] }),
|
|
698
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container justify-between", children: [
|
|
699
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
700
|
-
"button",
|
|
701
|
-
{
|
|
702
|
-
onClick: () => handleDelete(subtitle.id),
|
|
703
|
-
className: "btn-danger",
|
|
704
|
-
title: "Delete subtitle",
|
|
705
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { className: "icon-sm" })
|
|
706
|
-
}
|
|
707
|
-
),
|
|
708
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
709
|
-
"button",
|
|
710
|
-
{
|
|
711
|
-
onClick: () => handleSave(subtitle.id),
|
|
712
|
-
className: "btn-primary",
|
|
713
|
-
title: "Save subtitle",
|
|
714
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(Check, { className: "icon-sm" })
|
|
715
|
-
}
|
|
716
|
-
)
|
|
717
|
-
] })
|
|
718
|
-
]
|
|
719
|
-
},
|
|
720
|
-
subtitle.id
|
|
721
|
-
)),
|
|
722
|
-
subtitles.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
723
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No subtitles yet" }),
|
|
724
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-subtext", children: 'Click "Add" to create your first subtitle' })
|
|
725
|
-
] }) }) })
|
|
726
|
-
] });
|
|
727
|
-
}
|
|
728
593
|
const FileInput = ({
|
|
729
594
|
acceptFileTypes,
|
|
730
595
|
onFileLoad,
|
|
@@ -797,6 +662,83 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
797
662
|
__publicField(_MediaManagerSingleton, "instance", null);
|
|
798
663
|
let MediaManagerSingleton = _MediaManagerSingleton;
|
|
799
664
|
const getMediaManager = () => MediaManagerSingleton.getInstance();
|
|
665
|
+
const EXTENSIONS = {
|
|
666
|
+
video: ["mp4", "webm", "ogg", "mov", "mkv", "m3u8"],
|
|
667
|
+
audio: ["mp3", "wav", "ogg", "m4a", "aac", "flac"],
|
|
668
|
+
image: ["jpg", "jpeg", "png", "gif", "webp", "svg"]
|
|
669
|
+
};
|
|
670
|
+
function isValidUrl(url) {
|
|
671
|
+
try {
|
|
672
|
+
new URL(url);
|
|
673
|
+
return true;
|
|
674
|
+
} catch {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function matchesType(url, type) {
|
|
679
|
+
const pathname = (() => {
|
|
680
|
+
try {
|
|
681
|
+
return new URL(url).pathname.toLowerCase();
|
|
682
|
+
} catch {
|
|
683
|
+
return url.toLowerCase();
|
|
684
|
+
}
|
|
685
|
+
})();
|
|
686
|
+
const ext = pathname.split(".").pop() || "";
|
|
687
|
+
return EXTENSIONS[type].includes(ext);
|
|
688
|
+
}
|
|
689
|
+
function UrlInput({
|
|
690
|
+
type,
|
|
691
|
+
onSubmit
|
|
692
|
+
}) {
|
|
693
|
+
const [url, setUrl] = react.useState("");
|
|
694
|
+
const [error, setError] = react.useState("");
|
|
695
|
+
const tryAdd = async () => {
|
|
696
|
+
const trimmed = url.trim();
|
|
697
|
+
if (!trimmed) return;
|
|
698
|
+
if (!isValidUrl(trimmed)) {
|
|
699
|
+
setError("Enter a valid URL");
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (!matchesType(trimmed, type)) {
|
|
703
|
+
setError(`URL must be a ${type} (${EXTENSIONS[type].join(", ")})`);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
setError("");
|
|
707
|
+
onSubmit(trimmed);
|
|
708
|
+
setUrl("");
|
|
709
|
+
};
|
|
710
|
+
const onKeyDown = (e) => {
|
|
711
|
+
if (e.key === "Enter") {
|
|
712
|
+
e.preventDefault();
|
|
713
|
+
void tryAdd();
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
717
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", children: [
|
|
718
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
719
|
+
"input",
|
|
720
|
+
{
|
|
721
|
+
type: "url",
|
|
722
|
+
placeholder: `Paste ${type} URL...`,
|
|
723
|
+
value: url,
|
|
724
|
+
onChange: (e) => setUrl(e.target.value),
|
|
725
|
+
onKeyDown,
|
|
726
|
+
className: "input w-full"
|
|
727
|
+
}
|
|
728
|
+
),
|
|
729
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
730
|
+
"button",
|
|
731
|
+
{
|
|
732
|
+
className: "btn-ghost",
|
|
733
|
+
onClick: () => void tryAdd(),
|
|
734
|
+
"aria-label": `Add ${type} by URL`,
|
|
735
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Plus, { size: 16 })
|
|
736
|
+
}
|
|
737
|
+
)
|
|
738
|
+
] }),
|
|
739
|
+
error ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-error", children: error }) : null
|
|
740
|
+
] });
|
|
741
|
+
}
|
|
800
742
|
const initialMediaState = {
|
|
801
743
|
items: [],
|
|
802
744
|
searchQuery: "",
|
|
@@ -964,24 +906,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
964
906
|
acceptFileTypes: config.acceptFileTypes
|
|
965
907
|
};
|
|
966
908
|
};
|
|
967
|
-
const SearchInput = ({
|
|
968
|
-
searchQuery,
|
|
969
|
-
setSearchQuery
|
|
970
|
-
}) => {
|
|
971
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "search-container", children: [
|
|
972
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
973
|
-
"input",
|
|
974
|
-
{
|
|
975
|
-
type: "text",
|
|
976
|
-
placeholder: "Search media...",
|
|
977
|
-
value: searchQuery,
|
|
978
|
-
onChange: (e) => setSearchQuery(e.target.value),
|
|
979
|
-
className: "input search-input w-full"
|
|
980
|
-
}
|
|
981
|
-
),
|
|
982
|
-
/* @__PURE__ */ jsxRuntime.jsx(Search, { className: "search-icon" })
|
|
983
|
-
] });
|
|
984
|
-
};
|
|
985
909
|
const useAudioPreview = () => {
|
|
986
910
|
const [playingAudio, setPlayingAudio] = react.useState(null);
|
|
987
911
|
const audioRef = react.useRef(null);
|
|
@@ -1021,22 +945,15 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1021
945
|
};
|
|
1022
946
|
const AudioPanel = ({
|
|
1023
947
|
items,
|
|
1024
|
-
searchQuery,
|
|
1025
|
-
onSearchChange,
|
|
1026
948
|
onItemSelect,
|
|
1027
949
|
onFileUpload,
|
|
1028
|
-
acceptFileTypes
|
|
950
|
+
acceptFileTypes,
|
|
951
|
+
onUrlAdd
|
|
1029
952
|
}) => {
|
|
1030
953
|
const { playingAudio, togglePlayPause } = useAudioPreview();
|
|
1031
954
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
1032
955
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Audio Library" }),
|
|
1033
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "
|
|
1034
|
-
SearchInput,
|
|
1035
|
-
{
|
|
1036
|
-
searchQuery,
|
|
1037
|
-
setSearchQuery: onSearchChange
|
|
1038
|
-
}
|
|
1039
|
-
) }),
|
|
956
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(UrlInput, { type: "audio", onSubmit: onUrlAdd }) }),
|
|
1040
957
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1041
958
|
FileInput,
|
|
1042
959
|
{
|
|
@@ -1088,13 +1005,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1088
1005
|
}) }),
|
|
1089
1006
|
items.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
1090
1007
|
/* @__PURE__ */ jsxRuntime.jsx(WandSparkles, { className: "empty-state-icon" }),
|
|
1091
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No audio files found" })
|
|
1092
|
-
searchQuery && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-subtext", children: "Try adjusting your search" })
|
|
1008
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No audio files found" })
|
|
1093
1009
|
] }) })
|
|
1094
1010
|
] })
|
|
1095
1011
|
] });
|
|
1096
1012
|
};
|
|
1097
1013
|
const AudioPanelContainer = (props) => {
|
|
1014
|
+
const { addItem } = useMedia("audio");
|
|
1015
|
+
const mediaManager = getMediaManager();
|
|
1098
1016
|
const {
|
|
1099
1017
|
items,
|
|
1100
1018
|
searchQuery,
|
|
@@ -1112,6 +1030,24 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1112
1030
|
},
|
|
1113
1031
|
props.videoResolution
|
|
1114
1032
|
);
|
|
1033
|
+
const onUrlAdd = async (url) => {
|
|
1034
|
+
const nameFromUrl = (() => {
|
|
1035
|
+
try {
|
|
1036
|
+
const u = new URL(url);
|
|
1037
|
+
const parts = u.pathname.split("/").filter(Boolean);
|
|
1038
|
+
return decodeURIComponent(parts[parts.length - 1] || url);
|
|
1039
|
+
} catch {
|
|
1040
|
+
return url;
|
|
1041
|
+
}
|
|
1042
|
+
})();
|
|
1043
|
+
const newItem = await mediaManager.addItem({
|
|
1044
|
+
name: nameFromUrl,
|
|
1045
|
+
url,
|
|
1046
|
+
type: "audio",
|
|
1047
|
+
metadata: { source: "url" }
|
|
1048
|
+
});
|
|
1049
|
+
addItem(newItem);
|
|
1050
|
+
};
|
|
1115
1051
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1116
1052
|
AudioPanel,
|
|
1117
1053
|
{
|
|
@@ -1121,27 +1057,21 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1121
1057
|
onItemSelect: handleSelection,
|
|
1122
1058
|
onFileUpload: handleFileUpload,
|
|
1123
1059
|
isLoading,
|
|
1124
|
-
acceptFileTypes
|
|
1060
|
+
acceptFileTypes,
|
|
1061
|
+
onUrlAdd
|
|
1125
1062
|
}
|
|
1126
1063
|
);
|
|
1127
1064
|
};
|
|
1128
1065
|
function ImagePanel({
|
|
1129
1066
|
items,
|
|
1130
|
-
searchQuery,
|
|
1131
|
-
onSearchChange,
|
|
1132
1067
|
onItemSelect,
|
|
1133
1068
|
onFileUpload,
|
|
1134
|
-
acceptFileTypes
|
|
1069
|
+
acceptFileTypes,
|
|
1070
|
+
onUrlAdd
|
|
1135
1071
|
}) {
|
|
1136
1072
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
1137
1073
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Image Library" }),
|
|
1138
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "
|
|
1139
|
-
SearchInput,
|
|
1140
|
-
{
|
|
1141
|
-
searchQuery,
|
|
1142
|
-
setSearchQuery: onSearchChange
|
|
1143
|
-
}
|
|
1144
|
-
) }),
|
|
1074
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(UrlInput, { type: "image", onSubmit: onUrlAdd }) }),
|
|
1145
1075
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1146
1076
|
FileInput,
|
|
1147
1077
|
{
|
|
@@ -1178,13 +1108,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1178
1108
|
)) }),
|
|
1179
1109
|
items.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
1180
1110
|
/* @__PURE__ */ jsxRuntime.jsx(WandSparkles, { className: "empty-state-icon" }),
|
|
1181
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No images found" })
|
|
1182
|
-
searchQuery && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-subtext", children: "Try adjusting your search" })
|
|
1111
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No images found" })
|
|
1183
1112
|
] }) })
|
|
1184
1113
|
] })
|
|
1185
1114
|
] });
|
|
1186
1115
|
}
|
|
1187
1116
|
function ImagePanelContainer(props) {
|
|
1117
|
+
const { addItem } = useMedia("image");
|
|
1118
|
+
const mediaManager = getMediaManager();
|
|
1188
1119
|
const {
|
|
1189
1120
|
items,
|
|
1190
1121
|
searchQuery,
|
|
@@ -1202,6 +1133,24 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1202
1133
|
},
|
|
1203
1134
|
props.videoResolution
|
|
1204
1135
|
);
|
|
1136
|
+
const onUrlAdd = async (url) => {
|
|
1137
|
+
const nameFromUrl = (() => {
|
|
1138
|
+
try {
|
|
1139
|
+
const u = new URL(url);
|
|
1140
|
+
const parts = u.pathname.split("/").filter(Boolean);
|
|
1141
|
+
return decodeURIComponent(parts[parts.length - 1] || url);
|
|
1142
|
+
} catch {
|
|
1143
|
+
return url;
|
|
1144
|
+
}
|
|
1145
|
+
})();
|
|
1146
|
+
const newItem = await mediaManager.addItem({
|
|
1147
|
+
name: nameFromUrl,
|
|
1148
|
+
url,
|
|
1149
|
+
type: "image",
|
|
1150
|
+
metadata: { source: "url" }
|
|
1151
|
+
});
|
|
1152
|
+
addItem(newItem);
|
|
1153
|
+
};
|
|
1205
1154
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1206
1155
|
ImagePanel,
|
|
1207
1156
|
{
|
|
@@ -1211,7 +1160,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1211
1160
|
onItemSelect: handleSelection,
|
|
1212
1161
|
onFileUpload: handleFileUpload,
|
|
1213
1162
|
isLoading,
|
|
1214
|
-
acceptFileTypes
|
|
1163
|
+
acceptFileTypes,
|
|
1164
|
+
onUrlAdd
|
|
1215
1165
|
}
|
|
1216
1166
|
);
|
|
1217
1167
|
}
|
|
@@ -1255,22 +1205,15 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1255
1205
|
};
|
|
1256
1206
|
function VideoPanel({
|
|
1257
1207
|
items,
|
|
1258
|
-
searchQuery,
|
|
1259
|
-
onSearchChange,
|
|
1260
1208
|
onItemSelect,
|
|
1261
1209
|
onFileUpload,
|
|
1262
|
-
acceptFileTypes
|
|
1210
|
+
acceptFileTypes,
|
|
1211
|
+
onUrlAdd
|
|
1263
1212
|
}) {
|
|
1264
1213
|
const { playingVideo, togglePlayPause } = useVideoPreview();
|
|
1265
1214
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
1266
1215
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Video Library" }),
|
|
1267
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1268
|
-
SearchInput,
|
|
1269
|
-
{
|
|
1270
|
-
searchQuery,
|
|
1271
|
-
setSearchQuery: onSearchChange
|
|
1272
|
-
}
|
|
1273
|
-
) }),
|
|
1216
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(UrlInput, { type: "video", onSubmit: onUrlAdd }) }),
|
|
1274
1217
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1275
1218
|
FileInput,
|
|
1276
1219
|
{
|
|
@@ -1304,7 +1247,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1304
1247
|
}
|
|
1305
1248
|
}
|
|
1306
1249
|
),
|
|
1307
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "media-duration", children: "0:13" }),
|
|
1308
1250
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "media-actions", children: [
|
|
1309
1251
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1310
1252
|
"button",
|
|
@@ -1339,17 +1281,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1339
1281
|
)) }),
|
|
1340
1282
|
items.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
1341
1283
|
/* @__PURE__ */ jsxRuntime.jsx(WandSparkles, { className: "empty-state-icon" }),
|
|
1342
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No videos found" })
|
|
1343
|
-
searchQuery && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-subtext", children: "Try adjusting your search" })
|
|
1284
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No videos found" })
|
|
1344
1285
|
] }) })
|
|
1345
1286
|
] })
|
|
1346
1287
|
] });
|
|
1347
1288
|
}
|
|
1348
1289
|
function VideoPanelContainer(props) {
|
|
1290
|
+
const { addItem } = useMedia("video");
|
|
1291
|
+
const mediaManager = getMediaManager();
|
|
1349
1292
|
const {
|
|
1350
1293
|
items,
|
|
1351
|
-
searchQuery,
|
|
1352
|
-
setSearchQuery,
|
|
1353
1294
|
handleSelection,
|
|
1354
1295
|
handleFileUpload,
|
|
1355
1296
|
isLoading,
|
|
@@ -1363,16 +1304,33 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1363
1304
|
},
|
|
1364
1305
|
props.videoResolution
|
|
1365
1306
|
);
|
|
1307
|
+
const onUrlAdd = async (url) => {
|
|
1308
|
+
const nameFromUrl = (() => {
|
|
1309
|
+
try {
|
|
1310
|
+
const u = new URL(url);
|
|
1311
|
+
const parts = u.pathname.split("/").filter(Boolean);
|
|
1312
|
+
return decodeURIComponent(parts[parts.length - 1] || url);
|
|
1313
|
+
} catch {
|
|
1314
|
+
return url;
|
|
1315
|
+
}
|
|
1316
|
+
})();
|
|
1317
|
+
const newItem = await mediaManager.addItem({
|
|
1318
|
+
name: nameFromUrl,
|
|
1319
|
+
url,
|
|
1320
|
+
type: "video",
|
|
1321
|
+
metadata: { source: "url" }
|
|
1322
|
+
});
|
|
1323
|
+
addItem(newItem);
|
|
1324
|
+
};
|
|
1366
1325
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1367
1326
|
VideoPanel,
|
|
1368
1327
|
{
|
|
1369
1328
|
items,
|
|
1370
|
-
searchQuery,
|
|
1371
|
-
onSearchChange: setSearchQuery,
|
|
1372
1329
|
onItemSelect: handleSelection,
|
|
1373
1330
|
onFileUpload: handleFileUpload,
|
|
1374
1331
|
isLoading,
|
|
1375
|
-
acceptFileTypes
|
|
1332
|
+
acceptFileTypes,
|
|
1333
|
+
onUrlAdd
|
|
1376
1334
|
}
|
|
1377
1335
|
);
|
|
1378
1336
|
}
|
|
@@ -1706,6 +1664,24 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1706
1664
|
const textPanelProps = useTextPanel(props);
|
|
1707
1665
|
return /* @__PURE__ */ jsxRuntime.jsx(TextPanel, { ...textPanelProps });
|
|
1708
1666
|
}
|
|
1667
|
+
const SearchInput = ({
|
|
1668
|
+
searchQuery,
|
|
1669
|
+
setSearchQuery
|
|
1670
|
+
}) => {
|
|
1671
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "search-container", children: [
|
|
1672
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1673
|
+
"input",
|
|
1674
|
+
{
|
|
1675
|
+
type: "text",
|
|
1676
|
+
placeholder: "Search media...",
|
|
1677
|
+
value: searchQuery,
|
|
1678
|
+
onChange: (e) => setSearchQuery(e.target.value),
|
|
1679
|
+
className: "input search-input w-full"
|
|
1680
|
+
}
|
|
1681
|
+
),
|
|
1682
|
+
/* @__PURE__ */ jsxRuntime.jsx(Search, { className: "search-icon" })
|
|
1683
|
+
] });
|
|
1684
|
+
};
|
|
1709
1685
|
function IconPanel({
|
|
1710
1686
|
icons,
|
|
1711
1687
|
loading,
|
|
@@ -2283,9 +2259,198 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
2283
2259
|
});
|
|
2284
2260
|
return /* @__PURE__ */ jsxRuntime.jsx(CirclePanel, { ...circlePanelProps });
|
|
2285
2261
|
}
|
|
2262
|
+
function SubtitlesPanel({
|
|
2263
|
+
subtitles,
|
|
2264
|
+
addSubtitle,
|
|
2265
|
+
splitSubtitle,
|
|
2266
|
+
deleteSubtitle,
|
|
2267
|
+
updateSubtitle
|
|
2268
|
+
}) {
|
|
2269
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
|
|
2270
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "panel-title", children: "Subtitles" }),
|
|
2271
|
+
subtitles.map((subtitle, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2272
|
+
"div",
|
|
2273
|
+
{
|
|
2274
|
+
className: "panel-section gap-2",
|
|
2275
|
+
children: [
|
|
2276
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2277
|
+
"input",
|
|
2278
|
+
{
|
|
2279
|
+
type: "text",
|
|
2280
|
+
placeholder: "Enter subtitle text",
|
|
2281
|
+
value: subtitle.t,
|
|
2282
|
+
onChange: (e) => updateSubtitle(i, { ...subtitle, t: e.target.value }),
|
|
2283
|
+
className: "input-dark"
|
|
2284
|
+
}
|
|
2285
|
+
) }),
|
|
2286
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container justify-between", children: [
|
|
2287
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2288
|
+
"button",
|
|
2289
|
+
{
|
|
2290
|
+
onClick: () => splitSubtitle(i),
|
|
2291
|
+
className: "btn-ghost",
|
|
2292
|
+
title: "Split subtitle",
|
|
2293
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Scissors, { className: "icon-sm" })
|
|
2294
|
+
}
|
|
2295
|
+
),
|
|
2296
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2297
|
+
"button",
|
|
2298
|
+
{
|
|
2299
|
+
onClick: () => deleteSubtitle(i),
|
|
2300
|
+
className: "btn-ghost",
|
|
2301
|
+
title: "Delete subtitle",
|
|
2302
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { className: "icon-sm", color: "var(--color-red-500)" })
|
|
2303
|
+
}
|
|
2304
|
+
)
|
|
2305
|
+
] })
|
|
2306
|
+
]
|
|
2307
|
+
},
|
|
2308
|
+
i
|
|
2309
|
+
)),
|
|
2310
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: addSubtitle, className: "btn-primary w-full", title: "Add subtitle", children: "Add" }) })
|
|
2311
|
+
] });
|
|
2312
|
+
}
|
|
2313
|
+
const CAPTION_PROPS = {
|
|
2314
|
+
[timeline.CAPTION_STYLE.WORD_BG_HIGHLIGHT]: {
|
|
2315
|
+
font: {
|
|
2316
|
+
size: 50,
|
|
2317
|
+
weight: 700,
|
|
2318
|
+
family: "Bangers"
|
|
2319
|
+
},
|
|
2320
|
+
colors: {
|
|
2321
|
+
text: "#ffffff",
|
|
2322
|
+
highlight: "#ff4081",
|
|
2323
|
+
bgColor: "#444444"
|
|
2324
|
+
},
|
|
2325
|
+
lineWidth: 0.35,
|
|
2326
|
+
stroke: "#000000",
|
|
2327
|
+
fontWeight: 700,
|
|
2328
|
+
shadowOffset: [-3, 3],
|
|
2329
|
+
shadowColor: "#000000"
|
|
2330
|
+
},
|
|
2331
|
+
[timeline.CAPTION_STYLE.WORD_BY_WORD]: {
|
|
2332
|
+
font: {
|
|
2333
|
+
size: 50,
|
|
2334
|
+
weight: 700,
|
|
2335
|
+
family: "Bangers"
|
|
2336
|
+
},
|
|
2337
|
+
colors: {
|
|
2338
|
+
text: "#ffffff",
|
|
2339
|
+
highlight: "#ff4081",
|
|
2340
|
+
bgColor: "#444444"
|
|
2341
|
+
},
|
|
2342
|
+
lineWidth: 0.35,
|
|
2343
|
+
stroke: "#000000",
|
|
2344
|
+
shadowOffset: [-2, 2],
|
|
2345
|
+
shadowColor: "#000000",
|
|
2346
|
+
shadowBlur: 5
|
|
2347
|
+
},
|
|
2348
|
+
[timeline.CAPTION_STYLE.WORD_BY_WORD_WITH_BG]: {
|
|
2349
|
+
font: {
|
|
2350
|
+
size: 50,
|
|
2351
|
+
weight: 700,
|
|
2352
|
+
family: "Bangers"
|
|
2353
|
+
},
|
|
2354
|
+
colors: {
|
|
2355
|
+
text: "#ffffff",
|
|
2356
|
+
highlight: "#ff4081",
|
|
2357
|
+
bgColor: "#444444"
|
|
2358
|
+
},
|
|
2359
|
+
lineWidth: 0.35,
|
|
2360
|
+
shadowOffset: [-2, 2],
|
|
2361
|
+
shadowColor: "#000000",
|
|
2362
|
+
shadowBlur: 5
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
const useSubtitlesPanel = () => {
|
|
2366
|
+
const [subtitles, setSubtitles] = react.useState([]);
|
|
2367
|
+
const subtitlesTrack = react.useRef(null);
|
|
2368
|
+
const { editor } = timeline.useTimelineContext();
|
|
2369
|
+
const fetchSubtitles = async () => {
|
|
2370
|
+
const editorSubtitlesTrack = editor.getSubtiltesTrack();
|
|
2371
|
+
if (editorSubtitlesTrack) {
|
|
2372
|
+
subtitlesTrack.current = editorSubtitlesTrack;
|
|
2373
|
+
setSubtitles(
|
|
2374
|
+
editorSubtitlesTrack.getElements().map((element) => ({
|
|
2375
|
+
s: element.getStart(),
|
|
2376
|
+
e: element.getEnd(),
|
|
2377
|
+
t: element.getText()
|
|
2378
|
+
}))
|
|
2379
|
+
);
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2382
|
+
react.useEffect(() => {
|
|
2383
|
+
fetchSubtitles();
|
|
2384
|
+
}, []);
|
|
2385
|
+
const checkSubtitlesTrack = () => {
|
|
2386
|
+
var _a;
|
|
2387
|
+
if (!subtitlesTrack.current) {
|
|
2388
|
+
subtitlesTrack.current = editor.addTrack("Subtitles", "caption");
|
|
2389
|
+
const props = {
|
|
2390
|
+
capStyle: timeline.CAPTION_STYLE.WORD_BG_HIGHLIGHT,
|
|
2391
|
+
...CAPTION_PROPS[timeline.CAPTION_STYLE.WORD_BG_HIGHLIGHT],
|
|
2392
|
+
x: 0,
|
|
2393
|
+
y: 200,
|
|
2394
|
+
applyToAll: true
|
|
2395
|
+
};
|
|
2396
|
+
(_a = subtitlesTrack.current) == null ? void 0 : _a.setProps(props);
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
const addSubtitle = () => {
|
|
2400
|
+
const newSubtitle = { s: 0, e: 0, t: "New Subtitle" };
|
|
2401
|
+
if (subtitles.length > 0) {
|
|
2402
|
+
newSubtitle.s = subtitles[subtitles.length - 1].e;
|
|
2403
|
+
}
|
|
2404
|
+
newSubtitle.e = newSubtitle.s + 1;
|
|
2405
|
+
setSubtitles([...subtitles, newSubtitle]);
|
|
2406
|
+
checkSubtitlesTrack();
|
|
2407
|
+
const captionElement = new timeline.CaptionElement(
|
|
2408
|
+
newSubtitle.t,
|
|
2409
|
+
newSubtitle.s,
|
|
2410
|
+
newSubtitle.e
|
|
2411
|
+
);
|
|
2412
|
+
editor.addElementToTrack(subtitlesTrack.current, captionElement);
|
|
2413
|
+
};
|
|
2414
|
+
const splitSubtitle = async (index) => {
|
|
2415
|
+
if (subtitlesTrack.current) {
|
|
2416
|
+
const element = subtitlesTrack.current.getElements()[index];
|
|
2417
|
+
const splitResult = await editor.splitElement(
|
|
2418
|
+
element,
|
|
2419
|
+
element.getStart() + element.getDuration() / 2
|
|
2420
|
+
);
|
|
2421
|
+
if (splitResult.success) {
|
|
2422
|
+
fetchSubtitles();
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
const deleteSubtitle = (index) => {
|
|
2427
|
+
setSubtitles(subtitles.filter((_, i) => i !== index));
|
|
2428
|
+
if (subtitlesTrack.current) {
|
|
2429
|
+
editor.removeElement(subtitlesTrack.current.getElements()[index]);
|
|
2430
|
+
}
|
|
2431
|
+
};
|
|
2432
|
+
const updateSubtitle = (index, subtitle) => {
|
|
2433
|
+
setSubtitles(subtitles.map((sub, i) => i === index ? subtitle : sub));
|
|
2434
|
+
if (subtitlesTrack.current) {
|
|
2435
|
+
const element = subtitlesTrack.current.getElements()[index];
|
|
2436
|
+
element.setText(subtitle.t);
|
|
2437
|
+
editor.updateElement(element);
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
return {
|
|
2441
|
+
subtitles,
|
|
2442
|
+
addSubtitle,
|
|
2443
|
+
splitSubtitle,
|
|
2444
|
+
deleteSubtitle,
|
|
2445
|
+
updateSubtitle
|
|
2446
|
+
};
|
|
2447
|
+
};
|
|
2448
|
+
function SubtitlesPanelContainer() {
|
|
2449
|
+
const subtitlesPanelProps = useSubtitlesPanel();
|
|
2450
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SubtitlesPanel, { ...subtitlesPanelProps });
|
|
2451
|
+
}
|
|
2286
2452
|
const ElementPanelContainer = ({
|
|
2287
2453
|
selectedTool,
|
|
2288
|
-
setSelectedTool,
|
|
2289
2454
|
videoResolution,
|
|
2290
2455
|
selectedElement,
|
|
2291
2456
|
addElement,
|
|
@@ -2293,9 +2458,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
2293
2458
|
}) => {
|
|
2294
2459
|
const addNewElement = async (element) => {
|
|
2295
2460
|
await addElement(element);
|
|
2296
|
-
if (!["image", "video", "audio"].includes(selectedTool)) {
|
|
2297
|
-
setSelectedTool("none");
|
|
2298
|
-
}
|
|
2299
2461
|
};
|
|
2300
2462
|
const renderLibrary = () => {
|
|
2301
2463
|
switch (selectedTool) {
|
|
@@ -2369,11 +2531,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
2369
2531
|
}
|
|
2370
2532
|
);
|
|
2371
2533
|
case "subtitle":
|
|
2372
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2534
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SubtitlesPanelContainer, {});
|
|
2373
2535
|
default:
|
|
2374
2536
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
|
|
2375
2537
|
/* @__PURE__ */ jsxRuntime.jsx(WandSparkles, { className: "empty-state-icon" }),
|
|
2376
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Select
|
|
2538
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Select an element from toolbar" })
|
|
2377
2539
|
] }) }) });
|
|
2378
2540
|
}
|
|
2379
2541
|
};
|
|
@@ -2922,6 +3084,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
2922
3084
|
if (!selectedElement) {
|
|
2923
3085
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "properties-header", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: "Select Element to see properties" }) }) });
|
|
2924
3086
|
}
|
|
3087
|
+
if (selectedElement.getType() === "caption") {
|
|
3088
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "properties-header", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: "Not available for sub-title" }) }) });
|
|
3089
|
+
}
|
|
2925
3090
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2926
3091
|
selectedProp === "element-props" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2927
3092
|
ElementProps,
|