@twick/studio 0.15.18 → 0.15.20
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 +25 -0
- package/dist/components/container/element-panel-container.d.ts +4 -1
- package/dist/components/container/generate-media-panel-container.d.ts +13 -0
- package/dist/components/properties/caption-prop.d.ts +6 -21
- package/dist/components/shared/cloud-media-upload.d.ts +15 -0
- package/dist/components/shared/index.d.ts +1 -0
- package/dist/helpers/constant.d.ts +36 -0
- package/dist/hooks/use-cloud-media-upload.d.ts +27 -0
- package/dist/hooks/use-generate-image.d.ts +10 -0
- package/dist/hooks/use-generate-video.d.ts +10 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +974 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +978 -67
- package/dist/index.mjs.map +1 -1
- package/dist/studio.css +6 -1
- package/dist/types/generation.d.ts +44 -0
- package/dist/types/index.d.ts +16 -0
- package/package.json +13 -12
package/dist/index.mjs
CHANGED
|
@@ -2,9 +2,9 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
5
|
-
import { forwardRef, createElement, useState, useEffect, useRef, createContext, useContext,
|
|
6
|
-
import { useTimelineContext, TrackElement, Track, ImageElement, AudioElement, VideoElement, TextElement, RectElement, CircleElement, CAPTION_STYLE, CaptionElement, ElementTextEffect, ElementAnimation, PLAYER_STATE } from "@twick/timeline";
|
|
7
|
-
import { AudioElement as AudioElement2, CAPTION_COLOR, CAPTION_FONT, CAPTION_STYLE as CAPTION_STYLE2, CAPTION_STYLE_OPTIONS, CaptionElement as CaptionElement2, CircleElement as CircleElement2, ElementAdder, ElementAnimation as ElementAnimation2, ElementCloner, ElementDeserializer, ElementFrameEffect, ElementRemover, ElementSerializer, ElementSplitter, ElementTextEffect as ElementTextEffect2, ElementUpdater, ElementValidator, INITIAL_TIMELINE_DATA, IconElement, ImageElement as ImageElement2, PROCESS_STATE, RectElement as RectElement2, TIMELINE_ACTION, TIMELINE_ELEMENT_TYPE, TextElement as TextElement2, TimelineEditor, TimelineProvider, Track as Track2, TrackElement as TrackElement2, VideoElement as VideoElement2, WORDS_PER_PHRASE, generateShortUuid, getCurrentElements, getTotalDuration, isElementId, isTrackId, useTimelineContext as useTimelineContext2 } from "@twick/timeline";
|
|
5
|
+
import { forwardRef, createElement, useState, useEffect, useRef, useCallback, createContext, useContext, useMemo } from "react";
|
|
6
|
+
import { useTimelineContext, TrackElement, Track, ImageElement, AudioElement, VideoElement, TextElement, RectElement, CircleElement, CAPTION_STYLE, CaptionElement, ElementTextEffect, ElementAnimation, CAPTION_STYLE_OPTIONS, PLAYER_STATE } from "@twick/timeline";
|
|
7
|
+
import { AudioElement as AudioElement2, CAPTION_COLOR, CAPTION_FONT, CAPTION_STYLE as CAPTION_STYLE2, CAPTION_STYLE_OPTIONS as CAPTION_STYLE_OPTIONS2, CaptionElement as CaptionElement2, CircleElement as CircleElement2, ElementAdder, ElementAnimation as ElementAnimation2, ElementCloner, ElementDeserializer, ElementFrameEffect, ElementRemover, ElementSerializer, ElementSplitter, ElementTextEffect as ElementTextEffect2, ElementUpdater, ElementValidator, INITIAL_TIMELINE_DATA, IconElement, ImageElement as ImageElement2, PROCESS_STATE, RectElement as RectElement2, TIMELINE_ACTION, TIMELINE_ELEMENT_TYPE, TextElement as TextElement2, TimelineEditor, TimelineProvider, Track as Track2, TrackElement as TrackElement2, VideoElement as VideoElement2, WORDS_PER_PHRASE, generateShortUuid, getCurrentElements, getTotalDuration, isElementId, isTrackId, useTimelineContext as useTimelineContext2 } from "@twick/timeline";
|
|
8
8
|
import VideoEditor, { useEditorManager, BrowserMediaManager, TIMELINE_DROP_MEDIA_TYPE, AVAILABLE_TEXT_FONTS, TEXT_EFFECTS, ANIMATIONS } from "@twick/video-editor";
|
|
9
9
|
import { ANIMATIONS as ANIMATIONS2, BaseMediaManager, BrowserMediaManager as BrowserMediaManager2, PlayerControls, TEXT_EFFECTS as TEXT_EFFECTS2, TimelineManager, default as default2, animationGifs, getAnimationGif, setElementColors, useEditorManager as useEditorManager2, usePlayerControl, useTimelineControl } from "@twick/video-editor";
|
|
10
10
|
import { useLivePlayerContext } from "@twick/live-player";
|
|
@@ -561,7 +561,8 @@ const toolCategories = [
|
|
|
561
561
|
{ id: "text", name: "Text", icon: "Type", description: "Add text elements" },
|
|
562
562
|
{ id: "circle", name: "Circle", icon: "Circle", description: "Add a circle element" },
|
|
563
563
|
{ id: "rect", name: "Rect", icon: "Rect", description: "Add a rectangle element" },
|
|
564
|
-
{ id: "caption", name: "Caption", icon: "MessageSquare", description: "Manage captions" }
|
|
564
|
+
{ id: "caption", name: "Caption", icon: "MessageSquare", description: "Manage captions" },
|
|
565
|
+
{ id: "generate-media", name: "Generate", icon: "Wand2", description: "Generate image or video with AI" }
|
|
565
566
|
];
|
|
566
567
|
const getIcon = (iconName) => {
|
|
567
568
|
switch (iconName) {
|
|
@@ -585,6 +586,8 @@ const getIcon = (iconName) => {
|
|
|
585
586
|
return Square;
|
|
586
587
|
case "MessageSquare":
|
|
587
588
|
return MessageSquare;
|
|
589
|
+
case "Wand2":
|
|
590
|
+
return WandSparkles;
|
|
588
591
|
default:
|
|
589
592
|
return Plus;
|
|
590
593
|
}
|
|
@@ -752,6 +755,182 @@ const useStudioManager = () => {
|
|
|
752
755
|
updateElement
|
|
753
756
|
};
|
|
754
757
|
};
|
|
758
|
+
const putFileWithProgress = (uploadUrl, file, onProgress) => {
|
|
759
|
+
return new Promise((resolve, reject) => {
|
|
760
|
+
const xhr = new XMLHttpRequest();
|
|
761
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
762
|
+
if (e.lengthComputable) {
|
|
763
|
+
onProgress(e.loaded / e.total * 100);
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
xhr.addEventListener("load", () => {
|
|
767
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
768
|
+
onProgress(100);
|
|
769
|
+
resolve();
|
|
770
|
+
} else {
|
|
771
|
+
reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
xhr.addEventListener("error", () => reject(new Error("Upload failed")));
|
|
775
|
+
xhr.addEventListener("abort", () => reject(new Error("Upload aborted")));
|
|
776
|
+
xhr.open("PUT", uploadUrl);
|
|
777
|
+
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
|
778
|
+
xhr.send(file);
|
|
779
|
+
});
|
|
780
|
+
};
|
|
781
|
+
const useCloudMediaUpload = (config) => {
|
|
782
|
+
const { uploadApiUrl, provider } = config;
|
|
783
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
784
|
+
const [progress, setProgress] = useState(0);
|
|
785
|
+
const [error, setError] = useState(null);
|
|
786
|
+
const resetError = useCallback(() => {
|
|
787
|
+
setError(null);
|
|
788
|
+
}, []);
|
|
789
|
+
const uploadFile = useCallback(
|
|
790
|
+
async (file) => {
|
|
791
|
+
setIsUploading(true);
|
|
792
|
+
setProgress(0);
|
|
793
|
+
setError(null);
|
|
794
|
+
try {
|
|
795
|
+
if (provider === "s3") {
|
|
796
|
+
const presignRes = await fetch(uploadApiUrl, {
|
|
797
|
+
method: "POST",
|
|
798
|
+
headers: { "Content-Type": "application/json" },
|
|
799
|
+
body: JSON.stringify({
|
|
800
|
+
filename: file.name,
|
|
801
|
+
contentType: file.type || "application/octet-stream"
|
|
802
|
+
})
|
|
803
|
+
});
|
|
804
|
+
if (!presignRes.ok) {
|
|
805
|
+
const errBody = await presignRes.json().catch(() => ({}));
|
|
806
|
+
throw new Error(
|
|
807
|
+
errBody.error ?? `Failed to get upload URL: ${presignRes.statusText}`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
const presignData = await presignRes.json();
|
|
811
|
+
const uploadUrl = presignData.uploadUrl;
|
|
812
|
+
await putFileWithProgress(uploadUrl, file, setProgress);
|
|
813
|
+
const publicUrl = uploadUrl.split("?")[0];
|
|
814
|
+
return { url: publicUrl };
|
|
815
|
+
}
|
|
816
|
+
if (provider === "gcs") {
|
|
817
|
+
setProgress(10);
|
|
818
|
+
const formData = new FormData();
|
|
819
|
+
formData.append("file", file);
|
|
820
|
+
const uploadRes = await fetch(uploadApiUrl, {
|
|
821
|
+
method: "POST",
|
|
822
|
+
body: formData
|
|
823
|
+
});
|
|
824
|
+
if (!uploadRes.ok) {
|
|
825
|
+
const errBody = await uploadRes.json().catch(() => ({}));
|
|
826
|
+
throw new Error(
|
|
827
|
+
errBody.error ?? `Upload failed: ${uploadRes.statusText}`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
setProgress(100);
|
|
831
|
+
const data = await uploadRes.json();
|
|
832
|
+
if (!data.url) {
|
|
833
|
+
throw new Error("Upload response missing url");
|
|
834
|
+
}
|
|
835
|
+
return { url: data.url };
|
|
836
|
+
}
|
|
837
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
838
|
+
} catch (err) {
|
|
839
|
+
const message = err instanceof Error ? err.message : "Upload failed";
|
|
840
|
+
setError(message);
|
|
841
|
+
throw err;
|
|
842
|
+
} finally {
|
|
843
|
+
setIsUploading(false);
|
|
844
|
+
setProgress(0);
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
[uploadApiUrl, provider]
|
|
848
|
+
);
|
|
849
|
+
return {
|
|
850
|
+
uploadFile,
|
|
851
|
+
isUploading,
|
|
852
|
+
progress,
|
|
853
|
+
error,
|
|
854
|
+
resetError
|
|
855
|
+
};
|
|
856
|
+
};
|
|
857
|
+
const CloudMediaUpload = ({
|
|
858
|
+
onSuccess,
|
|
859
|
+
onError,
|
|
860
|
+
accept,
|
|
861
|
+
uploadApiUrl,
|
|
862
|
+
provider,
|
|
863
|
+
buttonText = "Upload to cloud",
|
|
864
|
+
className,
|
|
865
|
+
disabled = false,
|
|
866
|
+
id: providedId,
|
|
867
|
+
icon
|
|
868
|
+
}) => {
|
|
869
|
+
const id = providedId ?? `cloud-media-upload-${Math.random().toString(36).slice(2, 9)}`;
|
|
870
|
+
const inputRef = useRef(null);
|
|
871
|
+
const {
|
|
872
|
+
uploadFile,
|
|
873
|
+
isUploading,
|
|
874
|
+
progress,
|
|
875
|
+
error,
|
|
876
|
+
resetError
|
|
877
|
+
} = useCloudMediaUpload({ uploadApiUrl, provider });
|
|
878
|
+
const handleFileChange = async (e) => {
|
|
879
|
+
var _a;
|
|
880
|
+
const file = (_a = e.target.files) == null ? void 0 : _a[0];
|
|
881
|
+
if (!file) return;
|
|
882
|
+
try {
|
|
883
|
+
const { url } = await uploadFile(file);
|
|
884
|
+
onSuccess(url, file);
|
|
885
|
+
if (inputRef.current) {
|
|
886
|
+
inputRef.current.value = "";
|
|
887
|
+
}
|
|
888
|
+
} catch (err) {
|
|
889
|
+
const message = err instanceof Error ? err.message : "Upload failed";
|
|
890
|
+
onError == null ? void 0 : onError(message);
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
const handleLabelClick = () => {
|
|
894
|
+
if (disabled || isUploading) return;
|
|
895
|
+
resetError();
|
|
896
|
+
};
|
|
897
|
+
return /* @__PURE__ */ jsxs("div", { className: "file-input-container cloud-media-upload-container", children: [
|
|
898
|
+
/* @__PURE__ */ jsx(
|
|
899
|
+
"input",
|
|
900
|
+
{
|
|
901
|
+
ref: inputRef,
|
|
902
|
+
type: "file",
|
|
903
|
+
accept,
|
|
904
|
+
className: "file-input-hidden",
|
|
905
|
+
id,
|
|
906
|
+
onChange: handleFileChange,
|
|
907
|
+
disabled: disabled || isUploading,
|
|
908
|
+
"aria-label": buttonText
|
|
909
|
+
}
|
|
910
|
+
),
|
|
911
|
+
/* @__PURE__ */ jsxs(
|
|
912
|
+
"label",
|
|
913
|
+
{
|
|
914
|
+
htmlFor: id,
|
|
915
|
+
className: className ?? "btn-primary file-input-label",
|
|
916
|
+
onClick: handleLabelClick,
|
|
917
|
+
style: { pointerEvents: disabled || isUploading ? "none" : void 0 },
|
|
918
|
+
children: [
|
|
919
|
+
icon ?? /* @__PURE__ */ jsx(Upload, { className: "icon-sm" }),
|
|
920
|
+
isUploading ? `${Math.round(progress)}%` : buttonText
|
|
921
|
+
]
|
|
922
|
+
}
|
|
923
|
+
),
|
|
924
|
+
isUploading && /* @__PURE__ */ jsx("div", { className: "cloud-media-upload-progress", role: "progressbar", "aria-valuenow": progress, "aria-valuemin": 0, "aria-valuemax": 100, children: /* @__PURE__ */ jsx(
|
|
925
|
+
"div",
|
|
926
|
+
{
|
|
927
|
+
className: "cloud-media-upload-progress-fill",
|
|
928
|
+
style: { width: `${progress}%` }
|
|
929
|
+
}
|
|
930
|
+
) }),
|
|
931
|
+
error && /* @__PURE__ */ jsx("div", { className: "cloud-media-upload-error", role: "alert", children: error })
|
|
932
|
+
] });
|
|
933
|
+
};
|
|
755
934
|
const _MediaManagerSingleton = class _MediaManagerSingleton {
|
|
756
935
|
constructor() {
|
|
757
936
|
}
|
|
@@ -1309,19 +1488,42 @@ const AudioPanelContainer = (props) => {
|
|
|
1309
1488
|
});
|
|
1310
1489
|
addItem(newItem);
|
|
1311
1490
|
};
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
{
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1491
|
+
const onCloudUploadSuccess = async (url, file) => {
|
|
1492
|
+
var _a;
|
|
1493
|
+
const newItem = await mediaManager.addItem({
|
|
1494
|
+
name: file.name,
|
|
1495
|
+
url,
|
|
1496
|
+
type: "audio",
|
|
1497
|
+
metadata: { source: ((_a = props.uploadConfig) == null ? void 0 : _a.provider) ?? "s3" }
|
|
1498
|
+
});
|
|
1499
|
+
addItem(newItem);
|
|
1500
|
+
};
|
|
1501
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1502
|
+
props.uploadConfig && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(
|
|
1503
|
+
CloudMediaUpload,
|
|
1504
|
+
{
|
|
1505
|
+
uploadApiUrl: props.uploadConfig.uploadApiUrl,
|
|
1506
|
+
provider: props.uploadConfig.provider,
|
|
1507
|
+
accept: "audio/*",
|
|
1508
|
+
onSuccess: onCloudUploadSuccess,
|
|
1509
|
+
buttonText: "Upload audio",
|
|
1510
|
+
className: "btn-ghost w-full"
|
|
1511
|
+
}
|
|
1512
|
+
) }),
|
|
1513
|
+
/* @__PURE__ */ jsx(
|
|
1514
|
+
AudioPanel,
|
|
1515
|
+
{
|
|
1516
|
+
items,
|
|
1517
|
+
searchQuery,
|
|
1518
|
+
onSearchChange: setSearchQuery,
|
|
1519
|
+
onItemSelect: handleSelection,
|
|
1520
|
+
onFileUpload: handleFileUpload,
|
|
1521
|
+
isLoading,
|
|
1522
|
+
acceptFileTypes,
|
|
1523
|
+
onUrlAdd
|
|
1524
|
+
}
|
|
1525
|
+
)
|
|
1526
|
+
] });
|
|
1325
1527
|
};
|
|
1326
1528
|
function ImagePanel({
|
|
1327
1529
|
items,
|
|
@@ -1407,19 +1609,42 @@ function ImagePanelContainer(props) {
|
|
|
1407
1609
|
});
|
|
1408
1610
|
addItem(newItem);
|
|
1409
1611
|
};
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
{
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1612
|
+
const onCloudUploadSuccess = async (url, file) => {
|
|
1613
|
+
var _a;
|
|
1614
|
+
const newItem = await mediaManager.addItem({
|
|
1615
|
+
name: file.name,
|
|
1616
|
+
url,
|
|
1617
|
+
type: "image",
|
|
1618
|
+
metadata: { source: ((_a = props.uploadConfig) == null ? void 0 : _a.provider) ?? "s3" }
|
|
1619
|
+
});
|
|
1620
|
+
addItem(newItem);
|
|
1621
|
+
};
|
|
1622
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1623
|
+
props.uploadConfig && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(
|
|
1624
|
+
CloudMediaUpload,
|
|
1625
|
+
{
|
|
1626
|
+
uploadApiUrl: props.uploadConfig.uploadApiUrl,
|
|
1627
|
+
provider: props.uploadConfig.provider,
|
|
1628
|
+
accept: "image/*",
|
|
1629
|
+
onSuccess: onCloudUploadSuccess,
|
|
1630
|
+
buttonText: "Upload image",
|
|
1631
|
+
className: "btn-ghost w-full"
|
|
1632
|
+
}
|
|
1633
|
+
) }),
|
|
1634
|
+
/* @__PURE__ */ jsx(
|
|
1635
|
+
ImagePanel,
|
|
1636
|
+
{
|
|
1637
|
+
items,
|
|
1638
|
+
searchQuery,
|
|
1639
|
+
onSearchChange: setSearchQuery,
|
|
1640
|
+
onItemSelect: handleSelection,
|
|
1641
|
+
onFileUpload: handleFileUpload,
|
|
1642
|
+
isLoading,
|
|
1643
|
+
acceptFileTypes,
|
|
1644
|
+
onUrlAdd
|
|
1645
|
+
}
|
|
1646
|
+
)
|
|
1647
|
+
] });
|
|
1423
1648
|
}
|
|
1424
1649
|
const useVideoPreview = () => {
|
|
1425
1650
|
const [playingVideo, setPlayingVideo] = useState(null);
|
|
@@ -1573,17 +1798,40 @@ function VideoPanelContainer(props) {
|
|
|
1573
1798
|
});
|
|
1574
1799
|
addItem(newItem);
|
|
1575
1800
|
};
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
{
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1801
|
+
const onCloudUploadSuccess = async (url, file) => {
|
|
1802
|
+
var _a;
|
|
1803
|
+
const newItem = await mediaManager.addItem({
|
|
1804
|
+
name: file.name,
|
|
1805
|
+
url,
|
|
1806
|
+
type: "video",
|
|
1807
|
+
metadata: { source: ((_a = props.uploadConfig) == null ? void 0 : _a.provider) ?? "s3" }
|
|
1808
|
+
});
|
|
1809
|
+
addItem(newItem);
|
|
1810
|
+
};
|
|
1811
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1812
|
+
props.uploadConfig && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(
|
|
1813
|
+
CloudMediaUpload,
|
|
1814
|
+
{
|
|
1815
|
+
uploadApiUrl: props.uploadConfig.uploadApiUrl,
|
|
1816
|
+
provider: props.uploadConfig.provider,
|
|
1817
|
+
accept: "video/*",
|
|
1818
|
+
onSuccess: onCloudUploadSuccess,
|
|
1819
|
+
buttonText: "Upload video",
|
|
1820
|
+
className: "btn-ghost w-full"
|
|
1821
|
+
}
|
|
1822
|
+
) }),
|
|
1823
|
+
/* @__PURE__ */ jsx(
|
|
1824
|
+
VideoPanel,
|
|
1825
|
+
{
|
|
1826
|
+
items,
|
|
1827
|
+
onItemSelect: handleSelection,
|
|
1828
|
+
onFileUpload: handleFileUpload,
|
|
1829
|
+
isLoading,
|
|
1830
|
+
acceptFileTypes,
|
|
1831
|
+
onUrlAdd
|
|
1832
|
+
}
|
|
1833
|
+
)
|
|
1834
|
+
] });
|
|
1587
1835
|
}
|
|
1588
1836
|
function TextPanel({
|
|
1589
1837
|
textContent,
|
|
@@ -2584,6 +2832,42 @@ const CAPTION_PROPS = {
|
|
|
2584
2832
|
shadowOffset: [-2, 2],
|
|
2585
2833
|
shadowColor: "#000000",
|
|
2586
2834
|
shadowBlur: 5
|
|
2835
|
+
},
|
|
2836
|
+
[CAPTION_STYLE.OUTLINE_ONLY]: {
|
|
2837
|
+
font: {
|
|
2838
|
+
size: 42,
|
|
2839
|
+
weight: 600,
|
|
2840
|
+
family: "Arial"
|
|
2841
|
+
},
|
|
2842
|
+
colors: {
|
|
2843
|
+
text: "#ffffff",
|
|
2844
|
+
highlight: "#ff4081",
|
|
2845
|
+
bgColor: "#000000"
|
|
2846
|
+
},
|
|
2847
|
+
lineWidth: 0.5,
|
|
2848
|
+
stroke: "#000000",
|
|
2849
|
+
fontWeight: 600,
|
|
2850
|
+
shadowOffset: [0, 0],
|
|
2851
|
+
shadowColor: "#000000",
|
|
2852
|
+
shadowBlur: 0
|
|
2853
|
+
},
|
|
2854
|
+
[CAPTION_STYLE.SOFT_BOX]: {
|
|
2855
|
+
font: {
|
|
2856
|
+
size: 40,
|
|
2857
|
+
weight: 600,
|
|
2858
|
+
family: "Montserrat"
|
|
2859
|
+
},
|
|
2860
|
+
colors: {
|
|
2861
|
+
text: "#ffffff",
|
|
2862
|
+
highlight: "#ff4081",
|
|
2863
|
+
bgColor: "#333333"
|
|
2864
|
+
},
|
|
2865
|
+
lineWidth: 0.2,
|
|
2866
|
+
stroke: "#000000",
|
|
2867
|
+
fontWeight: 600,
|
|
2868
|
+
shadowOffset: [-1, 1],
|
|
2869
|
+
shadowColor: "rgba(0,0,0,0.3)",
|
|
2870
|
+
shadowBlur: 3
|
|
2587
2871
|
}
|
|
2588
2872
|
};
|
|
2589
2873
|
const useCaptionsPanel = () => {
|
|
@@ -2673,12 +2957,324 @@ function CaptionsPanelContainer() {
|
|
|
2673
2957
|
const captionsPanelProps = useCaptionsPanel();
|
|
2674
2958
|
return /* @__PURE__ */ jsx(CaptionsPanel, { ...captionsPanelProps });
|
|
2675
2959
|
}
|
|
2960
|
+
const FAL_IMAGE_ENDPOINTS = [
|
|
2961
|
+
{
|
|
2962
|
+
provider: "fal",
|
|
2963
|
+
endpointId: "fal-ai/flux-pro/kontext",
|
|
2964
|
+
label: "FLUX.1 Kontext [pro]",
|
|
2965
|
+
description: "Professional image generation with context-aware editing",
|
|
2966
|
+
popularity: 5,
|
|
2967
|
+
category: "image",
|
|
2968
|
+
inputAsset: ["image"],
|
|
2969
|
+
availableDimensions: [
|
|
2970
|
+
{ width: 1024, height: 1024, label: "1024x1024 (1:1)" },
|
|
2971
|
+
{ width: 1024, height: 576, label: "1024x576 (16:9)" },
|
|
2972
|
+
{ width: 576, height: 1024, label: "576x1024 (9:16)" }
|
|
2973
|
+
]
|
|
2974
|
+
},
|
|
2975
|
+
{
|
|
2976
|
+
provider: "fal",
|
|
2977
|
+
endpointId: "fal-ai/flux/dev",
|
|
2978
|
+
label: "FLUX.1 [dev]",
|
|
2979
|
+
description: "High-quality image generation",
|
|
2980
|
+
popularity: 5,
|
|
2981
|
+
category: "image",
|
|
2982
|
+
minSteps: 1,
|
|
2983
|
+
maxSteps: 50,
|
|
2984
|
+
defaultSteps: 28,
|
|
2985
|
+
minGuidanceScale: 1,
|
|
2986
|
+
maxGuidanceScale: 20,
|
|
2987
|
+
defaultGuidanceScale: 3.5,
|
|
2988
|
+
hasSeed: true
|
|
2989
|
+
},
|
|
2990
|
+
{
|
|
2991
|
+
provider: "fal",
|
|
2992
|
+
endpointId: "fal-ai/flux/schnell",
|
|
2993
|
+
label: "FLUX.1 [schnell]",
|
|
2994
|
+
description: "Ultra-fast image generation",
|
|
2995
|
+
popularity: 4,
|
|
2996
|
+
category: "image",
|
|
2997
|
+
defaultSteps: 4,
|
|
2998
|
+
availableDimensions: [
|
|
2999
|
+
{ width: 1024, height: 1024, label: "1024x1024 (1:1)" },
|
|
3000
|
+
{ width: 1024, height: 576, label: "1024x576 (16:9)" },
|
|
3001
|
+
{ width: 576, height: 1024, label: "576x1024 (9:16)" }
|
|
3002
|
+
]
|
|
3003
|
+
},
|
|
3004
|
+
{
|
|
3005
|
+
provider: "fal",
|
|
3006
|
+
endpointId: "fal-ai/gemini-25-flash-image",
|
|
3007
|
+
label: "Gemini 2.5 Flash Image",
|
|
3008
|
+
description: "Rapid text-to-image generation",
|
|
3009
|
+
popularity: 5,
|
|
3010
|
+
category: "image",
|
|
3011
|
+
availableDimensions: [
|
|
3012
|
+
{ width: 1024, height: 1024, label: "1024x1024 (1:1)" },
|
|
3013
|
+
{ width: 1024, height: 768, label: "1024x768 (4:3)" },
|
|
3014
|
+
{ width: 768, height: 1024, label: "768x1024 (3:4)" },
|
|
3015
|
+
{ width: 1024, height: 576, label: "1024x576 (16:9)" },
|
|
3016
|
+
{ width: 576, height: 1024, label: "576x1024 (9:16)" }
|
|
3017
|
+
]
|
|
3018
|
+
},
|
|
3019
|
+
{
|
|
3020
|
+
provider: "fal",
|
|
3021
|
+
endpointId: "fal-ai/ideogram/v3",
|
|
3022
|
+
label: "Ideogram V3",
|
|
3023
|
+
description: "Advanced text-to-image with superior text rendering",
|
|
3024
|
+
popularity: 5,
|
|
3025
|
+
category: "image",
|
|
3026
|
+
hasSeed: true,
|
|
3027
|
+
hasNegativePrompt: true
|
|
3028
|
+
}
|
|
3029
|
+
];
|
|
3030
|
+
const FAL_VIDEO_ENDPOINTS = [
|
|
3031
|
+
{
|
|
3032
|
+
provider: "fal",
|
|
3033
|
+
endpointId: "fal-ai/veo3",
|
|
3034
|
+
label: "Veo 3",
|
|
3035
|
+
description: "Google Veo 3 text-to-video",
|
|
3036
|
+
popularity: 5,
|
|
3037
|
+
category: "video",
|
|
3038
|
+
availableDurations: [4, 6, 8],
|
|
3039
|
+
defaultDuration: 8,
|
|
3040
|
+
availableDimensions: [
|
|
3041
|
+
{ width: 576, height: 1024, label: "576x1024 (9:16)" },
|
|
3042
|
+
{ width: 1024, height: 576, label: "1024x576 (16:9)" },
|
|
3043
|
+
{ width: 1024, height: 1024, label: "1024x1024 (1:1)" }
|
|
3044
|
+
]
|
|
3045
|
+
},
|
|
3046
|
+
{
|
|
3047
|
+
provider: "fal",
|
|
3048
|
+
endpointId: "fal-ai/veo3/fast",
|
|
3049
|
+
label: "Veo 3 Fast",
|
|
3050
|
+
description: "Accelerated Veo 3 text-to-video",
|
|
3051
|
+
popularity: 5,
|
|
3052
|
+
category: "video",
|
|
3053
|
+
availableDurations: [4, 6, 8],
|
|
3054
|
+
defaultDuration: 8,
|
|
3055
|
+
availableDimensions: [
|
|
3056
|
+
{ width: 576, height: 1024, label: "576x1024 (9:16)" },
|
|
3057
|
+
{ width: 1024, height: 576, label: "1024x576 (16:9)" },
|
|
3058
|
+
{ width: 1024, height: 1024, label: "1024x1024 (1:1)" }
|
|
3059
|
+
]
|
|
3060
|
+
},
|
|
3061
|
+
{
|
|
3062
|
+
provider: "fal",
|
|
3063
|
+
endpointId: "fal-ai/veo3/image-to-video",
|
|
3064
|
+
label: "Veo 3 Image-to-Video",
|
|
3065
|
+
description: "Animate images with Veo 3",
|
|
3066
|
+
popularity: 5,
|
|
3067
|
+
category: "video",
|
|
3068
|
+
inputAsset: ["image"],
|
|
3069
|
+
availableDurations: [8],
|
|
3070
|
+
defaultDuration: 8
|
|
3071
|
+
},
|
|
3072
|
+
{
|
|
3073
|
+
provider: "fal",
|
|
3074
|
+
endpointId: "fal-ai/kling-video/v2.5-turbo/pro/text-to-video",
|
|
3075
|
+
label: "Kling 2.5 Turbo Pro",
|
|
3076
|
+
description: "Text-to-video with fluid motion",
|
|
3077
|
+
popularity: 5,
|
|
3078
|
+
category: "video",
|
|
3079
|
+
availableDurations: [5, 10],
|
|
3080
|
+
defaultDuration: 5,
|
|
3081
|
+
availableDimensions: [
|
|
3082
|
+
{ width: 1024, height: 576, label: "1024x576 (16:9)" },
|
|
3083
|
+
{ width: 576, height: 1024, label: "576x1024 (9:16)" },
|
|
3084
|
+
{ width: 1024, height: 1024, label: "1024x1024 (1:1)" }
|
|
3085
|
+
]
|
|
3086
|
+
}
|
|
3087
|
+
];
|
|
3088
|
+
const DEFAULT_IMAGE_DURATION = 5;
|
|
3089
|
+
function GenerateMediaPanelContainer({
|
|
3090
|
+
videoResolution,
|
|
3091
|
+
addElement,
|
|
3092
|
+
studioConfig
|
|
3093
|
+
}) {
|
|
3094
|
+
var _a;
|
|
3095
|
+
const { getCurrentTime } = useLivePlayerContext();
|
|
3096
|
+
const [tab, setTab] = useState("image");
|
|
3097
|
+
const [prompt2, setPrompt] = useState("");
|
|
3098
|
+
const [selectedEndpointId, setSelectedEndpointId] = useState("");
|
|
3099
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
3100
|
+
const [error, setError] = useState(null);
|
|
3101
|
+
const [status, setStatus] = useState(null);
|
|
3102
|
+
const imageService = studioConfig == null ? void 0 : studioConfig.imageGenerationService;
|
|
3103
|
+
const videoService = studioConfig == null ? void 0 : studioConfig.videoGenerationService;
|
|
3104
|
+
const hasAnyService = !!imageService || !!videoService;
|
|
3105
|
+
const endpoints = tab === "image" ? FAL_IMAGE_ENDPOINTS : FAL_VIDEO_ENDPOINTS;
|
|
3106
|
+
const defaultEndpointId = ((_a = endpoints[0]) == null ? void 0 : _a.endpointId) ?? "";
|
|
3107
|
+
useEffect(() => {
|
|
3108
|
+
if (!selectedEndpointId && defaultEndpointId) {
|
|
3109
|
+
setSelectedEndpointId(defaultEndpointId);
|
|
3110
|
+
}
|
|
3111
|
+
}, [tab, defaultEndpointId, selectedEndpointId]);
|
|
3112
|
+
const pollStatus = useCallback(
|
|
3113
|
+
async (requestId) => {
|
|
3114
|
+
const service = tab === "image" ? imageService : videoService;
|
|
3115
|
+
if (!service) return;
|
|
3116
|
+
const interval = setInterval(async () => {
|
|
3117
|
+
try {
|
|
3118
|
+
const result = await service.getRequestStatus(requestId);
|
|
3119
|
+
if (result.status === "completed" && result.url) {
|
|
3120
|
+
clearInterval(interval);
|
|
3121
|
+
setIsGenerating(false);
|
|
3122
|
+
setStatus(null);
|
|
3123
|
+
setError(null);
|
|
3124
|
+
const currentTime = getCurrentTime();
|
|
3125
|
+
const duration = result.duration ?? DEFAULT_IMAGE_DURATION;
|
|
3126
|
+
if (tab === "image") {
|
|
3127
|
+
const element = new ImageElement(result.url, videoResolution);
|
|
3128
|
+
element.setStart(currentTime);
|
|
3129
|
+
element.setEnd(currentTime + duration);
|
|
3130
|
+
addElement(element);
|
|
3131
|
+
} else {
|
|
3132
|
+
const element = new VideoElement(result.url, videoResolution);
|
|
3133
|
+
element.setStart(currentTime);
|
|
3134
|
+
element.setEnd(currentTime + duration);
|
|
3135
|
+
addElement(element);
|
|
3136
|
+
}
|
|
3137
|
+
} else if (result.status === "failed") {
|
|
3138
|
+
clearInterval(interval);
|
|
3139
|
+
setIsGenerating(false);
|
|
3140
|
+
setStatus(null);
|
|
3141
|
+
setError(result.error ?? "Generation failed");
|
|
3142
|
+
}
|
|
3143
|
+
} catch {
|
|
3144
|
+
}
|
|
3145
|
+
}, 3e3);
|
|
3146
|
+
return () => clearInterval(interval);
|
|
3147
|
+
},
|
|
3148
|
+
[tab, imageService, videoService, getCurrentTime, videoResolution, addElement]
|
|
3149
|
+
);
|
|
3150
|
+
const handleGenerate = useCallback(async () => {
|
|
3151
|
+
if (!prompt2.trim()) {
|
|
3152
|
+
setError("Enter a prompt");
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
if (tab === "image" && !imageService) {
|
|
3156
|
+
setError("Image generation not configured");
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
if (tab === "video" && !videoService) {
|
|
3160
|
+
setError("Video generation not configured");
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
setIsGenerating(true);
|
|
3164
|
+
setError(null);
|
|
3165
|
+
setStatus("Starting...");
|
|
3166
|
+
try {
|
|
3167
|
+
const endpointId = selectedEndpointId || defaultEndpointId;
|
|
3168
|
+
if (tab === "image" && imageService) {
|
|
3169
|
+
const requestId = await imageService.generateImage({
|
|
3170
|
+
provider: "fal",
|
|
3171
|
+
endpointId,
|
|
3172
|
+
prompt: prompt2.trim()
|
|
3173
|
+
});
|
|
3174
|
+
if (requestId) {
|
|
3175
|
+
setStatus("Generating image...");
|
|
3176
|
+
pollStatus(requestId);
|
|
3177
|
+
}
|
|
3178
|
+
} else if (tab === "video" && videoService) {
|
|
3179
|
+
const requestId = await videoService.generateVideo({
|
|
3180
|
+
provider: "fal",
|
|
3181
|
+
endpointId,
|
|
3182
|
+
prompt: prompt2.trim()
|
|
3183
|
+
});
|
|
3184
|
+
if (requestId) {
|
|
3185
|
+
setStatus("Generating video (this may take several minutes)...");
|
|
3186
|
+
pollStatus(requestId);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
} catch (err) {
|
|
3190
|
+
const msg = err instanceof Error ? err.message : "Generation failed";
|
|
3191
|
+
setError(msg);
|
|
3192
|
+
setIsGenerating(false);
|
|
3193
|
+
setStatus(null);
|
|
3194
|
+
}
|
|
3195
|
+
}, [
|
|
3196
|
+
tab,
|
|
3197
|
+
prompt2,
|
|
3198
|
+
selectedEndpointId,
|
|
3199
|
+
defaultEndpointId,
|
|
3200
|
+
imageService,
|
|
3201
|
+
videoService,
|
|
3202
|
+
pollStatus
|
|
3203
|
+
]);
|
|
3204
|
+
if (!hasAnyService) {
|
|
3205
|
+
return /* @__PURE__ */ jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "Image and video generation require configuration. Add imageGenerationService and videoGenerationService to StudioConfig." }) });
|
|
3206
|
+
}
|
|
3207
|
+
return /* @__PURE__ */ jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
|
|
3208
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 mb-2", children: [
|
|
3209
|
+
/* @__PURE__ */ jsx(
|
|
3210
|
+
"button",
|
|
3211
|
+
{
|
|
3212
|
+
type: "button",
|
|
3213
|
+
className: `btn-ghost ${tab === "image" ? "active" : ""}`,
|
|
3214
|
+
onClick: () => setTab("image"),
|
|
3215
|
+
disabled: !imageService,
|
|
3216
|
+
children: "Image"
|
|
3217
|
+
}
|
|
3218
|
+
),
|
|
3219
|
+
/* @__PURE__ */ jsx(
|
|
3220
|
+
"button",
|
|
3221
|
+
{
|
|
3222
|
+
type: "button",
|
|
3223
|
+
className: `btn-ghost ${tab === "video" ? "active" : ""}`,
|
|
3224
|
+
onClick: () => setTab("video"),
|
|
3225
|
+
disabled: !videoService,
|
|
3226
|
+
children: "Video"
|
|
3227
|
+
}
|
|
3228
|
+
)
|
|
3229
|
+
] }),
|
|
3230
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
|
|
3231
|
+
/* @__PURE__ */ jsx("label", { className: "block text-sm mb-1", children: "Model" }),
|
|
3232
|
+
/* @__PURE__ */ jsx(
|
|
3233
|
+
"select",
|
|
3234
|
+
{
|
|
3235
|
+
className: "w-full p-2 border rounded",
|
|
3236
|
+
value: selectedEndpointId,
|
|
3237
|
+
onChange: (e) => setSelectedEndpointId(e.target.value),
|
|
3238
|
+
disabled: isGenerating,
|
|
3239
|
+
children: endpoints.map((ep) => /* @__PURE__ */ jsx("option", { value: ep.endpointId, children: ep.label }, ep.endpointId))
|
|
3240
|
+
}
|
|
3241
|
+
)
|
|
3242
|
+
] }),
|
|
3243
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
|
|
3244
|
+
/* @__PURE__ */ jsx("label", { className: "block text-sm mb-1", children: "Prompt" }),
|
|
3245
|
+
/* @__PURE__ */ jsx(
|
|
3246
|
+
"textarea",
|
|
3247
|
+
{
|
|
3248
|
+
className: "w-full p-2 border rounded min-h-[80px]",
|
|
3249
|
+
value: prompt2,
|
|
3250
|
+
onChange: (e) => setPrompt(e.target.value),
|
|
3251
|
+
placeholder: "Describe the image or video you want...",
|
|
3252
|
+
disabled: isGenerating
|
|
3253
|
+
}
|
|
3254
|
+
)
|
|
3255
|
+
] }),
|
|
3256
|
+
error && /* @__PURE__ */ jsx("div", { className: "mb-2 text-red-600 text-sm", children: error }),
|
|
3257
|
+
status && /* @__PURE__ */ jsx("div", { className: "mb-2 text-sm text-gray-600", children: status }),
|
|
3258
|
+
/* @__PURE__ */ jsx(
|
|
3259
|
+
"button",
|
|
3260
|
+
{
|
|
3261
|
+
type: "button",
|
|
3262
|
+
className: "btn-primary w-full",
|
|
3263
|
+
onClick: handleGenerate,
|
|
3264
|
+
disabled: isGenerating || !prompt2.trim(),
|
|
3265
|
+
children: isGenerating ? "Generating..." : `Generate ${tab}`
|
|
3266
|
+
}
|
|
3267
|
+
)
|
|
3268
|
+
] }) });
|
|
3269
|
+
}
|
|
2676
3270
|
const ElementPanelContainer = ({
|
|
2677
3271
|
selectedTool,
|
|
2678
3272
|
videoResolution,
|
|
2679
3273
|
selectedElement,
|
|
2680
3274
|
addElement,
|
|
2681
|
-
updateElement
|
|
3275
|
+
updateElement,
|
|
3276
|
+
uploadConfig,
|
|
3277
|
+
studioConfig
|
|
2682
3278
|
}) => {
|
|
2683
3279
|
const addNewElement = async (element) => {
|
|
2684
3280
|
await addElement(element);
|
|
@@ -2692,7 +3288,8 @@ const ElementPanelContainer = ({
|
|
|
2692
3288
|
videoResolution,
|
|
2693
3289
|
selectedElement,
|
|
2694
3290
|
addElement: addNewElement,
|
|
2695
|
-
updateElement
|
|
3291
|
+
updateElement,
|
|
3292
|
+
uploadConfig
|
|
2696
3293
|
}
|
|
2697
3294
|
);
|
|
2698
3295
|
case "audio":
|
|
@@ -2702,7 +3299,8 @@ const ElementPanelContainer = ({
|
|
|
2702
3299
|
videoResolution,
|
|
2703
3300
|
selectedElement,
|
|
2704
3301
|
addElement: addNewElement,
|
|
2705
|
-
updateElement
|
|
3302
|
+
updateElement,
|
|
3303
|
+
uploadConfig
|
|
2706
3304
|
}
|
|
2707
3305
|
);
|
|
2708
3306
|
case "video":
|
|
@@ -2712,7 +3310,8 @@ const ElementPanelContainer = ({
|
|
|
2712
3310
|
videoResolution,
|
|
2713
3311
|
selectedElement,
|
|
2714
3312
|
addElement: addNewElement,
|
|
2715
|
-
updateElement
|
|
3313
|
+
updateElement,
|
|
3314
|
+
uploadConfig
|
|
2716
3315
|
}
|
|
2717
3316
|
);
|
|
2718
3317
|
case "text":
|
|
@@ -2746,6 +3345,17 @@ const ElementPanelContainer = ({
|
|
|
2746
3345
|
);
|
|
2747
3346
|
case "caption":
|
|
2748
3347
|
return /* @__PURE__ */ jsx(CaptionsPanelContainer, {});
|
|
3348
|
+
case "generate-media":
|
|
3349
|
+
return /* @__PURE__ */ jsx(
|
|
3350
|
+
GenerateMediaPanelContainer,
|
|
3351
|
+
{
|
|
3352
|
+
videoResolution,
|
|
3353
|
+
selectedElement,
|
|
3354
|
+
addElement: addNewElement,
|
|
3355
|
+
updateElement,
|
|
3356
|
+
studioConfig
|
|
3357
|
+
}
|
|
3358
|
+
);
|
|
2749
3359
|
default:
|
|
2750
3360
|
return /* @__PURE__ */ jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxs("div", { className: "empty-state-content", children: [
|
|
2751
3361
|
/* @__PURE__ */ jsx(WandSparkles, { className: "empty-state-icon" }),
|
|
@@ -3220,6 +3830,258 @@ function Animation({
|
|
|
3220
3830
|
})() })
|
|
3221
3831
|
] });
|
|
3222
3832
|
}
|
|
3833
|
+
const CAPTION_FONT2 = {
|
|
3834
|
+
size: 40,
|
|
3835
|
+
family: "Bangers"
|
|
3836
|
+
};
|
|
3837
|
+
const CAPTION_COLOR2 = {
|
|
3838
|
+
text: "#ffffff",
|
|
3839
|
+
highlight: "#ff4081",
|
|
3840
|
+
bgColor: "#8C52FF",
|
|
3841
|
+
outlineColor: "#000000"
|
|
3842
|
+
};
|
|
3843
|
+
function CaptionPropPanel({
|
|
3844
|
+
selectedElement,
|
|
3845
|
+
updateElement
|
|
3846
|
+
}) {
|
|
3847
|
+
const { editor, changeLog } = useTimelineContext();
|
|
3848
|
+
const captionRef = useRef(null);
|
|
3849
|
+
const [capStyle, setCapStyle] = useState(
|
|
3850
|
+
CAPTION_STYLE_OPTIONS[CAPTION_STYLE.WORD_BG_HIGHLIGHT]
|
|
3851
|
+
);
|
|
3852
|
+
const [fontSize, setFontSize] = useState(CAPTION_FONT2.size);
|
|
3853
|
+
const [fontFamily, setFontFamily] = useState(CAPTION_FONT2.family);
|
|
3854
|
+
const [colors, setColors] = useState({
|
|
3855
|
+
text: CAPTION_COLOR2.text,
|
|
3856
|
+
highlight: CAPTION_COLOR2.highlight,
|
|
3857
|
+
bgColor: CAPTION_COLOR2.bgColor,
|
|
3858
|
+
outlineColor: CAPTION_COLOR2.outlineColor
|
|
3859
|
+
});
|
|
3860
|
+
const track = selectedElement instanceof CaptionElement ? editor.getTrackById(selectedElement.getTrackId()) : null;
|
|
3861
|
+
const trackProps = (track == null ? void 0 : track.getProps()) ?? {};
|
|
3862
|
+
const applyToAll = (trackProps == null ? void 0 : trackProps.applyToAll) ?? false;
|
|
3863
|
+
const handleUpdateCaption = (updates) => {
|
|
3864
|
+
const captionElement = selectedElement;
|
|
3865
|
+
if (!captionElement) return;
|
|
3866
|
+
if (applyToAll && track) {
|
|
3867
|
+
const nextFont = {
|
|
3868
|
+
size: updates.fontSize ?? fontSize,
|
|
3869
|
+
family: updates.fontFamily ?? fontFamily
|
|
3870
|
+
};
|
|
3871
|
+
const nextColors = updates.colors ?? colors;
|
|
3872
|
+
const nextCapStyle = updates.style ?? (capStyle == null ? void 0 : capStyle.value);
|
|
3873
|
+
track.setProps({
|
|
3874
|
+
...trackProps,
|
|
3875
|
+
capStyle: nextCapStyle,
|
|
3876
|
+
font: { ...(trackProps == null ? void 0 : trackProps.font) ?? {}, ...nextFont },
|
|
3877
|
+
colors: nextColors
|
|
3878
|
+
});
|
|
3879
|
+
editor.refresh();
|
|
3880
|
+
} else {
|
|
3881
|
+
const elementProps = captionElement.getProps() ?? {};
|
|
3882
|
+
captionElement.setProps({
|
|
3883
|
+
...elementProps,
|
|
3884
|
+
capStyle: updates.style ?? (capStyle == null ? void 0 : capStyle.value),
|
|
3885
|
+
font: {
|
|
3886
|
+
size: updates.fontSize ?? fontSize,
|
|
3887
|
+
family: updates.fontFamily ?? fontFamily
|
|
3888
|
+
},
|
|
3889
|
+
colors: updates.colors ?? colors
|
|
3890
|
+
});
|
|
3891
|
+
updateElement == null ? void 0 : updateElement(captionElement);
|
|
3892
|
+
}
|
|
3893
|
+
};
|
|
3894
|
+
useEffect(() => {
|
|
3895
|
+
var _a, _b;
|
|
3896
|
+
const captionElement = selectedElement;
|
|
3897
|
+
if (captionElement) {
|
|
3898
|
+
if (captionRef.current) {
|
|
3899
|
+
captionRef.current.value = captionElement == null ? void 0 : captionElement.getText();
|
|
3900
|
+
}
|
|
3901
|
+
const props = applyToAll ? trackProps : captionElement.getProps() ?? {};
|
|
3902
|
+
const _capStyle = props == null ? void 0 : props.capStyle;
|
|
3903
|
+
if (_capStyle && _capStyle in CAPTION_STYLE_OPTIONS) {
|
|
3904
|
+
setCapStyle(CAPTION_STYLE_OPTIONS[_capStyle]);
|
|
3905
|
+
}
|
|
3906
|
+
setFontSize(((_a = props == null ? void 0 : props.font) == null ? void 0 : _a.size) ?? CAPTION_FONT2.size);
|
|
3907
|
+
setFontFamily(((_b = props == null ? void 0 : props.font) == null ? void 0 : _b.family) ?? CAPTION_FONT2.family);
|
|
3908
|
+
const c = props == null ? void 0 : props.colors;
|
|
3909
|
+
setColors({
|
|
3910
|
+
text: (c == null ? void 0 : c.text) ?? CAPTION_COLOR2.text,
|
|
3911
|
+
highlight: (c == null ? void 0 : c.highlight) ?? CAPTION_COLOR2.highlight,
|
|
3912
|
+
bgColor: (c == null ? void 0 : c.bgColor) ?? CAPTION_COLOR2.bgColor,
|
|
3913
|
+
outlineColor: (c == null ? void 0 : c.outlineColor) ?? CAPTION_COLOR2.outlineColor
|
|
3914
|
+
});
|
|
3915
|
+
}
|
|
3916
|
+
}, [selectedElement, applyToAll, changeLog]);
|
|
3917
|
+
if (!(selectedElement instanceof CaptionElement)) {
|
|
3918
|
+
return null;
|
|
3919
|
+
}
|
|
3920
|
+
return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
|
|
3921
|
+
/* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
|
|
3922
|
+
/* @__PURE__ */ jsx("label", { className: "label-dark", children: "Caption Style" }),
|
|
3923
|
+
/* @__PURE__ */ jsx(
|
|
3924
|
+
"select",
|
|
3925
|
+
{
|
|
3926
|
+
value: capStyle.value,
|
|
3927
|
+
onChange: (e) => {
|
|
3928
|
+
const val = e.target.value;
|
|
3929
|
+
if (val in CAPTION_STYLE_OPTIONS) {
|
|
3930
|
+
setCapStyle(CAPTION_STYLE_OPTIONS[val]);
|
|
3931
|
+
}
|
|
3932
|
+
handleUpdateCaption({ style: e.target.value });
|
|
3933
|
+
},
|
|
3934
|
+
className: "select-dark w-full",
|
|
3935
|
+
children: Object.values(CAPTION_STYLE_OPTIONS).map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
|
|
3936
|
+
}
|
|
3937
|
+
)
|
|
3938
|
+
] }),
|
|
3939
|
+
/* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
|
|
3940
|
+
/* @__PURE__ */ jsx("label", { className: "label-dark", children: "Font Size" }),
|
|
3941
|
+
/* @__PURE__ */ jsxs("div", { className: "slider-container", children: [
|
|
3942
|
+
/* @__PURE__ */ jsx(
|
|
3943
|
+
"input",
|
|
3944
|
+
{
|
|
3945
|
+
type: "range",
|
|
3946
|
+
min: "8",
|
|
3947
|
+
max: "72",
|
|
3948
|
+
step: "1",
|
|
3949
|
+
value: fontSize,
|
|
3950
|
+
onChange: (e) => {
|
|
3951
|
+
const value = Number(e.target.value);
|
|
3952
|
+
setFontSize(value);
|
|
3953
|
+
handleUpdateCaption({ fontSize: value });
|
|
3954
|
+
},
|
|
3955
|
+
className: "slider-purple"
|
|
3956
|
+
}
|
|
3957
|
+
),
|
|
3958
|
+
/* @__PURE__ */ jsxs("span", { className: "slider-value", children: [
|
|
3959
|
+
fontSize,
|
|
3960
|
+
"px"
|
|
3961
|
+
] })
|
|
3962
|
+
] })
|
|
3963
|
+
] }),
|
|
3964
|
+
/* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
|
|
3965
|
+
/* @__PURE__ */ jsx("label", { className: "label-dark", children: "Font" }),
|
|
3966
|
+
/* @__PURE__ */ jsxs(
|
|
3967
|
+
"select",
|
|
3968
|
+
{
|
|
3969
|
+
value: fontFamily,
|
|
3970
|
+
onChange: (e) => {
|
|
3971
|
+
const value = e.target.value;
|
|
3972
|
+
setFontFamily(value);
|
|
3973
|
+
handleUpdateCaption({ fontFamily: value });
|
|
3974
|
+
},
|
|
3975
|
+
className: "select-dark w-full",
|
|
3976
|
+
children: [
|
|
3977
|
+
/* @__PURE__ */ jsx("option", { value: "Bangers", children: "Bangers" }),
|
|
3978
|
+
/* @__PURE__ */ jsx("option", { value: "Arial", children: "Arial" }),
|
|
3979
|
+
/* @__PURE__ */ jsx("option", { value: "Helvetica", children: "Helvetica" }),
|
|
3980
|
+
/* @__PURE__ */ jsx("option", { value: "Times New Roman", children: "Times New Roman" })
|
|
3981
|
+
]
|
|
3982
|
+
}
|
|
3983
|
+
)
|
|
3984
|
+
] }),
|
|
3985
|
+
/* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
|
|
3986
|
+
/* @__PURE__ */ jsx("label", { className: "label-dark", children: "Colors" }),
|
|
3987
|
+
/* @__PURE__ */ jsxs("div", { className: "color-section", children: [
|
|
3988
|
+
/* @__PURE__ */ jsxs("div", { className: "color-control", children: [
|
|
3989
|
+
/* @__PURE__ */ jsx("label", { className: "label-small", children: "Text Color" }),
|
|
3990
|
+
/* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
|
|
3991
|
+
/* @__PURE__ */ jsx(
|
|
3992
|
+
"input",
|
|
3993
|
+
{
|
|
3994
|
+
type: "color",
|
|
3995
|
+
value: colors.text,
|
|
3996
|
+
onChange: (e) => {
|
|
3997
|
+
const newColors = { ...colors, text: e.target.value };
|
|
3998
|
+
setColors(newColors);
|
|
3999
|
+
handleUpdateCaption({ colors: newColors });
|
|
4000
|
+
},
|
|
4001
|
+
className: "color-picker"
|
|
4002
|
+
}
|
|
4003
|
+
),
|
|
4004
|
+
/* @__PURE__ */ jsx(
|
|
4005
|
+
"input",
|
|
4006
|
+
{
|
|
4007
|
+
type: "text",
|
|
4008
|
+
value: colors.text,
|
|
4009
|
+
onChange: (e) => {
|
|
4010
|
+
const newColors = { ...colors, text: e.target.value };
|
|
4011
|
+
setColors(newColors);
|
|
4012
|
+
handleUpdateCaption({ colors: newColors });
|
|
4013
|
+
},
|
|
4014
|
+
className: "color-text"
|
|
4015
|
+
}
|
|
4016
|
+
)
|
|
4017
|
+
] })
|
|
4018
|
+
] }),
|
|
4019
|
+
/* @__PURE__ */ jsxs("div", { className: "color-control", children: [
|
|
4020
|
+
/* @__PURE__ */ jsx("label", { className: "label-small", children: "Background Color" }),
|
|
4021
|
+
/* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
|
|
4022
|
+
/* @__PURE__ */ jsx(
|
|
4023
|
+
"input",
|
|
4024
|
+
{
|
|
4025
|
+
type: "color",
|
|
4026
|
+
value: colors.bgColor,
|
|
4027
|
+
onChange: (e) => {
|
|
4028
|
+
const newColors = { ...colors, bgColor: e.target.value };
|
|
4029
|
+
setColors(newColors);
|
|
4030
|
+
handleUpdateCaption({ colors: newColors });
|
|
4031
|
+
},
|
|
4032
|
+
className: "color-picker"
|
|
4033
|
+
}
|
|
4034
|
+
),
|
|
4035
|
+
/* @__PURE__ */ jsx(
|
|
4036
|
+
"input",
|
|
4037
|
+
{
|
|
4038
|
+
type: "text",
|
|
4039
|
+
value: colors.bgColor,
|
|
4040
|
+
onChange: (e) => {
|
|
4041
|
+
const newColors = { ...colors, bgColor: e.target.value };
|
|
4042
|
+
setColors(newColors);
|
|
4043
|
+
handleUpdateCaption({ colors: newColors });
|
|
4044
|
+
},
|
|
4045
|
+
className: "color-text"
|
|
4046
|
+
}
|
|
4047
|
+
)
|
|
4048
|
+
] })
|
|
4049
|
+
] }),
|
|
4050
|
+
/* @__PURE__ */ jsxs("div", { className: "color-control", children: [
|
|
4051
|
+
/* @__PURE__ */ jsx("label", { className: "label-small", children: "Outline Color" }),
|
|
4052
|
+
/* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
|
|
4053
|
+
/* @__PURE__ */ jsx(
|
|
4054
|
+
"input",
|
|
4055
|
+
{
|
|
4056
|
+
type: "color",
|
|
4057
|
+
value: colors.outlineColor,
|
|
4058
|
+
onChange: (e) => {
|
|
4059
|
+
const newColors = { ...colors, outlineColor: e.target.value };
|
|
4060
|
+
setColors(newColors);
|
|
4061
|
+
handleUpdateCaption({ colors: newColors });
|
|
4062
|
+
},
|
|
4063
|
+
className: "color-picker"
|
|
4064
|
+
}
|
|
4065
|
+
),
|
|
4066
|
+
/* @__PURE__ */ jsx(
|
|
4067
|
+
"input",
|
|
4068
|
+
{
|
|
4069
|
+
type: "text",
|
|
4070
|
+
value: colors.outlineColor,
|
|
4071
|
+
onChange: (e) => {
|
|
4072
|
+
const newColors = { ...colors, outlineColor: e.target.value };
|
|
4073
|
+
setColors(newColors);
|
|
4074
|
+
handleUpdateCaption({ colors: newColors });
|
|
4075
|
+
},
|
|
4076
|
+
className: "color-text"
|
|
4077
|
+
}
|
|
4078
|
+
)
|
|
4079
|
+
] })
|
|
4080
|
+
] })
|
|
4081
|
+
] })
|
|
4082
|
+
] })
|
|
4083
|
+
] });
|
|
4084
|
+
}
|
|
3223
4085
|
const MIN_DB = -60;
|
|
3224
4086
|
const MAX_DB = 6;
|
|
3225
4087
|
function linearToDb(linear) {
|
|
@@ -3667,6 +4529,7 @@ function TextPropsPanel({
|
|
|
3667
4529
|
)
|
|
3668
4530
|
] });
|
|
3669
4531
|
}
|
|
4532
|
+
const DEFAULT_CANVAS_BACKGROUND = "#000000";
|
|
3670
4533
|
function PropertiesPanelContainer({
|
|
3671
4534
|
selectedElement,
|
|
3672
4535
|
updateElement,
|
|
@@ -3676,26 +4539,66 @@ function PropertiesPanelContainer({
|
|
|
3676
4539
|
pollingIntervalMs,
|
|
3677
4540
|
videoResolution
|
|
3678
4541
|
}) {
|
|
4542
|
+
const { editor, present } = useTimelineContext();
|
|
4543
|
+
const backgroundColor = (present == null ? void 0 : present.backgroundColor) ?? editor.getBackgroundColor() ?? DEFAULT_CANVAS_BACKGROUND;
|
|
4544
|
+
const handleBackgroundColorChange = useCallback(
|
|
4545
|
+
(value) => {
|
|
4546
|
+
editor.setBackgroundColor(value);
|
|
4547
|
+
},
|
|
4548
|
+
[editor]
|
|
4549
|
+
);
|
|
3679
4550
|
const title = selectedElement instanceof TextElement ? selectedElement.getText() : (selectedElement == null ? void 0 : selectedElement.getName()) || (selectedElement == null ? void 0 : selectedElement.getType()) || "Element";
|
|
3680
4551
|
return /* @__PURE__ */ jsxs("aside", { className: "properties-panel", "aria-label": "Element properties inspector", children: [
|
|
3681
4552
|
/* @__PURE__ */ jsxs("div", { className: "properties-header", children: [
|
|
3682
4553
|
!selectedElement && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: "Composition" }),
|
|
3683
|
-
selectedElement && selectedElement.getType() === "caption" && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: "
|
|
4554
|
+
selectedElement && selectedElement.getType() === "caption" && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: "Caption" }),
|
|
3684
4555
|
selectedElement && selectedElement.getType() !== "caption" && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: title })
|
|
3685
4556
|
] }),
|
|
3686
4557
|
/* @__PURE__ */ jsxs("div", { className: "prop-content", children: [
|
|
3687
4558
|
!selectedElement && /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
|
|
3688
4559
|
/* @__PURE__ */ jsx("div", { className: "panel-title", children: "Canvas & Render" }),
|
|
3689
|
-
/* @__PURE__ */
|
|
3690
|
-
/* @__PURE__ */
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
4560
|
+
/* @__PURE__ */ jsxs("div", { className: "properties-group", children: [
|
|
4561
|
+
/* @__PURE__ */ jsxs("div", { className: "property-section", children: [
|
|
4562
|
+
/* @__PURE__ */ jsx("span", { className: "property-label", children: "Size" }),
|
|
4563
|
+
/* @__PURE__ */ jsxs("span", { className: "properties-size-readonly", children: [
|
|
4564
|
+
videoResolution.width,
|
|
4565
|
+
" × ",
|
|
4566
|
+
videoResolution.height
|
|
4567
|
+
] })
|
|
4568
|
+
] }),
|
|
4569
|
+
/* @__PURE__ */ jsxs("div", { className: "color-control", children: [
|
|
4570
|
+
/* @__PURE__ */ jsx("label", { className: "label-small", children: "Background Color" }),
|
|
4571
|
+
/* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
|
|
4572
|
+
/* @__PURE__ */ jsx(
|
|
4573
|
+
"input",
|
|
4574
|
+
{
|
|
4575
|
+
type: "color",
|
|
4576
|
+
value: backgroundColor,
|
|
4577
|
+
onChange: (e) => handleBackgroundColorChange(e.target.value),
|
|
4578
|
+
className: "color-picker"
|
|
4579
|
+
}
|
|
4580
|
+
),
|
|
4581
|
+
/* @__PURE__ */ jsx(
|
|
4582
|
+
"input",
|
|
4583
|
+
{
|
|
4584
|
+
type: "text",
|
|
4585
|
+
value: backgroundColor,
|
|
4586
|
+
onChange: (e) => handleBackgroundColorChange(e.target.value),
|
|
4587
|
+
className: "color-text"
|
|
4588
|
+
}
|
|
4589
|
+
)
|
|
4590
|
+
] })
|
|
3695
4591
|
] })
|
|
3696
|
-
] })
|
|
4592
|
+
] })
|
|
3697
4593
|
] }),
|
|
3698
|
-
selectedElement
|
|
4594
|
+
selectedElement instanceof CaptionElement && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
|
|
4595
|
+
CaptionPropPanel,
|
|
4596
|
+
{
|
|
4597
|
+
selectedElement,
|
|
4598
|
+
updateElement
|
|
4599
|
+
}
|
|
4600
|
+
) }),
|
|
4601
|
+
selectedElement && !(selectedElement instanceof CaptionElement) && /* @__PURE__ */ jsx(Fragment, { children: (() => {
|
|
3699
4602
|
const isText = selectedElement instanceof TextElement;
|
|
3700
4603
|
const isVideo = selectedElement instanceof VideoElement;
|
|
3701
4604
|
const isAudio = selectedElement instanceof AudioElement;
|
|
@@ -3857,7 +4760,7 @@ function TwickStudio({ studioConfig }) {
|
|
|
3857
4760
|
addElement,
|
|
3858
4761
|
updateElement
|
|
3859
4762
|
} = useStudioManager();
|
|
3860
|
-
const { videoResolution, setVideoResolution } = useTimelineContext();
|
|
4763
|
+
const { editor, present, videoResolution, setVideoResolution } = useTimelineContext();
|
|
3861
4764
|
const {
|
|
3862
4765
|
onNewProject,
|
|
3863
4766
|
onLoadProject,
|
|
@@ -3871,16 +4774,20 @@ function TwickStudio({ studioConfig }) {
|
|
|
3871
4774
|
pollingIntervalMs
|
|
3872
4775
|
} = useGenerateCaptions(studioConfig);
|
|
3873
4776
|
const twickStudiConfig = useMemo(
|
|
3874
|
-
() =>
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
...
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
4777
|
+
() => {
|
|
4778
|
+
var _a2;
|
|
4779
|
+
return {
|
|
4780
|
+
canvasMode: true,
|
|
4781
|
+
...studioConfig || {},
|
|
4782
|
+
videoProps: {
|
|
4783
|
+
...(studioConfig == null ? void 0 : studioConfig.videoProps) || {},
|
|
4784
|
+
width: videoResolution.width,
|
|
4785
|
+
height: videoResolution.height,
|
|
4786
|
+
backgroundColor: (present == null ? void 0 : present.backgroundColor) ?? editor.getBackgroundColor() ?? ((_a2 = studioConfig == null ? void 0 : studioConfig.videoProps) == null ? void 0 : _a2.backgroundColor)
|
|
4787
|
+
}
|
|
4788
|
+
};
|
|
4789
|
+
},
|
|
4790
|
+
[videoResolution, studioConfig, present == null ? void 0 : present.backgroundColor, editor]
|
|
3884
4791
|
);
|
|
3885
4792
|
return /* @__PURE__ */ jsx(MediaProvider, { children: /* @__PURE__ */ jsxs("div", { className: "studio-container", children: [
|
|
3886
4793
|
/* @__PURE__ */ jsx(
|
|
@@ -3909,7 +4816,9 @@ function TwickStudio({ studioConfig }) {
|
|
|
3909
4816
|
setSelectedTool,
|
|
3910
4817
|
selectedElement,
|
|
3911
4818
|
addElement,
|
|
3912
|
-
updateElement
|
|
4819
|
+
updateElement,
|
|
4820
|
+
uploadConfig: twickStudiConfig.uploadConfig,
|
|
4821
|
+
studioConfig: twickStudiConfig
|
|
3913
4822
|
}
|
|
3914
4823
|
) }),
|
|
3915
4824
|
/* @__PURE__ */ jsx("main", { className: "main-container", children: /* @__PURE__ */ jsx("div", { className: "canvas-wrapper", children: /* @__PURE__ */ jsx(
|
|
@@ -3989,11 +4898,12 @@ export {
|
|
|
3989
4898
|
CAPTION_FONT,
|
|
3990
4899
|
CAPTION_PROPS,
|
|
3991
4900
|
CAPTION_STYLE2 as CAPTION_STYLE,
|
|
3992
|
-
CAPTION_STYLE_OPTIONS,
|
|
4901
|
+
CAPTION_STYLE_OPTIONS2 as CAPTION_STYLE_OPTIONS,
|
|
3993
4902
|
CaptionElement2 as CaptionElement,
|
|
3994
4903
|
CaptionsPanel,
|
|
3995
4904
|
CircleElement2 as CircleElement,
|
|
3996
4905
|
CirclePanel,
|
|
4906
|
+
CloudMediaUpload,
|
|
3997
4907
|
ElementAdder,
|
|
3998
4908
|
ElementAnimation2 as ElementAnimation,
|
|
3999
4909
|
ElementCloner,
|
|
@@ -4044,6 +4954,7 @@ export {
|
|
|
4044
4954
|
isElementId,
|
|
4045
4955
|
isTrackId,
|
|
4046
4956
|
setElementColors,
|
|
4957
|
+
useCloudMediaUpload,
|
|
4047
4958
|
useEditorManager2 as useEditorManager,
|
|
4048
4959
|
useGenerateCaptions,
|
|
4049
4960
|
useLivePlayerContext2 as useLivePlayerContext,
|