Merge branch 'test' into feature/keyframes-continued

This commit is contained in:
FromTheLOLAshes committed 2024-04-04 10:59:52 +02:00
commit 9bb0e8891c
32 files changed
+917 -260

No files matched your search

+21 -7
View File
@@ -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>
);
}
+3
View File
@@ -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
View File
@@ -1,4 +1,3 @@
import { useState, useEffect } from 'react';
import { Transition } from '@headlessui/react';
import { twMerge } from 'tailwind-merge';
+17 -27
View File
@@ -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>
);
+2 -2
View File
@@ -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>
)
}
+6
View File
@@ -0,0 +1,6 @@
import { AuthenticationContext, AuthenticationProvider } from "./Authentication";
import { UserInfo, AUTH_STATUS } from "./types";
export type { UserInfo };
export { AUTH_STATUS, AuthenticationContext, AuthenticationProvider };
+59
View File
@@ -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,
}
+78
View File
@@ -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;
});
}
+8 -2
View File
@@ -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);
-5
View File
@@ -1,5 +0,0 @@
import { joinClassNames } from "./joinCassNames";
export {
joinClassNames
}
-3
View File
@@ -1,3 +0,0 @@
export function joinClassNames(...classes:string[]) {
return classes.filter(Boolean).join(' ')
};
+24
View File
@@ -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>
</>
);
}
}
+23 -14
View File
@@ -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;
};
+9 -3
View File
@@ -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();
}
}
+22 -11
View File
@@ -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);
+40 -2
View File
@@ -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) => {
+116 -27
View File
@@ -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 {
+7 -6
View File
@@ -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
View File
@@ -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>
-35
View File
@@ -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>
// );
// }
}
+95
View File
@@ -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>
);
}
+71
View File
@@ -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
View File
@@ -21,11 +21,11 @@
body {
font-family: "Fira Sans", sans-serif;
color: #fff;
color-scheme: dark;
}
.topbar-spacer {
height: 68px;
width: 100vw;
}
+42 -5
View File
@@ -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",
+1
View File
@@ -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.