padding changes

This commit is contained in:
BFlat committed 2023-09-28 06:24:30 +07:00
1 parent 4c57d367bc
commit 33a5e4252f
3 files changed
+319 -208

No files matched your search

@@ -1,59 +1,90 @@
import React from 'react';
import { animated, useSpring } from '@react-spring/web';
import React from "react";
import { animated, useSpring } from "@react-spring/web";
import { Spinner } from "components/common";
import { springs } from "resources";
import DynamicButton from './DynamicButton';
import DynamicButton from "./DynamicButton";
const ProgressLi = ({ children, disabled = false }: { children?: any, disabled?: boolean }) => {
const ProgressLi = ({
children,
disabled = false,
}: {
children?: any;
disabled?: boolean;
}) => {
const style = useSpring({
...springs.soft,
opacity: disabled ? .25 : 1
opacity: disabled ? 0.25 : 1,
});
return <animated.li {...{ style }}>
<svg>
<circle {...{ cx: 16, cy: 16, r: 15, strokeWidth: "2", }}/>
{ <polyline {...{
fill: "none",
points: "9.5 18 14.5 22 22.5 12",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: "4",
}}/> }
</svg>
{ children }
</animated.li>
return (
<animated.li {...{ style }}>
<svg>
<circle {...{ cx: 16, cy: 16, r: 15, strokeWidth: "2" }} />
{
<polyline
{...{
fill: "none",
points: "9.5 18 14.5 22 22.5 12",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: "4",
}}
/>
}
</svg>
{children}
</animated.li>
);
};
export default function FaceAnimatorTitle({ ...rest }) {
const { audioProps, audioReady, imageProps, imageReady, indexSet, page, submit, t } = rest;
const {
audioProps,
audioReady,
imageProps,
imageReady,
indexSet,
page,
submit,
t,
} = rest;
const noAudio = !audioReady || !audioProps.file;
const noImg = !imageReady || !imageProps.file;
const incomplete = noAudio || noImg;
const working = imageProps.working && audioProps.working;
const slides = [t("inputs.generate"),<Spinner />,t("inputs.makeAnother")];
const slides = [t("inputs.generate"), <Spinner />, t("inputs.makeAnother")];
const onClick = () => {
if (page === 2) {
imageProps.clear(); audioProps.clear(); indexSet(0);
imageProps.clear();
audioProps.clear();
indexSet(0);
} else if (!incomplete && !working) submit();
};
return <div {...{ className: "progress-header" }}>
<h1 {...{ className: "fw-bold text-center text-md-start progress-heading" }}>
{ t("headings.title") }
</h1>
<ul {...{ className: 'async-progress-tracker' }}>
<ProgressLi {...{ disabled: noImg }}>
{ t("headings.image") }
</ProgressLi>
<ProgressLi {...{ disabled: noAudio }}>
{ t("headings.audio") }
</ProgressLi>
</ul>
<DynamicButton {...{ className: "face-animation-submit", disabled: incomplete || working, onClick, slides, index: page }}/>
<p {...{ className: "progress-description" }}>
{ t("headings.subtitle") }
</p>
</div>
};
return (
<div {...{ className: "progress-header" }}>
<h1
{...{ className: "fw-bold text-center text-md-start progress-heading" }}
>
{t("headings.title")}
</h1>
<ul {...{ className: "async-progress-tracker py-3 py-lg-0" }}>
<ProgressLi {...{ disabled: noImg }}>{t("headings.image")}</ProgressLi>
<ProgressLi {...{ disabled: noAudio }}>
{t("headings.audio")}
</ProgressLi>
</ul>
<DynamicButton
{...{
className: "face-animation-submit",
disabled: incomplete || working,
onClick,
slides,
index: page,
}}
/>
<p {...{ className: "progress-description" }}>{t("headings.subtitle")}</p>
</div>
);
}
@@ -2,98 +2,103 @@
$under992: max-width 992px;
.lipsync-editor {
padding: 1rem;
padding-top: 2rem;
.media-input-column {
display: inline-block;
}
@include breakpoint($under992) {
.media-input-column:first-of-type { margin-bottom: 1rem }
}
.media-input-column {
display: inline-block;
}
@include breakpoint($under992) {
.media-input-column:first-of-type {
margin-bottom: 1rem;
}
}
}
.progress-header {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 1rem;
display: flex;
flex-wrap: wrap;
align-items: center;
// padding: 1rem;
.progress-heading {
display: inline-block;
margin: auto 1rem auto 0;
}
.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;
.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;
}
li {
display: flex;
align-items: center;
font-size: 1.25rem;
font-weight: 500;
}
svg {
margin-right: .5rem;
height: 2rem;
width: 2rem;
stroke: white;
}
}
svg {
margin-right: 0.5rem;
height: 2rem;
width: 2rem;
stroke: white;
}
}
.progress-description {
margin-top: 1rem;
width: 100%;
}
.progress-description {
margin-top: 1rem;
width: 100%;
}
button {
margin: 0 0 0 auto;
background-color: #ccccff;
width: fit-content;
button {
margin: 0 0 0 auto;
background-color: #ccccff;
width: fit-content;
@include breakpoint($under992) {
margin: 1rem 0 0;
width: 100%;
@include breakpoint($under992) {
margin: 1rem 0 0;
width: 100%;
.button-slide {
width: 100%;
}
}
}
.button-slide {
width: 100%;
}
}
}
}
.lipsync-panel {
position: relative;
overflow: hidden;
.lipsync-panel {
position: relative;
overflow: hidden;
}
.lipsync-working, .lipsync-working-notice {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
.lipsync-working,
.lipsync-working-notice {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.lipsync-working-notice {
flex-direction: column;
margin: 5rem;
flex-direction: column;
margin: 5rem;
.working-title {
white-space: nowrap;
}
.working-title {
white-space: nowrap;
}
}
.lipsync-success {
padding: .5rem 1rem 1rem;
padding: 0.5rem 1rem 1rem;
h3 { margin: 1rem auto 0 0; }
h3 {
margin: 1rem auto 0 0;
}
.job-id {
color: rgba(#fff, .66);
font-size: .75rem;
}
}
.job-id {
color: rgba(#fff, 0.66);
font-size: 0.75rem;
}
}
@@ -1,5 +1,5 @@
import React, { useState } from "react";
import { animated, useTransition } from '@react-spring/web';
import { animated, useTransition } from "@react-spring/web";
import { v4 as uuidv4 } from "uuid";
import { useFile, useLocalize } from "hooks";
import { AudioInput, ImageInput, Spinner } from "components/common";
@@ -20,15 +20,28 @@ import {
// EnqueueFaceAnimationRequest,
} from "@storyteller/components/src/api/face_animation/EnqueueFaceAnimation";
// import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import FaceAnimatorTitle from './FaceAnimatorTitle';
import FaceAnimatorSuccess from './FaceAnimatorSuccess';
import './LipsyncEditor.scss';
import FaceAnimatorTitle from "./FaceAnimatorTitle";
import FaceAnimatorSuccess from "./FaceAnimatorSuccess";
import "./LipsyncEditor.scss";
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
import { FrontendInferenceJobType, InferenceJob } from "@storyteller/components/src/jobs/InferenceJob";
import {
FrontendInferenceJobType,
InferenceJob,
} from "@storyteller/components/src/jobs/InferenceJob";
interface LipSyncProps { audioProps: any, children?: any, imageProps: any, index: number, toggle: any, style: any, enqueueInferenceJob: any, sessionSubscriptionsWrapper: any, t: any, inferenceJobsByCategory: any, };
interface EditorProps {
interface LipSyncProps {
audioProps: any;
children?: any;
imageProps: any;
index: number;
toggle: any;
style: any;
enqueueInferenceJob: any;
sessionSubscriptionsWrapper: any;
t: any;
inferenceJobsByCategory: any;
}
interface EditorProps {
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
enqueueInferenceJob: (
jobToken: string,
@@ -36,9 +49,9 @@ interface EditorProps {
) => void;
inferenceJobs: Array<InferenceJob>;
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
};
}
const softSpring = { config: { mass: 1, tension: 80, friction: 10 } }
const softSpring = { config: { mass: 1, tension: 80, friction: 10 } };
// const InputPage = ({ audioProps, imageProps, toggle, style, t }: LipSyncProps ) => {
@@ -59,60 +72,107 @@ const softSpring = { config: { mass: 1, tension: 80, friction: 10 } }
// </div>
// </animated.div>};
const InputPage = ({ audioProps, imageProps, toggle, style, t }: LipSyncProps ) => {
const InputPage = ({
audioProps,
imageProps,
toggle,
style,
t,
}: LipSyncProps) => {
return (
<animated.div {...{ className: "lipsync-editor row", style }}>
<div {...{ className: "media-input-column col-lg-6" }}>
<h5>{t("headings.image")}</h5>
<ImageInput
{...{
...imageProps,
onRest: () => toggle.image(imageProps.file ? true : false),
}}
/>
</div>
<div {...{ className: "media-input-column col-lg-6" }}>
<h5>{t("headings.audio")}</h5>
<AudioInput
{...{
...audioProps,
onRest: (p: any, c: any, item: any, l: any) => {
toggle.audio(!!audioProps.file);
},
hideActions: true,
}}
/>
</div>
</animated.div>
);
};
return <animated.div {...{ className: "lipsync-editor row", style }}>
<div {...{ className: "media-input-column col-lg-6" }}>
<h5>{ t("headings.image") }</h5>
<ImageInput {...{ ...imageProps, onRest: () => toggle.image(imageProps.file ? true : false) }}/>
</div>
<div {...{ className: "media-input-column col-lg-6" }}>
<h5>{ t("headings.audio") }</h5>
<AudioInput {...{ ...audioProps, onRest: (p:any,c:any,item:any,l:any) => {
toggle.audio(!!audioProps.file);
}, hideActions: true } }/>
</div>
</animated.div>};
const Working = ({ audioProps, imageProps, index, style, t }: LipSyncProps ) => {
const workStatus = ["",t("status.uploadingAudio"),t("status.uploadingImage"),t("status.requestingAnimation"),""];
const Working = ({ audioProps, imageProps, index, style, t }: LipSyncProps) => {
const workStatus = [
"",
t("status.uploadingAudio"),
t("status.uploadingImage"),
t("status.requestingAnimation"),
"",
];
const transitions = useTransition(index, {
...springs.soft,
from: { opacity: 0, position: "absolute" },
enter: { opacity: 1, position: "relative" },
leave: { opacity: 0, position: "absolute" },
});
return <animated.div {...{ className: "lipsync-working", style }}>
<div {...{ className: "lipsync-working-notice" }}>
<h2 {...{ className: "working-title" }}> { transitions(({ opacity, position }, i) => {
return <animated.span {...{ className: "working-title-text", style: { opacity, position: position as any} }}>{ workStatus[index] } ...</animated.span>;
}) }</h2>
<Spinner />
</div>
</animated.div>;
return (
<animated.div {...{ className: "lipsync-working", style }}>
<div {...{ className: "lipsync-working-notice" }}>
<h2 {...{ className: "working-title" }}>
{" "}
{transitions(({ opacity, position }, i) => {
return (
<animated.span
{...{
className: "working-title-text",
style: { opacity, position: position as any },
}}
>
{workStatus[index]} ...
</animated.span>
);
})}
</h2>
<Spinner />
</div>
</animated.div>
);
};
export default function LipsyncEditor({ enqueueInferenceJob, sessionSubscriptionsWrapper, inferenceJobsByCategory, ...rest }: EditorProps) {
export default function LipsyncEditor({
enqueueInferenceJob,
sessionSubscriptionsWrapper,
inferenceJobsByCategory,
...rest
}: EditorProps) {
const { t } = useLocalize("FaceAnimator");
// the ready states are set by functions which run after the upload input animation is completed, which then illuminates the respective checkmark in a staggered way to draw attention to the workflow, and reduces concurrent animations
const [imageReady,imageReadySet] = useState<boolean>(false);
const [audioReady,audioReadySet] = useState<boolean>(false);
const readyMedia = (m:number) => (t:boolean) => [imageReadySet,audioReadySet][m](t);
const [imageReady, imageReadySet] = useState<boolean>(false);
const [audioReady, audioReadySet] = useState<boolean>(false);
const readyMedia = (m: number) => (t: boolean) =>
[imageReadySet, audioReadySet][m](t);
const audioProps = useFile({}); // contains upload inout state and controls, see docs
const imageProps = useFile({}); // contains upload inout state and controls, see docs
const [index,indexSet] = useState<number>(0); // index = slideshow slide position
const [index, indexSet] = useState<number>(0); // index = slideshow slide position
const makeRequest = (mode: number) => ({
uuid_idempotency_token: uuidv4(),
file: mode ? imageProps.file : audioProps.file,
source: "file",
type: mode ? "image" : "audio"
uuid_idempotency_token: uuidv4(),
file: mode ? imageProps.file : audioProps.file,
source: "file",
type: mode ? "image" : "audio",
});
const upImageAndMerge = async (audio: any) => ({ audio, image: await UploadImage(makeRequest(1)) });
const upImageAndMerge = async (audio: any) => ({
audio,
image: await UploadImage(makeRequest(1)),
});
const submit = async () => {
if (!audioProps.file) return false;
@@ -120,41 +180,50 @@ export default function LipsyncEditor({ enqueueInferenceJob, sessionSubscription
indexSet(1); // set audio working page
UploadAudio(makeRequest(0)) // start audio (0) upload
.then(res => {
if ("upload_token" in res) {
indexSet(2); // set image working page
}
return upImageAndMerge(res); // start image (1) upload, replace with Upload(imageRequest)
})
.then(responses => {
if ("upload_token" in responses.image) {
indexSet(3); // set face animator API working page
return EnqueueFaceAnimation({
uuid_idempotency_token: uuidv4(),
audio_source: {
maybe_media_upload_token: responses.audio.upload_token,
},
image_source: {
maybe_media_upload_token: responses.image.upload_token,
},
});
}
})
.then(res => {
if (res && res.inference_job_token) {
enqueueInferenceJob(
res.inference_job_token,
FrontendInferenceJobType.FaceAnimation
);
indexSet(4); // set face animator API success page
}
})
.catch(e => {
return { success : false };
});
.then((res) => {
if ("upload_token" in res) {
indexSet(2); // set image working page
}
return upImageAndMerge(res); // start image (1) upload, replace with Upload(imageRequest)
})
.then((responses) => {
if ("upload_token" in responses.image) {
indexSet(3); // set face animator API working page
return EnqueueFaceAnimation({
uuid_idempotency_token: uuidv4(),
audio_source: {
maybe_media_upload_token: responses.audio.upload_token,
},
image_source: {
maybe_media_upload_token: responses.image.upload_token,
},
});
}
})
.then((res) => {
if (res && res.inference_job_token) {
enqueueInferenceJob(
res.inference_job_token,
FrontendInferenceJobType.FaceAnimation
);
indexSet(4); // set face animator API success page
}
})
.catch((e) => {
return { success: false };
});
};
const page = index === 0 ? 0 : index === 4 ? 2 : 1;
const headerProps = { audioProps, audioReady, imageProps, imageReady, indexSet, page, submit , t};
const headerProps = {
audioProps,
audioReady,
imageProps,
imageReady,
indexSet,
page,
submit,
t,
};
const transitions = useTransition(index, {
...softSpring,
@@ -163,25 +232,31 @@ export default function LipsyncEditor({ enqueueInferenceJob, sessionSubscription
leave: { opacity: 0, position: "absolute" },
});
return <div>
<div {...{ className: "container" }}>
<div {...{ className: "panel lipsync-panel" }}>
<FaceAnimatorTitle { ...headerProps }/>
{ transitions((style, i) => {
const Page = [InputPage,Working,FaceAnimatorSuccess][page];
return Page ? <Page {...{
audioProps,
imageProps,
enqueueInferenceJob,
sessionSubscriptionsWrapper,
inferenceJobsByCategory,
index,
t,
toggle: { audio: readyMedia(1), image: readyMedia(0) },
style
}}/> : <></>
}) }
</div>
return (
<div {...{ className: "container-panel pt-4" }}>
<div {...{ className: "panel lipsync-panel p-3 py-4 p-md-4" }}>
<FaceAnimatorTitle {...headerProps} />
{transitions((style, i) => {
const Page = [InputPage, Working, FaceAnimatorSuccess][page];
return Page ? (
<Page
{...{
audioProps,
imageProps,
enqueueInferenceJob,
sessionSubscriptionsWrapper,
inferenceJobsByCategory,
index,
t,
toggle: { audio: readyMedia(1), image: readyMedia(0) },
style,
}}
/>
) : (
<></>
);
})}
</div>
</div>;
};
</div>
);
}