mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 18:21:12 +00:00
added upload progress and upload errors to entity input
This commit is contained in:
1 parent
349ca44af5
commit
178d59dc13
9 files changed
+203
-33
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
export default MakeMultipartRequest;
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
+69
-5
@@ -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 <Spinner />;
|
||||
const MediaError = ({ resetUploader }: SlideProps) => {
|
||||
return (
|
||||
<>
|
||||
<h4>There was a problem with your upload</h4>
|
||||
<Button
|
||||
{...{
|
||||
label: "Try another upload",
|
||||
onClick: resetUploader,
|
||||
variant: "secondary",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MediaBusy = ({ uploaderBusy, uploadProgress }: SlideProps) => {
|
||||
return (
|
||||
<div {...{ className: "fy-entity-input-loader" }}>
|
||||
{uploaderBusy ? (
|
||||
<>
|
||||
Uploading
|
||||
<div {...{ className: "fy-entity-input-progress-indicator" }}>
|
||||
<div {...{ className: "fy-entity-input-progress-number" }}>
|
||||
{uploadProgress}%
|
||||
</div>
|
||||
<WorkIndicator
|
||||
{...{
|
||||
failure: false,
|
||||
max: 100,
|
||||
progressPercentage: uploadProgress,
|
||||
stage: 1,
|
||||
success: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
}}
|
||||
/>,
|
||||
<AnimatedSlide
|
||||
{...{
|
||||
className: "fy-entity-input-error",
|
||||
render: MediaError,
|
||||
resetUploader,
|
||||
...sharedProps,
|
||||
}}
|
||||
/>,
|
||||
][i];
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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({
|
||||
<DashedCircle
|
||||
{...{
|
||||
className: "work-indicator-circle-marker",
|
||||
max,
|
||||
progressPercentage,
|
||||
stage,
|
||||
}}
|
||||
|
||||
@@ -30,6 +30,7 @@ export default function useMediaUploader({ autoUpload, onSuccess = n }: Props) {
|
||||
const [engineSubtype, engineSubtypeSet] = useState<
|
||||
MediaFileSubtype | undefined
|
||||
>();
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user