upload component update

This commit is contained in:
BFlat committed 2023-09-28 09:02:16 +07:00
1 parent 74eccc3d50
commit fbbdd03ebf
7 files changed
+258 -21

No files matched your search

@@ -1158,6 +1158,14 @@ styles with important tags:
}
}
.file-size {
font-size: 14px;
color: rgba($white, 0.75);
padding: 4px 8px;
border-radius: 5px;
background-color: $secondary;
}
.nav-vc {
flex-wrap: nowrap;
height: auto;
@@ -1311,6 +1319,10 @@ styles with important tags:
font-weight: 500 !important;
}
.bg-stepper {
background-color: lighten($panel, 4%);
}
.stepper-container {
width: 100%;
display: flex;
@@ -1318,14 +1330,14 @@ styles with important tags:
align-items: center;
.stepper-number {
font-size: 16px;
font-size: 22px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
min-width: 32px;
width: 36px;
height: 36px;
min-width: 36px;
background-color: $secondary;
border-radius: 48px;
opacity: 0.4;
@@ -1354,6 +1366,7 @@ styles with important tags:
height: 3px;
background: $secondary;
transition: all 0.2s;
min-width: 80px;
&.active {
background-color: $primary;
@@ -1484,6 +1497,11 @@ textarea {
padding: 2rem !important;
}
.px-lg-4 {
padding-left: 2rem !important;
padding-right: 2rem !important;
}
.lead {
font-size: 18px;
}
@@ -4,12 +4,15 @@ import { panel } from "data/animation";
interface PanelProps {
children: React.ReactNode;
padding?: boolean;
}
function Panel(props: PanelProps) {
return (
<motion.div className="container-panel pb-5 mb-4" variants={panel}>
<div className="panel p-3 py-4 p-md-4">{props.children}</div>
<motion.div className="container-panel" variants={panel}>
<div className={`panel ${props.padding ? "p-3 py-4 p-md-4" : ""}`}>
{props.children}
</div>
</motion.div>
);
}
@@ -54,12 +54,20 @@ function VoiceDesignerPage(this: any) {
<Panel>
{/* Stepper */}
<Stepper steps={steps} currentStep={currentStep} />
<div className="p-3 px-lg-4 bg-stepper">
<Stepper steps={steps} currentStep={currentStep} />
</div>
<div className="my-4">{displayStep(currentStep)}</div>
<div className="p-3 py-4 p-md-4">{displayStep(currentStep)}</div>
{/* Navigation Controls */}
<StepperControls onBack={handleBack} onNext={handleNext} />
<StepperControls
steps={steps}
currentStep={currentStep}
onBack={handleBack}
onNext={handleNext}
/>
</Panel>
</motion.div>
);
@@ -31,7 +31,7 @@ const Stepper: React.FC<StepperProps> = ({ steps, currentStep }) => {
</div>
{index !== steps.length - 1 && (
<div
className={`stepper-line ${index < currentStep ? "active" : ""}`}
className={`stepper-line ${index <= currentStep ? "active" : ""}`}
/>
)}
</div>
@@ -3,21 +3,34 @@ import React from "react";
interface StepperControlsProps {
onBack: () => void;
onNext: () => void;
steps: string[];
currentStep: number;
}
const StepperControls: React.FC<StepperControlsProps> = ({
onBack,
onNext,
steps,
currentStep,
}) => {
return (
<div className="d-flex gap-3">
<button className="btn btn-secondary w-100" onClick={onBack}>
Back
</button>
<button className="btn btn-primary w-100" onClick={onNext}>
Next
</button>
</div>
<>
{currentStep !== steps.length - 1 && (
<div className="p-3 pb-4 px-lg-4 pt-0 d-flex gap-3">
{currentStep !== 0 && (
<button className="btn btn-secondary w-100" onClick={onBack}>
Back
</button>
)}
{currentStep !== steps.length - 1 && (
<button className="btn btn-primary w-100" onClick={onNext}>
{currentStep !== steps.length - 2 ? "Continue" : "Create Voice"}
</button>
)}
</div>
)}
</>
);
};
@@ -0,0 +1,168 @@
import {
faFileArrowUp,
faFileAudio,
faTrash,
faWaveform,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useState } from "react";
import { FileUploader } from "react-drag-drop-files";
import { InputVcAudioPlayer } from "v2/view/_common/InputVcAudioPlayer";
import { v4 as uuidv4 } from "uuid";
import {
UploadAudio,
UploadAudioIsOk,
UploadAudioRequest,
} from "@storyteller/components/src/api/upload/UploadAudio";
const FILE_TYPES = ["MP3", "WAV", "FLAC", "OGG"];
interface Props {
setMediaUploadToken: (token?: string) => void;
formIsCleared: boolean;
setFormIsCleared: (cleared: boolean) => void;
setCanConvert: (canConvert: boolean) => void;
changeConvertIdempotencyToken: () => void;
}
function VoiceDesignerUploadComponent(props: Props) {
const [files, setFiles] = useState<any[]>([]);
const [audioLinks, setAudioLinks] = useState<string[]>([]);
const [isUploadDisabled, setIsUploadDisabled] = useState<boolean>(false);
// Auto generated
const [idempotencyToken, setIdempotencyToken] = useState(uuidv4());
const handleChange = (file: any) => {
if (files.length < 20) {
setFiles([...files, file]);
setIdempotencyToken(uuidv4());
const audioUrl = URL.createObjectURL(file);
setAudioLinks([...audioLinks, audioUrl ?? ""]);
props.setFormIsCleared(false);
props.setCanConvert(false);
props.changeConvertIdempotencyToken();
setIsUploadDisabled(false);
}
};
const handleDragOver = (e: React.DragEvent<HTMLDivElement>): void => {
e.preventDefault();
e.stopPropagation();
e.currentTarget.classList.add("upload-zone-drag");
};
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>): void => {
e.preventDefault();
e.stopPropagation();
e.currentTarget.classList.remove("upload-zone-drag");
};
const getFileSize = (file: any) =>
file && file.size >= 1024 * 1024
? (file.size / 1024 / 1024).toFixed(2) + " MB"
: file
? `${Math.floor(file.size / 1024)} KB`
: null;
const uploadBtnClass = isUploadDisabled
? "btn btn-uploaded w-100 disabled"
: "btn btn-primary w-100";
const handleRemoveFile = (index: number) => {
const updatedFiles = [...files];
updatedFiles.splice(index, 1);
setFiles(updatedFiles);
const updatedAudioLinks = [...audioLinks];
updatedAudioLinks.splice(index, 1);
setAudioLinks(updatedAudioLinks);
};
return (
<div className="d-flex flex-column gap-3">
{/* Usage refer to https://github.com/KarimMokhtar/react-drag-drop-files */}
<FileUploader
handleChange={handleChange}
name="file"
types={FILE_TYPES}
maxSize={50}
children={
<div
className="panel panel-inner upload-zone d-flex align-items-center justify-content-center p-4"
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
>
<div className="me-4">
<FontAwesomeIcon icon={faFileArrowUp} className="upload-icon" />
</div>
<div>
<div className="pb-0">
<u className="fw-medium">Upload files</u> or drop them here...
</div>
<div className="d-flex gap-1">
<div>
<p className="opacity-50">
{FILE_TYPES.join(", ").toString()} supported
</p>
</div>
</div>
</div>
</div>
}
/>
<h4 className="m-0 mt-3">
Voice Samples <span className="opacity-75">({files.length}/20)</span>
</h4>
{files.length > 0 ? (
<div className="d-flex flex-column gap-3">
{files.map((file, index) => (
<div key={index} className="panel panel-inner rounded p-3">
<div className="d-flex flex-column gap-2">
<div className="d-flex align-items-center">
<div className="flex-grow-1 mb-1">
{file.name}
<span className="file-size ms-2">{getFileSize(file)}</span>
</div>
<button
className="btn btn-link p-0"
onClick={() => handleRemoveFile(index)}
>
<FontAwesomeIcon icon={faTrash} className="me-2" />
Remove
</button>
</div>
<InputVcAudioPlayer filename={audioLinks[index]} />
</div>
</div>
))}
</div>
) : (
<div className="panel panel-inner text-center p-5 rounded-5 h-100">
<div className="d-flex flex-column opacity-75 h-100 justify-content-center">
<FontAwesomeIcon icon={faWaveform} className="fs-3 mb-3" />
<h5>No voice samples yet</h5>
<p>Selected samples will appear here.</p>
</div>
</div>
)}
</div>
);
}
const LoadingIcon: React.FC = () => {
return (
<>
<span
className="spinner-border spinner-border-sm ms-3"
role="status"
aria-hidden="true"
></span>
<span className="visually-hidden">Loading...</span>
</>
);
};
export default VoiceDesignerUploadComponent;
@@ -1,9 +1,36 @@
import React from "react";
import React, { useState } from "react";
import VoiceDesignerUploadComponent from "../VoiceDesignerUploadComponent";
import { v4 as uuidv4 } from "uuid";
function UploadSamples() {
const [convertLoading, setConvertLoading] = useState(false);
const [canConvert, setCanConvert] = useState(false);
const [mediaUploadToken, setMediaUploadToken] = useState<string | undefined>(
undefined
);
const [convertIdempotencyToken, setConvertIdempotencyToken] = useState(
uuidv4()
);
const [formIsCleared, setFormIsCleared] = useState(false);
const changeConvertIdempotencyToken = () => {
setConvertIdempotencyToken(uuidv4());
};
return (
<div>
<input className="form-control" type="text" />
<div className="d-flex flex-column gap-4">
<div>
<label className="sub-title">Upload Audio</label>
<div className="d-flex flex-column gap-3 upload-component">
<VoiceDesignerUploadComponent
setMediaUploadToken={setMediaUploadToken}
formIsCleared={formIsCleared}
setFormIsCleared={setFormIsCleared}
setCanConvert={setCanConvert}
changeConvertIdempotencyToken={changeConvertIdempotencyToken}
/>
</div>
</div>
</div>
);
}