mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
Merge pull request #233 from storytold/bflat-fy-vc-loading-states
loading states for upload and convert buttons
This commit is contained in:
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>
|
||||
|
||||
+77
-48
@@ -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
|
||||
|
||||
+39
-4
@@ -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;
|
||||
Reference in new issue
Block a user