mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-14 22:02:36 +00:00
Merge branch 'test' into feature/keyframes-continued
This commit is contained in:
commit
9bb0e8891c
32 files changed
+917
-260
No files matched your search
@@ -1,20 +1,34 @@
|
||||
import React from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Label } from "./Typography";
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
className?: string;
|
||||
icon?: IconDefinition
|
||||
}
|
||||
|
||||
export function Input({ label, className, ...rest }: InputProps) {
|
||||
export function Input({label, icon, className, ...rest }: InputProps) {
|
||||
return (
|
||||
<div className={"flex flex-col " + className}>
|
||||
<div className={twMerge("flex flex-col gap-2", className)}>
|
||||
{label && <Label>{label}</Label>}
|
||||
|
||||
<input
|
||||
className="h-10 rounded-md bg-brand-secondary p-3 text-white outline-none outline-offset-0 transition-all duration-150 ease-in-out focus:outline-brand-primary"
|
||||
{...rest}
|
||||
/>
|
||||
<div className="relative w-full">
|
||||
{icon && (
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className="absolute pt-2.5 pl-3 h-5"
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
className={twMerge(
|
||||
"w-full h-10 rounded-md bg-brand-secondary p-3 text-white transition-all duration-150 ease-in-out outline-none outline-offset-0 focus:outline-brand-primary",
|
||||
icon? "pl-12" : "",
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -37,6 +37,7 @@ export const InputVector = ({ x, y, z, onChange }: InputVectorProps) => {
|
||||
>
|
||||
<input
|
||||
className={twMerge(inputCommonClasses, "focus:outline-axis-x")}
|
||||
type="number"
|
||||
onChange={handleOnChange}
|
||||
ref={xRef}
|
||||
value={x}
|
||||
@@ -50,6 +51,7 @@ export const InputVector = ({ x, y, z, onChange }: InputVectorProps) => {
|
||||
>
|
||||
<input
|
||||
className={twMerge(inputCommonClasses, "focus:outline-axis-y")}
|
||||
type="number"
|
||||
onChange={handleOnChange}
|
||||
ref={yRef}
|
||||
value={y}
|
||||
@@ -63,6 +65,7 @@ export const InputVector = ({ x, y, z, onChange }: InputVectorProps) => {
|
||||
>
|
||||
<input
|
||||
className={twMerge(inputCommonClasses, "focus:outline-axis-z")}
|
||||
type="number"
|
||||
onChange={handleOnChange}
|
||||
ref={zRef}
|
||||
value={z}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NavLink, NavLinkProps } from "@remix-run/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface TypoProps {
|
||||
@@ -7,53 +8,42 @@ interface TypoProps {
|
||||
const baseTypo = "text-white";
|
||||
|
||||
export const H1 = ({ className, children }: TypoProps) => (
|
||||
<h1 className={twMerge(baseTypo, "text-2xl font-medium", className)}>
|
||||
{children}
|
||||
</h1>
|
||||
<h1 className={twMerge(baseTypo, "text-2xl font-medium", className)}>{children}</h1>
|
||||
);
|
||||
|
||||
export const H2 = ({ className, children }: TypoProps) => (
|
||||
<h2 className={twMerge(baseTypo, "text-xl font-medium", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</h2>
|
||||
<h2 className={twMerge(baseTypo, "text-xl font-medium", className)}>{children}</h2>
|
||||
);
|
||||
|
||||
export const H3 = ({ className, children }: TypoProps) => (
|
||||
<h3 className={twMerge(baseTypo, "text-lg font-medium", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</h3>
|
||||
<h3 className={twMerge(baseTypo, "text-lg font-medium", className)}>{children}</h3>
|
||||
);
|
||||
|
||||
export const H4 = ({ className, children }: TypoProps) => (
|
||||
<h4 className={twMerge(baseTypo, "text-base font-medium", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</h4>
|
||||
<h4 className={twMerge(baseTypo, "text-base font-medium", className)}>{children}</h4>
|
||||
);
|
||||
|
||||
export const Label = ({ className, children }: TypoProps) => (
|
||||
<label className={twMerge(baseTypo, "mb-2 text-base font-medium", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</label>
|
||||
<label className={twMerge(baseTypo, "mb-2 text-base font-medium", className)}>{children}</label>
|
||||
);
|
||||
|
||||
export const H5 = ({ className, children }: TypoProps) => (
|
||||
<h5 className={twMerge(baseTypo, "text-sm font-medium", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</h5>
|
||||
<h5 className={twMerge(baseTypo, "text-sm font-medium", className)}>{children}</h5>
|
||||
);
|
||||
|
||||
export const H6 = ({ className, children }: TypoProps) => (
|
||||
<h6 className={twMerge(baseTypo, "text-sm font-light", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</h6>
|
||||
<h6 className={twMerge(baseTypo, "text-sm font-light", className)}>{children}</h6>
|
||||
);
|
||||
|
||||
export const Link = ({className, ...rest}:NavLinkProps)=>
|
||||
<NavLink
|
||||
className={twMerge(
|
||||
"text-brand-primary hover:text-brand-primary-400",
|
||||
className as string,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
export const P = ({ className, children }: TypoProps) => (
|
||||
<p className={twMerge(baseTypo, className)}> {children} </p>
|
||||
);
|
||||
@@ -4,7 +4,7 @@ import { ButtonLink } from "./ButtonLink";
|
||||
import { ButtonIconSelect } from "./ButtonIconSelect";
|
||||
import { Input } from "./Input";
|
||||
import { InputVector } from "./InputVector";
|
||||
import { H1, H2, H3, H4, H5, H6, P, Label} from "./Typography";
|
||||
import { H1, H2, H3, H4, H5, H6, P, Label, Link} from "./Typography";
|
||||
import { ItemPicker } from "./ItemPicker";
|
||||
import { Textarea } from "./Textarea";
|
||||
import { LoadingBar } from "./LoadingBar";
|
||||
@@ -15,7 +15,7 @@ export {
|
||||
ButtonIcon,
|
||||
ButtonLink,
|
||||
ButtonIconSelect,
|
||||
H1,H2,H3,H4,H5,H6,P,Label,
|
||||
H1,H2,H3,H4,H5,H6,P,Label,Link,
|
||||
ItemPicker,
|
||||
Textarea,
|
||||
Input,
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
createContext,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useState,
|
||||
ReactNode,
|
||||
Dispatch,
|
||||
SetStateAction
|
||||
} from "react";
|
||||
import { useCookies } from 'react-cookie';
|
||||
|
||||
import {
|
||||
SessionResponse,
|
||||
AuthState,
|
||||
} from "./types";
|
||||
|
||||
import { CreateSession, DestroySession, GetSession } from "./utilities";
|
||||
|
||||
export const AuthenticationContext = createContext<{
|
||||
authState?: AuthState,
|
||||
setAuthState?: Dispatch<SetStateAction<AuthState | undefined>>,
|
||||
persistLoginOrOut?: ()=>void,
|
||||
loginAndGetUserInfo?: (
|
||||
usernameOrEmail: string,
|
||||
password:string,
|
||||
failureCallback?:()=>void
|
||||
)=>void;
|
||||
logout?: ()=>void;
|
||||
}>({});
|
||||
|
||||
export const AuthenticationProvider = ({children}:{children:ReactNode})=>{
|
||||
const [authCookies, setAuthCookie, removeAuthCookie] = useCookies(['userInfo']);
|
||||
const [authState, setAuthState] = useState<AuthState | undefined>(undefined);
|
||||
|
||||
const persistLoginOrOut = useCallback(()=>{
|
||||
GetSession().then((
|
||||
res: SessionResponse
|
||||
)=>{
|
||||
if(res.success && res.user){
|
||||
setAuthCookie('userInfo', res.user);
|
||||
setAuthState({
|
||||
isLoggedIn: true,
|
||||
userInfo: res.user
|
||||
});
|
||||
}else{
|
||||
removeAuthCookie('userInfo');
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loginAndGetUserInfo = (usernameOrEmail: string, password:string, failureCallback?:()=>void)=>{
|
||||
CreateSession({usernameOrEmail, password})
|
||||
.then((respond)=>{
|
||||
GetSession().then((
|
||||
res: SessionResponse
|
||||
)=>{
|
||||
if(res.success && res.user && setAuthState){
|
||||
setAuthCookie('userInfo', res.user);
|
||||
setAuthState({
|
||||
isLoggedIn: true,
|
||||
userInfo: res.user
|
||||
});
|
||||
}else{
|
||||
if(failureCallback) failureCallback();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const logout = ()=>{
|
||||
DestroySession().then((res)=>{
|
||||
if (res && setAuthState) {
|
||||
setAuthState({
|
||||
isLoggedIn: false,
|
||||
userInfo: undefined
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
useEffect(()=>{
|
||||
if (authCookies !== undefined && authState===undefined){
|
||||
persistLoginOrOut();
|
||||
}
|
||||
},[]);
|
||||
|
||||
return(
|
||||
<AuthenticationContext.Provider value={{
|
||||
authState,
|
||||
setAuthState,
|
||||
persistLoginOrOut,
|
||||
loginAndGetUserInfo,
|
||||
logout,
|
||||
}}>
|
||||
{children}
|
||||
</AuthenticationContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { AuthenticationContext, AuthenticationProvider } from "./Authentication";
|
||||
import { UserInfo, AUTH_STATUS } from "./types";
|
||||
|
||||
|
||||
export type { UserInfo };
|
||||
export { AUTH_STATUS, AuthenticationContext, AuthenticationProvider };
|
||||
@@ -0,0 +1,59 @@
|
||||
export enum AUTH_STATUS {
|
||||
LOGGED_IN = "logged_in",
|
||||
LOGGING = "logging",
|
||||
LOGGED_OUT = "logged_out",
|
||||
}
|
||||
|
||||
export type AuthState = {
|
||||
isLoggedIn: boolean;
|
||||
userInfo?: UserInfo;
|
||||
}
|
||||
export interface UserInfo {
|
||||
user_token: string,
|
||||
username: string,
|
||||
display_name: string,
|
||||
email_gravatar_hash: string,
|
||||
|
||||
// Rollout feature flags
|
||||
can_access_studio: boolean,
|
||||
maybe_feature_flags: string[],
|
||||
|
||||
// Usage
|
||||
can_use_tts: boolean,
|
||||
can_use_w2l: boolean,
|
||||
can_delete_own_tts_results: boolean,
|
||||
can_delete_own_w2l_results: boolean,
|
||||
can_delete_own_account: boolean,
|
||||
|
||||
// Contribution
|
||||
can_upload_tts_models: boolean,
|
||||
can_upload_w2l_templates: boolean,
|
||||
can_delete_own_tts_models: boolean,
|
||||
can_delete_own_w2l_templates: boolean,
|
||||
|
||||
// Moderation
|
||||
can_approve_w2l_templates: boolean,
|
||||
can_edit_other_users_profiles: boolean,
|
||||
can_edit_other_users_tts_models: boolean,
|
||||
can_edit_other_users_w2l_templates: boolean,
|
||||
can_delete_other_users_tts_models: boolean,
|
||||
can_delete_other_users_tts_results: boolean,
|
||||
can_delete_other_users_w2l_templates: boolean,
|
||||
can_delete_other_users_w2l_results: boolean,
|
||||
can_ban_users: boolean,
|
||||
can_delete_users: boolean,
|
||||
}
|
||||
|
||||
// Responses from the `/session` endpoint.
|
||||
export interface SessionResponse {
|
||||
// API call was successful
|
||||
success: boolean,
|
||||
// Whether the user is logged in
|
||||
logged_in?: boolean,
|
||||
// Extended user details (only if logged in)
|
||||
user?: UserInfo | undefined | null,
|
||||
|
||||
// if success is false, there will be an error_reason
|
||||
error_reason?: string,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
import {
|
||||
SessionResponse,
|
||||
} from "./types";
|
||||
|
||||
const apiHost = "https://api.fakeyou.com";
|
||||
// const createAccount = `${apiHost}}/create_account`;
|
||||
const login = `${apiHost}/v1/login`;
|
||||
const getSession = `${apiHost}/v1/session`;
|
||||
const logout = `${apiHost}/v1/logout`;
|
||||
|
||||
export const CreateSession = async ({
|
||||
usernameOrEmail, password
|
||||
}:{
|
||||
usernameOrEmail: string;
|
||||
password: string;
|
||||
})=> {
|
||||
//TODO: This SHOULD NOT be sent in plaintext
|
||||
const request = {
|
||||
username_or_email: usernameOrEmail,
|
||||
password: password,
|
||||
}
|
||||
|
||||
return await fetch(login,{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(request)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => { return res; })
|
||||
.catch(e => {
|
||||
return {
|
||||
success: false,
|
||||
error_reason: "frontend_error",
|
||||
};
|
||||
});
|
||||
}
|
||||
export const GetSession = async () => {
|
||||
return await fetch(getSession, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
const response : SessionResponse = res;
|
||||
return response;
|
||||
})
|
||||
.catch(e => {
|
||||
return {
|
||||
success: false,
|
||||
error_reason: "frontend_error",
|
||||
};
|
||||
});
|
||||
}
|
||||
export async function DestroySession() : Promise<boolean> {
|
||||
return await fetch(logout, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
const response : SessionResponse = res;
|
||||
return response.success;
|
||||
})
|
||||
.catch(e => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
import { createContext } from "react";
|
||||
|
||||
export const TopBarInnerContext = createContext<{
|
||||
TopBarInner: JSX.Element | null;
|
||||
setTopBarInner: (el: JSX.Element | null)=>void;
|
||||
TopBarInner: {
|
||||
location: string,
|
||||
node: React.ReactNode,
|
||||
} | null;
|
||||
setTopBarInner: (el: {
|
||||
location: string,
|
||||
node: React.ReactNode,
|
||||
} | null)=>void;
|
||||
} | null>(null);
|
||||
@@ -1,5 +0,0 @@
|
||||
import { joinClassNames } from "./joinCassNames";
|
||||
|
||||
export {
|
||||
joinClassNames
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export function joinClassNames(...classes:string[]) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useContext, } from "react";
|
||||
|
||||
import { AuthenticationContext } from "~/contexts/Authentication";
|
||||
import { Button, ButtonLink } from "~/components";
|
||||
|
||||
export const AuthButtons = ()=>{
|
||||
const {authState, logout} = useContext(AuthenticationContext);
|
||||
const handleLogout = ()=>{
|
||||
if(logout)logout();
|
||||
}
|
||||
if ( authState && authState.isLoggedIn ) {
|
||||
return(
|
||||
<Button onClick={handleLogout}>Log Out</Button>
|
||||
);
|
||||
}else{
|
||||
return(
|
||||
<>
|
||||
<ButtonLink variant="secondary" to='/login'>Login</ButtonLink>
|
||||
<ButtonLink to='/signup'>Sign Up</ButtonLink>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { useContext } from "react";
|
||||
import { Button } from "~/components/Button";
|
||||
import {
|
||||
faChevronLeft,
|
||||
faRightToBracket,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { useContext, } from "react";
|
||||
import { useLocation } from "@remix-run/react";
|
||||
|
||||
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { TopBarInnerContext } from "~/contexts/TopBarInner";
|
||||
import { ButtonLink } from "~/components";
|
||||
|
||||
import { AuthButtons } from "./AuthButtons";
|
||||
|
||||
export const TopBar = () => {
|
||||
const currentLocation = useLocation().pathname;
|
||||
|
||||
return (
|
||||
<header
|
||||
className="fixed left-0 top-0 w-full
|
||||
@@ -27,17 +30,19 @@ export const TopBar = () => {
|
||||
alt="Logo FakeYou StoryTeller.ai"
|
||||
/>
|
||||
</a>
|
||||
<ButtonLink to={"/"} variant="secondary" icon={faChevronLeft}>
|
||||
Back to Dashboard
|
||||
</ButtonLink>
|
||||
{currentLocation !== "/" &&
|
||||
<ButtonLink to={"/"} variant="secondary" icon={faChevronLeft}>
|
||||
Back to Dashboard
|
||||
</ButtonLink>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<TopBarInner />
|
||||
<TopBarInner currentLocation={currentLocation}/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button icon={faRightToBracket}>Login</Button>
|
||||
<div className="flex justify-end gap-2">
|
||||
<AuthButtons />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -45,7 +50,11 @@ export const TopBar = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const TopBarInner = () => {
|
||||
const TopBarInner = ({currentLocation}:{currentLocation:string}) => {
|
||||
const { TopBarInner } = useContext(TopBarInnerContext) || {};
|
||||
return TopBarInner;
|
||||
if(!TopBarInner || TopBarInner.location !== currentLocation){
|
||||
return null;
|
||||
}
|
||||
return TopBarInner.node;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useLocation } from "@remix-run/react";
|
||||
|
||||
import {
|
||||
useContext,
|
||||
useEffect,
|
||||
@@ -8,7 +10,7 @@ import { TopBarInnerContext } from "~/contexts/TopBarInner";
|
||||
|
||||
|
||||
export const TopBarHelmet = (props:{
|
||||
children: JSX.Element | null;
|
||||
children: React.ReactNode | null;
|
||||
})=>{
|
||||
const [showChild, setShowChild] = useState(false);
|
||||
// Wait until after client-side hydration to show
|
||||
@@ -24,13 +26,17 @@ export const TopBarHelmet = (props:{
|
||||
}
|
||||
|
||||
const LazyLoadTopBarInnerContent = (props:{
|
||||
children: JSX.Element | null;
|
||||
children: React.ReactNode | null;
|
||||
})=>{
|
||||
const { setTopBarInner } = useContext(TopBarInnerContext) || {};
|
||||
const location = useLocation().pathname;
|
||||
|
||||
useLayoutEffect(()=>{
|
||||
if(setTopBarInner){
|
||||
setTopBarInner(props.children);
|
||||
setTopBarInner({
|
||||
location: location,
|
||||
node: props.children
|
||||
});
|
||||
}
|
||||
},[setTopBarInner]);
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ export const ControlsTopButtons = () => {
|
||||
type: APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
group: ClipGroup.OBJECT,
|
||||
object_name: "",
|
||||
object_name: "TEST BUTTON",
|
||||
object_uuid: "",
|
||||
version: "1",
|
||||
objectVectors: {
|
||||
|
||||
@@ -103,7 +103,7 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: uuid.v4(),
|
||||
media_id: "m_w5t517krrf63f3tj2288vsffmj87zw",
|
||||
type: ClipType.ANIMATION,
|
||||
length: 25,
|
||||
name: "Walk",
|
||||
@@ -126,7 +126,7 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: uuid.v4(),
|
||||
media_id: "m_403phjvjkbbaxxbz8y7r6qjay07mfd",
|
||||
type: ClipType.AUDIO,
|
||||
length: 25,
|
||||
name: "Talk",
|
||||
|
||||
@@ -35,6 +35,7 @@ export class LipSyncClip {
|
||||
audio_data: AudioData | undefined;
|
||||
lipsync: LipSync;
|
||||
blendshape_helper: BlendShapeHelper;
|
||||
faces: THREE.Mesh[]
|
||||
|
||||
constructor(version: number, media_id: string, volume: number) {
|
||||
this.version = version;
|
||||
@@ -47,6 +48,7 @@ export class LipSyncClip {
|
||||
this.blendshape_helper = new BlendShapeHelper(0, 0, 0);
|
||||
// we might need 3 of these one for each character ...
|
||||
this.lipsync = new LipSync();
|
||||
this.faces = [];
|
||||
}
|
||||
|
||||
// lip sync will be generated through TTS
|
||||
@@ -81,8 +83,8 @@ export class LipSyncClip {
|
||||
const blendShapeIndexA = c.morphTargetDictionary["aa"];
|
||||
if (blendShapeIndexE != null) {
|
||||
this.blendshape_helper = new BlendShapeHelper(blendShapeIndexA, blendShapeIndexE, blendShapeIndexO);
|
||||
this.faces.push(c);
|
||||
resolve(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,42 +93,48 @@ export class LipSyncClip {
|
||||
}
|
||||
|
||||
|
||||
async play(object: THREE.Object3D) {
|
||||
if (this.audio_data?.audioBuffer == null) { await this.download_audio(); }
|
||||
if (this.lipsync.face == null) {
|
||||
this.lipsync = new LipSync(await this._detect_face(object));
|
||||
this.lipsync.startLipSyncFromAudioBuffer(this.audio_data?.audioBuffer);
|
||||
async play(object: THREE.Object3D) {
|
||||
if (this.audio_data?.audioBuffer == null) { await this.download_audio(); }
|
||||
if (this.lipsync.face == null) {
|
||||
this.lipsync = new LipSync(await this._detect_face(object));
|
||||
this.lipsync.startLipSyncFromAudioBuffer(this.audio_data?.audioBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.lipsync == null) { return; }
|
||||
this.lipsync.destroy();
|
||||
}
|
||||
|
||||
setBlends(ah: number, ee: number, oh: number) {
|
||||
this.faces.forEach((element: THREE.Mesh) => {
|
||||
if (element.morphTargetInfluences) {
|
||||
element.morphTargetInfluences[this.blendshape_helper.ee] = ee;
|
||||
element.morphTargetInfluences[this.blendshape_helper.ah] = ah;
|
||||
element.morphTargetInfluences[this.blendshape_helper.oh] = oh;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
step() {
|
||||
if (this.lipsync == null) { return; }
|
||||
const positions = this.lipsync.update();
|
||||
this.setBlends(positions["ee"], positions["ah"], positions["oh"]);
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.lipsync.face != undefined) {
|
||||
this.setBlends(0,0,0);
|
||||
}
|
||||
this.lipsync = new LipSync();
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
return JSON.stringify({
|
||||
version: this.version,
|
||||
media_id: this.media_id,
|
||||
type: this.type,
|
||||
volume: this.volume,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.lipsync == null) { return; }
|
||||
this.lipsync.destroy();
|
||||
}
|
||||
|
||||
step() {
|
||||
if (this.lipsync == null) { return; }
|
||||
const positions = this.lipsync.update();
|
||||
this.lipsync.face.morphTargetInfluences[this.blendshape_helper.ee] = positions["ee"];
|
||||
this.lipsync.face.morphTargetInfluences[this.blendshape_helper.ah] = positions["ah"];
|
||||
this.lipsync.face.morphTargetInfluences[this.blendshape_helper.oh] = positions["oh"];
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.lipsync.face != undefined) {
|
||||
this.lipsync.face.morphTargetInfluences[this.blendshape_helper.ee] = 0;
|
||||
this.lipsync.face.morphTargetInfluences[this.blendshape_helper.ah] = 0
|
||||
this.lipsync.face.morphTargetInfluences[this.blendshape_helper.oh] = 0;
|
||||
}
|
||||
this.lipsync = new LipSync();
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
return JSON.stringify({
|
||||
version: this.version,
|
||||
media_id: this.media_id,
|
||||
type: this.type,
|
||||
volume: this.volume,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,34 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export class TransformFrame {
|
||||
position: THREE.Vector3
|
||||
rotation: THREE.Vector3
|
||||
scale: THREE.Vector3
|
||||
offset: number
|
||||
keyframe_uuid: string
|
||||
|
||||
constructor(position: THREE.Vector3, rotation: THREE.Vector3, scale: THREE.Vector3, offset: number, keyframe_uuid: string) {
|
||||
this.position = position;
|
||||
this.rotation = rotation;
|
||||
this.scale = scale;
|
||||
this.offset = offset;
|
||||
this.keyframe_uuid = keyframe_uuid;
|
||||
}
|
||||
}
|
||||
|
||||
export class TransformClip {
|
||||
version: number;
|
||||
media_id: string;
|
||||
object_uuid: string;
|
||||
type: "transform";
|
||||
positions: THREE.Vector3[];
|
||||
rotations: THREE.Vector3[];
|
||||
scales: THREE.Vector3[];
|
||||
keyframes: TransformFrame[];
|
||||
length: number;
|
||||
|
||||
step_frame: number;
|
||||
looping: boolean;
|
||||
|
||||
offset: number;
|
||||
|
||||
constructor(version: number, object_uuid: string, length: number, media_id: string = "") {
|
||||
this.version = version;
|
||||
this.media_id = media_id;
|
||||
@@ -21,40 +37,93 @@ export class TransformClip {
|
||||
|
||||
this.length = length;
|
||||
|
||||
this.positions = [];
|
||||
this.rotations = [];
|
||||
this.scales = [];
|
||||
this.keyframes = [];
|
||||
|
||||
this.step_frame = 0;
|
||||
this.offset = 0;
|
||||
|
||||
this.looping = false;
|
||||
}
|
||||
|
||||
private findNextNumber(location: number) {
|
||||
// Sort the transform frames based on their offset in ascending order
|
||||
this.keyframes.sort((a, b) => a.offset - b.offset);
|
||||
|
||||
let currentKeyframe: TransformFrame | undefined;
|
||||
let nextKeyframe: TransformFrame | undefined;
|
||||
|
||||
this.length = this.keyframes[this.keyframes.length-1].offset+this.offset;
|
||||
|
||||
for (let frame of this.keyframes) {
|
||||
if (frame.offset <= location) {
|
||||
currentKeyframe = frame;
|
||||
} else if (frame.offset > location) {
|
||||
nextKeyframe = frame;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { currentKeyframe, nextKeyframe };
|
||||
}
|
||||
|
||||
public setOffset(keyframe_uuid: string, keyframe_offset: number) {
|
||||
this.keyframes.forEach(item => {
|
||||
if (item.keyframe_uuid === keyframe_uuid) {
|
||||
item.offset = keyframe_offset;
|
||||
console.log("Set keyframe!!");
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public removeKeyframe(keyframe_uuid: string) {
|
||||
this.keyframes = this.keyframes.filter(item => item.keyframe_uuid !== keyframe_uuid);
|
||||
}
|
||||
|
||||
step(object: THREE.Object3D, offset: number, frame: number) {
|
||||
this.step_frame = frame-offset;
|
||||
if (this.step_frame < 0) { this.step_frame = 0; }
|
||||
if (this.step_frame >= this.length) { return; } // Reached max frames.
|
||||
if(this.positions.length < 2) { return; } // If there are enough points in the scene.
|
||||
this.offset = offset;
|
||||
this.step_frame = frame - offset;
|
||||
//if (this.step_frame < 0) { this.step_frame = 0; }
|
||||
//if (this.step_frame >= this.length) { return; } // Reached max frames.
|
||||
if (this.keyframes.length < 2) { return; } // If there are enough points in the scene.
|
||||
|
||||
let time_frame = (this.step_frame/this.length);
|
||||
// Find the current and next keyframes based on time_frame
|
||||
let { currentKeyframe, nextKeyframe } = this.findNextNumber(this.step_frame);
|
||||
if (nextKeyframe != undefined && currentKeyframe != undefined) {
|
||||
let small_step_frame = this.step_frame - currentKeyframe.offset;
|
||||
let location = (small_step_frame / (nextKeyframe.offset - currentKeyframe.offset));
|
||||
if (location < 0) {
|
||||
location = 0;
|
||||
}
|
||||
let pos_s = currentKeyframe?.position;
|
||||
let pos_e = nextKeyframe?.position;
|
||||
let points_pos = [new THREE.Vector3(pos_s.x, pos_s.y, pos_s.z), new THREE.Vector3(pos_e.x, pos_e.y, pos_e.z)]
|
||||
let curve_pos = new THREE.CatmullRomCurve3(points_pos);
|
||||
let point_pos = curve_pos.getPoint(location);
|
||||
object.position.copy(point_pos);
|
||||
|
||||
let curve = new THREE.CatmullRomCurve3(this.positions);
|
||||
let point = curve.getPoint(time_frame);
|
||||
object.position.copy(point);
|
||||
let rot_s = currentKeyframe?.rotation;
|
||||
let rot_e = nextKeyframe?.rotation;
|
||||
let quat_s = new THREE.Quaternion().setFromEuler(new THREE.Euler(rot_s.x, rot_s.y, rot_s.z));
|
||||
let quat_e = new THREE.Quaternion().setFromEuler(new THREE.Euler(rot_e.x, rot_e.y, rot_e.z));
|
||||
let quaternion = new THREE.Quaternion();
|
||||
quaternion.slerpQuaternions(quat_s, quat_e, location);
|
||||
object.quaternion.copy(quaternion);
|
||||
|
||||
let curve_rot = new THREE.CatmullRomCurve3(this.rotations);
|
||||
let point_rot = curve_rot.getPoint(time_frame);
|
||||
object.rotation.set(point_rot.x, point_rot.y, point_rot.z);
|
||||
|
||||
let curve_scale = new THREE.CatmullRomCurve3(this.scales);
|
||||
let point_scale = curve_scale.getPoint(time_frame);
|
||||
object.scale.copy(point_scale);
|
||||
let scale_s = currentKeyframe?.scale;
|
||||
let scale_e = nextKeyframe?.scale;
|
||||
let points_scale = [new THREE.Vector3(scale_s.x, scale_s.y, scale_s.z), new THREE.Vector3(scale_e.x, scale_e.y, scale_e.z)]
|
||||
let curve_scale = new THREE.CatmullRomCurve3(points_scale);
|
||||
let point_scale = curve_scale.getPoint(location);
|
||||
object.scale.copy(point_scale);
|
||||
}
|
||||
}
|
||||
|
||||
reset(object: THREE.Object3D) {
|
||||
if (this.positions.length > 0) {
|
||||
let first_pos = this.positions[0];
|
||||
let first_rot = this.rotations[0];
|
||||
let first_scl = this.scales[0];
|
||||
if (this.keyframes.length > 0) {
|
||||
let first_pos = this.keyframes[0].position;
|
||||
let first_rot = this.keyframes[0].rotation;
|
||||
let first_scl = this.keyframes[0].scale;
|
||||
object.position.copy(first_pos);
|
||||
object.rotation.set(first_rot.x, first_rot.y, first_rot.z);
|
||||
object.scale.copy(first_scl);
|
||||
@@ -62,34 +131,13 @@ export class TransformClip {
|
||||
}
|
||||
}
|
||||
|
||||
add_position(position: THREE.Vector3) {
|
||||
this.positions.push(new THREE.Vector3(position.x, position.y, position.z));
|
||||
}
|
||||
|
||||
remove_position(position: THREE.Vector3) {
|
||||
this.positions = this.positions.filter(positions => {
|
||||
return !position.equals(position);
|
||||
});
|
||||
}
|
||||
|
||||
add_rotation(rotation: THREE.Vector3) {
|
||||
this.rotations.push(new THREE.Vector3(rotation.x, rotation.y, rotation.z));
|
||||
}
|
||||
|
||||
remove_rotation(rotation: THREE.Vector3) {
|
||||
this.rotations = this.rotations.filter(rotations => {
|
||||
return !rotation.equals(rotation);
|
||||
});
|
||||
}
|
||||
|
||||
add_scale(scale: THREE.Vector3) {
|
||||
this.scales.push(new THREE.Vector3(scale.x, scale.y, scale.z));
|
||||
}
|
||||
|
||||
remove_scale(scale: THREE.Vector3) {
|
||||
this.scales = this.scales.filter(scales => {
|
||||
return !scale.equals(scale);
|
||||
});
|
||||
add_frame(position: THREE.Vector3, rotation: THREE.Vector3, scale: THREE.Vector3, offset: number, keyframe_uuid: string) {
|
||||
this.keyframes.push(new TransformFrame(
|
||||
position,
|
||||
rotation,
|
||||
scale,
|
||||
offset,
|
||||
keyframe_uuid));
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
@@ -99,9 +147,7 @@ export class TransformClip {
|
||||
object_uuid: this.object_uuid,
|
||||
length: this.length,
|
||||
type: this.type,
|
||||
position: this.positions,
|
||||
rotation: this.rotations,
|
||||
scale: this.scales,
|
||||
keyframes: this.keyframes,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ export class AnimationEngine {
|
||||
object_uuid,
|
||||
1.0, 1.0,
|
||||
clip_name);
|
||||
this.clips[object_uuid]._load_animation();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@ class Editor {
|
||||
positive_prompt: string;
|
||||
negative_prompt: string;
|
||||
art_style: ArtStyle;
|
||||
|
||||
last_scrub: number;
|
||||
// Default params.
|
||||
constructor() {
|
||||
console.log(
|
||||
@@ -169,6 +171,7 @@ class Editor {
|
||||
this.playback = false;
|
||||
this.playback_location = 0;
|
||||
this.max_length = 10;
|
||||
this.last_scrub = 0;
|
||||
// Audio Engine Test.
|
||||
|
||||
this.render_width = 1280;
|
||||
@@ -344,8 +347,6 @@ class Editor {
|
||||
);
|
||||
}
|
||||
|
||||
console.log(this.cam_obj);
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR,
|
||||
payload: {
|
||||
@@ -511,6 +512,7 @@ class Editor {
|
||||
return;
|
||||
}
|
||||
this.last_selected = this.selected;
|
||||
this.selected = undefined;
|
||||
this.control.detach();
|
||||
this.activeScene.scene.remove(this.control);
|
||||
this.outlinePass.selectedObjects = [];
|
||||
@@ -519,7 +521,7 @@ class Editor {
|
||||
async _serialize_timeline() {
|
||||
// note the database from the server is the source of truth for all the data.
|
||||
// Test code here
|
||||
const object: any = await this.activeScene.load_glb(
|
||||
const object: THREE.Object3D = await this.activeScene.load_glb(
|
||||
"./resources/models/fox/fox.glb",
|
||||
);
|
||||
|
||||
@@ -649,9 +651,10 @@ class Editor {
|
||||
async _test_demo() {
|
||||
// note the database from the server is the source of truth for all the data.
|
||||
// Test code here
|
||||
const object: any = await this.activeScene.load_glb(
|
||||
"./resources/models/fox/fox.glb",
|
||||
const object: THREE.Object3D = await this.activeScene.load_glb(
|
||||
"m_9f3d3z94kk6m25zywyz6an3p43fjtw",
|
||||
);
|
||||
object.uuid = "CH1";
|
||||
|
||||
// Load timeline creates the the clips from the datastructure and loads them in here.
|
||||
// load object into the engine for lip syncing
|
||||
@@ -841,8 +844,11 @@ class Editor {
|
||||
|
||||
if (this.cameraViewControls && this.camera_person_mode) {
|
||||
this.cameraViewControls.update(5 * delta_time);
|
||||
if (this.cam_obj) {
|
||||
if (this.timeline.is_playing == false) {
|
||||
if(this.cam_obj){
|
||||
if (this.last_scrub != this.timeline.scrubber_frame_position) {
|
||||
this.camera.position.copy(this.cam_obj.position);
|
||||
this.camera.rotation.copy(this.cam_obj.rotation);
|
||||
} else if(this.timeline.is_playing == false) {
|
||||
this.cam_obj.position.copy(this.camera.position);
|
||||
this.cam_obj.rotation.copy(this.camera.rotation);
|
||||
} else {
|
||||
@@ -857,7 +863,13 @@ class Editor {
|
||||
this.render_camera.rotation.copy(this.cam_obj.rotation);
|
||||
}
|
||||
|
||||
this.timeline.update();
|
||||
this.updateSelectedUI();
|
||||
|
||||
if(this.timeline.is_playing){
|
||||
this.timeline.update();
|
||||
}
|
||||
|
||||
this.last_scrub = this.timeline.scrubber_frame_position;
|
||||
|
||||
this.renderScene();
|
||||
}
|
||||
@@ -1010,9 +1022,9 @@ class Editor {
|
||||
URL.revokeObjectURL(url);
|
||||
document.body.removeChild(downloadLink);
|
||||
|
||||
const data = await this.api_manager.uploadMedia(blob, "tmp.wav");
|
||||
console.log(data);
|
||||
const data = await this.api_manager.uploadMedia(blob, "render.mp4");
|
||||
// Create a link to download the file
|
||||
|
||||
}
|
||||
|
||||
async generateFrame() {
|
||||
@@ -1282,7 +1294,6 @@ class Editor {
|
||||
}
|
||||
this.activeScene.selected = this.selected;
|
||||
|
||||
this.updateSelectedUI();
|
||||
// this.update_properties()
|
||||
this.activeScene.scene.add(this.control);
|
||||
this.control.attach(this.selected);
|
||||
|
||||
@@ -77,7 +77,7 @@ class Scene {
|
||||
}
|
||||
|
||||
_create_camera_obj() {
|
||||
this.load_glb("/resources/models/camera/camera.glb", false).then((cam_obj) => {
|
||||
this.load_glb("m_cxh4asqhapdz10j880755dg4yevshb", false).then((cam_obj) => {
|
||||
cam_obj.userData["name"] = "::CAM::";
|
||||
cam_obj.name = "::CAM::";
|
||||
cam_obj.position.set(0, 0.6, 1.5);
|
||||
@@ -97,7 +97,45 @@ class Scene {
|
||||
}
|
||||
}
|
||||
|
||||
async load_glb(filepath: string, auto_add: boolean = true): Promise<THREE.Object3D> { //: Promise<THREE.Object3D> {
|
||||
async getMediaURL(media_id: string) {
|
||||
//This is for prod when we have the proper info on the url.
|
||||
let api_base_url = "https://api.fakeyou.com";
|
||||
let url = `${api_base_url}/v1/media_files/file/${media_id}`
|
||||
let responce = await fetch(url);
|
||||
let json = await JSON.parse(await responce.text());
|
||||
let bucketPath = json["media_file"]["public_bucket_path"];
|
||||
let media_base_url = "https://storage.googleapis.com/vocodes-public"
|
||||
let media_url = `${media_base_url}${bucketPath}`
|
||||
return media_url;
|
||||
}
|
||||
|
||||
async load_glb(media_id: string, auto_add: boolean = true): Promise<THREE.Object3D> { //: Promise<THREE.Object3D> {
|
||||
return new Promise(async (resolve) => {
|
||||
let glbLoader = new GLTFLoader();
|
||||
glbLoader.load(await this.getMediaURL(media_id), (glb) => {
|
||||
glb.scene.children.forEach(child => {
|
||||
child.traverse((c: THREE.Object3D) => {
|
||||
if (c instanceof THREE.Mesh) {
|
||||
c.material.metalness = 0.0;
|
||||
c.material.specular = 0.5;
|
||||
c.castShadow = true;
|
||||
c.receiveShadow = true;
|
||||
c.material.transparent = false;
|
||||
}
|
||||
});
|
||||
if (child.type == "Group") {
|
||||
if (auto_add) { this.scene.add(child.children[0]); }
|
||||
resolve(child.children[0]);
|
||||
return;
|
||||
}
|
||||
if (auto_add) { this.scene.add(child); }
|
||||
resolve(child);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async load_glb_absolute(filepath: string, auto_add: boolean = true): Promise<THREE.Object3D> { //: Promise<THREE.Object3D> {
|
||||
return new Promise((resolve) => {
|
||||
let glbLoader = new GLTFLoader();
|
||||
glbLoader.load(filepath, (glb) => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "../Queue/QueueNames";
|
||||
import { toEngineActions } from "../Queue/toEngineActions";
|
||||
import { fromEngineActions } from "../Queue/fromEngineActions";
|
||||
import { ClipType } from "~/pages/PageEnigma/models/track";
|
||||
import { ClipGroup, ClipType } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
// Every object uuid / entity has a track.
|
||||
export class TimelineCurrentState {
|
||||
@@ -39,6 +39,7 @@ export class TimeLine {
|
||||
timeline_items: ClipUI[];
|
||||
|
||||
timeline_limit: number;
|
||||
absolute_end: number;
|
||||
scrubber_frame_position: number;
|
||||
is_playing: boolean;
|
||||
|
||||
@@ -63,7 +64,8 @@ export class TimeLine {
|
||||
scene: Scene,
|
||||
) {
|
||||
this.timeline_items = [];
|
||||
this.timeline_limit = 60 * 5; // 5 seconds
|
||||
this.absolute_end = 60 * 12;
|
||||
this.timeline_limit = this.absolute_end; // 5 seconds
|
||||
|
||||
this.is_playing = false;
|
||||
this.scrubber_frame_position = 0; // in frames into the tl
|
||||
@@ -104,6 +106,12 @@ export class TimeLine {
|
||||
case toEngineActions.ADD_KEYFRAME:
|
||||
await this.addKeyFrame(data);
|
||||
break;
|
||||
case toEngineActions.UPDATE_KEYFRAME:
|
||||
await this.updateKeyFrame(data);
|
||||
break;
|
||||
case toEngineActions.DELETE_KEYFRAME:
|
||||
await this.deleteKeyFrame(data);
|
||||
break;
|
||||
case toEngineActions.ADD_CLIP:
|
||||
await this.addClip(data);
|
||||
break;
|
||||
@@ -135,18 +143,41 @@ export class TimeLine {
|
||||
// rotation: XYZ;
|
||||
// scale: XYZ;
|
||||
// selected?: boolean;
|
||||
const data_json = data["data"];
|
||||
const uuid = data_json["object_uuid"];
|
||||
|
||||
let object_name = this.scene.get_object_by_uuid(uuid)?.name;
|
||||
if (object_name == undefined) {
|
||||
object_name = "undefined";
|
||||
}
|
||||
|
||||
const new_item = this.transform_engine.addFrame(
|
||||
uuid,
|
||||
this.absolute_end,
|
||||
data_json["position"],
|
||||
data_json["rotation"],
|
||||
data_json["scale"],
|
||||
data_json["offset"],
|
||||
data_json["keyframe_uuid"],
|
||||
);
|
||||
if (new_item) {
|
||||
await this.addPlayableClip(
|
||||
new ClipUI(
|
||||
data_json["version"],
|
||||
ClipType.TRANSFORM,
|
||||
data_json["group"],
|
||||
object_name,
|
||||
"",
|
||||
uuid,
|
||||
0,
|
||||
this.absolute_end,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async addClip(data: any) {
|
||||
let object_uuid = data["data"]["object_uuid"];
|
||||
|
||||
if (object_uuid.length < 6) {
|
||||
object_uuid = this.scene.selected?.uuid; //data['data']['object_uuid'];
|
||||
if (object_uuid == undefined) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const object_uuid = data["data"]["object_uuid"];
|
||||
const media_id = data["data"]["media_id"];
|
||||
const name = data["data"]["name"];
|
||||
const group = data["data"]["group"];
|
||||
@@ -155,6 +186,37 @@ export class TimeLine {
|
||||
const offset = data["data"]["offset"];
|
||||
const end_offset = data["data"]["length"] + offset;
|
||||
|
||||
switch (type) {
|
||||
case "animation":
|
||||
this.animation_engine.load_object(object_uuid, media_id, name);
|
||||
break;
|
||||
case "transform":
|
||||
this.transform_engine.loadObject(object_uuid, data["data"]["length"]);
|
||||
break;
|
||||
case "audio":
|
||||
if (group == "character") {
|
||||
this.lipSync_engine.load_object(object_uuid, media_id);
|
||||
// media id for this as well it can be downloaded
|
||||
this.addPlayableClip(
|
||||
new ClipUI(
|
||||
version,
|
||||
ClipType.AUDIO,
|
||||
group,
|
||||
name,
|
||||
media_id,
|
||||
object_uuid,
|
||||
offset,
|
||||
end_offset,
|
||||
),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
console.log("Audio!");
|
||||
this.audio_engine.loadClip(media_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// media id for this as well it can be downloaded
|
||||
this.addPlayableClip(
|
||||
new ClipUI(
|
||||
@@ -168,19 +230,39 @@ export class TimeLine {
|
||||
end_offset,
|
||||
),
|
||||
);
|
||||
|
||||
switch (type) {
|
||||
case ClipType.ANIMATION:
|
||||
this.animation_engine.load_object(object_uuid, media_id, name);
|
||||
break;
|
||||
case ClipType.TRANSFORM:
|
||||
this.transform_engine.loadObject(object_uuid, data["data"]["length"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteKeyFrame(data: any) {
|
||||
const keyframe_uuid = data["data"]["keyframe_uuid"];
|
||||
const object_uuid = data["data"]["object_uuid"];
|
||||
this.transform_engine.clips[object_uuid].removeKeyframe(keyframe_uuid);
|
||||
}
|
||||
|
||||
public async updateKeyFrame(data: any) {
|
||||
const keyframe_uuid = data["data"]["keyframe_uuid"];
|
||||
const keyframe_offset = data["data"]["offset"];
|
||||
const object_uuid = data["data"]["object_uuid"];
|
||||
this.transform_engine.clips[object_uuid].setOffset(
|
||||
keyframe_uuid,
|
||||
keyframe_offset,
|
||||
);
|
||||
}
|
||||
|
||||
public async updateClip(data: any) {
|
||||
// only length and offset changes here.
|
||||
console.log(data);
|
||||
|
||||
const object_uuid = data["data"]["object_uuid"];
|
||||
const media_id = data["data"]["media_id"];
|
||||
const offset = data["data"]["offset"];
|
||||
const length = data["data"]["length"] + offset;
|
||||
|
||||
for (const element of this.timeline_items) {
|
||||
if (element.media_id == media_id && element.object_uuid == object_uuid) {
|
||||
element.length = length;
|
||||
element.offset = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
public async deleteClip(data: any) {
|
||||
console.log(data);
|
||||
@@ -220,6 +302,7 @@ export class TimeLine {
|
||||
if (this.is_playing) {
|
||||
return;
|
||||
}
|
||||
this.update();
|
||||
this.setScrubberPosition(data["data"]["currentTime"]);
|
||||
}
|
||||
|
||||
@@ -294,34 +377,40 @@ export class TimeLine {
|
||||
// element.play()
|
||||
// remove the element from the list
|
||||
const object = this.scene.get_object_by_uuid(element.object_uuid);
|
||||
if (element.type == "transform") {
|
||||
if (element.type === ClipType.TRANSFORM) {
|
||||
if (object && this.transform_engine.clips[element.object_uuid]) {
|
||||
this.transform_engine.clips[element.object_uuid].length =
|
||||
element.length - element.offset;
|
||||
this.transform_engine.clips[element.object_uuid].step(
|
||||
object,
|
||||
element.offset,
|
||||
this.scrubber_frame_position,
|
||||
);
|
||||
element.length =
|
||||
this.transform_engine.clips[element.object_uuid].length;
|
||||
}
|
||||
} else if (element.type == "audio") {
|
||||
} else if (
|
||||
element.group !== ClipGroup.CHARACTER &&
|
||||
element.type === ClipType.AUDIO
|
||||
) {
|
||||
if (this.scrubber_frame_position + 1 >= element.length) {
|
||||
this.audio_engine.stopClip(element.media_id);
|
||||
} else {
|
||||
this.audio_engine.playClip(element.media_id);
|
||||
}
|
||||
} else if (element.type == "lipsync") {
|
||||
} else if (
|
||||
element.group === ClipGroup.CHARACTER &&
|
||||
element.type === ClipType.AUDIO
|
||||
) {
|
||||
if (this.scrubber_frame_position + 1 >= element.length) {
|
||||
this.lipSync_engine.clips[element.object_uuid].stop();
|
||||
} else if (object) {
|
||||
await this.lipSync_engine.clips[element.object_uuid].play(object);
|
||||
this.lipSync_engine.clips[element.object_uuid].step();
|
||||
}
|
||||
} else if (element.type == "animation") {
|
||||
} else if (element.type === ClipType.ANIMATION) {
|
||||
if (object) {
|
||||
await this.animation_engine.clips[object.uuid].play(object);
|
||||
this.animation_engine.clips[object.uuid].step(
|
||||
this.scrubber_frame_position / 60,
|
||||
this.scrubber_frame_position / 120, // Double FPS for best result.
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -15,13 +15,14 @@ class TransformEngine {
|
||||
return this.clips[object_uuid]
|
||||
}
|
||||
|
||||
addFrame(object: THREE.Object3D, clip_length:number = 2) {
|
||||
if(this.clips[object.uuid] == null) {
|
||||
this.clips[object.uuid] = new TransformClip(this.version, object.uuid, clip_length);
|
||||
addFrame(object_uuid: string, clip_length:number = 2, position: THREE.Vector3, rotation: THREE.Vector3, scale: THREE.Vector3, offset: number, keyframe_uuid: string): boolean{
|
||||
if(this.clips[object_uuid] == null) {
|
||||
this.clips[object_uuid] = new TransformClip(this.version, object_uuid, clip_length);
|
||||
this.clips[object_uuid].add_frame(position, rotation, scale, offset, keyframe_uuid);
|
||||
return false;
|
||||
}
|
||||
this.clips[object.uuid].add_position(object.position);
|
||||
this.clips[object.uuid].add_rotation(new THREE.Vector3(object.rotation.x, object.rotation.y, object.rotation.z));
|
||||
this.clips[object.uuid].add_scale(object.scale);
|
||||
this.clips[object_uuid].add_frame(position, rotation, scale, offset, keyframe_uuid);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-12
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { LinksFunction } from "@remix-run/deno";
|
||||
import {
|
||||
@@ -16,6 +17,7 @@ import baseCss from "./styles/base.css?url";
|
||||
import { LoadingDotsBricks } from "~/components";
|
||||
import { TopBar } from "./modules/TopBar";
|
||||
import { TopBarInnerContext } from "~/contexts/TopBarInner";
|
||||
import { AuthenticationProvider, UserInfo } from "./contexts/Authentication";
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{
|
||||
@@ -47,7 +49,10 @@ export const links: LinksFunction = () => [
|
||||
|
||||
export default function App() {
|
||||
const [topBarInnerComponent, setTopBarInnerComponent] =
|
||||
useState<JSX.Element | null>(null);
|
||||
useState<{
|
||||
location: string,
|
||||
node: React.ReactNode,
|
||||
} | null>(null);
|
||||
const [showLoader, setShowLoader] = useState<boolean>(true);
|
||||
useEffect(()=>{
|
||||
setTimeout(()=>setShowLoader(false), 2500);
|
||||
@@ -63,16 +68,20 @@ export default function App() {
|
||||
</head>
|
||||
<body className="overflow-hidden bg-ui-background">
|
||||
<CompleteTakeoverLoadingScreen isShowing={showLoader}/>
|
||||
<TopBarInnerContext.Provider
|
||||
value={{
|
||||
TopBarInner: topBarInnerComponent,
|
||||
setTopBarInner: setTopBarInnerComponent,
|
||||
}}
|
||||
>
|
||||
<div className="topbar-spacer" />
|
||||
<Outlet />
|
||||
<TopBar />
|
||||
</TopBarInnerContext.Provider>
|
||||
<CookiesProvider defaultSetOptions={{ path: '/' }}>
|
||||
<AuthenticationProvider>
|
||||
<TopBarInnerContext.Provider
|
||||
value={{
|
||||
TopBarInner: topBarInnerComponent,
|
||||
setTopBarInner: setTopBarInnerComponent,
|
||||
}}
|
||||
>
|
||||
<div className="topbar-spacer" />
|
||||
<Outlet />
|
||||
<TopBar />
|
||||
</TopBarInnerContext.Provider>
|
||||
</AuthenticationProvider>
|
||||
</CookiesProvider>
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</body>
|
||||
|
||||
@@ -1,40 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { PageEnigma } from "../pages/PageEnigma";
|
||||
|
||||
import { Button } from "~/components/Button";
|
||||
|
||||
export default function IdealEnigma() {
|
||||
//const [riddle, setRiddle] = useState<string>("");
|
||||
//const [submitted, setSubmitted] = useState<boolean>(false);
|
||||
|
||||
//const handleInput = (event:React.ChangeEvent<HTMLInputElement>)=>{
|
||||
// setRiddle(event.target.value);
|
||||
//}
|
||||
|
||||
//const handleSubmit = () => {
|
||||
// const state = riddle.length > 5 && riddle[riddle.length-1] === "!";
|
||||
// setSubmitted(state);
|
||||
//}
|
||||
|
||||
return <PageEnigma />;
|
||||
// if(submitted){
|
||||
// return <PageEnigmaComponent />
|
||||
// }else{
|
||||
// return(
|
||||
// <div className="w-full flex justify-center content-center">
|
||||
// <form
|
||||
// className="mt-20 flex flex-col content-center"
|
||||
// onSubmit={handleSubmit}
|
||||
// >
|
||||
// <label className='text-white'>
|
||||
// Riddle:
|
||||
// </label>
|
||||
// <input type="text" value={riddle} onChange={handleInput} />
|
||||
// <br/>
|
||||
// <Button type="submit" className='text-white'>Submit</Button>
|
||||
// </form>
|
||||
// </div>
|
||||
|
||||
// );
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { useContext, useEffect, useState, useRef } from "react";
|
||||
import { useNavigate } from "@remix-run/react";
|
||||
import { useCookies } from 'react-cookie';
|
||||
import { faKey, faUser } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { AuthenticationContext } from "~/contexts/Authentication";
|
||||
|
||||
import {
|
||||
Button,
|
||||
H1,
|
||||
Input,
|
||||
Link,
|
||||
P,
|
||||
} from '~/components';
|
||||
|
||||
import { LoadingDots } from "~/components";
|
||||
|
||||
export default function LoginScreen() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [showLoader, setShowLoader] = useState<string|undefined>(undefined);
|
||||
const formRef = useRef<HTMLFormElement | null>(null);
|
||||
const [authCookies, setAuthCookie, removeAuthCookie] = useCookies(['userInfo']);
|
||||
const {authState, loginAndGetUserInfo} = useContext(AuthenticationContext);
|
||||
|
||||
|
||||
const handleOnSumbit = (ev: React.FormEvent<HTMLFormElement>)=>{
|
||||
ev.preventDefault();
|
||||
if(formRef.current){
|
||||
const form = new FormData(formRef.current);
|
||||
const usernameOrEmail = form.get("usernameOrEmail")?.toString();
|
||||
const password = form.get("password")?.toString();
|
||||
if( usernameOrEmail && password && loginAndGetUserInfo){
|
||||
setShowLoader("Authenticating");
|
||||
loginAndGetUserInfo(
|
||||
usernameOrEmail,
|
||||
password,
|
||||
()=>{
|
||||
setShowLoader(undefined);
|
||||
});
|
||||
}
|
||||
}
|
||||
}// end handleOnSubmit
|
||||
|
||||
useEffect(()=>{
|
||||
if(authState && authState.isLoggedIn)
|
||||
navigate("/");
|
||||
},[authState]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed w-full overflow-scroll"
|
||||
style={{height: "calc(100% - 72px)"}}
|
||||
>
|
||||
<div className="w-10/12 max-w-2xl mx-auto my-6">
|
||||
<H1 className="text-center">Login to FakeYou</H1>
|
||||
</div>
|
||||
<div
|
||||
className='relative bg-ui-panel w-10/12 max-w-2xl mx-auto my-6 rounded-lg p-6 border border-ui-panel-border overflow-hidden'
|
||||
>
|
||||
<form ref={formRef} onSubmit={handleOnSumbit}>
|
||||
<Input
|
||||
label="Username or Email"
|
||||
icon={faUser}
|
||||
name="usernameOrEmail"
|
||||
placeholder="Username or Email"
|
||||
required
|
||||
/>
|
||||
<br />
|
||||
<Input
|
||||
label="Password"
|
||||
icon={faKey}
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
<br />
|
||||
<div className="flex justify-end gap-2">
|
||||
<P>Don't have an account?</P>
|
||||
<Link to='/signup'>Sign Up</Link>
|
||||
</div>
|
||||
<Button>Login</Button>
|
||||
</form>
|
||||
<LoadingDots
|
||||
className="absolute top-0 left-0 w-full h-full"
|
||||
isShowing={showLoader !== undefined}
|
||||
message={showLoader}
|
||||
type="bricks"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Form } from "@remix-run/react";
|
||||
import { faEnvelope, faKey, faUser } from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
Button,
|
||||
H1,
|
||||
Input,
|
||||
Link,
|
||||
P,
|
||||
} from '~/components';
|
||||
|
||||
export default function LoginScreen() {
|
||||
return (
|
||||
<div
|
||||
className="fixed w-full overflow-scroll"
|
||||
style={{height: "calc(100% - 72px)"}}
|
||||
>
|
||||
<div className="w-10/12 max-w-2xl mx-auto my-6">
|
||||
<H1 className="text-center">Sign Up to FakeYou</H1>
|
||||
</div>
|
||||
<div
|
||||
className='bg-ui-panel w-10/12 max-w-2xl mx-auto my-6 rounded-lg p-6 border border-ui-panel-border'
|
||||
>
|
||||
<Form method="post" >
|
||||
<Input
|
||||
label="Username"
|
||||
icon={faUser}
|
||||
placeholder="Username"
|
||||
name="username"
|
||||
required
|
||||
/>
|
||||
<br />
|
||||
<Input
|
||||
type="email"
|
||||
label="Email"
|
||||
icon={faEnvelope}
|
||||
placeholder="Email"
|
||||
name="email"
|
||||
required
|
||||
/>
|
||||
<br />
|
||||
<Input
|
||||
type="password"
|
||||
label="Password"
|
||||
icon={faKey}
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
<br />
|
||||
<Input
|
||||
type="password"
|
||||
label="Password Confrimation"
|
||||
icon={faKey}
|
||||
placeholder="Password COnfirmation"
|
||||
name="password-confirmation"
|
||||
required
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<Button>Sign up</Button>
|
||||
<br />
|
||||
<div className="flex gap-2">
|
||||
<P>Already have an account?</P>
|
||||
<Link to='/login'>Log in instead</Link>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -21,11 +21,11 @@
|
||||
body {
|
||||
font-family: "Fira Sans", sans-serif;
|
||||
color: #fff;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
.topbar-spacer {
|
||||
height: 68px;
|
||||
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
|
||||
Generated
+42
-5
@@ -27,6 +27,7 @@
|
||||
"ccapture.js": "github:aceslowman/ccapture.js#npm-fix",
|
||||
"isbot": "^4.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-cookie": "^7.1.4",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
@@ -2334,6 +2335,15 @@
|
||||
"@types/unist": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
@@ -2391,14 +2401,12 @@
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
||||
"dev": true
|
||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.2.57",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.57.tgz",
|
||||
"integrity": "sha512-ZvQsktJgSYrQiMirAN60y4O/LRevIV8hUzSOSNB6gfR3/o3wCBFQx3sPwIYtuDMeiVgsSS3UzCV26tEzgnfvQw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
@@ -2417,8 +2425,7 @@
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.5.7",
|
||||
@@ -6120,6 +6127,14 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
|
||||
@@ -10385,6 +10400,19 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-cookie": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.1.4.tgz",
|
||||
"integrity": "sha512-wDxxa/HYaSXSMlyWJvJ5uZTzIVtQTPf1gMksFgwAz/2/W3lCtY8r4OChCXMPE7wax0PAdMY97UkNJedGv7KnDw==",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.5",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"universal-cookie": "^7.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
@@ -12478,6 +12506,15 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-cookie": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.1.4.tgz",
|
||||
"integrity": "sha512-Q+DVJsdykStWRMtXr2Pdj3EF98qZHUH/fXv/gwFz/unyToy1Ek1w5GsWt53Pf38tT8Gbcy5QNsj61Xe9TggP4g==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"cookie": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"ccapture.js": "github:aceslowman/ccapture.js#npm-fix",
|
||||
"isbot": "^4.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-cookie": "^7.1.4",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
|
||||
Binary file not shown.
Reference in new issue
Block a user