Merge pull request #233 from storytold/bflat-fy-vc-loading-states

loading states for upload and convert buttons
This commit is contained in:
Brandon Thomas authored and GitHub committed 2023-05-02 16:08:50 -07:00
commit 609d140706
4 files changed
+130 -58

No files matched your search

@@ -1282,6 +1282,12 @@ select {
background-color: $danger !important;
}
.btn-uploaded {
background-color: lighten($secondary, 5%) !important;
border-color: lighten($secondary, 5%) !important;
color: $white !important;
}
.video-card-title,
.video-card-text {
color: $white !important;
@@ -55,7 +55,7 @@ interface Props {
function VcModelListPage(props: Props) {
usePrefixedDocumentTitle("Voice Conversion");
const [convertLoading, setConvertLoading] = useState(false);
const [canConvert, setCanConvert] = useState(false);
const [mediaUploadToken, setMediaUploadToken] = useState<string | undefined>(
@@ -135,6 +135,8 @@ function VcModelListPage(props: Props) {
return;
}
setConvertLoading(true);
let request: EnqueueVoiceConversionRequest = {
uuid_idempotency_token: convertIdempotencyToken,
voice_conversion_model_token:
@@ -154,15 +156,15 @@ function VcModelListPage(props: Props) {
FrontendInferenceJobType.VoiceConversion
);
}
setConvertLoading(false);
};
const handleFormSubmit = async (ev: React.FormEvent<HTMLFormElement>) => {
ev.preventDefault();
};
const loading = false; // TODO: Remove.
const speakButtonClass = loading
const speakButtonClass = convertLoading
? "btn btn-primary w-100 disabled"
: "btn btn-primary w-100";
@@ -323,7 +325,7 @@ function VcModelListPage(props: Props) {
className="me-2"
/>
Convert
{loading && <LoadingIcon />}
{convertLoading && <LoadingIcon />}
</button>
</div>
</div>
@@ -366,7 +368,7 @@ function VcModelListPage(props: Props) {
className="me-2"
/>
Convert
{loading && <LoadingIcon />}
{convertLoading && <LoadingIcon />}
</button>
</div>
</div>
@@ -1,23 +1,29 @@
import React, { useEffect, useMemo } from "react";
import React, { useEffect, useState, useMemo } from "react";
import { v4 as uuidv4 } from "uuid";
import { useAudioRecorder } from "react-audio-voice-recorder";
import { InputVcAudioPlayer } from "../../../../_common/InputVcAudioPlayer";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMicrophone, faUpload } from "@fortawesome/pro-solid-svg-icons";
import { UploadAudio, UploadAudioIsOk, UploadAudioRequest } from "@storyteller/components/src/api/upload/UploadAudio";
import { faTrash } from "@fortawesome/free-solid-svg-icons";
import { faFileArrowUp, faMicrophone } from "@fortawesome/pro-solid-svg-icons";
import {
UploadAudio,
UploadAudioIsOk,
UploadAudioRequest,
} from "@storyteller/components/src/api/upload/UploadAudio";
import { faCheck, faTrash } from "@fortawesome/free-solid-svg-icons";
interface Props {
setMediaUploadToken: (token?: string) => void,
setMediaUploadToken: (token?: string) => void;
formIsCleared: boolean,
setFormIsCleared: (cleared: boolean) => void,
formIsCleared: boolean;
setFormIsCleared: (cleared: boolean) => void;
setCanConvert: (canConvert: boolean) => void,
changeConvertIdempotencyToken: () => void,
setCanConvert: (canConvert: boolean) => void;
changeConvertIdempotencyToken: () => void;
}
export default function RecordComponent(props: Props) {
const [uploadLoading, setUploadLoading] = useState(false);
const [isUploadDisabled, setIsUploadDisabled] = useState<boolean>(false);
const { startRecording, stopRecording, recordingBlob, isRecording } =
useAudioRecorder();
@@ -44,7 +50,7 @@ export default function RecordComponent(props: Props) {
// props.setMediaUploadToken(result.upload_token);
// }
//})();
}, [recordingBlob])
}, [recordingBlob]);
const handleStartRecording = async () => {
startRecording();
@@ -55,6 +61,7 @@ export default function RecordComponent(props: Props) {
stopRecording();
props.setFormIsCleared(false);
props.setCanConvert(false);
setIsUploadDisabled(false);
};
const handleClear = () => {
@@ -66,11 +73,13 @@ export default function RecordComponent(props: Props) {
};
const handleUpload = async () => {
const request : UploadAudioRequest = {
const request: UploadAudioRequest = {
uuid_idempotency_token: uuidv4(), // TODO: only send on change.
file: recordingBlob,
source: 'device',
}
source: "device",
};
setUploadLoading(true);
let result = await UploadAudio(request);
@@ -79,15 +88,19 @@ export default function RecordComponent(props: Props) {
props.setFormIsCleared(false);
props.setCanConvert(true);
props.changeConvertIdempotencyToken();
setIsUploadDisabled(true);
}
}
setUploadLoading(false);
};
const enableMediaReview = !props.formIsCleared && recordingBlob !== undefined;
const enableUploadButton = !props.formIsCleared && recordingBlob !== undefined && !isRecording;
const enableUploadButton =
!props.formIsCleared && recordingBlob !== undefined && !isRecording;
const speakButtonClass = enableUploadButton
? "btn btn-primary w-100"
: "btn btn-primary w-100 disabled";
const speakButtonClass = isUploadDisabled
? "btn btn-uploaded w-100 disabled"
: "btn btn-primary w-100";
return (
<div className="d-flex flex-column gap-3" id="record-audio">
@@ -111,36 +124,40 @@ export default function RecordComponent(props: Props) {
)}
{enableMediaReview ? (
<>
<RecordedAudioComponent recordingBlob={recordingBlob} />
<div className="d-flex gap-3">
<button
className={speakButtonClass}
onClick={handleUpload}
type="submit"
disabled={!enableUploadButton}
>
<FontAwesomeIcon
icon={faUpload}
className="me-2"
/>
Upload
</button>
<button
className="btn btn-destructive w-100"
onClick={handleClear}
>
<FontAwesomeIcon icon={faTrash} className="me-2" />
Clear
</button>
</div>
</>
) : (
<></>
)
}
<>
<RecordedAudioComponent recordingBlob={recordingBlob} />
<div className="d-flex gap-3">
<button
className={speakButtonClass}
onClick={handleUpload}
type="submit"
disabled={
isUploadDisabled || uploadLoading || !enableUploadButton
}
>
{isUploadDisabled ? (
<>
<FontAwesomeIcon icon={faCheck} className="me-2" />
Uploaded
</>
) : (
<>
<FontAwesomeIcon icon={faFileArrowUp} className="me-2" />
Upload Audio
</>
)}
{uploadLoading && <LoadingIcon />}
</button>
<button className="btn btn-destructive w-100" onClick={handleClear}>
<FontAwesomeIcon icon={faTrash} className="me-2" />
Clear
</button>
</div>
</>
) : (
<></>
)}
</div>
);
}
@@ -150,7 +167,6 @@ interface RecorderProps {
}
function RecordedAudioComponent(props: RecorderProps) {
// Only generate the URL on change.
const audioLink = useMemo(() => {
if (!props.recordingBlob) {
@@ -170,6 +186,19 @@ function RecordedAudioComponent(props: RecorderProps) {
);
}
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>
</>
);
};
/*
In case you'd like to update colors of the icons just follow the instruction here:
https://github.com/samhirtarif/react-audio-recorder/issues/19#issuecomment-1420248073
@@ -1,4 +1,5 @@
import {
faCheck,
faFileArrowUp,
faFileAudio,
faTrash,
@@ -30,6 +31,7 @@ function UploadComponent(props: Props) {
const [file, setFile] = useState<any>(undefined);
const [audioLink, setAudioLink] = useState<string>();
const [isUploadDisabled, setIsUploadDisabled] = useState<boolean>(false);
const [uploadLoading, setUploadLoading] = useState(false);
// Auto generated
const [idempotencyToken, setIdempotencyToken] = useState(uuidv4());
@@ -43,6 +45,7 @@ function UploadComponent(props: Props) {
props.setFormIsCleared(false);
props.setCanConvert(false);
props.changeConvertIdempotencyToken();
setIsUploadDisabled(false);
};
const handleDragOver = (e: React.DragEvent<HTMLDivElement>): void => {
@@ -61,6 +64,7 @@ function UploadComponent(props: Props) {
setFile(null);
setAudioLink("");
setIdempotencyToken(uuidv4());
setIsUploadDisabled(false);
props.setMediaUploadToken(undefined); // clear
props.setFormIsCleared(true);
props.setCanConvert(false);
@@ -72,6 +76,8 @@ function UploadComponent(props: Props) {
return false;
}
setUploadLoading(true);
const request: UploadAudioRequest = {
uuid_idempotency_token: idempotencyToken,
file: file,
@@ -86,6 +92,8 @@ function UploadComponent(props: Props) {
props.setFormIsCleared(false);
props.setCanConvert(true);
}
setUploadLoading(false);
};
const fileSize =
@@ -95,6 +103,10 @@ function UploadComponent(props: Props) {
? `${Math.floor(file.size / 1024)} KB`
: null;
const uploadBtnClass = isUploadDisabled
? "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 */}
@@ -178,15 +190,25 @@ function UploadComponent(props: Props) {
{file ? (
<div className="d-flex gap-3">
<button
className="btn btn-primary w-100"
className={uploadBtnClass}
onClick={() => {
handleUploadFile();
}}
type="submit"
disabled={isUploadDisabled}
disabled={uploadLoading || isUploadDisabled}
>
<FontAwesomeIcon icon={faFileArrowUp} className="me-2" />
Upload Audio
{isUploadDisabled ? (
<>
<FontAwesomeIcon icon={faCheck} className="me-2" />
Uploaded
</>
) : (
<>
<FontAwesomeIcon icon={faFileArrowUp} className="me-2" />
Upload Audio
</>
)}
{uploadLoading && <LoadingIcon />}
</button>
<button className="btn btn-destructive w-100" onClick={handleClear}>
@@ -201,4 +223,17 @@ function UploadComponent(props: Props) {
);
}
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 UploadComponent;