@twick/studio 0.15.16 → 0.15.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/container/element-panel-container.d.ts +3 -1
- package/dist/components/shared/cloud-media-upload.d.ts +15 -0
- package/dist/components/shared/index.d.ts +1 -0
- package/dist/hooks/use-cloud-media-upload.d.ts +27 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +295 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +296 -44
- package/dist/index.mjs.map +1 -1
- package/dist/studio.css +6 -1
- package/dist/types/index.d.ts +11 -0
- package/package.json +14 -14
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ 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,
|
|
5
|
+
import { forwardRef, createElement, useState, useEffect, useRef, useCallback, createContext, useContext, useMemo } from "react";
|
|
6
6
|
import { useTimelineContext, TrackElement, Track, ImageElement, AudioElement, VideoElement, TextElement, RectElement, CircleElement, CAPTION_STYLE, CaptionElement, ElementTextEffect, ElementAnimation, PLAYER_STATE } from "@twick/timeline";
|
|
7
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";
|
|
8
8
|
import VideoEditor, { useEditorManager, BrowserMediaManager, TIMELINE_DROP_MEDIA_TYPE, AVAILABLE_TEXT_FONTS, TEXT_EFFECTS, ANIMATIONS } from "@twick/video-editor";
|
|
@@ -752,6 +752,182 @@ const useStudioManager = () => {
|
|
|
752
752
|
updateElement
|
|
753
753
|
};
|
|
754
754
|
};
|
|
755
|
+
const putFileWithProgress = (uploadUrl, file, onProgress) => {
|
|
756
|
+
return new Promise((resolve, reject) => {
|
|
757
|
+
const xhr = new XMLHttpRequest();
|
|
758
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
759
|
+
if (e.lengthComputable) {
|
|
760
|
+
onProgress(e.loaded / e.total * 100);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
xhr.addEventListener("load", () => {
|
|
764
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
765
|
+
onProgress(100);
|
|
766
|
+
resolve();
|
|
767
|
+
} else {
|
|
768
|
+
reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
xhr.addEventListener("error", () => reject(new Error("Upload failed")));
|
|
772
|
+
xhr.addEventListener("abort", () => reject(new Error("Upload aborted")));
|
|
773
|
+
xhr.open("PUT", uploadUrl);
|
|
774
|
+
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
|
775
|
+
xhr.send(file);
|
|
776
|
+
});
|
|
777
|
+
};
|
|
778
|
+
const useCloudMediaUpload = (config) => {
|
|
779
|
+
const { uploadApiUrl, provider } = config;
|
|
780
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
781
|
+
const [progress, setProgress] = useState(0);
|
|
782
|
+
const [error, setError] = useState(null);
|
|
783
|
+
const resetError = useCallback(() => {
|
|
784
|
+
setError(null);
|
|
785
|
+
}, []);
|
|
786
|
+
const uploadFile = useCallback(
|
|
787
|
+
async (file) => {
|
|
788
|
+
setIsUploading(true);
|
|
789
|
+
setProgress(0);
|
|
790
|
+
setError(null);
|
|
791
|
+
try {
|
|
792
|
+
if (provider === "s3") {
|
|
793
|
+
const presignRes = await fetch(uploadApiUrl, {
|
|
794
|
+
method: "POST",
|
|
795
|
+
headers: { "Content-Type": "application/json" },
|
|
796
|
+
body: JSON.stringify({
|
|
797
|
+
filename: file.name,
|
|
798
|
+
contentType: file.type || "application/octet-stream"
|
|
799
|
+
})
|
|
800
|
+
});
|
|
801
|
+
if (!presignRes.ok) {
|
|
802
|
+
const errBody = await presignRes.json().catch(() => ({}));
|
|
803
|
+
throw new Error(
|
|
804
|
+
errBody.error ?? `Failed to get upload URL: ${presignRes.statusText}`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
const presignData = await presignRes.json();
|
|
808
|
+
const uploadUrl = presignData.uploadUrl;
|
|
809
|
+
await putFileWithProgress(uploadUrl, file, setProgress);
|
|
810
|
+
const publicUrl = uploadUrl.split("?")[0];
|
|
811
|
+
return { url: publicUrl };
|
|
812
|
+
}
|
|
813
|
+
if (provider === "gcs") {
|
|
814
|
+
setProgress(10);
|
|
815
|
+
const formData = new FormData();
|
|
816
|
+
formData.append("file", file);
|
|
817
|
+
const uploadRes = await fetch(uploadApiUrl, {
|
|
818
|
+
method: "POST",
|
|
819
|
+
body: formData
|
|
820
|
+
});
|
|
821
|
+
if (!uploadRes.ok) {
|
|
822
|
+
const errBody = await uploadRes.json().catch(() => ({}));
|
|
823
|
+
throw new Error(
|
|
824
|
+
errBody.error ?? `Upload failed: ${uploadRes.statusText}`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
setProgress(100);
|
|
828
|
+
const data = await uploadRes.json();
|
|
829
|
+
if (!data.url) {
|
|
830
|
+
throw new Error("Upload response missing url");
|
|
831
|
+
}
|
|
832
|
+
return { url: data.url };
|
|
833
|
+
}
|
|
834
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
835
|
+
} catch (err) {
|
|
836
|
+
const message = err instanceof Error ? err.message : "Upload failed";
|
|
837
|
+
setError(message);
|
|
838
|
+
throw err;
|
|
839
|
+
} finally {
|
|
840
|
+
setIsUploading(false);
|
|
841
|
+
setProgress(0);
|
|
842
|
+
}
|
|
843
|
+
},
|
|
844
|
+
[uploadApiUrl, provider]
|
|
845
|
+
);
|
|
846
|
+
return {
|
|
847
|
+
uploadFile,
|
|
848
|
+
isUploading,
|
|
849
|
+
progress,
|
|
850
|
+
error,
|
|
851
|
+
resetError
|
|
852
|
+
};
|
|
853
|
+
};
|
|
854
|
+
const CloudMediaUpload = ({
|
|
855
|
+
onSuccess,
|
|
856
|
+
onError,
|
|
857
|
+
accept,
|
|
858
|
+
uploadApiUrl,
|
|
859
|
+
provider,
|
|
860
|
+
buttonText = "Upload to cloud",
|
|
861
|
+
className,
|
|
862
|
+
disabled = false,
|
|
863
|
+
id: providedId,
|
|
864
|
+
icon
|
|
865
|
+
}) => {
|
|
866
|
+
const id = providedId ?? `cloud-media-upload-${Math.random().toString(36).slice(2, 9)}`;
|
|
867
|
+
const inputRef = useRef(null);
|
|
868
|
+
const {
|
|
869
|
+
uploadFile,
|
|
870
|
+
isUploading,
|
|
871
|
+
progress,
|
|
872
|
+
error,
|
|
873
|
+
resetError
|
|
874
|
+
} = useCloudMediaUpload({ uploadApiUrl, provider });
|
|
875
|
+
const handleFileChange = async (e) => {
|
|
876
|
+
var _a;
|
|
877
|
+
const file = (_a = e.target.files) == null ? void 0 : _a[0];
|
|
878
|
+
if (!file) return;
|
|
879
|
+
try {
|
|
880
|
+
const { url } = await uploadFile(file);
|
|
881
|
+
onSuccess(url, file);
|
|
882
|
+
if (inputRef.current) {
|
|
883
|
+
inputRef.current.value = "";
|
|
884
|
+
}
|
|
885
|
+
} catch (err) {
|
|
886
|
+
const message = err instanceof Error ? err.message : "Upload failed";
|
|
887
|
+
onError == null ? void 0 : onError(message);
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
const handleLabelClick = () => {
|
|
891
|
+
if (disabled || isUploading) return;
|
|
892
|
+
resetError();
|
|
893
|
+
};
|
|
894
|
+
return /* @__PURE__ */ jsxs("div", { className: "file-input-container cloud-media-upload-container", children: [
|
|
895
|
+
/* @__PURE__ */ jsx(
|
|
896
|
+
"input",
|
|
897
|
+
{
|
|
898
|
+
ref: inputRef,
|
|
899
|
+
type: "file",
|
|
900
|
+
accept,
|
|
901
|
+
className: "file-input-hidden",
|
|
902
|
+
id,
|
|
903
|
+
onChange: handleFileChange,
|
|
904
|
+
disabled: disabled || isUploading,
|
|
905
|
+
"aria-label": buttonText
|
|
906
|
+
}
|
|
907
|
+
),
|
|
908
|
+
/* @__PURE__ */ jsxs(
|
|
909
|
+
"label",
|
|
910
|
+
{
|
|
911
|
+
htmlFor: id,
|
|
912
|
+
className: className ?? "btn-primary file-input-label",
|
|
913
|
+
onClick: handleLabelClick,
|
|
914
|
+
style: { pointerEvents: disabled || isUploading ? "none" : void 0 },
|
|
915
|
+
children: [
|
|
916
|
+
icon ?? /* @__PURE__ */ jsx(Upload, { className: "icon-sm" }),
|
|
917
|
+
isUploading ? `${Math.round(progress)}%` : buttonText
|
|
918
|
+
]
|
|
919
|
+
}
|
|
920
|
+
),
|
|
921
|
+
isUploading && /* @__PURE__ */ jsx("div", { className: "cloud-media-upload-progress", role: "progressbar", "aria-valuenow": progress, "aria-valuemin": 0, "aria-valuemax": 100, children: /* @__PURE__ */ jsx(
|
|
922
|
+
"div",
|
|
923
|
+
{
|
|
924
|
+
className: "cloud-media-upload-progress-fill",
|
|
925
|
+
style: { width: `${progress}%` }
|
|
926
|
+
}
|
|
927
|
+
) }),
|
|
928
|
+
error && /* @__PURE__ */ jsx("div", { className: "cloud-media-upload-error", role: "alert", children: error })
|
|
929
|
+
] });
|
|
930
|
+
};
|
|
755
931
|
const _MediaManagerSingleton = class _MediaManagerSingleton {
|
|
756
932
|
constructor() {
|
|
757
933
|
}
|
|
@@ -1309,19 +1485,42 @@ const AudioPanelContainer = (props) => {
|
|
|
1309
1485
|
});
|
|
1310
1486
|
addItem(newItem);
|
|
1311
1487
|
};
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
{
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1488
|
+
const onCloudUploadSuccess = async (url, file) => {
|
|
1489
|
+
var _a;
|
|
1490
|
+
const newItem = await mediaManager.addItem({
|
|
1491
|
+
name: file.name,
|
|
1492
|
+
url,
|
|
1493
|
+
type: "audio",
|
|
1494
|
+
metadata: { source: ((_a = props.uploadConfig) == null ? void 0 : _a.provider) ?? "s3" }
|
|
1495
|
+
});
|
|
1496
|
+
addItem(newItem);
|
|
1497
|
+
};
|
|
1498
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1499
|
+
props.uploadConfig && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(
|
|
1500
|
+
CloudMediaUpload,
|
|
1501
|
+
{
|
|
1502
|
+
uploadApiUrl: props.uploadConfig.uploadApiUrl,
|
|
1503
|
+
provider: props.uploadConfig.provider,
|
|
1504
|
+
accept: "audio/*",
|
|
1505
|
+
onSuccess: onCloudUploadSuccess,
|
|
1506
|
+
buttonText: "Upload audio",
|
|
1507
|
+
className: "btn-ghost w-full"
|
|
1508
|
+
}
|
|
1509
|
+
) }),
|
|
1510
|
+
/* @__PURE__ */ jsx(
|
|
1511
|
+
AudioPanel,
|
|
1512
|
+
{
|
|
1513
|
+
items,
|
|
1514
|
+
searchQuery,
|
|
1515
|
+
onSearchChange: setSearchQuery,
|
|
1516
|
+
onItemSelect: handleSelection,
|
|
1517
|
+
onFileUpload: handleFileUpload,
|
|
1518
|
+
isLoading,
|
|
1519
|
+
acceptFileTypes,
|
|
1520
|
+
onUrlAdd
|
|
1521
|
+
}
|
|
1522
|
+
)
|
|
1523
|
+
] });
|
|
1325
1524
|
};
|
|
1326
1525
|
function ImagePanel({
|
|
1327
1526
|
items,
|
|
@@ -1407,19 +1606,42 @@ function ImagePanelContainer(props) {
|
|
|
1407
1606
|
});
|
|
1408
1607
|
addItem(newItem);
|
|
1409
1608
|
};
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
{
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1609
|
+
const onCloudUploadSuccess = async (url, file) => {
|
|
1610
|
+
var _a;
|
|
1611
|
+
const newItem = await mediaManager.addItem({
|
|
1612
|
+
name: file.name,
|
|
1613
|
+
url,
|
|
1614
|
+
type: "image",
|
|
1615
|
+
metadata: { source: ((_a = props.uploadConfig) == null ? void 0 : _a.provider) ?? "s3" }
|
|
1616
|
+
});
|
|
1617
|
+
addItem(newItem);
|
|
1618
|
+
};
|
|
1619
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1620
|
+
props.uploadConfig && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(
|
|
1621
|
+
CloudMediaUpload,
|
|
1622
|
+
{
|
|
1623
|
+
uploadApiUrl: props.uploadConfig.uploadApiUrl,
|
|
1624
|
+
provider: props.uploadConfig.provider,
|
|
1625
|
+
accept: "image/*",
|
|
1626
|
+
onSuccess: onCloudUploadSuccess,
|
|
1627
|
+
buttonText: "Upload image",
|
|
1628
|
+
className: "btn-ghost w-full"
|
|
1629
|
+
}
|
|
1630
|
+
) }),
|
|
1631
|
+
/* @__PURE__ */ jsx(
|
|
1632
|
+
ImagePanel,
|
|
1633
|
+
{
|
|
1634
|
+
items,
|
|
1635
|
+
searchQuery,
|
|
1636
|
+
onSearchChange: setSearchQuery,
|
|
1637
|
+
onItemSelect: handleSelection,
|
|
1638
|
+
onFileUpload: handleFileUpload,
|
|
1639
|
+
isLoading,
|
|
1640
|
+
acceptFileTypes,
|
|
1641
|
+
onUrlAdd
|
|
1642
|
+
}
|
|
1643
|
+
)
|
|
1644
|
+
] });
|
|
1423
1645
|
}
|
|
1424
1646
|
const useVideoPreview = () => {
|
|
1425
1647
|
const [playingVideo, setPlayingVideo] = useState(null);
|
|
@@ -1573,17 +1795,40 @@ function VideoPanelContainer(props) {
|
|
|
1573
1795
|
});
|
|
1574
1796
|
addItem(newItem);
|
|
1575
1797
|
};
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
{
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1798
|
+
const onCloudUploadSuccess = async (url, file) => {
|
|
1799
|
+
var _a;
|
|
1800
|
+
const newItem = await mediaManager.addItem({
|
|
1801
|
+
name: file.name,
|
|
1802
|
+
url,
|
|
1803
|
+
type: "video",
|
|
1804
|
+
metadata: { source: ((_a = props.uploadConfig) == null ? void 0 : _a.provider) ?? "s3" }
|
|
1805
|
+
});
|
|
1806
|
+
addItem(newItem);
|
|
1807
|
+
};
|
|
1808
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1809
|
+
props.uploadConfig && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(
|
|
1810
|
+
CloudMediaUpload,
|
|
1811
|
+
{
|
|
1812
|
+
uploadApiUrl: props.uploadConfig.uploadApiUrl,
|
|
1813
|
+
provider: props.uploadConfig.provider,
|
|
1814
|
+
accept: "video/*",
|
|
1815
|
+
onSuccess: onCloudUploadSuccess,
|
|
1816
|
+
buttonText: "Upload video",
|
|
1817
|
+
className: "btn-ghost w-full"
|
|
1818
|
+
}
|
|
1819
|
+
) }),
|
|
1820
|
+
/* @__PURE__ */ jsx(
|
|
1821
|
+
VideoPanel,
|
|
1822
|
+
{
|
|
1823
|
+
items,
|
|
1824
|
+
onItemSelect: handleSelection,
|
|
1825
|
+
onFileUpload: handleFileUpload,
|
|
1826
|
+
isLoading,
|
|
1827
|
+
acceptFileTypes,
|
|
1828
|
+
onUrlAdd
|
|
1829
|
+
}
|
|
1830
|
+
)
|
|
1831
|
+
] });
|
|
1587
1832
|
}
|
|
1588
1833
|
function TextPanel({
|
|
1589
1834
|
textContent,
|
|
@@ -2678,7 +2923,8 @@ const ElementPanelContainer = ({
|
|
|
2678
2923
|
videoResolution,
|
|
2679
2924
|
selectedElement,
|
|
2680
2925
|
addElement,
|
|
2681
|
-
updateElement
|
|
2926
|
+
updateElement,
|
|
2927
|
+
uploadConfig
|
|
2682
2928
|
}) => {
|
|
2683
2929
|
const addNewElement = async (element) => {
|
|
2684
2930
|
await addElement(element);
|
|
@@ -2692,7 +2938,8 @@ const ElementPanelContainer = ({
|
|
|
2692
2938
|
videoResolution,
|
|
2693
2939
|
selectedElement,
|
|
2694
2940
|
addElement: addNewElement,
|
|
2695
|
-
updateElement
|
|
2941
|
+
updateElement,
|
|
2942
|
+
uploadConfig
|
|
2696
2943
|
}
|
|
2697
2944
|
);
|
|
2698
2945
|
case "audio":
|
|
@@ -2702,7 +2949,8 @@ const ElementPanelContainer = ({
|
|
|
2702
2949
|
videoResolution,
|
|
2703
2950
|
selectedElement,
|
|
2704
2951
|
addElement: addNewElement,
|
|
2705
|
-
updateElement
|
|
2952
|
+
updateElement,
|
|
2953
|
+
uploadConfig
|
|
2706
2954
|
}
|
|
2707
2955
|
);
|
|
2708
2956
|
case "video":
|
|
@@ -2712,7 +2960,8 @@ const ElementPanelContainer = ({
|
|
|
2712
2960
|
videoResolution,
|
|
2713
2961
|
selectedElement,
|
|
2714
2962
|
addElement: addNewElement,
|
|
2715
|
-
updateElement
|
|
2963
|
+
updateElement,
|
|
2964
|
+
uploadConfig
|
|
2716
2965
|
}
|
|
2717
2966
|
);
|
|
2718
2967
|
case "text":
|
|
@@ -3680,7 +3929,7 @@ function PropertiesPanelContainer({
|
|
|
3680
3929
|
return /* @__PURE__ */ jsxs("aside", { className: "properties-panel", "aria-label": "Element properties inspector", children: [
|
|
3681
3930
|
/* @__PURE__ */ jsxs("div", { className: "properties-header", children: [
|
|
3682
3931
|
!selectedElement && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: "Composition" }),
|
|
3683
|
-
selectedElement && selectedElement.getType() === "caption" && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: "
|
|
3932
|
+
selectedElement && selectedElement.getType() === "caption" && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: "Edit from the captions panel" }),
|
|
3684
3933
|
selectedElement && selectedElement.getType() !== "caption" && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: title })
|
|
3685
3934
|
] }),
|
|
3686
3935
|
/* @__PURE__ */ jsxs("div", { className: "prop-content", children: [
|
|
@@ -3909,7 +4158,8 @@ function TwickStudio({ studioConfig }) {
|
|
|
3909
4158
|
setSelectedTool,
|
|
3910
4159
|
selectedElement,
|
|
3911
4160
|
addElement,
|
|
3912
|
-
updateElement
|
|
4161
|
+
updateElement,
|
|
4162
|
+
uploadConfig: twickStudiConfig.uploadConfig
|
|
3913
4163
|
}
|
|
3914
4164
|
) }),
|
|
3915
4165
|
/* @__PURE__ */ jsx("main", { className: "main-container", children: /* @__PURE__ */ jsx("div", { className: "canvas-wrapper", children: /* @__PURE__ */ jsx(
|
|
@@ -3994,6 +4244,7 @@ export {
|
|
|
3994
4244
|
CaptionsPanel,
|
|
3995
4245
|
CircleElement2 as CircleElement,
|
|
3996
4246
|
CirclePanel,
|
|
4247
|
+
CloudMediaUpload,
|
|
3997
4248
|
ElementAdder,
|
|
3998
4249
|
ElementAnimation2 as ElementAnimation,
|
|
3999
4250
|
ElementCloner,
|
|
@@ -4044,6 +4295,7 @@ export {
|
|
|
4044
4295
|
isElementId,
|
|
4045
4296
|
isTrackId,
|
|
4046
4297
|
setElementColors,
|
|
4298
|
+
useCloudMediaUpload,
|
|
4047
4299
|
useEditorManager2 as useEditorManager,
|
|
4048
4300
|
useGenerateCaptions,
|
|
4049
4301
|
useLivePlayerContext2 as useLivePlayerContext,
|