mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 09:48:44 +00:00
UploaderComponent revised as Uploader at new shared component directory. Uploader uses the new hook useUploader which allows for state to exist external to the component
This commit is contained in:
1 parent
00f37b1d30
commit
7541f76f72
11 files changed
+324
-35
No files matched your search
@@ -0,0 +1,12 @@
|
||||
import React from "react";
|
||||
|
||||
export default function LoadingIcon() {
|
||||
return <>
|
||||
<span
|
||||
className="spinner-border spinner-border-sm ms-3"
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</>;
|
||||
};
|
||||
@@ -0,0 +1,113 @@
|
||||
import React from "react";
|
||||
import { FileUploader } from "react-drag-drop-files";
|
||||
import { faCheck, faFileArrowUp, faFileAudio, faTrash } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { InputVcAudioPlayer } from "v2/view/_common/InputVcAudioPlayer";
|
||||
import LoadingIcon from "./LoadingIcon";
|
||||
|
||||
const FILE_TYPES = ["MP3", "WAV", "FLAC", "OGG"];
|
||||
|
||||
interface Idempotency { token: any; set: (file?: any) => void; }
|
||||
|
||||
interface Props {
|
||||
audioLink?: string;
|
||||
file?: any;
|
||||
fileSet?: (file?: any) => void;
|
||||
canConvertSet?: (canConvert: boolean) => void;
|
||||
handleUploadFile?: () => void;
|
||||
idempotency?: Idempotency;
|
||||
onChange?: (file?: any) => void;
|
||||
onClear?: (file?: any) => void;
|
||||
uploading?: boolean;
|
||||
uploadDisabled?: boolean;
|
||||
}
|
||||
|
||||
const n = () => {};
|
||||
|
||||
export default function Uploader({ audioLink = "", file, fileSet = n, canConvertSet = n, handleUploadFile = n, idempotency = { token: '', set: n }, onChange = n, onClear = n, uploading = false, uploadDisabled = false }: Props) {
|
||||
|
||||
const handleChange = (file: any) => {
|
||||
onChange({ target: { name: 'uploader', value: file }});
|
||||
};
|
||||
|
||||
const handleClear = () => { onClear(); };
|
||||
|
||||
const onDragEvent = (onOff: number) => (e: React.DragEvent<HTMLDivElement>): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.currentTarget.classList[onOff ? "add" : "remove" ]("upload-zone-drag");
|
||||
};
|
||||
|
||||
const fileSize =
|
||||
file && file.size >= 1024 * 1024
|
||||
? (file.size / 1024 / 1024).toFixed(2) + " MB"
|
||||
: file
|
||||
? `${Math.floor(file.size / 1024)} KB`
|
||||
: null;
|
||||
|
||||
const uploadBtnClass = uploadDisabled
|
||||
? "btn btn-uploaded w-100 disabled"
|
||||
: "btn btn-primary w-100";
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-column gap-3">
|
||||
{/* Usage refer to https://github.com/KarimMokhtar/react-drag-drop-files */}
|
||||
<FileUploader {...{ handleChange, maxSize: 50, name: "file", types: FILE_TYPES }}
|
||||
children={
|
||||
<div {...{ className: "panel panel-inner upload-zone d-flex align-items-center p-3",
|
||||
onDragLeave: onDragEvent(1), onDragOver: onDragEvent(0) }}>
|
||||
<div className="me-4">
|
||||
<FontAwesomeIcon {...{ className: "upload-icon", icon: file ? faFileAudio : faFileArrowUp }}/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="pb-0">
|
||||
{file ? (
|
||||
<span className="filename" title={file.name}>
|
||||
{file.name.slice(0, file.name.lastIndexOf("."))}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<u className="fw-medium">Upload a file</u> or drop it
|
||||
here...
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="d-flex gap-1">
|
||||
<div>
|
||||
{file ? (
|
||||
<p>
|
||||
<span className="opacity-50">
|
||||
{file && `${file.name.split(".").pop().toUpperCase()}`}{" "}
|
||||
file size: {fileSize}
|
||||
</span>{" "}
|
||||
<u className="fw-medium opacity-100 ms-1">Change file</u>
|
||||
</p>
|
||||
) : (
|
||||
<p className="opacity-50">
|
||||
{FILE_TYPES.join(", ").toString()} supported
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{ file && <>
|
||||
<div className="panel panel-inner rounded p-3">
|
||||
<InputVcAudioPlayer {...{ filename: audioLink as string }}/>
|
||||
</div>
|
||||
<div className="d-flex gap-3">
|
||||
<button {...{ className: uploadBtnClass, disabled: uploading || uploadDisabled, onClick: () => handleUploadFile(), type: "submit", }}>
|
||||
<FontAwesomeIcon {...{ className: "me-2", icon: uploadDisabled ? faCheck : faFileArrowUp, }}/>
|
||||
{ uploading && <LoadingIcon /> }
|
||||
</button>
|
||||
<button className="btn btn-destructive w-100" onClick={handleClear}>
|
||||
<FontAwesomeIcon icon={faTrash} className="me-2" />
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</> }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './Uploader';
|
||||
@@ -0,0 +1,5 @@
|
||||
import Uploader from './Uploader';
|
||||
|
||||
export {
|
||||
Uploader,
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
childProps?: any;
|
||||
titleIcon?: JSX.Element;
|
||||
extra?: React.ComponentType;
|
||||
titleComponent?: React.ComponentType;
|
||||
title?: string;
|
||||
subText: string;
|
||||
showButtons: boolean;
|
||||
@@ -10,32 +11,23 @@ interface Props {
|
||||
}
|
||||
|
||||
|
||||
//export default function PageHeader({ actionButtons, showButtons, subText, Extra, title, titleIcon }: Props)
|
||||
|
||||
const PageHeader: React.FunctionComponent<Props> = ({ actionButtons, showButtons, subText, extra, title, titleIcon }) => {
|
||||
console.log('aaa',extra);
|
||||
const Extra = extra || null;
|
||||
// return <div>Hiii</div>;
|
||||
return <Extra/>;
|
||||
// return <div className="container-panel hero-section py-4">
|
||||
// <div className="panel">
|
||||
// <div className="p-3 py-4 p-md-4">
|
||||
// <Extra hello="true"/>
|
||||
// {/* {
|
||||
// Title ? (<Title />) : <h1 className="fw-bold text-center text-md-start">
|
||||
// { titleIcon }
|
||||
// { title }
|
||||
// </h1>
|
||||
// }*/}
|
||||
// <p className="text-center text-md-start pt-1">{ subText }</p>
|
||||
// { showButtons && (
|
||||
// <div className="d-flex flex-column flex-md-row gap-3 justify-content-center justify-content-md-start mt-4">
|
||||
// { actionButtons }
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>;
|
||||
export default function PageHeader({ actionButtons, childProps, showButtons, subText, titleComponent: Title, title, titleIcon }: Props) {
|
||||
return <div className="container-panel hero-section py-4">
|
||||
<div className="panel">
|
||||
<div className="p-3 py-4 p-md-4">
|
||||
{ Title ? <Title {...childProps }/> : <h1 className="fw-bold text-center text-md-start">
|
||||
{ titleIcon }
|
||||
{ title }
|
||||
</h1> }
|
||||
<p className="text-center text-md-start pt-1">{ subText }</p>
|
||||
{ showButtons && (
|
||||
<div className="d-flex flex-column flex-md-row gap-3 justify-content-center justify-content-md-start mt-4">
|
||||
{ actionButtons }
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default PageHeader;
|
||||
// export default PageHeader;
|
||||
File renamed without changes.
@@ -0,0 +1,3 @@
|
||||
import useUploader from './useUploader';
|
||||
|
||||
export { useUploader };
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./useUploader";
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useState } from 'react';
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { UploadAudio, UploadAudioIsOk, UploadAudioRequest } from "@storyteller/components/src/api/upload/UploadAudio";
|
||||
|
||||
const n = (o: any) => o;
|
||||
|
||||
interface Props {
|
||||
formClearedSet?: (cleared: boolean) => void;
|
||||
}
|
||||
|
||||
export default function useUploader({ formClearedSet = n }: Props) {
|
||||
const [file, fileSet] = useState<any>(undefined);
|
||||
const [audioLink, audioLinkSet] = useState<string>();
|
||||
const [uploading, uploadingSet] = useState(false);
|
||||
const [uploadDisabled, uploadDisabledSet] = useState<boolean>(false);
|
||||
const [mediaUploadToken, mediaUploadTokenSet] = useState<string | undefined>(undefined);
|
||||
const [convertIdempotencyToken, convertIdempotencyTokenSet] = useState(uuidv4());
|
||||
const [canConvert, canConvertSet] = useState(false);
|
||||
const [iToken, iTokenSet] = useState(uuidv4()); // Auto generated
|
||||
const resetIToken = () => iTokenSet(uuidv4());
|
||||
|
||||
const handleUploadFile = async () => {
|
||||
if (file === undefined) { return false; }
|
||||
|
||||
uploadingSet(true);
|
||||
|
||||
const request: UploadAudioRequest = {
|
||||
uuid_idempotency_token: iToken,
|
||||
file: file,
|
||||
source: "file",
|
||||
};
|
||||
|
||||
let result = await UploadAudio(request);
|
||||
|
||||
if (UploadAudioIsOk(result)) {
|
||||
uploadDisabledSet(true);
|
||||
mediaUploadTokenSet(result.upload_token);
|
||||
formClearedSet(false);
|
||||
canConvertSet(true);
|
||||
}
|
||||
|
||||
uploadingSet(false);
|
||||
};
|
||||
const inputChange = (file?: any) => {
|
||||
convertIdempotencyTokenSet(uuidv4());
|
||||
fileSet(file || null);
|
||||
audioLinkSet(file ? URL.createObjectURL(file) : "");
|
||||
canConvertSet(false);
|
||||
resetIToken();
|
||||
uploadDisabledSet(false);
|
||||
formClearedSet(!file);
|
||||
};
|
||||
const onChange = ({ target = {} }: { target: any }) => {
|
||||
inputChange(target.value);
|
||||
};
|
||||
const onClear = () => {
|
||||
inputChange();
|
||||
mediaUploadTokenSet(undefined);
|
||||
};
|
||||
|
||||
return { audioLink, canConvert, convertIdempotencyToken, file, fileSet, handleUploadFile, idempotency: { token: iToken, set: iTokenSet}, mediaUploadToken, mediaUploadTokenSet, onChange, onClear, resetIToken, uploading, uploadDisabled, uploadingSet };
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
.progress-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.progress-heading {
|
||||
display: inline-block;
|
||||
margin: auto 1rem auto 0;
|
||||
}
|
||||
|
||||
.async-progress-tracker {
|
||||
display: inline-flex;
|
||||
justify-content: space-around;
|
||||
flex-grow: 1;
|
||||
margin-right: 1rem;
|
||||
padding-left: 0;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
svg {
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
margin-right: .5rem;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
stroke: white;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0 0 0 auto;
|
||||
background-color: #ccccff;
|
||||
// flex-grow: 1;
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,73 @@
|
||||
import React from "react";
|
||||
import { PageHeader } from "components/layout"
|
||||
import { useUploader } from "hooks";
|
||||
import { Uploader } from "components/common";
|
||||
import { PageHeader } from "components/layout";
|
||||
import './LipsyncEditor.scss';
|
||||
|
||||
const Title: React.ComponentType = () => <div>
|
||||
<h1 className="fw-bold text-center text-md-start">
|
||||
Hello
|
||||
</h1>
|
||||
</div>;
|
||||
// interface Props {
|
||||
// titleIcon?: JSX.Element;
|
||||
// extra?: React.ComponentType;
|
||||
// title?: string;
|
||||
// subText: string;
|
||||
// showButtons: boolean;
|
||||
// actionButtons?: JSX.Element;
|
||||
// }
|
||||
|
||||
const ProgressCheck = ({ disabled = false }: {disabled?: boolean}) => <svg>
|
||||
{ !disabled && <polyline {...{
|
||||
fill: 'none',
|
||||
points: '7 16 12 20 20 10',
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
strokeWidth: '4',
|
||||
}}/> }
|
||||
</svg>;
|
||||
|
||||
const Title = ({ ...rest }) => {
|
||||
console.log('🥚',rest)
|
||||
return <div {...{ className: 'progress-header' }}>
|
||||
<h1 {...{ className: "fw-bold text-center text-md-start progress-heading" }}>
|
||||
Generate a lipsync
|
||||
</h1>
|
||||
<ul {...{ className: 'async-progress-tracker' }}>
|
||||
<li>
|
||||
<ProgressCheck {...{}}/>
|
||||
Image
|
||||
</li>
|
||||
<li>
|
||||
<ProgressCheck {...{ disabled: false }}/>
|
||||
Audio
|
||||
</li>
|
||||
</ul>
|
||||
<button className="btn btn-primary">
|
||||
Generate
|
||||
</button>
|
||||
</div>
|
||||
};
|
||||
|
||||
export default function LipsyncEditor({ ...rest }) {
|
||||
const { file: audioFile, ...uploader } = useUploader({});
|
||||
|
||||
const subText = "Select an image with a clear face, or one of our existing templates, then choose either text to speech or uploaded audio(eg. music). Then you can generate a beautifully lipsynced video.";
|
||||
const headerProps = {
|
||||
childProps: { audioFile },
|
||||
titleComponent: Title,
|
||||
subText,
|
||||
showButtons: false
|
||||
};
|
||||
|
||||
return <div>
|
||||
<PageHeader {...{ extra: Title, subText: "some text", showButtons: false,}}/>
|
||||
<PageHeader { ...headerProps }/>
|
||||
<div className="container">
|
||||
<div className="panel d-flex p-md-4">
|
||||
<div {...{ className: "col-12 col-lg-6" }}>
|
||||
<h5>Image</h5>
|
||||
</div>
|
||||
<div {...{ className: "col-12 col-lg-6" }}>
|
||||
<h5>Audio</h5>
|
||||
<Uploader {...{ file: audioFile, ...uploader }}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
Reference in new issue
Block a user