mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
CameraCapture uploads videos
This commit is contained in:
1 parent
e624a701e0
commit
b5324681da
11 files changed
+448
-158
No files matched your search
+18
-18
@@ -82,25 +82,25 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fy-entity-input-progress-indicator {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
// .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-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 {
|
||||
|
||||
+12
-17
@@ -91,23 +91,17 @@ 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>
|
||||
</>
|
||||
<WorkIndicator
|
||||
{...{
|
||||
failure: false,
|
||||
label: "Uploading",
|
||||
max: 100,
|
||||
progressPercentage: uploadProgress,
|
||||
stage: 1,
|
||||
showPercentage: true,
|
||||
success: false,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
@@ -370,6 +364,7 @@ export default function EntityInput({
|
||||
onSelect,
|
||||
queryUser,
|
||||
render: EntityInputEmpty,
|
||||
selectToken,
|
||||
showWebcam,
|
||||
type,
|
||||
...sharedProps,
|
||||
|
||||
+3
@@ -36,6 +36,7 @@ interface EmptySlideProps extends SlideProps {
|
||||
accept: AcceptTypes | AcceptTypes[];
|
||||
inputProps?: any;
|
||||
queryUser?: string;
|
||||
selectToken: (token: string) => void;
|
||||
showWebcam: boolean;
|
||||
type: EntityModeProp;
|
||||
}
|
||||
@@ -44,6 +45,7 @@ export default function EntityInputEmpty({
|
||||
accept,
|
||||
inputProps,
|
||||
queryUser,
|
||||
selectToken,
|
||||
showWebcam,
|
||||
type,
|
||||
...rest
|
||||
@@ -80,6 +82,7 @@ export default function EntityInputEmpty({
|
||||
open({
|
||||
component: CameraCapture,
|
||||
padding: false,
|
||||
props: { selectToken },
|
||||
width: "square",
|
||||
});
|
||||
|
||||
|
||||
+10
-13
@@ -136,9 +136,7 @@ export default function JobItem({
|
||||
});
|
||||
const headingBounce = useSpring(makeBounce(8));
|
||||
const subtitleBounce = useSpring(makeBounce(6, 30));
|
||||
const percentFade = useSpring({
|
||||
opacity: jobIsAlive ? 1 : 0,
|
||||
});
|
||||
|
||||
const subtitle = maybeFailureCategory
|
||||
? `${failures(maybeFailureCategory)}`
|
||||
: t(`subtitles.${jobStatus}`);
|
||||
@@ -188,16 +186,15 @@ export default function JobItem({
|
||||
return (
|
||||
<>
|
||||
<div {...{ ...outerProps("fy-inference-job-indicator"), ...hoverSet }}>
|
||||
<div {...{ className: "fy-inference-job-indicator-container" }}>
|
||||
<a.div
|
||||
{...{ className: "fy-inference-job-progress", style: percentFade }}
|
||||
>
|
||||
{progressPercentage}%
|
||||
</a.div>
|
||||
<WorkIndicator
|
||||
{...{ failure, progressPercentage, stage: dashStatus(), success }}
|
||||
/>
|
||||
</div>
|
||||
<WorkIndicator
|
||||
{...{
|
||||
failure,
|
||||
progressPercentage,
|
||||
stage: dashStatus(),
|
||||
showPercentage: jobIsAlive,
|
||||
success,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div {...{ ...outerProps("fy-inference-job-details"), ...hoverSet }}>
|
||||
<a.h6 {...{ style: headingBounce }}>
|
||||
|
||||
+89
-7
@@ -2,12 +2,39 @@
|
||||
@import "scss/site-refresh.scss";
|
||||
|
||||
.fy-camera-capture-modal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// background: red;
|
||||
width: 75vh;
|
||||
height: 75vh;
|
||||
|
||||
position: relative;
|
||||
|
||||
.fy-camera-capture-close {
|
||||
@include video-tint;
|
||||
border: none;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 1rem;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($secondary-very-dark, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
.fy-camera-capture-slide {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fy-camera-capture-centered {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.fy-camera-capture-display {
|
||||
// background: red;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
@@ -41,19 +68,74 @@
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
// background: rgba(#00f, 0.25);
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
margin-bottom: 0.25rem;
|
||||
|
||||
&.fy-camera-capture-btn-record {
|
||||
circle {
|
||||
stroke: $primary;
|
||||
}
|
||||
rect {
|
||||
fill: $primary;
|
||||
}
|
||||
&:hover {
|
||||
circle {
|
||||
stroke: $primary-light;
|
||||
}
|
||||
rect {
|
||||
fill: $primary-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.fy-camera-capture-btn-stop {
|
||||
circle {
|
||||
stroke: $secondary-lightest;
|
||||
}
|
||||
rect {
|
||||
fill: $secondary-lightest;
|
||||
}
|
||||
&:hover {
|
||||
circle {
|
||||
stroke: #fff;
|
||||
}
|
||||
rect {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
circle,
|
||||
rect {
|
||||
transition: all 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
circle {
|
||||
fill: none;
|
||||
stroke: $primary;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
rect {
|
||||
fill: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fy-camera-capture-preview {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.fy-camera-capture-float-button {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
|
||||
&:first-of-type {
|
||||
left: 1rem;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+193
-77
@@ -1,55 +1,57 @@
|
||||
import React, { useCallback, useRef, useState } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import Webcam from "react-webcam";
|
||||
import { a, useSpring } from "@react-spring/web";
|
||||
// import { MediaRecorder, IMediaRecorder } from "extendable-media-recorder";
|
||||
import { a, TransitionFn, useSpring, useTransition } from "@react-spring/web";
|
||||
import { WorkIndicator } from "components/svg";
|
||||
import { useInterval, useMediaUploader } from "hooks";
|
||||
import { UploaderResponse } from "components/entities/EntityTypes";
|
||||
import { Button, ModalUtilities, Spinner } from "components/common";
|
||||
import { faClose } from "@fortawesome/pro-solid-svg-icons";
|
||||
import CapturePreview from "./CapturePreview";
|
||||
import "./CameraCapture.scss";
|
||||
|
||||
import { ModalUtilities } from "components/common";
|
||||
interface CameraCaptureProps extends ModalUtilities {
|
||||
selectToken: (token: string) => void;
|
||||
}
|
||||
|
||||
// interface Props {
|
||||
// value?: any;
|
||||
// }
|
||||
|
||||
export default function CameraCapture({ handleClose }: ModalUtilities) {
|
||||
export default function CameraCapture({
|
||||
handleClose,
|
||||
selectToken,
|
||||
}: CameraCaptureProps) {
|
||||
const webcamRef = useRef<Webcam>(null);
|
||||
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
||||
const [capturing, capturingSet] = useState(false);
|
||||
const [recordedChunks, setRecordedChunks] = useState([]);
|
||||
const [recordedChunks, recordedChunksSet] = useState([]);
|
||||
const [counter, counterSet] = useState(0);
|
||||
const [blob, blobSet] = useState<Blob | null>(null);
|
||||
const [cameraStarted, cameraStartedSet] = useState(false);
|
||||
|
||||
const hours = Math.floor(counter / 3600);
|
||||
const minutes = Math.floor((counter - hours * 3600) / 60);
|
||||
const seconds = counter - hours * 3600 - minutes * 60;
|
||||
|
||||
const timeString =
|
||||
hours.toString().padStart(2, "0") +
|
||||
":" +
|
||||
minutes.toString().padStart(2, "0") +
|
||||
":" +
|
||||
seconds.toString().padStart(2, "0");
|
||||
|
||||
const handleDataAvailable = useCallback(
|
||||
({ data }) => {
|
||||
if (data.size > 0) {
|
||||
setRecordedChunks(prev => prev.concat(data));
|
||||
recordedChunksSet(prev => prev.concat(data));
|
||||
}
|
||||
},
|
||||
[setRecordedChunks]
|
||||
[recordedChunksSet]
|
||||
);
|
||||
|
||||
const handleDownload = React.useCallback(() => {
|
||||
if (recordedChunks.length) {
|
||||
const blob = new Blob(recordedChunks, {
|
||||
type: "video/webm",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
document.body.appendChild(a);
|
||||
// a.style = "display: none";
|
||||
a.href = url;
|
||||
a.download = "react-webcam-stream-capture.webm";
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
setRecordedChunks([]);
|
||||
}
|
||||
}, [recordedChunks]);
|
||||
|
||||
const mainToggleClick = () => {
|
||||
if (!capturing) {
|
||||
capturingSet(true);
|
||||
|
||||
if (webcamRef.current && webcamRef.current.stream) {
|
||||
console.log("🧢", webcamRef.current.stream);
|
||||
mediaRecorderRef.current = new MediaRecorder(webcamRef.current.stream, {
|
||||
mimeType: "video/webm",
|
||||
mimeType: "video/mp4",
|
||||
});
|
||||
|
||||
mediaRecorderRef.current.addEventListener(
|
||||
@@ -73,67 +75,181 @@ export default function CameraCapture({ handleClose }: ModalUtilities) {
|
||||
xy: capturing ? 9 : 8,
|
||||
});
|
||||
|
||||
const resetCapture = () => {
|
||||
cameraStartedSet(false);
|
||||
blobSet(null);
|
||||
recordedChunksSet([]);
|
||||
counterSet(0);
|
||||
};
|
||||
|
||||
const {
|
||||
busy: uploaderBusy,
|
||||
createUpload,
|
||||
error: uploaderError,
|
||||
uploadProgress,
|
||||
} = useMediaUploader({
|
||||
onSuccess: (res: UploaderResponse) => {
|
||||
handleClose();
|
||||
selectToken(res.media_file_token);
|
||||
},
|
||||
});
|
||||
|
||||
const index = uploaderError
|
||||
? 3
|
||||
: uploaderBusy
|
||||
? 2
|
||||
: blob && !capturing
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
const upload = () => {
|
||||
if (blob) {
|
||||
const file = new File([blob], "test-upload.mp4", { type: "video/mp4" });
|
||||
createUpload(file);
|
||||
}
|
||||
};
|
||||
|
||||
const transitions: TransitionFn<
|
||||
number,
|
||||
{ opacity: number; transform: string }
|
||||
> = useTransition(index, {
|
||||
config: { mass: 1, tension: 80, friction: 10 },
|
||||
from: { opacity: 0, transform: `translateX(${5}rem)` },
|
||||
enter: { opacity: 1, transform: `translateX(0)` },
|
||||
leave: { opacity: 0, transform: `translateX(${5}rem)` },
|
||||
});
|
||||
|
||||
useInterval({
|
||||
eventProps: { capturing, counter },
|
||||
interval: 1000,
|
||||
locked: !capturing,
|
||||
onTick: () => {
|
||||
if (capturing) {
|
||||
counterSet(currentCounter => currentCounter + 1);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!blob && !capturing && recordedChunks.length) {
|
||||
blobSet(
|
||||
new Blob(recordedChunks, {
|
||||
type: "video/mp4",
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [blob, capturing, recordedChunks]);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...{
|
||||
className: "fy-camera-capture-modal",
|
||||
}}
|
||||
>
|
||||
<Webcam
|
||||
audio
|
||||
<Button
|
||||
{...{
|
||||
audioConstraints: {
|
||||
sampleSize: 16,
|
||||
channelCount: 2,
|
||||
},
|
||||
className: "fy-camera-capture-display",
|
||||
ref: webcamRef,
|
||||
videoConstraints: {
|
||||
width: 512,
|
||||
height: 512,
|
||||
facingMode: "user",
|
||||
},
|
||||
className: "fy-camera-capture-close",
|
||||
icon: faClose,
|
||||
onClick: () => handleClose(),
|
||||
square: true,
|
||||
}}
|
||||
/>
|
||||
<div {...{ className: "fy-camera-capture-controls" }}>
|
||||
<button
|
||||
{...{
|
||||
className: "fy-camera-capture-record-toggle",
|
||||
onClick: mainToggleClick,
|
||||
}}
|
||||
>
|
||||
<svg>
|
||||
<circle
|
||||
{transitions((style: any, i: number) => {
|
||||
return [
|
||||
<a.div {...{ className: "fy-camera-capture-slide", style }}>
|
||||
<Webcam
|
||||
audio
|
||||
{...{
|
||||
cx: 16,
|
||||
cy: 16,
|
||||
r: 15,
|
||||
mirrored: true,
|
||||
muted: true,
|
||||
className: "fy-camera-capture-display",
|
||||
onUserMedia: () => cameraStartedSet(true),
|
||||
ref: webcamRef,
|
||||
videoConstraints: {
|
||||
width: 512,
|
||||
height: 512,
|
||||
facingMode: "user",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<a.rect
|
||||
{cameraStarted ? (
|
||||
<div {...{ className: "fy-camera-capture-controls" }}>
|
||||
<button
|
||||
{...{
|
||||
className: "fy-camera-capture-record-toggle",
|
||||
onClick: mainToggleClick,
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
{...{
|
||||
className: capturing
|
||||
? "fy-camera-capture-btn-stop"
|
||||
: "fy-camera-capture-btn-record",
|
||||
}}
|
||||
>
|
||||
<circle
|
||||
{...{
|
||||
cx: 16,
|
||||
cy: 16,
|
||||
r: 15,
|
||||
}}
|
||||
/>
|
||||
<a.rect
|
||||
{...{
|
||||
x: mainToggleStyle.xy,
|
||||
y: mainToggleStyle.xy,
|
||||
height: mainToggleStyle.size,
|
||||
width: mainToggleStyle.size,
|
||||
rx: mainToggleStyle.rx,
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
{capturing ? timeString : "Record"}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div {...{ className: "fy-camera-capture-centered" }}>
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
</a.div>,
|
||||
|
||||
<CapturePreview
|
||||
{...{
|
||||
blob,
|
||||
resetCapture,
|
||||
style,
|
||||
upload,
|
||||
}}
|
||||
/>,
|
||||
<a.div
|
||||
{...{
|
||||
className: "fy-camera-capture-slide fy-camera-capture-centered",
|
||||
style,
|
||||
}}
|
||||
>
|
||||
<WorkIndicator
|
||||
{...{
|
||||
x: mainToggleStyle.xy,
|
||||
y: mainToggleStyle.xy,
|
||||
height: mainToggleStyle.size,
|
||||
width: mainToggleStyle.size,
|
||||
rx: mainToggleStyle.rx,
|
||||
failure: false,
|
||||
label: "Uploading",
|
||||
max: 100,
|
||||
progressPercentage: uploadProgress,
|
||||
stage: 1,
|
||||
showPercentage: true,
|
||||
success: false,
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
Record
|
||||
</button>
|
||||
{recordedChunks.length > 0 && (
|
||||
<button onClick={handleDownload}>Download</button>
|
||||
)}
|
||||
</div>
|
||||
{/* {capturing ? (
|
||||
<button onClick={handleStopCaptureClick}>Stop Capture</button>
|
||||
) : (
|
||||
<button onClick={handleStartCaptureClick}>Start Capture</button>
|
||||
)}
|
||||
{recordedChunks.length > 0 && (
|
||||
<button onClick={handleDownload}>Download</button>
|
||||
)}*/}
|
||||
</a.div>,
|
||||
<a.div
|
||||
{...{
|
||||
className: "fy-camera-capture-slide fy-camera-capture-centered",
|
||||
style,
|
||||
}}
|
||||
>
|
||||
error
|
||||
</a.div>,
|
||||
][i];
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import { a, SpringValues } from "@react-spring/web";
|
||||
import { Button } from "components/common";
|
||||
|
||||
interface CapturePreviewProps {
|
||||
blob: Blob | null;
|
||||
resetCapture: () => void;
|
||||
style: SpringValues;
|
||||
upload: () => void;
|
||||
}
|
||||
|
||||
export default function CapturePreview({
|
||||
blob,
|
||||
resetCapture,
|
||||
style,
|
||||
upload,
|
||||
}: CapturePreviewProps) {
|
||||
if (blob) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
return (
|
||||
<a.div {...{ className: "fy-camera-capture-slide", style }}>
|
||||
<video
|
||||
controls
|
||||
{...{ className: "fy-camera-capture-preview", src: url }}
|
||||
/>
|
||||
<Button
|
||||
{...{
|
||||
className: "fy-camera-capture-float-button",
|
||||
label: "Try again",
|
||||
onClick: () => resetCapture(),
|
||||
variant: "secondary",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
{...{
|
||||
className: "fy-camera-capture-float-button",
|
||||
label: "Upload and use",
|
||||
variant: "primary",
|
||||
onClick: upload,
|
||||
}}
|
||||
/>
|
||||
</a.div>
|
||||
);
|
||||
} else return null;
|
||||
}
|
||||
+6
-2
@@ -40,8 +40,8 @@
|
||||
}
|
||||
|
||||
.fy-modal-body-square {
|
||||
width: 75vh;
|
||||
height: 75vh;
|
||||
// width: 75vh;
|
||||
// height: 75vh;
|
||||
}
|
||||
|
||||
.fy-modal-body-wide,
|
||||
@@ -56,7 +56,11 @@
|
||||
box-shadow: 0px 9px 33px 1px rgba(19, 19, 27, 0.8);
|
||||
-webkit-box-shadow: 0px 9px 33px 1px rgba(19, 19, 27, 0.8);
|
||||
-moz-box-shadow: 0px 9px 33px 1px rgba(19, 19, 27, 0.8);
|
||||
}
|
||||
|
||||
.fy-modal-body-wide,
|
||||
.fy-modal-body-narrow,
|
||||
.fy-modal-body-small {
|
||||
padding: 1rem;
|
||||
|
||||
@include breakpoint($under992) {
|
||||
|
||||
+20
-8
@@ -1,17 +1,29 @@
|
||||
@import "scss/colours.scss";
|
||||
@import "scss/site-refresh.scss";
|
||||
|
||||
.work-indicator-label {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.work-indicator {
|
||||
@include medium-svg;
|
||||
// height: 2.25rem;
|
||||
// width: 2.25rem;
|
||||
// fill: none;
|
||||
position: relative;
|
||||
|
||||
// circle, line, polyline {
|
||||
// stroke-width: 4px;
|
||||
// stroke-linecap: round;
|
||||
// stroke: #fff;
|
||||
// }
|
||||
.work-indicator-progress {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
font-size: 0.625rem;
|
||||
line-height: 0.625rem;
|
||||
height: 0.625rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
polyline {
|
||||
transform: translate(6px, 6px);
|
||||
|
||||
+50
-16
@@ -1,37 +1,71 @@
|
||||
import React from "react";
|
||||
import { a, useSpring } from "@react-spring/web";
|
||||
import { AniX, Check, DashedCircle } from "components/svg";
|
||||
import { circle } from "../sharedSVGProps";
|
||||
import "./WorkIndicator.scss";
|
||||
|
||||
interface Props {
|
||||
interface WorkIndicatorProps {
|
||||
className?: string;
|
||||
failure: boolean;
|
||||
max?: number;
|
||||
progressPercentage: number;
|
||||
stage: number;
|
||||
showPercentage?: boolean;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export default function WorkIndicator({
|
||||
interface WorkIndicatorWrapperProps extends WorkIndicatorProps {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const WorkIndicatorSpinner = ({
|
||||
className,
|
||||
failure = false,
|
||||
max,
|
||||
progressPercentage,
|
||||
stage = 0,
|
||||
showPercentage,
|
||||
success = false,
|
||||
...rest
|
||||
}: Props) {
|
||||
}: WorkIndicatorProps) => {
|
||||
const style = useSpring({
|
||||
opacity: showPercentage ? 1 : 0,
|
||||
});
|
||||
|
||||
return (
|
||||
<svg {...{ className: "work-indicator", ...rest }}>
|
||||
<circle {...{ ...circle, className: "work-indicator-circle-track" }} />
|
||||
<DashedCircle
|
||||
{...{
|
||||
className: "work-indicator-circle-marker",
|
||||
max,
|
||||
progressPercentage,
|
||||
stage,
|
||||
}}
|
||||
/>
|
||||
<AniX {...{ checked: failure }} />
|
||||
<Check {...{ checked: success }} />
|
||||
</svg>
|
||||
<div
|
||||
{...{ className: `work-indicator${className ? " " + className : ""}` }}
|
||||
>
|
||||
<a.div {...{ className: "work-indicator-progress", style }}>
|
||||
{progressPercentage}%
|
||||
</a.div>
|
||||
<svg {...{ className: "work-indicator-circle", ...rest }}>
|
||||
<circle {...{ ...circle, className: "work-indicator-circle-track" }} />
|
||||
<DashedCircle
|
||||
{...{
|
||||
className: "work-indicator-circle-marker",
|
||||
max,
|
||||
progressPercentage,
|
||||
stage,
|
||||
}}
|
||||
/>
|
||||
<AniX {...{ checked: failure }} />
|
||||
<Check {...{ checked: success }} />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function WorkIndicator({
|
||||
label,
|
||||
...rest
|
||||
}: WorkIndicatorWrapperProps) {
|
||||
return label ? (
|
||||
<div {...{ className: "work-indicator-label" }}>
|
||||
{label}
|
||||
<WorkIndicatorSpinner {...rest} />
|
||||
</div>
|
||||
) : (
|
||||
<WorkIndicatorSpinner {...rest} />
|
||||
);
|
||||
}
|
||||
@@ -98,6 +98,7 @@ export default function useMediaUploader({ autoUpload, onSuccess = n }: Props) {
|
||||
return {
|
||||
busy: status === FetchStatus.in_progress,
|
||||
clear,
|
||||
createUpload,
|
||||
engineSubtype,
|
||||
engineSubtypeChange,
|
||||
error: status === FetchStatus.error,
|
||||
|
||||
Reference in new issue
Block a user