mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 02:00:46 +00:00
resolve merge conflicts
This commit is contained in:
commit
eb345128da
16 files changed
+946
-86
No files matched your search
@@ -2,18 +2,23 @@ import React from "react";
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
className?: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export default function LoadingSpinner({ className }: LoadingSpinnerProps) {
|
||||
export default function LoadingSpinner({
|
||||
className,
|
||||
label,
|
||||
}: LoadingSpinnerProps) {
|
||||
return (
|
||||
<div
|
||||
className={`pt-2 d-flex justify-content-center overflow-hidden ${
|
||||
className={`pt-2 d-flex justify-content-center align-items-center overflow-hidden gap-3 ${
|
||||
className ? className : ""
|
||||
}`.trim()}
|
||||
>
|
||||
<div className="spinner-border text-light" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
{label && <div className="fw-medium">{label}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -11,3 +11,9 @@
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.fy-scene-3d-fullscreen-intro {
|
||||
height: calc(100vh - 66px - 66px);
|
||||
width: 100%;
|
||||
border-radius: 0rem;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ interface Scene3DProps {
|
||||
urlParams?: string;
|
||||
objectId?: string;
|
||||
sceneMediaFileToken?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Scene3D({
|
||||
@@ -20,8 +21,8 @@ export default function Scene3D({
|
||||
objectId,
|
||||
sceneMediaFileToken,
|
||||
urlParams,
|
||||
className,
|
||||
}: Scene3DProps) {
|
||||
|
||||
let engineUrl = `https://engine.fakeyou.com?mode=${mode}`;
|
||||
|
||||
if (sceneMediaFileToken) {
|
||||
@@ -35,14 +36,9 @@ export default function Scene3D({
|
||||
<div
|
||||
className={`${
|
||||
fullScreen ? "fy-scene-3d-fullscreen" : "fy-scene-3d-default"
|
||||
}`.trim()}
|
||||
} ${className ? className : ""}`.trim()}
|
||||
>
|
||||
<IframeResizer
|
||||
src={engineUrl}
|
||||
width="100%"
|
||||
height="100%"
|
||||
id=""
|
||||
/>
|
||||
<IframeResizer src={engineUrl} width="100%" height="100%" id="" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -12,13 +12,9 @@ import {
|
||||
faPause,
|
||||
faVolume,
|
||||
faVolumeSlash,
|
||||
} from "@fortawesome/pro-solid-svg-icons"
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
Button,
|
||||
SelectionBubbles,
|
||||
VideoFakeyou
|
||||
} from "components/common";
|
||||
import { Button, SelectionBubbles, VideoFakeyou } from "components/common";
|
||||
import { VideoFakeyouProps } from "../VideoFakeyou/VideoFakeyou";
|
||||
|
||||
import { QuickTrimData, TrimStates, PlaybarStates } from "./types";
|
||||
@@ -26,35 +22,35 @@ import { formatSecondsToHHMMSSCS } from "./helpers";
|
||||
import TrimScrbber from "./TrimScrubber";
|
||||
import './styles.scss'
|
||||
|
||||
interface VideoQuickTrimProps extends VideoFakeyouProps{
|
||||
onSelect: (values: QuickTrimData)=>void;
|
||||
interface VideoQuickTrimProps extends VideoFakeyouProps {
|
||||
onSelect: (values: QuickTrimData) => void;
|
||||
}
|
||||
|
||||
const trimOptions: { [key: string]: number } = {
|
||||
"3s": 3,
|
||||
"5s": 5,
|
||||
"10s": 10,
|
||||
"15s" : 15,
|
||||
"15s": 15,
|
||||
};
|
||||
|
||||
const initialTrimState:TrimStates = {
|
||||
const initialTrimState: TrimStates = {
|
||||
canNotTrim: true,
|
||||
isScrubbingTrim: false,
|
||||
trimDuration: 0,
|
||||
trimStart: 0,
|
||||
trimEnd: 0,
|
||||
maxDuration: 0,
|
||||
}
|
||||
};
|
||||
|
||||
const initialPlaybarState:PlaybarStates = {
|
||||
const initialPlaybarState: PlaybarStates = {
|
||||
playbarWidth: 0,
|
||||
timeCursorOffset: 0,
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(function VideoQuickTrim({
|
||||
onSelect,
|
||||
...rest
|
||||
}: VideoQuickTrimProps){
|
||||
}: VideoQuickTrimProps) {
|
||||
// console.log("VideoQuickTrim Rerender!!");
|
||||
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
@@ -120,29 +116,30 @@ export default memo(function VideoQuickTrim({
|
||||
}, [playbarWidth, onSelect, isRepeatOn, trimEnd]); //END videoRefCallback
|
||||
|
||||
function handleWindowResize() {
|
||||
if(playbarRef.current !== null){
|
||||
if (playbarRef.current !== null) {
|
||||
const newWidth = playbarRef.current.getBoundingClientRect().width;
|
||||
setPlaybarState((curr)=>({
|
||||
setPlaybarState(curr => ({
|
||||
playbarWidth: newWidth,
|
||||
timeCursorOffset: (curr.timeCursorOffset / (curr.playbarWidth-8)) * (newWidth-8)
|
||||
timeCursorOffset:
|
||||
(curr.timeCursorOffset / (curr.playbarWidth - 8)) * (newWidth - 8),
|
||||
}));
|
||||
}
|
||||
}
|
||||
const playbarRefCallback = useCallback(node => {
|
||||
if(node !== null) {
|
||||
if (node !== null) {
|
||||
playbarRef.current = node;
|
||||
setPlaybarState((curr)=>({
|
||||
setPlaybarState(curr => ({
|
||||
...curr,
|
||||
playbarWidth: node.getBoundingClientRect().width
|
||||
playbarWidth: node.getBoundingClientRect().width,
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
useLayoutEffect(()=>{
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
useLayoutEffect(() => {
|
||||
window.addEventListener("resize", handleWindowResize);
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleWindowResize);
|
||||
window.removeEventListener("resize", handleWindowResize);
|
||||
};
|
||||
},[]);
|
||||
}, []);
|
||||
|
||||
const handleChangeTrimDuration = (selected: string) =>{
|
||||
if(!canNotTrim){
|
||||
@@ -171,7 +168,7 @@ export default memo(function VideoQuickTrim({
|
||||
}else{
|
||||
videoRef.current?.pause();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const trimScrubberWidth = videoRef.current
|
||||
? trimDuration > 0 && trimDuration < videoRef.current.duration
|
||||
@@ -190,24 +187,29 @@ export default memo(function VideoQuickTrim({
|
||||
/>
|
||||
|
||||
<div className="playpause-overlay" onClick={handlePlaypause}>
|
||||
{playpause === 'paused' &&
|
||||
<FontAwesomeIcon className="playpause-icon"
|
||||
icon={faPlay} size="8x"
|
||||
{playpause === "paused" && (
|
||||
<FontAwesomeIcon
|
||||
className="playpause-icon"
|
||||
icon={faPlay}
|
||||
size="8x"
|
||||
/>
|
||||
}
|
||||
{playpause === 'playing' &&
|
||||
<FontAwesomeIcon className="playpause-icon"
|
||||
icon={faPause} size="8x"
|
||||
)}
|
||||
{playpause === "playing" && (
|
||||
<FontAwesomeIcon
|
||||
className="playpause-icon"
|
||||
icon={faPause}
|
||||
size="8x"
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
{videoRef.current && canNotTrim &&
|
||||
{videoRef.current && canNotTrim && (
|
||||
<div className="warning-too-short">
|
||||
<div className="background"></div>
|
||||
<p>Warning: Sorry Your Video is TOO Short</p>
|
||||
</div>
|
||||
}
|
||||
</div>{/* END of Video Wrapper */}
|
||||
)}
|
||||
</div>
|
||||
{/* END of Video Wrapper */}
|
||||
<div className="playbar" ref={playbarRefCallback}>
|
||||
<div className="playbar-bg" />
|
||||
<TrimScrbber width={trimScrubberWidth} />
|
||||
@@ -218,7 +220,7 @@ export default memo(function VideoQuickTrim({
|
||||
<div className="playpause-external d-flex align-items-center flex-wrap mb-2">
|
||||
<Button
|
||||
className="button-playpause"
|
||||
icon={playpause === 'playing' ? faPause : faPlay}
|
||||
icon={playpause === "playing" ? faPause : faPlay}
|
||||
variant="secondary"
|
||||
onClick={handlePlaypause}
|
||||
/>
|
||||
@@ -232,12 +234,14 @@ export default memo(function VideoQuickTrim({
|
||||
className="button-mute"
|
||||
icon={isMuted ? faVolumeSlash : faVolume}
|
||||
variant="secondary"
|
||||
onClick={()=>setIsMuted((curr)=>(!curr))}
|
||||
onClick={() => setIsMuted(curr => !curr)}
|
||||
/>
|
||||
<div className="playtime d-flex">
|
||||
<span >
|
||||
<p>
|
||||
{`${formatSecondsToHHMMSSCS(videoRef.current?.currentTime || 0)}`}
|
||||
{`${formatSecondsToHHMMSSCS(
|
||||
videoRef.current?.currentTime || 0
|
||||
)}`}
|
||||
</p>
|
||||
</span>
|
||||
<div>/</div>
|
||||
@@ -251,6 +255,7 @@ export default memo(function VideoQuickTrim({
|
||||
<SelectionBubbles
|
||||
options={Object.keys(trimOptions)}
|
||||
onSelect={handleChangeTrimDuration}
|
||||
selectedStyle="outline"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
$playbar-height: 2rem;
|
||||
|
||||
.fy-video-quicktrim {
|
||||
.video-wrapper{
|
||||
.video-wrapper {
|
||||
position: relative;
|
||||
background-color: $bg;
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
@@ -11,71 +11,76 @@ $playbar-height: 2rem;
|
||||
border-left: $panel-inner-border 1px solid;
|
||||
border-right: $panel-inner-border 1px solid;
|
||||
}
|
||||
.fy-video{
|
||||
height: 500px;
|
||||
.fy-video {
|
||||
max-height: 500px;
|
||||
margin-bottom: -7px;
|
||||
|
||||
video {
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
}
|
||||
}
|
||||
.playpause-overlay{
|
||||
.playpause-overlay {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top:0;
|
||||
left:0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
svg{
|
||||
svg {
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
&:hover svg{
|
||||
&:hover svg {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.warning-too-short{
|
||||
.warning-too-short {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top:0;
|
||||
left:0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
div{
|
||||
div {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top:0;
|
||||
left:0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #e66462;
|
||||
opacity: 70%;
|
||||
}
|
||||
p{
|
||||
p {
|
||||
position: absolute;
|
||||
margin-top: 230px;
|
||||
width: 100%;
|
||||
top:0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.playbar{
|
||||
position:relative;
|
||||
.playbar {
|
||||
position: relative;
|
||||
height: $playbar-height;
|
||||
width: 100%;
|
||||
}
|
||||
.playbar-bg{
|
||||
.playbar-bg {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top:0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: $panel-inner-border;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
}
|
||||
.trim-scrubber{
|
||||
position: absolute;
|
||||
top:-1rem;
|
||||
top: -1rem;
|
||||
height: 3rem;
|
||||
background-color: #e66462;
|
||||
opacity: 0.8;
|
||||
@@ -92,16 +97,16 @@ $playbar-height: 2rem;
|
||||
color: $panel-inner-border;
|
||||
}
|
||||
}
|
||||
.playcursor{
|
||||
.playcursor {
|
||||
position: absolute;
|
||||
top:0;
|
||||
top: 0;
|
||||
width: 0.5rem;
|
||||
height: $playbar-height;
|
||||
background-color: $white;
|
||||
}
|
||||
|
||||
.playpause-external{
|
||||
.button-playpause{
|
||||
.playpause-external {
|
||||
.button-playpause {
|
||||
width: 55px;
|
||||
height: 40px;
|
||||
margin-right: 0.5rem;
|
||||
@@ -116,7 +121,7 @@ $playbar-height: 2rem;
|
||||
span{
|
||||
width: 50px;
|
||||
}
|
||||
div{
|
||||
div {
|
||||
width: 10px;
|
||||
margin-left: 3px;
|
||||
margin-right: 5px;
|
||||
@@ -124,4 +129,4 @@ $playbar-height: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ const resultPaths = {
|
||||
EngineComposition: "/media",
|
||||
FaceAnimation: "/media",
|
||||
TextToSpeech: "/media",
|
||||
VideoStyleTransfer: "/media",
|
||||
VoiceConversion: "/media",
|
||||
VoiceDesignerCreateVoice: "/voice-designer/voice",
|
||||
VoiceDesignerTts: "/media",
|
||||
|
||||
@@ -123,6 +123,9 @@ import TextToImagePage from "./pages/text_to_image/TextToImagePage";
|
||||
import DomainConfigProvider from "context/DomainConfigContext";
|
||||
import DevUpload from "./pages/dev_upload/DevUpload";
|
||||
import DevMediaInput from "./pages/dev_upload/DevMediaInput";
|
||||
import { StudioIntroPage } from "./pages/storyteller_studio_intro/StudioIntroPage";
|
||||
import StudioVSTPage from "./pages/storyteller_studio_intro/StudioVST/StudioVSTPage";
|
||||
import { StudioIntroResultPage } from "./pages/storyteller_studio_intro/StudioIntroResultPage";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
@@ -755,6 +758,28 @@ class PageContainer extends React.Component<
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/studio-intro/result/:jobToken?">
|
||||
<StudioIntroResultPage
|
||||
sessionWrapper={this.props.sessionWrapper}
|
||||
sessionSubscriptionsWrapper={
|
||||
this.props.sessionSubscriptionsWrapper
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/studio-intro/style/:jobToken?">
|
||||
<StudioVSTPage />
|
||||
</Route>
|
||||
|
||||
<Route path="/studio-intro/:mediaToken?">
|
||||
<StudioIntroPage
|
||||
sessionWrapper={this.props.sessionWrapper}
|
||||
sessionSubscriptionsWrapper={
|
||||
this.props.sessionSubscriptionsWrapper
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/commissions">
|
||||
<CommunityCommissionsPage />
|
||||
</Route>
|
||||
@@ -783,6 +808,11 @@ class PageContainer extends React.Component<
|
||||
<DevMediaInput />
|
||||
</Route>
|
||||
|
||||
{
|
||||
// <Route path="/dev-tts">
|
||||
// <DevTTS />
|
||||
// </Route>
|
||||
}
|
||||
{/* Route for initial voice creation */}
|
||||
<Route exact path="/voice-designer/create">
|
||||
<VoiceDesignerFormPage
|
||||
@@ -1309,15 +1339,6 @@ class PageContainer extends React.Component<
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/studio">
|
||||
<StorytellerStudioListPage
|
||||
sessionWrapper={this.props.sessionWrapper}
|
||||
sessionSubscriptionsWrapper={
|
||||
this.props.sessionSubscriptionsWrapper
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/commissions">
|
||||
<CommunityCommissionsPage />
|
||||
</Route>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.studio-intro-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 66px);
|
||||
}
|
||||
|
||||
.result-video-wrapper {
|
||||
max-width: 1100px;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import React from "react";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
|
||||
import { Button, Scene3D } from "components/common";
|
||||
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
import { useInferenceJobs } from "hooks";
|
||||
import { EnqueueEngineCompositing } from "@storyteller/components/src/api/engine_compositor/EnqueueEngineCompositing";
|
||||
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import "./StudioIntro.scss";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
}
|
||||
|
||||
function StudioIntroPage(props: Props) {
|
||||
const { mediaToken } = useParams<{ mediaToken: string }>();
|
||||
const history = useHistory();
|
||||
const inferenceJobs = useInferenceJobs(
|
||||
FrontendInferenceJobType.EngineComposition
|
||||
);
|
||||
|
||||
usePrefixedDocumentTitle("Storyteller Studio");
|
||||
|
||||
if (!props.sessionWrapper.canAccessStudio()) {
|
||||
return <StudioNotAvailable />;
|
||||
}
|
||||
|
||||
let engineParams = {};
|
||||
|
||||
if (mediaToken) {
|
||||
engineParams = {
|
||||
sceneMediaFileToken: mediaToken,
|
||||
};
|
||||
} else {
|
||||
engineParams = {
|
||||
objectId: "sample-room.gltf",
|
||||
};
|
||||
}
|
||||
|
||||
const onClick = () => {
|
||||
// This opens the job modal. Needs to be disabled.
|
||||
EnqueueEngineCompositing("", {
|
||||
uuid_idempotency_token: uuidv4(),
|
||||
media_file_token: mediaToken || "",
|
||||
}).then((res: any) => {
|
||||
if (res && res.success) {
|
||||
inferenceJobs.enqueue(res.inference_job_token);
|
||||
history.push(`/studio-intro/style/${res.inference_job_token}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="studio-intro-page">
|
||||
<Scene3D
|
||||
fullScreen={true}
|
||||
mode="studio"
|
||||
className="flex-grow-1"
|
||||
{...engineParams}
|
||||
/>
|
||||
<div className="d-flex justify-content-center p-3">
|
||||
<Button label="Create Movie from 3D Scene" onClick={onClick} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { StudioIntroPage };
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
import React, { useState } from "react";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { Button, Container, Panel } from "components/common";
|
||||
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import "./StudioIntro.scss";
|
||||
import LoadingSpinner from "components/common/LoadingSpinner";
|
||||
import { useMedia } from "hooks";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { MediaFileType } from "@storyteller/components/src/api/media_files";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
}
|
||||
|
||||
// This page just needs to poll the job and show the video when it's done.
|
||||
|
||||
function StudioIntroResultPage(props: Props) {
|
||||
const { jobToken } = useParams<{ jobToken: string }>();
|
||||
const history = useHistory();
|
||||
const [mediaToken, setMediaToken] = useState(
|
||||
"m_f5kp3hm74qeq16eq7536jb73jkbvkh"
|
||||
); // Set the media token after polling success
|
||||
|
||||
// const [jobExists, setJobExists] = useState<boolean | null>(null);
|
||||
|
||||
const [mediaFile, setMediaFile] = useState<MediaFileType>();
|
||||
|
||||
useMedia({
|
||||
mediaToken: mediaToken,
|
||||
onSuccess: (res: any) => {
|
||||
setMediaFile(res);
|
||||
},
|
||||
});
|
||||
|
||||
const mediaLink =
|
||||
mediaFile && new BucketConfig().getGcsUrl(mediaFile.public_bucket_path);
|
||||
|
||||
usePrefixedDocumentTitle("Storyteller Studio");
|
||||
|
||||
if (!jobToken) {
|
||||
history.push("/");
|
||||
}
|
||||
|
||||
// Should also check if job actually exists or not
|
||||
//
|
||||
// if (!jobExists) {
|
||||
// history.push("/");
|
||||
// }
|
||||
|
||||
return (
|
||||
<Container
|
||||
type="full"
|
||||
className="mt-4 d-flex flex-column align-items-center"
|
||||
>
|
||||
<Panel clear={true}>
|
||||
<h2 className="fw-bold mb-0 mb-4 text-center">Generation Result</h2>
|
||||
</Panel>
|
||||
<Panel className="overflow-hidden rounded result-video-wrapper">
|
||||
<div className="ratio ratio-16x9">
|
||||
{mediaToken ? (
|
||||
<video src={mediaLink} controls />
|
||||
) : (
|
||||
<LoadingSpinner label="Generating your movie..." />
|
||||
)}
|
||||
</div>
|
||||
</Panel>
|
||||
{mediaToken ? (
|
||||
<div className="d-flex justify-content-center p-4">
|
||||
<Button label="Sign up to use your own scene" />
|
||||
</div>
|
||||
) : null}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export { StudioIntroResultPage };
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { NavLink, useParams, useHistory } from "react-router-dom";
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
Panel,
|
||||
SelectionBubbles,
|
||||
TextArea,
|
||||
} from "components/common";
|
||||
import VideoQuickTrim, {
|
||||
QuickTrimData,
|
||||
} from "components/common/VideoQuickTrim";
|
||||
import EnqueueVideoStyleTransfer from "@storyteller/components/src/api/video_styleTransfer";
|
||||
import { initialValues } from "./defaultValues";
|
||||
import { mapRequest, VSTType } from "./helpers";
|
||||
import LoadingSpinner from "components/common/LoadingSpinner";
|
||||
// import SectionAdvanceOptions from "./sectionAdvanceOptions";
|
||||
|
||||
export default function PageVSTApp() {
|
||||
const { jobToken } = useParams<{ jobToken: string }>();
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const history = useHistory();
|
||||
const [mediaToken, setMediaToken] = useState(
|
||||
"m_wnnz9jey96dwhss9qk1891v9gmvnj8" // Currently hardcoded video media token value. Let a job polling function set the value when job returns media on success.
|
||||
);
|
||||
// const [jobExists, setJobExists] = useState<boolean | null>(null);
|
||||
const [vstValues, setVstValues] = useState<VSTType>({
|
||||
...initialValues,
|
||||
fileToken: mediaToken,
|
||||
});
|
||||
|
||||
const handleOnChange = (val: {
|
||||
[key: string]: number | string | boolean | undefined;
|
||||
}) => {
|
||||
setVstValues(curr => ({ ...curr, ...val }));
|
||||
};
|
||||
|
||||
const handleGenerate = () => {
|
||||
const request = mapRequest(vstValues);
|
||||
EnqueueVideoStyleTransfer(request).then(res => {
|
||||
if (res.success && res.inference_job_token) {
|
||||
console.log("Job enqueued successfully", res.inference_job_token);
|
||||
history.push(`/studio-intro/result/${res.inference_job_token}`);
|
||||
} else {
|
||||
console.log("Failed to enqueue job", res);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (videoRef?.current) {
|
||||
const ve = videoRef.current;
|
||||
ve.onloadedmetadata = () => {
|
||||
const newValues: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
maxDuration?: number;
|
||||
} = {};
|
||||
if (ve.duration) {
|
||||
newValues.maxDuration = ve.duration;
|
||||
}
|
||||
setVstValues(curr => ({
|
||||
...curr,
|
||||
...newValues,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
const styleMap: { [key: string]: string } = {
|
||||
Anime: "weight_yqexh77ntqyawzgh9fzash798", // set SD Model Weight Tokens, currently hardcoded all to Anime style
|
||||
Pixel: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
Painting: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
};
|
||||
|
||||
const handleStyleSelection = (selected: string) => {
|
||||
const selectedSdModelToken = styleMap[selected];
|
||||
setVstValues(curr => ({ ...curr, sdModelToken: selectedSdModelToken }));
|
||||
};
|
||||
|
||||
const styleOptions = Object.keys(styleMap);
|
||||
|
||||
if (!jobToken) {
|
||||
history.push("/");
|
||||
}
|
||||
|
||||
// Should also check if job actually exists or not
|
||||
//
|
||||
// if (!jobExists) {
|
||||
// history.push("/");
|
||||
// }
|
||||
|
||||
return (
|
||||
<Container type="panel" className="mt-5">
|
||||
<Panel clear={true}>
|
||||
<h2 className="fw-bold mb-0 mb-5 text-center">Style Your Scene</h2>
|
||||
</Panel>
|
||||
<Panel padding={true}>
|
||||
<div className="row g-5">
|
||||
<div className="col-12 col-md-6">
|
||||
{vstValues.fileToken ? (
|
||||
<VideoQuickTrim
|
||||
mediaToken={vstValues.fileToken}
|
||||
onSelect={(val: QuickTrimData) =>
|
||||
handleOnChange({
|
||||
trimStart: val.trimStartSeconds,
|
||||
trimEnd: val.trimEndSeconds,
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="ratio ratio-4x3 panel-inner rounded">
|
||||
<LoadingSpinner label="Loading Video" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-12 col-md-6 d-flex flex-column gap-3 justify-content-center">
|
||||
<div>
|
||||
<label className="sub-title">Select a Style</label>
|
||||
<SelectionBubbles
|
||||
options={styleOptions}
|
||||
onSelect={handleStyleSelection}
|
||||
selectedStyle="outline"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextArea
|
||||
label="Describe Your Scene"
|
||||
placeholder="Enter your description..."
|
||||
onChange={e => handleOnChange({ posPrompt: e.target.value })}
|
||||
value={vstValues.posPrompt}
|
||||
required={false}
|
||||
rows={5}
|
||||
resize={false}
|
||||
/>
|
||||
{/* <TextArea
|
||||
label="Negative Prompt"
|
||||
placeholder="Enter your negative prompt"
|
||||
onChange={e => handleOnChange({ negPrompt: e.target.value })}
|
||||
value={vstValues.negPrompt}
|
||||
required={false}
|
||||
/> */}
|
||||
{/* <SectionAdvanceOptions
|
||||
onChange={handleOnChange}
|
||||
vstValues={vstValues}
|
||||
/> */}
|
||||
<div className="d-flex gap-2 justify-content-end mt-3">
|
||||
<NavLink to="/">
|
||||
<Button label="Cancel" variant="secondary" />
|
||||
</NavLink>
|
||||
<Button
|
||||
label="Generate Your Movie"
|
||||
onClick={handleGenerate}
|
||||
variant="primary"
|
||||
disabled={!vstValues.fileToken}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
type CNPreset =
|
||||
'custom'|
|
||||
'closeup'|
|
||||
'default' |
|
||||
'halfbody'|
|
||||
'fullbody'|
|
||||
'landscape'|
|
||||
'typog';
|
||||
|
||||
const defaultPreset = {
|
||||
cnCanny: 0,
|
||||
cnDepth: 0,
|
||||
cnLineArtAnime: 0.7,
|
||||
cnLineArtRealistic: 0,
|
||||
cnLipsStrength: 0,
|
||||
cnOpenPose: 1,
|
||||
cnPipeFace: 0,
|
||||
cnSparseScribble: 0,
|
||||
cnSoftEdge: 1,
|
||||
cnRegularSteps: 20,
|
||||
}
|
||||
|
||||
const closeupPreset = {
|
||||
cnCanny: 0,
|
||||
cnDepth: 0,
|
||||
cnLineArtAnime: 0.7,
|
||||
cnLineArtRealistic: 0,
|
||||
cnLipsStrength: 0,
|
||||
cnOpenPose: 0,
|
||||
cnPipeFace: 0,
|
||||
cnSparseScribble: 0.7,
|
||||
cnSoftEdge: 0.7,
|
||||
cnRegularSteps: 20,
|
||||
}
|
||||
|
||||
const halfbodyPreset = {
|
||||
cnCanny: 0,
|
||||
cnDepth: 0,
|
||||
cnLineArtAnime: 0.7,
|
||||
cnLineArtRealistic: 0,
|
||||
cnLipsStrength: 0,
|
||||
cnOpenPose: 0.7,
|
||||
cnPipeFace: 0,
|
||||
cnSparseScribble: 0,
|
||||
cnSoftEdge: 1,
|
||||
cnRegularSteps: 20,
|
||||
}
|
||||
|
||||
const fullbodyPreset = {
|
||||
cnCanny: 0,
|
||||
cnDepth: 0,
|
||||
cnLineArtAnime: 0.7,
|
||||
cnLineArtRealistic: 0,
|
||||
cnLipsStrength: 0,
|
||||
cnOpenPose: 1,
|
||||
cnPipeFace: 0,
|
||||
cnSparseScribble: 0,
|
||||
cnSoftEdge: 1,
|
||||
cnRegularSteps: 20,
|
||||
}
|
||||
|
||||
const landscapePreset = {
|
||||
cnCanny: 0,
|
||||
cnDepth: 0.4,
|
||||
cnLineArtAnime: 0.7,
|
||||
cnLineArtRealistic: 0,
|
||||
cnLipsStrength: 0,
|
||||
cnOpenPose: 0,
|
||||
cnPipeFace: 0,
|
||||
cnSparseScribble: 0,
|
||||
cnSoftEdge: 0,
|
||||
cnRegularSteps: 20,
|
||||
}
|
||||
|
||||
const typogPreset = {
|
||||
cnCanny: 0,
|
||||
cnDepth: 0,
|
||||
cnLineArtAnime: 0.7,
|
||||
cnLineArtRealistic: 0,
|
||||
cnLipsStrength: 0,
|
||||
cnOpenPose: 0,
|
||||
cnPipeFace: 0,
|
||||
cnSparseScribble: 0,
|
||||
cnSoftEdge: 0.7,
|
||||
cnRegularSteps: 20,
|
||||
}
|
||||
|
||||
export type {
|
||||
CNPreset
|
||||
}
|
||||
export {
|
||||
defaultPreset,
|
||||
closeupPreset,
|
||||
halfbodyPreset,
|
||||
fullbodyPreset,
|
||||
landscapePreset,
|
||||
typogPreset,
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { defaultPreset as defaultCN } from "./cnPresets";
|
||||
|
||||
export { initialValues, hiddenValues };
|
||||
const initialValues = {
|
||||
//files Settings
|
||||
fileToken: "",
|
||||
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
|
||||
|
||||
//video Settings
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
maxDuration: 0,
|
||||
trimStart: 0,
|
||||
trimEnd: 0,
|
||||
|
||||
//Presents
|
||||
workflowConfig: "weight_99tz2nzbp5x9v55gqn5ekj1sd",
|
||||
sdModelToken: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
loraModelToken: "",
|
||||
loraModelStrength: 0,
|
||||
|
||||
//basics
|
||||
inputFps: 24,
|
||||
posPrompt: "",
|
||||
negPrompt: "",
|
||||
visibility: "private",
|
||||
|
||||
//Control Net
|
||||
...defaultCN,
|
||||
};
|
||||
|
||||
const hiddenValues = {
|
||||
posPrompt: "",
|
||||
negPrompt:
|
||||
"flare, lens flare, glare,naked, nsfw, text, logo, watermark:1.3, letterboxed,embedding:easynegative, embedding:badhandv4,(worst quality, low quality:1.4), lowres, blurry, monochrome, ",
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { hiddenValues } from "./defaultValues";
|
||||
|
||||
export type VSTType = {
|
||||
// File Settings
|
||||
fileToken: string;
|
||||
outputPath: string;
|
||||
// Video Settings
|
||||
width: number;
|
||||
height: number;
|
||||
maxDuration: number;
|
||||
trimStart: number;
|
||||
trimEnd: number;
|
||||
|
||||
// Presets
|
||||
workflowConfig: string;
|
||||
sdModelToken: string;
|
||||
// loraModelToken: string;
|
||||
// loraModelStrength: number;
|
||||
|
||||
//basics
|
||||
inputFps: number;
|
||||
posPrompt: string;
|
||||
negPrompt: string;
|
||||
visibility: string;
|
||||
|
||||
//advance
|
||||
cnCanny: number;
|
||||
cnDepth: number;
|
||||
cnLineArtAnime: number;
|
||||
cnLineArtRealistic: number;
|
||||
cnLipsStrength: number;
|
||||
cnOpenPose: number;
|
||||
cnPipeFace: number;
|
||||
cnSparseScribble: number;
|
||||
cnSoftEdge: number;
|
||||
cnRegularSteps: number
|
||||
}
|
||||
|
||||
export function isInputValid(keyValues: {[key:string]:number|string|boolean|undefined}){
|
||||
return (
|
||||
keyValues.sdModelToken
|
||||
&& keyValues.sdModelToken !== ""
|
||||
&& keyValues.posPrompt
|
||||
&& keyValues.posPrompt !== ""
|
||||
);
|
||||
}
|
||||
|
||||
export function mapRequest(vstValues: VSTType){
|
||||
//TODO: improve the typing of this
|
||||
return{
|
||||
"uuid_idempotency_token": uuidv4(),
|
||||
"maybe_sd_model": vstValues.sdModelToken,
|
||||
"maybe_workflow_config":vstValues.workflowConfig,
|
||||
"maybe_input_file": vstValues.fileToken,
|
||||
"maybe_trim_start_seconds": vstValues.trimStart,
|
||||
"maybe_trim_end_seconds": vstValues.trimEnd,
|
||||
"maybe_output_path": vstValues.outputPath,
|
||||
"creator_set_visibility": vstValues.visibility,
|
||||
"maybe_json_modifications": {
|
||||
"$.154.inputs.Value": vstValues.inputFps,
|
||||
"$.510.inputs.Text":
|
||||
hiddenValues.posPrompt + vstValues.posPrompt,
|
||||
"$.8.inputs.text":
|
||||
hiddenValues.negPrompt+ vstValues.negPrompt,
|
||||
|
||||
"$.401.inputs.Value": 0.7, //Denoise First Pass
|
||||
"$.140.inputs.Value": 1, // Every nth frame
|
||||
"$.536.inputs.boolean_number": 1, // use LCM
|
||||
"$.1075.inputs.Value": 1280,
|
||||
"$.1076.inputs.Value": 720,
|
||||
"$.1079.inputs.Value": 1280,
|
||||
"$.1080.inputs.Value": 720,
|
||||
|
||||
"$.403.inputs.Value": vstValues.cnSparseScribble,
|
||||
"$.771.inputs.Value": vstValues.cnOpenPose,
|
||||
"$.772.inputs.Value": vstValues.cnDepth,
|
||||
"$.796.inputs.Value": vstValues.cnLineArtRealistic,
|
||||
"$.797.inputs.Value": vstValues.cnLineArtAnime,
|
||||
"$.1398.inputs.Value": vstValues.cnSoftEdge,
|
||||
"$.1531.inputs.Value": vstValues.cnRegularSteps,
|
||||
|
||||
//"$.800.inputs.Value": 0, // vstValues.cnCanny,
|
||||
//"$.1636.inputs.Value": 0, //vstValues.cnLipsStrength,
|
||||
|
||||
// "$.208.inputs.lora_01": vstValues.loraModelToken,
|
||||
// "$.208.inputs.strength_01": vstValues.loraModelStrength
|
||||
// "$.1449.inputs.filename_prefix": "vid2vid/SparseUpscaleInterp",
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./StudioVSTPage";
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
import React, {useState} from 'react';
|
||||
|
||||
import {
|
||||
faBuildingColumns,
|
||||
faFaceViewfinder,
|
||||
faPerson,
|
||||
// faRotateLeft,
|
||||
faStar,
|
||||
// faSparkles,
|
||||
faText,
|
||||
faUser,
|
||||
|
||||
}
|
||||
from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import {
|
||||
// Accordion,
|
||||
Button,
|
||||
Label,
|
||||
// NumberSliderV2
|
||||
} from 'components/common';
|
||||
import { VSTType } from './helpers';
|
||||
import {
|
||||
CNPreset,
|
||||
closeupPreset,
|
||||
halfbodyPreset,
|
||||
fullbodyPreset,
|
||||
landscapePreset,
|
||||
typogPreset,
|
||||
defaultPreset,
|
||||
} from './cnPresets';
|
||||
|
||||
|
||||
export default function SectionAdvanceOptions({
|
||||
vstValues: vstVal,
|
||||
onChange: handleOnChange
|
||||
}:{
|
||||
vstValues : VSTType,
|
||||
onChange: (val:{[key: string]: number|string|boolean|undefined})=>void
|
||||
}){
|
||||
|
||||
const [preset, pickPreset] = useState<CNPreset>('default')
|
||||
function handlePreset(newPreset: CNPreset){
|
||||
switch (newPreset){
|
||||
case 'closeup':
|
||||
handleOnChange(closeupPreset);
|
||||
break;
|
||||
case 'halfbody':
|
||||
handleOnChange(halfbodyPreset);
|
||||
break;
|
||||
case 'fullbody':
|
||||
handleOnChange(fullbodyPreset);
|
||||
break;
|
||||
case 'landscape':
|
||||
handleOnChange(landscapePreset);
|
||||
break;
|
||||
case 'typog':
|
||||
handleOnChange(typogPreset);
|
||||
break;
|
||||
case 'custom':
|
||||
default:
|
||||
handleOnChange(defaultPreset);
|
||||
break;
|
||||
}
|
||||
pickPreset(newPreset);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Label label="Video's Camera Angle"/>
|
||||
{/* <div className="d-flex flex-wrap w-100">
|
||||
<div className="my-1 me-1">
|
||||
<Button
|
||||
icon={faStar}
|
||||
label="Default"
|
||||
isActive={preset==='default'}
|
||||
onClick={()=>handlePreset('default')}
|
||||
/>
|
||||
</div>
|
||||
<div className="my-1 me-1">
|
||||
<Button
|
||||
icon={faSparkles}
|
||||
label="Custom"
|
||||
isActive={preset==='custom'}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="d-flex flex-wrap w-100">
|
||||
<div className="m-1">
|
||||
<Button
|
||||
icon={faStar}
|
||||
label="Default"
|
||||
isActive={preset==='default'}
|
||||
onClick={()=>handlePreset('default')}
|
||||
/>
|
||||
</div>
|
||||
<div className="m-1">
|
||||
<Button
|
||||
icon={faFaceViewfinder}
|
||||
label="Face Closeups"
|
||||
isActive={preset==='closeup'}
|
||||
onClick={()=>handlePreset('closeup')}
|
||||
/>
|
||||
</div>
|
||||
<div className="m-1">
|
||||
<Button
|
||||
icon={faUser}
|
||||
label="Half Body Shots"
|
||||
isActive={preset==='halfbody'}
|
||||
onClick={()=>handlePreset('halfbody')}
|
||||
/>
|
||||
</div>
|
||||
<div className="m-1">
|
||||
<Button
|
||||
icon={faPerson}
|
||||
label="Full Body Shots"
|
||||
isActive={preset==='fullbody'}
|
||||
onClick={()=>handlePreset('fullbody')}
|
||||
/>
|
||||
</div>
|
||||
<div className="m-1">
|
||||
<Button
|
||||
icon={faBuildingColumns}
|
||||
label="Architecture or Landscape"
|
||||
isActive={preset==='landscape'}
|
||||
onClick={()=>handlePreset('landscape')}
|
||||
/>
|
||||
</div>
|
||||
<div className="m-1">
|
||||
<Button
|
||||
icon={faText}
|
||||
label="Flat Logos, or Typographies"
|
||||
isActive={preset==='typog'}
|
||||
onClick={()=>handlePreset('typog')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <Accordion className="mt-4">
|
||||
<Accordion.Item title={"Advanced Options"}>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: vstVal.cnCanny,
|
||||
label: "Canny",
|
||||
thumbTip: "Canny",
|
||||
onChange: (val)=>{
|
||||
handleOnChange({cnCanny: val});
|
||||
pickPreset('custom');
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: vstVal.cnDepth,
|
||||
label: "Depth",
|
||||
thumbTip: "Depth",
|
||||
onChange: (val)=>{
|
||||
handleOnChange({cnDepth: val});
|
||||
pickPreset('custom');
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: vstVal.cnLineArtAnime,
|
||||
label: "Line Art - Anime",
|
||||
thumbTip: "Line Art - Anime",
|
||||
onChange: (val)=>{
|
||||
handleOnChange({cnLineArtAnime: val});
|
||||
pickPreset('custom');
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: vstVal.cnLineArtRealistic,
|
||||
label: "Line Art - Realistic",
|
||||
thumbTip: "Line Art - Realistic)",
|
||||
onChange: (val)=>{
|
||||
handleOnChange({cnLineArtRealistic: val});
|
||||
pickPreset('custom');
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: vstVal.cnOpenPose,
|
||||
label: "OpenPose",
|
||||
thumbTip: "OpenPose",
|
||||
onChange: (val)=>{
|
||||
handleOnChange({cnOpenPose: val});
|
||||
pickPreset('custom');
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: vstVal.cnPipeFace,
|
||||
label: "Media Pipe Face",
|
||||
thumbTip: "Media Pipe Face",
|
||||
onChange: (val)=>{
|
||||
handleOnChange({cnPipeFace: val})
|
||||
pickPreset('custom');
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: vstVal.cnSparseScribble,
|
||||
label: "Sparse Scribble",
|
||||
thumbTip: "Sparse Scribble",
|
||||
onChange: (val)=>{
|
||||
handleOnChange({cnSparseScribble: val})
|
||||
pickPreset('custom');
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: vstVal.cnSoftEdge,
|
||||
label: "Soft Edge",
|
||||
thumbTip: "SoftEdge",
|
||||
onChange: (val)=>{
|
||||
handleOnChange({cnSoftEdge: val});
|
||||
pickPreset('custom');
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 1, max: 64, step: 1,
|
||||
initialValue: vstVal.cnRegularSteps,
|
||||
label: "Regular Steps",
|
||||
thumbTip: "Regular Steps",
|
||||
onChange: (val)=>{
|
||||
handleOnChange({cnRegularSteps: val});
|
||||
pickPreset('custom');
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex p-3 justify-content-end">
|
||||
<Button
|
||||
icon={faRotateLeft}
|
||||
label="Reset to Default"
|
||||
onClick={()=>{
|
||||
handlePreset('default');
|
||||
pickPreset('default');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Accordion.Item>
|
||||
</Accordion> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user