Merge branch 'feature/mvp' into feature/comp/playbar-w-trim
No files matched your search
@@ -16,6 +16,10 @@ export enum FrontendInferenceJobType {
|
||||
VideoStyleTransfer,
|
||||
}
|
||||
|
||||
export enum AllInferenceJobs { All }
|
||||
|
||||
export type JobListOptions = FrontendInferenceJobType | AllInferenceJobs;
|
||||
|
||||
// NB: Many of these fields are optional despite the response payload containing them
|
||||
// This is because we create temporary placeholder objects with just the token.
|
||||
export class InferenceJob {
|
||||
|
||||
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -26,6 +26,12 @@ interface Scene3DProps {
|
||||
// CSS classes to apply to the engine
|
||||
className?: string;
|
||||
|
||||
// Callback when the scene is first loaded
|
||||
// Not always 100% true. Sometimes it prematurely fires.
|
||||
onSceneReadyCallback?: () => void;
|
||||
|
||||
// Callback called when the scene is saved.
|
||||
// It will receive the argument of the new scene media token.
|
||||
onSceneSavedCallback?: (mediaToken: string) => void;
|
||||
}
|
||||
|
||||
@@ -35,6 +41,7 @@ export default function Scene3D({
|
||||
skybox,
|
||||
fullScreen = false,
|
||||
className,
|
||||
onSceneReadyCallback,
|
||||
onSceneSavedCallback,
|
||||
}: Scene3DProps) {
|
||||
|
||||
@@ -54,6 +61,10 @@ export default function Scene3D({
|
||||
|
||||
console.log("studio-ready message (2)");
|
||||
|
||||
if (onSceneReadyCallback !== undefined) {
|
||||
onSceneReadyCallback();
|
||||
}
|
||||
|
||||
// NB: Example of how to call the API in the other direction:
|
||||
// studio.postMessage("save-scene", engineBaseUrl);
|
||||
} else if (
|
||||
@@ -70,7 +81,7 @@ export default function Scene3D({
|
||||
} else if (event.data === "scene-save-failed") {
|
||||
console.error("Failed to save the scene!");
|
||||
}
|
||||
}, [onSceneSavedCallback]);
|
||||
}, [onSceneReadyCallback, onSceneSavedCallback]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-size: 14px;
|
||||
text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
&::after {
|
||||
@@ -140,14 +141,12 @@
|
||||
height: 50%;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
||||
z-index: 2;
|
||||
height: 35%;
|
||||
}
|
||||
|
||||
&.selected::after {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
rgba(desaturate(darken($primary, 20%), 30%), 70%),
|
||||
transparent
|
||||
);
|
||||
background: linear-gradient(to top, rgba(#632323, 90%), transparent);
|
||||
height: 35%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { FrontendInferenceJobType, InferenceJob } from "@storyteller/components/
|
||||
// import { useTransition } from "@react-spring/web";
|
||||
import JobItem from "./JobItem";
|
||||
import { useInferenceJobs, useLocalize, useSession } from "hooks";
|
||||
import { JobListTypes } from "hooks/useInferenceJobs/useInferenceJobs";
|
||||
import "./InferenceJobsList.scss";
|
||||
import { Button, Panel, JobQueueTicker } from "components/common";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -12,7 +11,6 @@ import { faClipboardList } from "@fortawesome/pro-solid-svg-icons";
|
||||
interface JobsListProps {
|
||||
failures: (fail: string) => string;
|
||||
jobType?: FrontendInferenceJobType;
|
||||
value?: JobListTypes;
|
||||
onSelect?: (e: any) => any;
|
||||
panel?: boolean;
|
||||
showJobQueue?: boolean;
|
||||
@@ -34,21 +32,15 @@ const resultPaths = {
|
||||
export default function InferenceJobsList({
|
||||
failures,
|
||||
jobType,
|
||||
value,
|
||||
onSelect,
|
||||
panel = true,
|
||||
showHeader = true,
|
||||
showJobQueue = false,
|
||||
showNoJobs = false
|
||||
}: JobsListProps) {
|
||||
const jobValue = value !== undefined ? value : jobType !== undefined ? (jobType || 0) + 1 : 0;
|
||||
// undefined specified here to allow 0.
|
||||
// jobType + 1 because the difference between FrontendInferenceJobType and JobListTypes is an "all" option
|
||||
|
||||
const { sessionSubscriptions } = useSession();
|
||||
const hasPaidFeatures = sessionSubscriptions?.hasPaidFeatures();
|
||||
const { inferenceJobs = [], jobStatusDescription } = useInferenceJobs(jobValue);
|
||||
|
||||
const { inferenceJobs = [], jobStatusDescription } = useInferenceJobs(jobType);
|
||||
const { t } = useLocalize("InferenceJobs");
|
||||
|
||||
const jobContent = (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react"; // useState
|
||||
import React, { useState } from "react"; // useState
|
||||
import InferenceJobsList from "components/layout/InferenceJobsList";
|
||||
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
// import { TempSelect as Select } from "components/common";
|
||||
// import { enumToKeyArr } from "resources";
|
||||
import { TempSelect as Select } from "components/common";
|
||||
import { enumToKeyArr } from "resources";
|
||||
import ModalHeader from "../ModalHeader";
|
||||
import { useLocalize } from "hooks";
|
||||
import { useLocalize, useSession } from "hooks";
|
||||
|
||||
interface Props {
|
||||
handleClose?: any;
|
||||
@@ -18,18 +18,17 @@ export default function InferenceJobsModal({
|
||||
showModalHeader = true,
|
||||
...rest
|
||||
}: Props) {
|
||||
// const presetFilter = enumToKeyArr(FrontendInferenceJobType)[inJobType];
|
||||
// const [jobType,jobTypeSet] = useState(inJobType > -1 ? presetFilter : "All");
|
||||
// const typeObj = ["All", ...Object.values(FrontendInferenceJobType)];
|
||||
|
||||
// const options = typeObj.filter(val => isNaN(Number(val)))
|
||||
// .map((value) => {
|
||||
// if (typeof value === "string") return { value, label: value }
|
||||
// return { label: "", value: "" };
|
||||
// });
|
||||
|
||||
const { canAccessStudio } = useSession();
|
||||
const presetFilter = enumToKeyArr(FrontendInferenceJobType)[inJobType];
|
||||
const [jobType,jobTypeSet] = useState(inJobType > -1 ? presetFilter : "All");
|
||||
const typeObj = ["All", ...Object.values(FrontendInferenceJobType)];
|
||||
const options = typeObj.filter(val => isNaN(Number(val)))
|
||||
.map((value) => {
|
||||
if (typeof value === "string") return { value, label: value }
|
||||
return { label: "", value: "" };
|
||||
});
|
||||
const { t } = useLocalize("InferenceJobs");
|
||||
|
||||
const selectedType = typeObj.indexOf(jobType) - 1;
|
||||
const failures = (fail = "") => {
|
||||
switch (fail) {
|
||||
default:
|
||||
@@ -40,8 +39,8 @@ export default function InferenceJobsModal({
|
||||
return (
|
||||
<>
|
||||
{ showModalHeader && <ModalHeader {...{ handleClose, title: t("core.jobsTitle") }} /> }
|
||||
{
|
||||
// <Select {...{ onChange: ({ target }: { target: any }) => jobTypeSet(target.value), options, value: jobType }} />
|
||||
{ canAccessStudio() &&
|
||||
<Select {...{ onChange: ({ target }: { target: any }) => jobTypeSet(target.value), options, value: jobType }} />
|
||||
}
|
||||
<InferenceJobsList
|
||||
{...{
|
||||
@@ -49,8 +48,7 @@ export default function InferenceJobsModal({
|
||||
onSelect: () => {
|
||||
if (handleClose) handleClose();
|
||||
},
|
||||
value: 0, // fixed for now
|
||||
// value: typeObj.indexOf(jobType),
|
||||
...selectedType > -1 ? { jobType: selectedType } : {},
|
||||
showHeader: false,
|
||||
showJobQueue: true,
|
||||
showNoJobs: true,
|
||||
|
||||
@@ -6,6 +6,5 @@ interface Props {
|
||||
|
||||
export default function ServerStatusChecker({ value }: Props) {
|
||||
useStatusPoll();
|
||||
console.log("🤬",);
|
||||
return null;
|
||||
};
|
||||
@@ -1,19 +1,70 @@
|
||||
import React, { useState } from "react";
|
||||
import { SessionContext } from "context";
|
||||
import React, { createContext, useState } from "react";
|
||||
// import { SessionContext } from "context";
|
||||
import { Modal } from "components/common";
|
||||
import AccountModal from "components/layout/AccountModal";
|
||||
import { ModalView } from "context/SessionContext";
|
||||
// import { ModalView } from "context/SessionContext";
|
||||
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
|
||||
import { StudioRolloutHostnameAllowed } from "@storyteller/components/src/utils/StudioRolloutHostnameAllowed";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
|
||||
export enum ModalView { // ignore this modal stuff for now -V
|
||||
Closed,
|
||||
Signup,
|
||||
Login,
|
||||
}
|
||||
|
||||
interface ModalProps { // this too
|
||||
close: () => void;
|
||||
open: () => void;
|
||||
view: ModalView;
|
||||
}
|
||||
|
||||
interface SessionContextType {
|
||||
canAccessStudio: () => boolean;
|
||||
canEditTtsModel: (token: string) => boolean;
|
||||
canBanUsers: () => boolean;
|
||||
check: () => boolean;
|
||||
loggedIn: boolean;
|
||||
modal: ModalProps;
|
||||
querySession?: any;
|
||||
querySubscriptions?: any;
|
||||
sessionFetched: boolean;
|
||||
sessionSubscriptions?: any;
|
||||
studioAccessCheck: (x:any) => any,
|
||||
user?: any;
|
||||
userTokenMatch: (token: string) => boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
children?: any;
|
||||
querySession: any;
|
||||
querySubscriptions: any;
|
||||
querySession: () => void;
|
||||
querySubscriptions: () => void;
|
||||
sessionFetched: boolean;
|
||||
sessionSubscriptions: any;
|
||||
sessionWrapper?: any;
|
||||
sessionSubscriptions: SessionSubscriptionsWrapper;
|
||||
sessionWrapper: SessionWrapper;
|
||||
}
|
||||
|
||||
// Functions are initially No-ops/dummies so that they are never undefined and never have to be called conditionally.
|
||||
// These functions will never actually fire because they are immediately redefined in the provider below,
|
||||
// as they must be as they utilize the provider's state.
|
||||
|
||||
export const SessionContext = createContext<SessionContextType>({
|
||||
canAccessStudio: () => false,
|
||||
canEditTtsModel: () => false,
|
||||
canBanUsers: () => false,
|
||||
check: () => false,
|
||||
loggedIn: false,
|
||||
sessionFetched: false,
|
||||
studioAccessCheck: () => null,
|
||||
modal: {
|
||||
close: () => {},
|
||||
open: () => {},
|
||||
view: ModalView.Closed,
|
||||
},
|
||||
userTokenMatch: () => false,
|
||||
});
|
||||
|
||||
export default function SessionProvider({
|
||||
children,
|
||||
querySession,
|
||||
@@ -22,19 +73,12 @@ export default function SessionProvider({
|
||||
sessionSubscriptions,
|
||||
sessionWrapper,
|
||||
}: Props) {
|
||||
const sessionResponse = sessionWrapper?.sessionStateResponse || {
|
||||
logged_in: false,
|
||||
};
|
||||
const sessionResponse = sessionWrapper?.sessionStateResponse || { logged_in: false, user: null };
|
||||
const { logged_in: loggedIn, user } = sessionResponse;
|
||||
const [view, viewSet] = useState(ModalView.Closed);
|
||||
const open = () => viewSet(ModalView.Signup);
|
||||
const close = () => {
|
||||
viewSet(ModalView.Closed);
|
||||
};
|
||||
const viewSwitch = () =>
|
||||
view === ModalView.Signup
|
||||
? viewSet(ModalView.Login)
|
||||
: viewSet(ModalView.Signup);
|
||||
const close = () => { viewSet(ModalView.Closed); };
|
||||
const viewSwitch = () => viewSet(view === ModalView.Signup ? ModalView.Login : ModalView.Signup);
|
||||
const check = () => {
|
||||
if (user) {
|
||||
return true;
|
||||
@@ -43,23 +87,31 @@ export default function SessionProvider({
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const userTokenMatch = (otherUserToken: string) =>
|
||||
!otherUserToken || !user?.user_token ? false : user.user_token === otherUserToken;
|
||||
const canEditTtsModel = (userToken: string) =>
|
||||
user?.canEditOtherUsersTtsModels || userTokenMatch(userToken);
|
||||
user?.can_delete_other_users_tts_models || userTokenMatch(userToken);
|
||||
const canBanUsers = () => user?.can_ban_users || false;
|
||||
const studioAccessCheck = (content: React.ElementType) => !sessionWrapper?.canAccessStudio() ? <StudioNotAvailable /> : content;
|
||||
const canAccessStudio = () => {
|
||||
const hostnameAllowed = StudioRolloutHostnameAllowed();
|
||||
const userAllowed = !!user?.can_access_studio || !!user?.maybe_feature_flags.includes("studio");
|
||||
return hostnameAllowed && userAllowed;
|
||||
}
|
||||
|
||||
const studioAccessCheck = (content: React.ElementType) => canAccessStudio() ? content : <StudioNotAvailable />;
|
||||
|
||||
const modal = { close, open, view };
|
||||
|
||||
return (
|
||||
<SessionContext.Provider
|
||||
{...{
|
||||
value: {
|
||||
canAccessStudio,
|
||||
canEditTtsModel,
|
||||
canBanUsers,
|
||||
check,
|
||||
loggedIn,
|
||||
modal: { close, open, view },
|
||||
modal,
|
||||
querySession,
|
||||
querySubscriptions,
|
||||
sessionFetched,
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default } from "./SessionProvider";
|
||||
export { default, SessionContext } from "./SessionProvider";
|
||||
@@ -5,11 +5,7 @@ import { InferenceJobsContext } from 'context';
|
||||
import { useModal } from "hooks";
|
||||
import { InferenceJobsModal } from "components/modals";
|
||||
|
||||
export enum JobListAll { All }
|
||||
|
||||
export type JobListTypes = FrontendInferenceJobType | JobListAll;
|
||||
|
||||
export default function useInferenceJobs(jobType: JobListTypes = 0, debug = false) {
|
||||
export default function useInferenceJobs(jobType?: FrontendInferenceJobType, debug = false) {
|
||||
const { byCategory, enqueue, inferenceJobs, queueStats } = useContext(InferenceJobsContext);
|
||||
const { open } = useModal();
|
||||
const openJobListModal = () => open({ component: InferenceJobsModal, props: { jobType } });
|
||||
@@ -20,7 +16,8 @@ export default function useInferenceJobs(jobType: JobListTypes = 0, debug = fals
|
||||
if (!noModalPls) { openJobListModal(); }
|
||||
enqueue(jobToken,jobType);
|
||||
},
|
||||
inferenceJobs: jobType === JobListAll.All ? inferenceJobs : (byCategory?.get(jobType - 1) || []),
|
||||
inferenceJobs: jobType === undefined ? inferenceJobs : (byCategory?.get(jobType) || []),
|
||||
// inferenceJobs: jobType === AllInferenceJobs.All ? inferenceJobs : (byCategory?.get(jobType) || []),
|
||||
jobStatusDescription: (jobState: JobState) => Object.keys(JobState).filter(key => isNaN(Number(key)))[jobState],
|
||||
queueStats
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
import { SessionContext } from 'context';
|
||||
import { useContext } from "react";
|
||||
import { SessionContext } from "components/providers/SessionProvider";
|
||||
|
||||
export default function useInferenceJobs() {
|
||||
return useContext(SessionContext);
|
||||
|
||||
@@ -20,9 +20,7 @@ interface Props {
|
||||
export default function EngineCompositor({ value, sessionWrapper }: Props) {
|
||||
const [mediaToken, mediaTokenSet] = useState();
|
||||
const onChange = ({ target }: any) => mediaTokenSet(target.value);
|
||||
const inferenceJobs = useInferenceJobs(
|
||||
FrontendInferenceJobType.EngineComposition
|
||||
);
|
||||
const inferenceJobs = useInferenceJobs(FrontendInferenceJobType.EngineComposition);
|
||||
|
||||
const onClick = () => {
|
||||
EnqueueEngineCompositing("", {
|
||||
|
||||
@@ -43,7 +43,6 @@ export default function FbxToGltfPage({
|
||||
const { inferenceJobs } = useInferenceJobs(
|
||||
FrontendInferenceJobType.ConvertFbxtoGltf
|
||||
);
|
||||
const hasConversionJobs = inferenceJobs && inferenceJobs.length > 0;
|
||||
|
||||
console.log("inferenceJobs", inferenceJobs);
|
||||
|
||||
@@ -151,11 +150,9 @@ export default function FbxToGltfPage({
|
||||
imageUrl="/images/header/fbx-to-gltf.png"
|
||||
/>
|
||||
|
||||
{hasConversionJobs && (
|
||||
<div className="mb-4">
|
||||
<FbxToGltfJobList />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Panel padding={true}>
|
||||
<div className="d-flex flex-column gap-3">
|
||||
|
||||
@@ -43,6 +43,8 @@ function LandingPage(props: Props) {
|
||||
|
||||
const isLoggedIn = props.sessionWrapper.isLoggedIn();
|
||||
|
||||
const canAccessStudio = props.sessionWrapper.canAccessStudio();
|
||||
|
||||
return (
|
||||
<>
|
||||
{domain.website === Website.StorytellerAi && !isLoggedIn && (
|
||||
@@ -79,7 +81,7 @@ function LandingPage(props: Props) {
|
||||
{/* STORYTELLER,AI */}
|
||||
{!isLoggedIn ? (
|
||||
<>
|
||||
<OnboardingSelection />
|
||||
{!canAccessStudio && <OnboardingSelection />}
|
||||
<VstSectionV2 />
|
||||
<TtsDemoSection
|
||||
sessionWrapper={props.sessionWrapper}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Badge, Panel } from "components/common";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import "./VstSectionV2.scss";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
@@ -22,39 +22,40 @@ interface VstSectionV2Props {}
|
||||
export default function VstSectionV2(props: VstSectionV2Props) {
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [isPlaying, setIsPlaying] = useState(true);
|
||||
// const [isPlaying, setIsPlaying] = useState(true);
|
||||
|
||||
// const togglePlayback = () => {
|
||||
// setIsPlaying(!isPlaying);
|
||||
// };
|
||||
|
||||
const handleVideoPlay = () => {
|
||||
if (!isPlaying) {
|
||||
setIsPlaying(true);
|
||||
}
|
||||
};
|
||||
// const handleVideoPlay = () => {
|
||||
// if (!isPlaying) {
|
||||
// setIsPlaying(true);
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleVideoPause = () => {
|
||||
if (isPlaying) {
|
||||
setIsPlaying(false);
|
||||
}
|
||||
};
|
||||
// const handleVideoPause = () => {
|
||||
// if (isPlaying) {
|
||||
// setIsPlaying(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
// const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (videoRef.current) {
|
||||
if (isPlaying) {
|
||||
videoRef.current.play();
|
||||
} else {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
}
|
||||
}, [isPlaying]);
|
||||
// useEffect(() => {
|
||||
// if (videoRef.current) {
|
||||
// if (isPlaying) {
|
||||
// videoRef.current.play();
|
||||
// } else {
|
||||
// videoRef.current.pause();
|
||||
// }
|
||||
// }
|
||||
// }, [isPlaying]);
|
||||
|
||||
const videoSet = [
|
||||
{
|
||||
src: "/videos/landing/vst/1-original.mp4",
|
||||
thumbnail: "/videos/landing/vst/1-original.jpg",
|
||||
styledVideos: [
|
||||
{
|
||||
src: "/videos/landing/vst/1-2d_flat_anime.mp4",
|
||||
@@ -80,6 +81,7 @@ export default function VstSectionV2(props: VstSectionV2Props) {
|
||||
},
|
||||
{
|
||||
src: "/videos/landing/vst/2-original.mp4",
|
||||
thumbnail: "/videos/landing/vst/2-original.jpg",
|
||||
styledVideos: [
|
||||
{
|
||||
src: "/videos/landing/vst/2-2d_flat_anime.mp4",
|
||||
@@ -157,16 +159,17 @@ export default function VstSectionV2(props: VstSectionV2Props) {
|
||||
muted
|
||||
playsInline
|
||||
controls={false}
|
||||
preload="auto"
|
||||
key={`${index}-${activeIndex}`}
|
||||
onPlay={handleVideoPlay}
|
||||
onPause={handleVideoPause}
|
||||
ref={videoRef => {
|
||||
if (videoRef && isPlaying) {
|
||||
videoRef.play();
|
||||
} else if (videoRef) {
|
||||
videoRef.pause();
|
||||
}
|
||||
}}
|
||||
// onPlay={handleVideoPlay}
|
||||
// onPause={handleVideoPause}
|
||||
// ref={videoRef => {
|
||||
// if (videoRef && isPlaying) {
|
||||
// videoRef.play();
|
||||
// } else if (videoRef) {
|
||||
// videoRef.pause();
|
||||
// }
|
||||
// }}
|
||||
>
|
||||
<source src={video.src} type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
@@ -186,10 +189,10 @@ export default function VstSectionV2(props: VstSectionV2Props) {
|
||||
{videoSet.map((video, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<div className="ratio ratio-16x9 vst-carousel-panel">
|
||||
<video loop muted playsInline controls={false}>
|
||||
<source src={video.src} type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<img
|
||||
src={video.thumbnail}
|
||||
alt="Demo Original Video Thumbnail"
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
@@ -218,16 +221,17 @@ export default function VstSectionV2(props: VstSectionV2Props) {
|
||||
muted
|
||||
playsInline
|
||||
controls={false}
|
||||
preload="auto"
|
||||
key={styledVideo.src}
|
||||
onPlay={handleVideoPlay}
|
||||
onPause={handleVideoPause}
|
||||
ref={videoRef => {
|
||||
if (videoRef && isPlaying) {
|
||||
videoRef.play();
|
||||
} else if (videoRef) {
|
||||
videoRef.pause();
|
||||
}
|
||||
}}
|
||||
// onPlay={handleVideoPlay}
|
||||
// onPause={handleVideoPause}
|
||||
// ref={videoRef => {
|
||||
// if (videoRef && isPlaying) {
|
||||
// videoRef.play();
|
||||
// } else if (videoRef) {
|
||||
// videoRef.pause();
|
||||
// }
|
||||
// }}
|
||||
>
|
||||
<source src={styledVideo.src} type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
|
||||
@@ -10,31 +10,31 @@ export default function OnboardingSelection(props: OnboardingSelectionProps) {
|
||||
|
||||
const cards = [
|
||||
{
|
||||
text: "Scene 1",
|
||||
backgroundImage: "",
|
||||
text: "Room Scene",
|
||||
backgroundImage: "/images/landing/onboarding/room-scene-thumbnail.webp",
|
||||
onClick: () => {
|
||||
history.push("/studio-intro/");
|
||||
history.push("/studio-intro/m_ejhs95fc5aybp36h4a79k7523ds6an");
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Scene 2",
|
||||
backgroundImage: "",
|
||||
onClick: () => {
|
||||
history.push("/studio-intro/");
|
||||
history.push("/studio-intro");
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Scene 3",
|
||||
backgroundImage: "",
|
||||
onClick: () => {
|
||||
history.push("/studio-intro/");
|
||||
history.push("/studio-intro");
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Scene 4",
|
||||
backgroundImage: "",
|
||||
onClick: () => {
|
||||
history.push("/studio-intro/");
|
||||
history.push("/studio-intro");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -19,7 +19,6 @@ interface Props {
|
||||
}
|
||||
|
||||
function StudioIntroPage(props: Props) {
|
||||
const [camera,cameraSet] = useState("rotation");
|
||||
const { mediaToken } = useParams<{ mediaToken: string }>();
|
||||
|
||||
const history = useHistory();
|
||||
@@ -32,11 +31,28 @@ function StudioIntroPage(props: Props) {
|
||||
// for subsequent steps of this flow.
|
||||
const [savedMediaToken, setSavedMediaToken] = useState(mediaToken);
|
||||
|
||||
// We don't show the "next step" buttons until the engine loads.
|
||||
// Unfortunately the engine sometimes fires this twice, with one instance
|
||||
// being called before the scene loads. Until this is fixed, we'll count the
|
||||
// number of event fires and assume we must have two calls.
|
||||
const [sceneIsLoadedCount, sceneIsLoadedCountSet] = useState(0);
|
||||
|
||||
// If the scene is saved, we know the user must have interacted.
|
||||
// This can serve as a second optional gate for enabling the next steps.
|
||||
const [sceneIsSaved, sceneIsSavedSet] = useState(false);
|
||||
|
||||
const [camera, cameraSet] = useState("rotation");
|
||||
|
||||
usePrefixedDocumentTitle("Storyteller Studio");
|
||||
|
||||
const onSaveCallback = useCallback((sceneMediaToken: string) => {
|
||||
setSavedMediaToken(sceneMediaToken);
|
||||
}, [setSavedMediaToken]);
|
||||
sceneIsSavedSet(true); // Just in case we missed the "scene loaded" event.
|
||||
}, [setSavedMediaToken, sceneIsSavedSet]);
|
||||
|
||||
const onSceneReadyCallback = useCallback(() => {
|
||||
sceneIsLoadedCountSet(sceneIsLoadedCount + 1);
|
||||
}, [sceneIsLoadedCount, sceneIsLoadedCountSet])
|
||||
|
||||
if (!props.sessionWrapper.canAccessStudio()) {
|
||||
return <StudioNotAvailable />;
|
||||
@@ -84,6 +100,30 @@ function StudioIntroPage(props: Props) {
|
||||
value: "static"
|
||||
}];
|
||||
|
||||
const sceneIsLoaded = sceneIsLoadedCount > 1 || sceneIsSaved;
|
||||
|
||||
let progressButtons = <></>;
|
||||
|
||||
if (sceneIsLoaded) {
|
||||
progressButtons = (
|
||||
<>
|
||||
<div {...{ className: "studio-intro-exporter" }}>
|
||||
<div {...{ className: "exporter-title" }}>
|
||||
<span>
|
||||
Add camera motion to render
|
||||
</span>
|
||||
</div>
|
||||
<div {...{ className: "exporter-controls" }}>
|
||||
<SegmentButtons {...{ onChange: ({ target }: { target: any }) => {
|
||||
cameraSet(target.value)
|
||||
}, options: cameraOpts, value: camera }}/>
|
||||
<Button label="Create Movie from 3D Scene" onClick={onClick} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="studio-intro-page">
|
||||
<Scene3D
|
||||
@@ -91,21 +131,11 @@ function StudioIntroPage(props: Props) {
|
||||
mode={EngineMode.Studio}
|
||||
asset={assetDescriptor}
|
||||
className="flex-grow-1"
|
||||
onSceneReadyCallback={onSceneReadyCallback}
|
||||
onSceneSavedCallback={onSaveCallback}
|
||||
/>
|
||||
<div {...{ className: "studio-intro-exporter" }}>
|
||||
<div {...{ className: "exporter-title" }}>
|
||||
<span>
|
||||
Add camera motion to render
|
||||
</span>
|
||||
</div>
|
||||
<div {...{ className: "exporter-controls" }}>
|
||||
<SegmentButtons {...{ onChange: ({ target }: { target: any }) => {
|
||||
cameraSet(target.value)
|
||||
}, options: cameraOpts, value: camera }}/>
|
||||
<Button label="Create Movie from 3D Scene" onClick={onClick} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{progressButtons}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,23 +75,23 @@ export default function PageVSTApp() {
|
||||
|
||||
const styleOptions = [
|
||||
{
|
||||
label: "Anime",
|
||||
imageUrl: "/images/dummy-image.jpg",
|
||||
label: "2D Anime",
|
||||
imageUrl: "/images/landing/onboarding/styles/style-2d-anime.webp",
|
||||
token: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
},
|
||||
{
|
||||
label: "Pixel",
|
||||
imageUrl: "/images/dummy-image-2.jpg",
|
||||
label: "3D Cartoon",
|
||||
imageUrl: "/images/landing/onboarding/styles/style-3d-cartoon.webp",
|
||||
token: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
},
|
||||
{
|
||||
label: "Painting",
|
||||
imageUrl: "/images/dummy-image-3.jpg",
|
||||
label: "Ink B&W",
|
||||
imageUrl: "/images/landing/onboarding/styles/style-ink-bw.webp",
|
||||
token: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
},
|
||||
{
|
||||
label: "Ink",
|
||||
imageUrl: "/images/dummy-image-4.jpg",
|
||||
label: "Origami",
|
||||
imageUrl: "/images/landing/onboarding/styles/style-origami.webp",
|
||||
token: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
},
|
||||
];
|
||||
|
||||