@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/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 = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
114
- const Check = createLucideIcon("check", __iconNode$o);
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 = [["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 = [
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$m);
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$l = [
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$l);
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$k = [
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$k);
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$j = [
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$j);
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$i = [
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$i);
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$h = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
191
- const LoaderCircle = createLucideIcon("loader-circle", __iconNode$h);
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$g = [
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$g);
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$f = [
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$f);
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$e = [
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$e);
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$d = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
232
- const Play = createLucideIcon("play", __iconNode$d);
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$c = [
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$c);
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$b = [
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$b);
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
- // { id: 'subtitle', name: 'Subtitles', icon: 'MessageSquare', description: 'Manage subtitles', shortcut: 'S' },
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 { editor, selectedItem, setSelectedItem } = timeline.useTimelineContext();
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
- if (isToolChanged.current) {
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: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
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: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
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(SubtitlesPanel, {});
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 a Tool to Begin" })
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,