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