diff --git a/src/website/package.json b/src/website/package.json
index c5f197810d..1f390d46c1 100644
--- a/src/website/package.json
+++ b/src/website/package.json
@@ -35,6 +35,7 @@
"@react-spring/types": "^9.7.3",
"@typeform/embed": "^4.8.1",
"@types/dom-mediacapture-record": "^1.0.19",
+ "axios": "^1.7.4",
"framer-motion": "6.5.1",
"local-storage": "^2.0.0",
"moment": "^2.29.4",
diff --git a/src/website/packages/components/src/api/MakeMultipartRequest.ts b/src/website/packages/components/src/api/MakeMultipartRequest.ts
index cdf891c84e..1af49d7c67 100644
--- a/src/website/packages/components/src/api/MakeMultipartRequest.ts
+++ b/src/website/packages/components/src/api/MakeMultipartRequest.ts
@@ -1,33 +1,39 @@
import GetApiHost from "./GetApiHost";
+import axios, { AxiosProgressEvent } from "axios";
-const { formatUrl, host = "" } = GetApiHost();
+const { formatUrl } = GetApiHost();
-const MakeMultipartRequest = (endpoint = "", body: any) => {
+export type OnUploadProgress = (progressEvent: AxiosProgressEvent) => void;
+
+const MakeMultipartRequest = (
+ endpoint = "",
+ body: any,
+ onUploadProgress?: OnUploadProgress
+) => {
const formData = new FormData();
- Object.keys(body).forEach((key) => formData.append(key, body[key]));
+ Object.keys(body).forEach(key => formData.append(key, body[key]));
- formData.append('source', "file");
+ formData.append("source", "file");
- return fetch(formatUrl(endpoint), {
- method: 'POST',
- credentials: 'include',
+ return axios
+ .post(formatUrl(endpoint), formData, {
+ withCredentials: true,
headers: {
- 'Accept': 'application/json',
+ Accept: "application/json",
},
- body: formData,
- })
- .then(res => res.json())
- .then(res => {
- if (res && res.success) {
- return res;
- } else {
- return { success : false };
- }
- })
- .catch(e => {
- return { success : false };
- });
-}
+ ...(onUploadProgress ? { onUploadProgress } : {}),
+ })
+ .then(({ data }) => {
+ if (data && data.success) {
+ return data;
+ } else {
+ return { success: false };
+ }
+ })
+ .catch(e => {
+ return { success: false };
+ });
+};
-export default MakeMultipartRequest;
\ No newline at end of file
+export default MakeMultipartRequest;
diff --git a/src/website/packages/components/src/api/media_files/UploadVideoMedia.ts b/src/website/packages/components/src/api/media_files/UploadVideoMedia.ts
index d0192f975d..ea68f1667b 100644
--- a/src/website/packages/components/src/api/media_files/UploadVideoMedia.ts
+++ b/src/website/packages/components/src/api/media_files/UploadVideoMedia.ts
@@ -1,4 +1,6 @@
-import MakeMultipartRequest from "../MakeMultipartRequest";
+import MakeMultipartRequest, {
+ OnUploadProgress,
+} from "../MakeMultipartRequest";
export interface UploadVideoMediaRequest {
file: any;
@@ -15,6 +17,13 @@ export interface UploadVideoMediaResponse {
success: boolean;
}
-export const UploadVideoMedia = (request: UploadVideoMediaRequest) => {
- return MakeMultipartRequest("/v1/media_files/upload/new_video", request);
+export const UploadVideoMedia = (
+ request: UploadVideoMediaRequest,
+ onUploadProgress?: OnUploadProgress
+) => {
+ return MakeMultipartRequest(
+ "/v1/media_files/upload/new_video",
+ request,
+ onUploadProgress
+ );
};
diff --git a/src/website/packages/fakeyou.com/src/components/entities/EntityInput/EntityInput.scss b/src/website/packages/fakeyou.com/src/components/entities/EntityInput/EntityInput.scss
index 7c39e7fda4..81894f5e18 100644
--- a/src/website/packages/fakeyou.com/src/components/entities/EntityInput/EntityInput.scss
+++ b/src/website/packages/fakeyou.com/src/components/entities/EntityInput/EntityInput.scss
@@ -81,6 +81,26 @@
color: rgba(#fff, 0.5);
pointer-events: none;
}
+
+ .fy-entity-input-progress-indicator {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+
+ .fy-entity-input-progress-number {
+ position: absolute;
+ text-align: center;
+ font-size: 0.625rem;
+ line-height: 0.625rem;
+ height: 0.625rem;
+ top: 50%;
+ transform: translateY(-50%);
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+ }
}
.fy-entity-input-landscape {
@@ -135,6 +155,17 @@
padding: 0.5rem;
}
+.fy-entity-input-error {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ button {
+ margin-top: 1rem;
+ }
+}
+
.fy-entity-input-preview-controls {
// background-color: green;
display: grid;
diff --git a/src/website/packages/fakeyou.com/src/components/entities/EntityInput/EntityInput.tsx b/src/website/packages/fakeyou.com/src/components/entities/EntityInput/EntityInput.tsx
index f9212623f6..14e5ea5cb7 100644
--- a/src/website/packages/fakeyou.com/src/components/entities/EntityInput/EntityInput.tsx
+++ b/src/website/packages/fakeyou.com/src/components/entities/EntityInput/EntityInput.tsx
@@ -5,7 +5,12 @@ import useMeasure from "react-use-measure";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
import Iframe from "react-iframe";
import { Area, Point } from "react-easy-crop";
-import { Label, Spinner, ZoomSliderOnChangeEvent } from "components/common";
+import {
+ Button,
+ Label,
+ Spinner,
+ ZoomSliderOnChangeEvent,
+} from "components/common";
import {
AcceptTypes,
EntityModeProp,
@@ -13,6 +18,7 @@ import {
MediaFilters,
UploaderResponse,
} from "components/entities/EntityTypes";
+import { WorkIndicator } from "components/svg";
import { useMedia, useMediaUploader } from "hooks";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import { Prompt } from "@storyteller/components/src/api/prompts/GetPrompts";
@@ -52,6 +58,9 @@ export interface SlideProps {
cropProps?: CropProps;
isNarrow: boolean;
media?: MediaFile;
+ resetUploader: () => void;
+ uploaderBusy: boolean;
+ uploadProgress: number;
}
interface AniProps {
@@ -63,8 +72,47 @@ interface AniProps {
style: any;
}
-const MediaBusy = () => {
- return ;
+const MediaError = ({ resetUploader }: SlideProps) => {
+ return (
+ <>
+
There was a problem with your upload
+
+ >
+ );
+};
+
+const MediaBusy = ({ uploaderBusy, uploadProgress }: SlideProps) => {
+ return (
+
+ {uploaderBusy ? (
+ <>
+ Uploading
+
+
+ {uploadProgress}%
+
+
+
+ >
+ ) : (
+
+ )}
+
+ );
};
const EntityInputFull = ({ clear, cropProps, isNarrow, media }: SlideProps) => {
@@ -216,7 +264,13 @@ export default function EntityInput({
onChange({ target: { name, value: token } });
};
- const { busy: uploaderBusy, inputProps } = useMediaUploader({
+ const {
+ busy: uploaderBusy,
+ error: uploaderError,
+ inputProps,
+ uploadProgress,
+ reset: resetUploader,
+ } = useMediaUploader({
autoUpload: true,
onSuccess: (res: UploaderResponse) => {
reload();
@@ -230,7 +284,7 @@ export default function EntityInput({
};
const busy = mediaBusy || uploaderBusy;
- const index = busy ? 0 : media ? 1 : 2;
+ const index = uploaderError ? 3 : busy ? 0 : media ? 1 : 2;
const [animating, animatingSet] = useState(false);
if (debug)
@@ -284,6 +338,7 @@ export default function EntityInput({
isLeaving,
style,
showFilters: showMediaBrowserFilters,
+ uploaderBusy,
};
return [
@@ -292,6 +347,7 @@ export default function EntityInput({
className:
"fy-entity-input-busy d-flex justify-content-center align-items-center",
render: MediaBusy,
+ uploadProgress,
...sharedProps,
}}
/>,
@@ -320,6 +376,14 @@ export default function EntityInput({
...rest,
}}
/>,
+ ,
][i];
})}
diff --git a/src/website/packages/fakeyou.com/src/components/svg/DashedCircle/DashedCircle.tsx b/src/website/packages/fakeyou.com/src/components/svg/DashedCircle/DashedCircle.tsx
index 086a3261fb..4bc7a65ded 100644
--- a/src/website/packages/fakeyou.com/src/components/svg/DashedCircle/DashedCircle.tsx
+++ b/src/website/packages/fakeyou.com/src/components/svg/DashedCircle/DashedCircle.tsx
@@ -4,16 +4,17 @@ import { circle } from "../sharedSVGProps";
interface Props {
className?: string;
+ max?: number;
progressPercentage: number;
stage: number;
}
export default function DashedCircle({
className = "",
+ max = 87,
progressPercentage,
stage = 0,
}: Props) {
- const max = 87;
const progressBar = (max - 16) * (progressPercentage / 100);
const progressSegment = progressBar / 3;
diff --git a/src/website/packages/fakeyou.com/src/components/svg/WorkIndicator/WorkIndicator.tsx b/src/website/packages/fakeyou.com/src/components/svg/WorkIndicator/WorkIndicator.tsx
index aad04f6952..38f7d09239 100644
--- a/src/website/packages/fakeyou.com/src/components/svg/WorkIndicator/WorkIndicator.tsx
+++ b/src/website/packages/fakeyou.com/src/components/svg/WorkIndicator/WorkIndicator.tsx
@@ -5,6 +5,7 @@ import "./WorkIndicator.scss";
interface Props {
failure: boolean;
+ max?: number;
progressPercentage: number;
stage: number;
success: boolean;
@@ -12,6 +13,7 @@ interface Props {
export default function WorkIndicator({
failure = false,
+ max,
progressPercentage,
stage = 0,
success = false,
@@ -23,6 +25,7 @@ export default function WorkIndicator({
();
+ const [uploadProgress, uploadProgressSet] = useState(0);
const { file, clear, inputProps } = useFile({
...(autoUpload
@@ -63,7 +64,12 @@ export default function useMediaUploader({ autoUpload, onSuccess = n }: Props) {
if (isAudio) return UploadAudioMedia(mediaConfig);
if (isEngineAsset) return UploadEngineAsset(engineConfig);
if (isImage) return UploadImageMedia(mediaConfig);
- else return UploadVideoMedia(mediaConfig);
+ else
+ return UploadVideoMedia(mediaConfig, uploadEvent =>
+ uploadProgressSet(
+ Math.round((uploadEvent.loaded * 100) / (uploadEvent?.total || 0))
+ )
+ );
};
if (inputFile) {
@@ -72,6 +78,8 @@ export default function useMediaUploader({ autoUpload, onSuccess = n }: Props) {
statusSet(FetchStatus.success);
onSuccess(res);
todo();
+ } else {
+ statusSet(FetchStatus.error);
}
});
}
@@ -92,6 +100,7 @@ export default function useMediaUploader({ autoUpload, onSuccess = n }: Props) {
clear,
engineSubtype,
engineSubtypeChange,
+ error: status === FetchStatus.error,
mediaClass,
mediaClassChange,
file,
@@ -103,6 +112,12 @@ export default function useMediaUploader({ autoUpload, onSuccess = n }: Props) {
),
isImage: isSelectedType(MediaFilters.image, extension(file?.name || "")),
isVideo: isSelectedType(MediaFilters.video, extension(file?.name || "")),
+ reset: () => {
+ clear();
+ statusSet(FetchStatus.ready);
+ },
+ status,
upload,
+ uploadProgress,
};
}
diff --git a/src/website/yarn.lock b/src/website/yarn.lock
index 36c1199a57..df2f50aa2a 100644
--- a/src/website/yarn.lock
+++ b/src/website/yarn.lock
@@ -4488,6 +4488,7 @@ __metadata:
"@react-spring/types": ^9.7.3
"@typeform/embed": ^4.8.1
"@types/dom-mediacapture-record": ^1.0.19
+ axios: ^1.7.4
craco: ^0.0.3
cross-env: ^7.0.3
framer-motion: 6.5.1
@@ -6972,6 +6973,17 @@ __metadata:
languageName: node
linkType: hard
+"axios@npm:^1.7.4":
+ version: 1.7.4
+ resolution: "axios@npm:1.7.4"
+ dependencies:
+ follow-redirects: ^1.15.6
+ form-data: ^4.0.0
+ proxy-from-env: ^1.1.0
+ checksum: 0c17039a9acfe6a566fca8431ba5c1b455c83d30ea6157fec68a6722878fcd30f3bd32d172f6bee0c51fe75ca98e6414ddcd968a87b5606b573731629440bfaf
+ languageName: node
+ linkType: hard
+
"axobject-query@npm:^2.2.0":
version: 2.2.0
resolution: "axobject-query@npm:2.2.0"
@@ -11022,6 +11034,16 @@ __metadata:
languageName: node
linkType: hard
+"follow-redirects@npm:^1.15.6":
+ version: 1.15.6
+ resolution: "follow-redirects@npm:1.15.6"
+ peerDependenciesMeta:
+ debug:
+ optional: true
+ checksum: a62c378dfc8c00f60b9c80cab158ba54e99ba0239a5dd7c81245e5a5b39d10f0c35e249c3379eae719ff0285fff88c365dd446fab19dee771f1d76252df1bbf5
+ languageName: node
+ linkType: hard
+
"for-each@npm:^0.3.3":
version: 0.3.3
resolution: "for-each@npm:0.3.3"
@@ -11087,6 +11109,17 @@ __metadata:
languageName: node
linkType: hard
+"form-data@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "form-data@npm:4.0.0"
+ dependencies:
+ asynckit: ^0.4.0
+ combined-stream: ^1.0.8
+ mime-types: ^2.1.12
+ checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c
+ languageName: node
+ linkType: hard
+
"form-data@npm:~2.3.2":
version: 2.3.3
resolution: "form-data@npm:2.3.3"
@@ -17489,6 +17522,13 @@ __metadata:
languageName: node
linkType: hard
+"proxy-from-env@npm:^1.1.0":
+ version: 1.1.0
+ resolution: "proxy-from-env@npm:1.1.0"
+ checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4
+ languageName: node
+ linkType: hard
+
"prr@npm:~1.0.1":
version: 1.0.1
resolution: "prr@npm:1.0.1"