mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
Merge branch 'test' into feature/resize-timeline
This commit is contained in:
commit
71dde2af2f
38 files changed
+1314
-485
No files matched your search
@@ -1,19 +0,0 @@
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export const H4 = ({
|
||||
className,
|
||||
children,
|
||||
}:{
|
||||
className?: string,
|
||||
children: React.ReactNode,
|
||||
})=>{
|
||||
return(
|
||||
<h4
|
||||
className={
|
||||
twMerge("text-base font-medium text-white", className)
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</h4>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +1,78 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { H4 } from '.';
|
||||
|
||||
|
||||
interface LoadingBarProps{
|
||||
id?: string;
|
||||
wrapperClassName?:string;
|
||||
innerWrapperClassName?: string;
|
||||
progressBackgroundClassName?: string;
|
||||
progressClassName?: string;
|
||||
label?: string;
|
||||
progress?: number;
|
||||
isShowing?: boolean;
|
||||
variant?: string;
|
||||
message?: string;
|
||||
wrapperClassName?: string;
|
||||
useFakeTimer?: number;
|
||||
}
|
||||
export const LoadingBar = ({
|
||||
wrapperClassName : propsWrapperClassName,
|
||||
innerWrapperClassName: propsInnerWrapperClassName,
|
||||
progressBackgroundClassName: propsProgressBackgroundClassName,
|
||||
progressClassName : propsProgressClassName,
|
||||
label,
|
||||
progress = 0,
|
||||
progress: propsProgress = 0,
|
||||
isShowing = true,
|
||||
variant = 'primary',
|
||||
message,
|
||||
wrapperClassName : propsWrapperClassName,
|
||||
useFakeTimer = (1000 * 60) //defaults to 1min
|
||||
useFakeTimer,
|
||||
...rest
|
||||
}: LoadingBarProps) => {
|
||||
const [progress, setProgress] = useState<number>(propsProgress);
|
||||
|
||||
// this takes the real progress coming from parent
|
||||
useEffect(()=>{
|
||||
setProgress(propsProgress);
|
||||
}, [propsProgress]);
|
||||
|
||||
// this sets a fake progress, it goes to 96%
|
||||
// in the amount of miliseconds provided by the flag
|
||||
useEffect(()=>{
|
||||
let loop: NodeJS.Timeout;
|
||||
if(useFakeTimer){
|
||||
if(useFakeTimer >= 30000){
|
||||
// this math produce 96 cuts so the progress bar updates more
|
||||
loop = setInterval(function step(){
|
||||
setProgress((curr)=>{
|
||||
if(curr<96){
|
||||
return curr + 3
|
||||
}else{
|
||||
clearInterval(loop);
|
||||
return curr;
|
||||
}
|
||||
})
|
||||
},Math.round(useFakeTimer/96)*3);
|
||||
}else{
|
||||
// this math produce less cuts if useFakeTimer predicts
|
||||
// a shorter load time, shorter thatn 30s
|
||||
const progressPerInterval = useFakeTimer/500
|
||||
loop = loop = setInterval(function step(){
|
||||
setProgress((curr)=>{
|
||||
if(curr + progressPerInterval < 96){
|
||||
return curr + progressPerInterval
|
||||
}else if (curr< 96 && curr + progressPerInterval >= 96){
|
||||
return 96;
|
||||
}else{
|
||||
clearInterval(loop);
|
||||
return curr;
|
||||
}
|
||||
})
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
return () => clearInterval(loop);
|
||||
}, []);
|
||||
|
||||
function getVariantClassNames(variant: string) {
|
||||
switch (variant) {
|
||||
@@ -31,24 +87,45 @@ export const LoadingBar = ({
|
||||
}
|
||||
|
||||
const wrapperClassName = twMerge(
|
||||
"w-full h-full flex flex-col justify-center items-center bg-ui-background p-4 gap-4",
|
||||
"w-full h-full relative bg-ui-background",
|
||||
propsWrapperClassName,
|
||||
)
|
||||
const innerWrapperClassName = twMerge(
|
||||
"w-full h-full p-4 gap-4 m-auto flex flex-col justify-center items-center",
|
||||
propsInnerWrapperClassName,
|
||||
);
|
||||
const progressBackgroundClassName = twMerge(
|
||||
"w-full bg-gray-500 rounded-full h-2.5",
|
||||
propsProgressBackgroundClassName
|
||||
);
|
||||
const progressClassName = twMerge(
|
||||
"h-2.5 rounded-full transition-all duration-1000",
|
||||
getVariantClassNames(variant),
|
||||
propsProgressClassName
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={wrapperClassName}>
|
||||
{label && <label>{label}</label>}
|
||||
<div className="w-full bg-gray-500 rounded-full h-2.5">
|
||||
<div
|
||||
className={progressClassName}
|
||||
style={{width: progress + '%'}}
|
||||
/>
|
||||
<Transition
|
||||
className={wrapperClassName}
|
||||
show={isShowing}
|
||||
enter="transition-opacity duration-150"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-1000"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
{...rest}
|
||||
>
|
||||
<div className={innerWrapperClassName}>
|
||||
{label && <label>{label}</label>}
|
||||
<div className={progressBackgroundClassName}>
|
||||
<div
|
||||
className={progressClassName}
|
||||
style={{width: progress + '%'}}
|
||||
/>
|
||||
</div>
|
||||
{message && <p>{message}</p>}
|
||||
</div>
|
||||
{message && <H4>{message}</H4>}
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +1,19 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { H4 } from '.';
|
||||
|
||||
interface LoadingDotsProps {
|
||||
className?: string;
|
||||
show?: boolean;
|
||||
transition?: boolean;
|
||||
transitionDuration?: number;
|
||||
isShowing?: boolean;
|
||||
};
|
||||
|
||||
interface LoadingDotsInnerProps {
|
||||
className?: string;
|
||||
show?: boolean;
|
||||
isShowing?: boolean;
|
||||
type?: 'typing'|'bricks';
|
||||
message?: string;
|
||||
transition?: boolean;
|
||||
transitionDuration?: number;
|
||||
};
|
||||
|
||||
export const LoadingDotsTyping = (props: LoadingDotsProps)=>{
|
||||
@@ -32,54 +29,34 @@ export const LoadingDotsBricks = (props: LoadingDotsProps)=>{
|
||||
|
||||
export function LoadingDots({
|
||||
className,
|
||||
show=true,
|
||||
transition = false,
|
||||
isShowing=true,
|
||||
type = 'typing',
|
||||
message,
|
||||
transitionDuration = 1000,
|
||||
}: LoadingDotsInnerProps){
|
||||
const [state, setState] = useState<'loading'|'loaded'|'completed'>('loading');
|
||||
const transitionClasses =
|
||||
(transition ? `transition-opacity duration-${transitionDuration} `: "")
|
||||
+ (state === 'loading' ? 'opacity-100' : 'opacity-0');
|
||||
const classNames = twMerge("w-full h-full flex flex-col justify-center items-center bg-ui-background gap-6", className);
|
||||
|
||||
const classNames = twMerge("w-full h-full flex flex-col justify-center items-center bg-ui-background gap-6",transitionClasses, className);
|
||||
useEffect(()=>{
|
||||
if(!show){
|
||||
if(transition){
|
||||
setState((curr)=>{
|
||||
if (curr === 'loading') return 'loaded'
|
||||
else return curr;
|
||||
});
|
||||
setTimeout(()=>setState('completed'), 2000);
|
||||
} else {
|
||||
setState('completed');
|
||||
return(
|
||||
<Transition
|
||||
className={classNames}
|
||||
show={isShowing}
|
||||
enter="transition-opacity duration-150"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-1000"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
{ type==='typing' &&
|
||||
<div className="dot-typing"></div>
|
||||
}
|
||||
}else{
|
||||
setState((curr) => {
|
||||
if (curr!=='loading') return 'loading';
|
||||
else return curr;
|
||||
});
|
||||
}
|
||||
}, [show, transition]);
|
||||
|
||||
if(state!== 'completed'){
|
||||
return(
|
||||
<div className={classNames}>
|
||||
{ type==='typing' &&
|
||||
<div className="dot-typing"></div>
|
||||
}
|
||||
{ type==='bricks' &&
|
||||
<div className="dot-bricks"></div>
|
||||
}
|
||||
{
|
||||
message &&
|
||||
<H4>{message}</H4>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
{ type==='bricks' &&
|
||||
<div className="dot-bricks"></div>
|
||||
}
|
||||
{
|
||||
message &&
|
||||
<H4>{message}</H4>
|
||||
}
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface TypogProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
const baseTypo = "text-white";
|
||||
|
||||
export const H1 = ({className,children,}:TypogProps)=>
|
||||
<h1 className={twMerge(baseTypo, "font-medium text-2xl",className)}>{children}</h1>;
|
||||
|
||||
export const H2 = ({className,children,}:TypogProps)=>
|
||||
<h2 className={twMerge(baseTypo, "font-medium text-xl", className)}> {children} </h2>;
|
||||
|
||||
export const H3 = ({className,children,}:TypogProps)=>
|
||||
<h3 className={twMerge(baseTypo, "font-medium text-lg", className)}> {children} </h3>;
|
||||
|
||||
export const H4 = ({className,children,}:TypogProps)=>
|
||||
<h4 className={twMerge(baseTypo, "font-medium text-base", className)}> {children} </h4>;
|
||||
|
||||
export const Label = ({className,children,}:TypogProps)=>
|
||||
<label className={twMerge(baseTypo, "font-medium text-base", className)}> {children} </label>;
|
||||
|
||||
export const H5 = ({className,children,}:TypogProps)=>
|
||||
<h5 className={twMerge(baseTypo, "font-medium text-sm",className)}> {children} </h5>;
|
||||
|
||||
export const H6 = ({className,children,}:TypogProps)=>
|
||||
<h6 className={twMerge(baseTypo, "font-light text-sm",className)}> {children} </h6>;
|
||||
|
||||
export const P = ({className,children,}:TypogProps)=>
|
||||
<p className={twMerge(baseTypo, className)}> {children} </p>;
|
||||
@@ -3,7 +3,7 @@ import { ButtonIcon } from "./ButtonIcon";
|
||||
import { ButtonLink } from "./ButtonLink";
|
||||
import { ButtonIconSelect } from "./ButtonIconSelect";
|
||||
import { Input } from "./Input";
|
||||
import { H4 } from "./H4";
|
||||
import { H1, H2, H3, H4, H5, H6, P, Label} from "./Typography";
|
||||
import { ItemPicker } from "./ItemPicker";
|
||||
import { Textarea } from "./Textarea";
|
||||
import { LoadingBar } from "./LoadingBar";
|
||||
@@ -14,7 +14,7 @@ export {
|
||||
ButtonIcon,
|
||||
ButtonLink,
|
||||
ButtonIconSelect,
|
||||
H4,
|
||||
H1,H2,H3,H4,H5,H6,P,Label,
|
||||
ItemPicker,
|
||||
Textarea,
|
||||
Input,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faChevronLeft,
|
||||
@@ -14,24 +13,20 @@ export const SidePanel = ({ children }: Props) => {
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="relative border-l border-l-ui-panel-border bg-ui-panel">
|
||||
<div className={
|
||||
"relative border-l border-l-ui-panel-border bg-ui-panel transition-all duration-300 ease-in-out"
|
||||
+ (isVisible ? " w-[23.5rem]" : " w-0")
|
||||
}
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsVisible(!isVisible)}
|
||||
className="absolute left-[-25px] top-1/2 flex rounded-l-lg bg-ui-controls px-2 py-3 align-middle text-sm text-white transition duration-150 ease-in-out hover:bg-ui-controls-button"
|
||||
className="hover:bg-ui-controls-button absolute left-[-25px] top-1/2 flex rounded-l-lg bg-ui-controls px-2 py-3 align-middle text-sm text-white"
|
||||
>
|
||||
<FontAwesomeIcon icon={isVisible ? faChevronRight : faChevronLeft} />
|
||||
</button>
|
||||
<Transition
|
||||
show={isVisible}
|
||||
enter="transition-transform duration-300"
|
||||
enterFrom="translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="transition-transform duration-300"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="translate-x-full"
|
||||
>
|
||||
<div className="relative w-[23.5rem] h-full">
|
||||
{children}
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,67 +0,0 @@
|
||||
import { useCallback, useLayoutEffect, useState } from "react";
|
||||
import { Dialog } from "@headlessui/react";
|
||||
|
||||
import { faXmark } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { TOP_BAR_HEIGHT } from "~/constants";
|
||||
|
||||
export const SidePanelInner = ({
|
||||
title,
|
||||
children,
|
||||
closeCallback,
|
||||
}: {
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
closeCallback: () => void;
|
||||
}) => {
|
||||
const [height, setHeight] = useState(0);
|
||||
const handleWindowResize = useCallback(() => {
|
||||
if (window) {
|
||||
setHeight(Math.ceil((window.innerHeight * 3) / 4) - TOP_BAR_HEIGHT);
|
||||
}
|
||||
return 0;
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
handleWindowResize();
|
||||
window.addEventListener("resize", handleWindowResize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleWindowResize);
|
||||
};
|
||||
}, [handleWindowResize]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col overflow-y-scroll border-y border-l border-ui-panel-border bg-ui-panel py-6 shadow-xl"
|
||||
style={{ height: height + "px" }}
|
||||
>
|
||||
<div className="px-4 sm:px-6">
|
||||
<div className="flex items-start justify-between">
|
||||
{!title && <span />}
|
||||
{title && (
|
||||
<Dialog.Title className="text-base font-semibold leading-6 text-white">
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
)}
|
||||
<div className="ml-3 flex h-7 items-center">
|
||||
<button
|
||||
type="button"
|
||||
className="relative rounded-md bg-brand-primary text-white hover:bg-brand-primary-400 focus:outline-none focus:ring-brand-primary-500"
|
||||
onClick={closeCallback}
|
||||
>
|
||||
{/* <span className="absolute -inset-2.5" /> */}
|
||||
<span className="sr-only">Close panel</span>
|
||||
<FontAwesomeIcon
|
||||
className="mx-1 mt-1 h-6 w-6"
|
||||
aria-hidden="true"
|
||||
icon={faXmark}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-6 flex-1">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { faGears } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Button } from "~/components/Button";
|
||||
|
||||
import { SidePanelInner } from "./SidePanelInnerOld";
|
||||
|
||||
interface SidePanelPropsI {
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SidePanel = ({ title, children }: SidePanelPropsI) => {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const handleOpen = () => setOpen(true);
|
||||
const handleClose = () => setOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button className="fixed right-0 top-20 mr-4 mt-2" onClick={handleOpen}>
|
||||
<FontAwesomeIcon className="fa-2xl -mx-2" icon={faGears} />
|
||||
</Button>
|
||||
<Transition.Root show={open} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={setOpen}>
|
||||
<div className="fixed inset-0" />
|
||||
|
||||
<div className="fixed inset-0 overflow-hidden">
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<div className="pointer-events-none fixed inset-y-0 right-0 top-16 mt-2 flex max-w-full pl-10 sm:pl-16">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="transform transition ease-in-out duration-500 sm:duration-700"
|
||||
enterFrom="translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="transform transition ease-in-out duration-500 sm:duration-700"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="translate-x-full"
|
||||
>
|
||||
<Dialog.Panel className="pointer-events-auto w-screen lg:max-w-md">
|
||||
<SidePanelInner title={title} closeCallback={handleClose}>
|
||||
{children}
|
||||
</SidePanelInner>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { SidePanel } from "./SidePanelOld";
|
||||
@@ -19,7 +19,7 @@ export const Tabs = ({ tabs: tabProps }: { tabs: TabType[] }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-md px-2 sm:px-0">
|
||||
<div className="w-full h-full px-2 sm:px-0 flex flex-col">
|
||||
<Tab.Group defaultIndex={3}>
|
||||
<Tab.List className="flex">
|
||||
{tabs.map((tab, idx) => (
|
||||
@@ -31,9 +31,9 @@ export const Tabs = ({ tabs: tabProps }: { tabs: TabType[] }) => {
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels>
|
||||
<Tab.Panels className="grow p-4">
|
||||
{tabs.map((tab, idx) => (
|
||||
<Tab.Panel key={idx} className="p-4 text-white focus:outline-none">
|
||||
<Tab.Panel key={idx}>
|
||||
{tab.children}
|
||||
</Tab.Panel>
|
||||
))}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Button } from "~/components/Button";
|
||||
import { ButtonLink } from "~/components/ButtonLink";
|
||||
import { TopBarHelmet } from "~/modules/TopBarHelmet";
|
||||
|
||||
import { SidePanel } from "~/modules/SidePanelOld";
|
||||
import { SidePanel } from "~/modules/SidePanel";
|
||||
import { SceneWithCube } from "./SceneWithCube";
|
||||
|
||||
export const PageCube = () => {
|
||||
@@ -64,7 +64,7 @@ export const PageCube = () => {
|
||||
<p className="text-white">Ration X: {rotationX ? rotationX : ""}</p>
|
||||
<p className="text-white">Ration Y: {rotationY ? rotationY : ""}</p>
|
||||
</div>
|
||||
<SidePanel title="Side Panel Title">
|
||||
<SidePanel>
|
||||
<p className="text-white">Side Panel Context</p>
|
||||
</SidePanel>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useReducer } from "react";
|
||||
import { useReducer, useEffect } from "react";
|
||||
|
||||
import { faSparkles } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { Button, LoadingDots } from "~/components";
|
||||
import { Button, LoadingBar, LoadingDots } from "~/components";
|
||||
import { TopBarHelmet } from "~/modules/TopBarHelmet/TopBarHelmet";
|
||||
import { SidePanel } from "~/modules/SidePanel";
|
||||
import { Controls3D } from "./comps/Controls3D";
|
||||
@@ -14,8 +14,12 @@ import { SidePanelTabs } from "./comps/SidePanelTabs";
|
||||
|
||||
import { EngineProvider } from "./contexts/EngineProvider";
|
||||
import { AppUIProvider } from "./contexts/AppUiContext";
|
||||
import { reducer, initialState, ACTION_TYPES } from "./reducer";
|
||||
import { VIEW_MODES } from "./reducer/types";
|
||||
import {
|
||||
appUiReducer,
|
||||
appUiInitialStateValues,
|
||||
APPUI_ACTION_TYPES,
|
||||
APPUI_VIEW_MODES,
|
||||
} from "./reducers";
|
||||
import { ViewSideBySide } from "./comps/ViewSideBySide";
|
||||
import {
|
||||
canDrop,
|
||||
@@ -29,19 +33,14 @@ import {
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const PageEnigmaComponent = () => {
|
||||
const [appUiState, dispatchAppUiState] = useReducer(reducer, initialState);
|
||||
useSignals();
|
||||
const { currX, currY } = currPosition.value;
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(
|
||||
() =>
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.HIDE_EDITOR_LOADER,
|
||||
}),
|
||||
500,
|
||||
);
|
||||
}, []);
|
||||
const [appUiState, dispatchAppUiState] = useReducer(
|
||||
appUiReducer,
|
||||
appUiInitialStateValues,
|
||||
);
|
||||
useSignals();
|
||||
|
||||
const lowerHeight = timelineHeight.value;
|
||||
return (
|
||||
@@ -62,7 +61,7 @@ export const PageEnigmaComponent = () => {
|
||||
<div className="relative w-full overflow-hidden bg-transparent">
|
||||
<div
|
||||
className={
|
||||
appUiState.viewMode === VIEW_MODES.SIDE_BY_SIDE
|
||||
appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? "invisible"
|
||||
: ""
|
||||
}
|
||||
@@ -83,15 +82,23 @@ export const PageEnigmaComponent = () => {
|
||||
<ControlsVideo />
|
||||
</div>
|
||||
</div>
|
||||
{appUiState.viewMode === VIEW_MODES.SIDE_BY_SIDE && (
|
||||
{appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE && (
|
||||
<ViewSideBySide />
|
||||
)}
|
||||
<LoadingDots
|
||||
className="absolute left-0 top-0"
|
||||
show={appUiState.showEditorLoader.isShow}
|
||||
isShowing={appUiState.showEditorLoader.isShowing}
|
||||
type="bricks"
|
||||
message={appUiState.showEditorLoader.message}
|
||||
transition
|
||||
/>
|
||||
<LoadingBar
|
||||
id="editor-loading-bar"
|
||||
wrapperClassName="absolute top-0 left-0"
|
||||
innerWrapperClassName="max-w-screen-sm"
|
||||
isShowing={appUiState.showEditorLoadingBar.isShowing}
|
||||
message={appUiState.showEditorLoadingBar.message}
|
||||
label={appUiState.showEditorLoadingBar.label}
|
||||
progress={appUiState.showEditorLoadingBar.progress}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useContext, useState } from "react";
|
||||
import { Button, H4, Input } from "~/components";
|
||||
import { Button, Input } from "~/components";
|
||||
import { ButtonDialogue } from "~/modules/ButtonDialogue";
|
||||
import { AppUiContext } from "../../contexts/AppUiContext";
|
||||
import { EngineContext } from "../../contexts/EngineContext";
|
||||
import { APPUI_ACTION_TYPES } from "../../reducers";
|
||||
|
||||
export const ControlsTopButtons = () => {
|
||||
const editorEngine = useContext(EngineContext);
|
||||
const [ appUiState, dispatchAppUiState ] = useContext(AppUiContext);
|
||||
const [sceneName, setSceneName] = useState<string>("");
|
||||
const [sceneToken, setSceneToken] = useState<string>("");
|
||||
|
||||
@@ -18,21 +21,74 @@ export const ControlsTopButtons = () => {
|
||||
editorEngine?.loadScene(sceneToken);
|
||||
};
|
||||
|
||||
|
||||
const handleButtonCameraView = () => {
|
||||
editorEngine?.switchCameraView();
|
||||
};
|
||||
|
||||
const handleButtonPlayBack = () => {
|
||||
editorEngine?.startPlayback();
|
||||
};
|
||||
|
||||
const handleButtonTest = () => {
|
||||
if(appUiState?.showEditorLoadingBar.isShowing){
|
||||
//TO GO TO A HUNDRED AND DISAPPER
|
||||
dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR,
|
||||
payload:{
|
||||
showEditorLoadingBar: {
|
||||
progress: 100,
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.HIDE_EDITOR_LOADINGBAR,
|
||||
});
|
||||
}, 1000);
|
||||
//END :TO GO TO A HUNDRED AND DISAPPER
|
||||
|
||||
//EAMPLE OF CHANING LOADING MESSAGE
|
||||
// dispatchAppUiState({
|
||||
// type: APPUI_ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR,
|
||||
// payload:{
|
||||
// showEditorLoadingBar: {
|
||||
// message: "new message",
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}else{
|
||||
dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.SHOW_EDITOR_LOADINGBAR,
|
||||
payload:{
|
||||
showEditorLoadingBar: {
|
||||
message: 'display of LoadingBar triggered by Test Button',
|
||||
progress: 10,
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//const handleButtonRender = () => {
|
||||
// editorEngine?.take_timeline_cam_clip();
|
||||
//};
|
||||
|
||||
const handleButtonRender = () => {
|
||||
editorEngine?.generateVideo();
|
||||
};
|
||||
|
||||
const handleButtonTakeFrame = () => {
|
||||
editorEngine?.take_timeline_cam_clip();
|
||||
};
|
||||
|
||||
// const handleButtonCameraView = () => {
|
||||
// editorEngine?.change_camera_view();
|
||||
// };
|
||||
|
||||
const handleButtonPlayBack = () => {
|
||||
editorEngine?.start_playback();
|
||||
};
|
||||
const handleButtonLoad = () => {
|
||||
|
||||
|
||||
};
|
||||
const handleButtonRender = () => {
|
||||
|
||||
};
|
||||
const handleButtonPlay = () => {};
|
||||
const handleButtonLoad = () => {};
|
||||
// const handleButtonRender = () => {};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 pl-3 pt-3">
|
||||
@@ -88,14 +144,24 @@ export const ControlsTopButtons = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" onClick={handleButtonPlayBack}>
|
||||
<Button variant="secondary" onClick={handleButtonCameraView}>
|
||||
Toggle Camera View
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={handleButtonLoad}>Load</Button>
|
||||
|
||||
<Button onClick={handleButtonLoad}>Test Button</Button>
|
||||
<Button onClick={handleButtonTakeFrame}>Take Frame</Button>
|
||||
<Button onClick={handleButtonRender}>Render</Button>
|
||||
<Button onClick={handleButtonPlay}>Play</Button>
|
||||
{/* <Button onClick={handleButtonLoad}>Load</Button>
|
||||
<Button onClick={handleButtonRender}>Render</Button> */}
|
||||
<Button
|
||||
onClick={handleButtonTest}
|
||||
className="bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary"
|
||||
style={{zIndex:9001}}
|
||||
>
|
||||
Test
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useContext } from "react";
|
||||
|
||||
import {
|
||||
faBackwardFast,
|
||||
faBackwardStep,
|
||||
@@ -5,9 +7,12 @@ import {
|
||||
faForwardStep,
|
||||
faPlay,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { EngineContext } from "../../contexts/EngineContext";
|
||||
import { ButtonIcon } from "~/components";
|
||||
|
||||
export const ControlsVideo = () => {
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const handleBackwardFast = () => {
|
||||
console.log("Controls Video: Backward-Fast clicked");
|
||||
};
|
||||
@@ -16,6 +21,8 @@ export const ControlsVideo = () => {
|
||||
};
|
||||
const handlePlay = () => {
|
||||
console.log("Controls Video: Play clicked");
|
||||
editorEngine?.startPlayback();
|
||||
|
||||
};
|
||||
const handleForwardStep = () => {
|
||||
console.log("Controls Video: Forward-Step clicked");
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { faSparkles } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { TopBarHelmet } from "~/modules/TopBarHelmet/TopBarHelmet";
|
||||
import { Button } from "~/components";
|
||||
import { EngineContext } from "../contexts/EngineContext";
|
||||
|
||||
export const Helmet = () => {
|
||||
const [helmetKey, setHelmetKey] = useState<number>(Date.now());
|
||||
const engineContext = useContext(EngineContext);
|
||||
|
||||
const handleGenerateVideo = () => {
|
||||
engineContext?.generateVideo();
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
setHelmetKey(Date.now());
|
||||
},[engineContext]);
|
||||
|
||||
return (
|
||||
<TopBarHelmet key={helmetKey}>
|
||||
<Button icon={faSparkles} onClick={handleGenerateVideo}>Generate Movie</Button>
|
||||
</TopBarHelmet>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export const PreviewEngineCamera = ()=>{
|
||||
|
||||
const [showLoader, setShowLoader] = useState<boolean>(true);
|
||||
useEffect(()=>{
|
||||
setTimeout(()=>setShowLoader(false), 5000);
|
||||
setTimeout(()=>setShowLoader(false), 500);
|
||||
},[]);
|
||||
return (
|
||||
<div
|
||||
@@ -29,7 +29,7 @@ export const PreviewEngineCamera = ()=>{
|
||||
src="/resources/uiAssets/video_player_placeholder.gif"
|
||||
/>
|
||||
<div className="absolute w-full h-full top-0 left-0">
|
||||
<LoadingDotsTyping show={showLoader} transition/>
|
||||
<LoadingDotsTyping isShowing={showLoader}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
} from "~/components"
|
||||
|
||||
import { AppUiContext } from '../../contexts/AppUiContext';
|
||||
import { ACTION_TYPES } from '../../reducer';
|
||||
import { VIEW_MODES } from '../../reducer/types';
|
||||
import { APPUI_ACTION_TYPES } from '../../reducers';
|
||||
import { APPUI_VIEW_MODES } from '../../reducers';
|
||||
|
||||
export const TabStylization = ()=>{
|
||||
const [appUiState, dispatchAppUiState] = useContext(AppUiContext);
|
||||
@@ -23,16 +23,16 @@ export const TabStylization = ()=>{
|
||||
const handleChangeViewMode = ()=>{
|
||||
if(dispatchAppUiState!==null){
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.ON_CHANGE_VIEW_MODE,
|
||||
type: APPUI_ACTION_TYPES.ON_CHANGE_VIEW_MODE,
|
||||
payload: {
|
||||
viewMode: appUiState?.viewMode === VIEW_MODES.SIDE_BY_SIDE
|
||||
? VIEW_MODES.EDITOR : VIEW_MODES.SIDE_BY_SIDE,
|
||||
viewMode: appUiState?.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? APPUI_VIEW_MODES.EDITOR : APPUI_VIEW_MODES.SIDE_BY_SIDE,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return(
|
||||
<>
|
||||
<div className="gap-2 w-full h-full overflow-scroll">
|
||||
<H4>Select Base Stylizer</H4>
|
||||
<div className="flex gap-2 my-2">
|
||||
<ItemPicker
|
||||
@@ -77,11 +77,11 @@ export const TabStylization = ()=>{
|
||||
<div className="flex gap-2 mt-6 justify-center">
|
||||
<Button onClick={handleChangeViewMode}>
|
||||
{
|
||||
appUiState?.viewMode === VIEW_MODES.SIDE_BY_SIDE
|
||||
appUiState?.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? "Back to Scene" : "Preview Side by Side"
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -17,8 +17,11 @@ import {
|
||||
timelineHeight,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { useQueueHandler } from "~/pages/PageEnigma/comps/Timeline/utils/useQueueHandler";
|
||||
import { EngineContext } from "../../contexts/EngineContext";
|
||||
|
||||
export const Timeline = () => {
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const {
|
||||
characters,
|
||||
objects,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useEffect, useState, } from "react";
|
||||
|
||||
import { LoadingDotsBricks } from "~/components";
|
||||
|
||||
@@ -42,7 +39,7 @@ export const ViewSideBySide = ()=>{
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-0 left-0 w-full h-full">
|
||||
<LoadingDotsBricks show={showLoader} transition/>
|
||||
<LoadingDotsBricks isShowing={showLoader}/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { createContext, Dispatch, ReactNode } from "react";
|
||||
import { State, Action } from "../reducer";
|
||||
import { AppUiState, AppUiAction } from "../reducers";
|
||||
|
||||
export const AppUiContext = createContext<[State|null, Dispatch<Action>|null]>([null,null]);
|
||||
export const AppUiContext = createContext<[
|
||||
AppUiState | null,
|
||||
Dispatch<AppUiAction>
|
||||
]>([ null ,()=>{}]);
|
||||
|
||||
export const AppUIProvider = ({
|
||||
value,
|
||||
children,
|
||||
}:{
|
||||
value:[State, Dispatch<Action>],
|
||||
value:[AppUiState, Dispatch<AppUiAction>],
|
||||
children: ReactNode
|
||||
})=>{
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import Editor from "~/pages/PageEnigma/js/editor";
|
||||
|
||||
import { EngineContext } from "./EngineContext";
|
||||
import { AppUiContext } from "./AppUiContext";
|
||||
|
||||
import { TrackContext } from "./TrackContext/TrackContext";
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
@@ -13,6 +13,8 @@ interface Props {
|
||||
export const EngineProvider = ({ children }: Props) => {
|
||||
const [editor, setEditor] = useState<Editor | null>(null);
|
||||
const [appUiState, dispatchAppUiState] = useContext(AppUiContext);
|
||||
// To talk to react land.
|
||||
const trackContext = useContext(TrackContext)
|
||||
|
||||
useEffect(() => {
|
||||
//componentDidMount
|
||||
|
||||
@@ -76,8 +76,26 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
{
|
||||
id: "ani-id1",
|
||||
offset: 0,
|
||||
length: 124,
|
||||
name: "ani 11",
|
||||
length: 25,
|
||||
name: "Sit",
|
||||
},
|
||||
{
|
||||
id: "ani-id2",
|
||||
offset: 0,
|
||||
length: 25,
|
||||
name: "Idle",
|
||||
},
|
||||
{
|
||||
id: "ani-id3",
|
||||
offset: 0,
|
||||
length: 25,
|
||||
name: "Stand",
|
||||
},
|
||||
{
|
||||
id: "ani-id4",
|
||||
offset: 0,
|
||||
length: 25,
|
||||
name: "Walk",
|
||||
},
|
||||
]);
|
||||
setAudioClips([]);
|
||||
|
||||
@@ -74,19 +74,16 @@ export class AnimationClip {
|
||||
this.clip_action.play();
|
||||
}
|
||||
}
|
||||
console.log("Play")
|
||||
}
|
||||
}
|
||||
|
||||
step(deltatime: number) {
|
||||
if (this.mixer == null) { return; }
|
||||
this.mixer?.update(deltatime);
|
||||
console.log("Update")
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.mixer?.stopAllAction();
|
||||
console.log("stop.")
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
|
||||
@@ -29,10 +29,11 @@ export class TransformClip {
|
||||
this.looping = false;
|
||||
}
|
||||
|
||||
step(object: THREE.Object3D) {
|
||||
//if (this.step_frame >= 60/this.length && this.looping == false) { return; } // Reached max frames.
|
||||
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.step_frame += 1;
|
||||
|
||||
let time_frame = (this.step_frame/this.length);
|
||||
|
||||
|
||||
@@ -8,6 +8,21 @@ import { TimelineDataState } from "./timeline";
|
||||
* Storyteller Studio API Manager
|
||||
* The source of truth of all these media items is the database in the cloud
|
||||
*/
|
||||
class MediaFile {
|
||||
public_bucket_path: string;
|
||||
media_type: string;
|
||||
media_token: string;
|
||||
|
||||
constructor(
|
||||
public_bucket_path: string,
|
||||
media_type: string,
|
||||
media_token: string,
|
||||
) {
|
||||
this.media_token = media_token;
|
||||
this.public_bucket_path = public_bucket_path;
|
||||
this.media_type = media_type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is designed to surface user customer facing messages as errors.
|
||||
@@ -50,33 +65,41 @@ class APIManager {
|
||||
public async saveSceneState(
|
||||
scene: THREE.Scene,
|
||||
scene_name: string,
|
||||
scene_glb_media_file_token: string | null = null,
|
||||
scene_media_file_token: string | null = null,
|
||||
timeline_state: TimelineDataState | null,
|
||||
timeline_state: TimelineDataState | null = null,
|
||||
): Promise<APIManagerResponseSuccess> {
|
||||
const file = await this.gltfExport(scene);
|
||||
|
||||
// will overwrite the scene on db if token exists
|
||||
const upload_glb_response = await this.uploadGLB(
|
||||
file,
|
||||
scene_media_file_token,
|
||||
scene_glb_media_file_token,
|
||||
);
|
||||
const glb_media_file_token = upload_glb_response["media_file_token"];
|
||||
const result_scene_glb_media_file_token =
|
||||
upload_glb_response["media_file_token"];
|
||||
|
||||
// now write the scene
|
||||
const save_scene_timeline_response = await this.saveSceneAndTimeLineState(
|
||||
glb_media_file_token,
|
||||
result_scene_glb_media_file_token,
|
||||
scene_media_file_token,
|
||||
scene_name,
|
||||
timeline_state,
|
||||
);
|
||||
|
||||
const result_scene_media_file_token =
|
||||
save_scene_timeline_response["media_file_token"];
|
||||
|
||||
const data = { media_file_token: result_scene_media_file_token };
|
||||
const data = {
|
||||
scene_glb_media_file_token: result_scene_glb_media_file_token,
|
||||
scene_media_file_token: result_scene_media_file_token,
|
||||
};
|
||||
return new APIManagerResponseSuccess("Scene Saved", data);
|
||||
}
|
||||
|
||||
|
||||
public async loadSceneState(scene_media_file_token: string | null): Promise<APIManagerResponseSuccess> {
|
||||
public async loadSceneState(
|
||||
scene_media_file_token: string | null,
|
||||
): Promise<APIManagerResponseSuccess> {
|
||||
const api_base_url = "https://api.fakeyou.com";
|
||||
const url = `${api_base_url}/v1/media_files/file/${scene_media_file_token}`;
|
||||
const response = await fetch(url);
|
||||
@@ -101,19 +124,28 @@ class APIManager {
|
||||
|
||||
console.log(`loadSceneState: ${JSON.stringify(json_result)}`);
|
||||
|
||||
const scene_glb_media_file_id: string = json_result["glb_media_file_id"];
|
||||
|
||||
const media_bucket_path = await this.getMediaFile(scene_glb_media_file_id);
|
||||
console.log(`GLB ${media_bucket_path}`)
|
||||
const scene_glb_media_file_token: string =
|
||||
json_result["scene_glb_media_file_token"];
|
||||
|
||||
const media_bucket_path = await this.getMediaFile(
|
||||
scene_glb_media_file_token,
|
||||
);
|
||||
console.log(`GLB ${media_bucket_path}`);
|
||||
let glbLoader = new GLTFLoader();
|
||||
// promisify this
|
||||
const loadGlb = (bucket_path: string): Promise<APIManagerResponseSuccess> => {
|
||||
const loadGlb = (
|
||||
bucket_path: string,
|
||||
): Promise<APIManagerResponseSuccess> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
glbLoader.load(bucket_path, (glb) => {
|
||||
if (glb) {
|
||||
const scene: THREE.Scene = glb.scene;
|
||||
const data = { "media_file_token": scene_glb_media_file_id , "scene": scene };
|
||||
console.log(`Data: ${data}`)
|
||||
const data = {
|
||||
scene_glb_media_file_token: scene_glb_media_file_token,
|
||||
scene_media_file_token: scene_media_file_token,
|
||||
scene: scene,
|
||||
};
|
||||
console.log(`Data: ${data}`);
|
||||
resolve(new APIManagerResponseSuccess("Success Loaded", data));
|
||||
} else {
|
||||
throw new APIManagerResponseError("Failed to Load GLB Scene");
|
||||
@@ -130,7 +162,7 @@ class APIManager {
|
||||
* @param media_file_token
|
||||
* @returns
|
||||
*/
|
||||
private async getMediaFile(media_file_token: string): Promise<string> {
|
||||
public async getMediaFile(media_file_token: string): Promise<string> {
|
||||
let api_base_url = "https://api.fakeyou.com";
|
||||
let url = `${api_base_url}/v1/media_files/file/${media_file_token}`;
|
||||
let response = await fetch(url);
|
||||
@@ -158,16 +190,16 @@ class APIManager {
|
||||
|
||||
private async uploadGLB(
|
||||
file: File,
|
||||
scene_media_file_token: string | null,
|
||||
scene_glb_media_file_token: string | null,
|
||||
): Promise<string> {
|
||||
const url = `${this.baseUrl}/v1/media_files/write/engine_asset`;
|
||||
let uuid = uuidv4();
|
||||
const form_data = new FormData();
|
||||
form_data.append("uuid_idempotency_token", uuid);
|
||||
|
||||
// update existing scene otherwise create new scene and use it's media_file_id
|
||||
if (scene_media_file_token != null) {
|
||||
form_data.append("media_file_token", scene_media_file_token);
|
||||
// update existing scene otherwise create new glb scene and use it's media_file_id
|
||||
if (scene_glb_media_file_token != null) {
|
||||
form_data.append("media_file_token", scene_glb_media_file_token);
|
||||
}
|
||||
|
||||
form_data.append("file", file);
|
||||
@@ -194,6 +226,7 @@ class APIManager {
|
||||
}
|
||||
|
||||
private async saveSceneAndTimeLineState(
|
||||
scene_glb_media_file_token: string | null,
|
||||
scene_media_file_token: string | null,
|
||||
scene_file_name: string,
|
||||
timeline_state: TimelineDataState | null, // only for now.
|
||||
@@ -201,9 +234,12 @@ class APIManager {
|
||||
const url = `${this.baseUrl}/v1/media_files/write/scene_file`;
|
||||
let uuid = uuidv4();
|
||||
|
||||
console.log(
|
||||
`Saving Scene scene_media_file_token:${scene_media_file_token} | scene_glb_media_file_token:${scene_glb_media_file_token}`,
|
||||
);
|
||||
// turn json into a blob
|
||||
const scene_schema = {
|
||||
glb_media_file_id: scene_media_file_token,
|
||||
scene_glb_media_file_token: scene_glb_media_file_token,
|
||||
scene_name: scene_file_name,
|
||||
timeline: { objects: [] },
|
||||
};
|
||||
@@ -214,6 +250,12 @@ class APIManager {
|
||||
const form_data = new FormData();
|
||||
|
||||
form_data.append("uuid_idempotency_token", uuid);
|
||||
|
||||
// overrwrites the scene json file and edits.
|
||||
if (scene_media_file_token != null) {
|
||||
form_data.append("media_file_token", scene_media_file_token);
|
||||
}
|
||||
|
||||
form_data.append("file", blob, file_name);
|
||||
form_data.append("source", "file");
|
||||
form_data.append("type", "scene_json");
|
||||
@@ -229,7 +271,7 @@ class APIManager {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new APIManagerResponseError("Failed to Send GLB Data");
|
||||
throw new APIManagerResponseError("Failed to Save Scene.");
|
||||
} else {
|
||||
const json_data = await response.json();
|
||||
// example response {"success":true,"media_file_token":"m_r1ztnn501g2rn0vv2np08nd6zy2fvt"}
|
||||
@@ -271,10 +313,25 @@ class APIManager {
|
||||
}
|
||||
}
|
||||
|
||||
public async batchGetMedia(media_tokens: []): Promise<string> {
|
||||
return "";
|
||||
public async getMediaBatch(media_tokens: string[]): Promise<MediaFile[]> {
|
||||
const tokens = media_tokens;
|
||||
const url = new URL(`${this.baseUrl}/v1/media_files/batch`);
|
||||
tokens.forEach((token) => url.searchParams.append("tokens", token));
|
||||
const result = await fetch(url)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
const result = data["media_files"].map((element) => {
|
||||
return new MediaFile(
|
||||
element["public_bucket_path"],
|
||||
element["media_type"],
|
||||
element["token"],
|
||||
);
|
||||
});
|
||||
return result;
|
||||
})
|
||||
.catch((error) => console.error("Error:", error));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default APIManager;
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as THREE from "three";
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
import { FreeCam } from "./free_cam";
|
||||
import { TransformControls } from "three/addons/controls/TransformControls.js";
|
||||
import Scene from "./scene.js";
|
||||
import APIManager from "./api_manager.js";
|
||||
@@ -11,14 +12,16 @@ import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js";
|
||||
import { SAOPass } from "three/addons/postprocessing/SAOPass.js";
|
||||
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
|
||||
import { BokehPass } from "three/addons/postprocessing/BokehPass.js";
|
||||
import { createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg";
|
||||
import { FFmpeg, createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg";
|
||||
import AudioEngine from "./audio_engine.js";
|
||||
import TransformEngine from "./transform_engine.js";
|
||||
import { TimeLine, TimelineDataState } from "./timeline.js";
|
||||
import { ClipUI } from "../datastructures/clips/clip_offset.js";
|
||||
import { LipSyncEngine } from "./lip_sync_engine.js";
|
||||
import { AnimationEngine } from "./animation_engine.js";
|
||||
import { ACTION_TYPES } from "../reducer";
|
||||
|
||||
import { PointerLockControls } from "three/addons/controls/PointerLockControls.js";
|
||||
import { APPUI_ACTION_TYPES } from "../reducers";
|
||||
|
||||
class EditorState {
|
||||
// {
|
||||
@@ -40,13 +43,15 @@ class Editor {
|
||||
version: number;
|
||||
activeScene: Scene;
|
||||
camera: any;
|
||||
render_camera: any;
|
||||
renderer: THREE.WebGLRenderer | undefined;
|
||||
clock: THREE.Clock | undefined;
|
||||
canvReference: any;
|
||||
composer: EffectComposer | undefined;
|
||||
effectFXAA: EffectComposer | undefined;
|
||||
outlinePass: OutlinePass | undefined;
|
||||
last_cam_pos: THREE.Vector3 | undefined;
|
||||
last_cam_pos: THREE.Vector3;
|
||||
last_cam_rot: THREE.Euler;
|
||||
saoPass: SAOPass | undefined;
|
||||
outputPass: OutputPass | undefined;
|
||||
bloomPass: UnrealBloomPass | undefined;
|
||||
@@ -61,7 +66,8 @@ class Editor {
|
||||
rendering: boolean;
|
||||
api_manager: APIManager;
|
||||
stats: any;
|
||||
orbit: OrbitControls | undefined;
|
||||
cameraViewControls: FreeCam | undefined;
|
||||
orbitControls: OrbitControls | undefined;
|
||||
locked: boolean;
|
||||
capturer: any;
|
||||
frame_buffer: any;
|
||||
@@ -77,13 +83,19 @@ class Editor {
|
||||
animation_engine: AnimationEngine;
|
||||
timeline: TimeLine;
|
||||
current_frame: number;
|
||||
|
||||
|
||||
lockControls: PointerLockControls | undefined;
|
||||
cam_obj: THREE.Object3D | undefined;
|
||||
renderPass: RenderPass | undefined;
|
||||
|
||||
current_scene_media_token_id: string | null;
|
||||
camera_person_mode: boolean;
|
||||
current_scene_media_token: string | null;
|
||||
current_scene_glb_media_token: string | null;
|
||||
|
||||
can_initialize: boolean;
|
||||
dispatchAppUiState: any; // todo figure out the type
|
||||
render_width: number;
|
||||
render_height: number;
|
||||
|
||||
// Default params.
|
||||
constructor() {
|
||||
console.log(
|
||||
@@ -106,13 +118,17 @@ class Editor {
|
||||
this.activeScene = new Scene("" + this.version);
|
||||
this.activeScene.initialize();
|
||||
this.camera;
|
||||
this.render_camera;
|
||||
this.renderer;
|
||||
this.clock;
|
||||
this.canvReference = null;
|
||||
this.cam_obj;
|
||||
this.composer;
|
||||
this.effectFXAA;
|
||||
this.outlinePass;
|
||||
this.last_cam_pos;
|
||||
this.last_cam_pos = new THREE.Vector3(0, 0, 0);
|
||||
this.last_cam_rot = new THREE.Euler(0, 0, 0);
|
||||
this.lockControls;
|
||||
this.saoPass;
|
||||
this.outputPass;
|
||||
this.bloomPass;
|
||||
@@ -130,7 +146,9 @@ class Editor {
|
||||
this.api_manager = new APIManager();
|
||||
// Debug & Movement.
|
||||
this.stats = null;
|
||||
this.orbit;
|
||||
this.cameraViewControls;
|
||||
this.orbitControls;
|
||||
this.camera_person_mode = false;
|
||||
this.locked = false;
|
||||
// Recording params.
|
||||
this.capturer = null;
|
||||
@@ -144,6 +162,9 @@ class Editor {
|
||||
this.max_length = 10;
|
||||
// Audio Engine Test.
|
||||
|
||||
this.render_width = 1280;
|
||||
this.render_height = 720;
|
||||
|
||||
this.audio_engine = new AudioEngine();
|
||||
this.transform_engine = new TransformEngine(this.version);
|
||||
this.lipsync_engine = new LipSyncEngine();
|
||||
@@ -158,15 +179,26 @@ class Editor {
|
||||
);
|
||||
|
||||
this.current_frame = 0;
|
||||
this.current_scene_media_token_id = null;
|
||||
|
||||
// Dispatcher
|
||||
this.dispatchAppUiState = null;
|
||||
|
||||
// Scene State
|
||||
this.current_scene_media_token = null;
|
||||
this.current_scene_glb_media_token = null;
|
||||
}
|
||||
|
||||
initialize(config:any) {
|
||||
initialize(config: any) {
|
||||
//setup reactland Callbacks
|
||||
this.dispatchAppUiState = config.dispatchAppUiState;
|
||||
|
||||
// this is called by the parent for some reason
|
||||
// this.dispatchAppUiState({
|
||||
// type: APPUI_ACTION_TYPES.SHOW_EDITOR_LOADER
|
||||
// });
|
||||
|
||||
if (this.can_initialize == false) {
|
||||
console.log(
|
||||
"Editor Already Initialized",
|
||||
);
|
||||
console.log("Editor Already Initialized");
|
||||
return;
|
||||
}
|
||||
this.can_initialize = false;
|
||||
@@ -181,13 +213,16 @@ class Editor {
|
||||
this.camera.position.z = 3;
|
||||
this.camera.position.y = 3;
|
||||
this.camera.position.x = -3;
|
||||
|
||||
this.render_camera = new THREE.PerspectiveCamera(70, width / height, 0.1, 50);
|
||||
|
||||
// Base WebGL render and clock for delta time.
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
canvas: this.canvReference,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
|
||||
|
||||
this.renderer.shadowMap.enabled = true;
|
||||
this.clock = new THREE.Clock();
|
||||
// Resizes the renderer.
|
||||
@@ -196,13 +231,33 @@ class Editor {
|
||||
window.addEventListener("resize", this.onWindowResize.bind(this));
|
||||
this._configure_post_pro();
|
||||
// Controls and movement.
|
||||
this.orbit = new OrbitControls(this.camera, this.renderer.domElement);
|
||||
|
||||
this.lockControls = new PointerLockControls(
|
||||
this.camera,
|
||||
this.renderer.domElement,
|
||||
);
|
||||
this.cameraViewControls = new FreeCam(
|
||||
this.camera,
|
||||
this.renderer.domElement,
|
||||
);
|
||||
this.cameraViewControls.movementSpeed = 1;
|
||||
this.cameraViewControls.domElement = this.renderer.domElement;
|
||||
this.cameraViewControls.rollSpeed = Math.PI / 24;
|
||||
this.cameraViewControls.autoForward = false;
|
||||
this.cameraViewControls.dragToLook = true;
|
||||
this.cameraViewControls.enabled = false;
|
||||
|
||||
this.orbitControls = new OrbitControls(
|
||||
this.camera,
|
||||
this.renderer.domElement,
|
||||
);
|
||||
|
||||
this.control = new TransformControls(this.camera, this.renderer.domElement);
|
||||
// OnClick and MouseMove events.
|
||||
window.addEventListener("mousemove", this.onMouseMove.bind(this), false);
|
||||
window.addEventListener("click", this.onMouseClick.bind(this), false);
|
||||
// Base control and debug stuff remove debug in prod.
|
||||
this._initialize_control();
|
||||
this._initializeControl();
|
||||
// Resets canvas size.
|
||||
this.onWindowResize();
|
||||
// Creates the main update loop.
|
||||
@@ -212,30 +267,92 @@ class Editor {
|
||||
|
||||
this._test_demo();
|
||||
|
||||
|
||||
this.renderer.domElement.addEventListener("mousedown", this.onMouseDown.bind(this), false);
|
||||
this.renderer.domElement.addEventListener("mouseup", this.onMouseUp.bind(this), false);
|
||||
this.renderer.domElement.addEventListener("onContextMenu", this.onContextMenu.bind(this), false);
|
||||
|
||||
this.cam_obj = this.activeScene.get_object_by_name("::CAM::");
|
||||
if (this.cam_obj) {
|
||||
this.add_transform_clip_base("Camera Object", this.cam_obj, 0, 150)
|
||||
}
|
||||
|
||||
// saving state of the scene
|
||||
this.current_scene_media_token_id = null;
|
||||
//setup reactland Callbacks
|
||||
this.dispatchAppUiState = config.dispatchAppUiState
|
||||
this.current_scene_media_token = null;
|
||||
this.current_scene_glb_media_token = null;
|
||||
|
||||
this.renderer.domElement.addEventListener(
|
||||
"mousedown",
|
||||
this.onMouseDown.bind(this),
|
||||
false,
|
||||
);
|
||||
this.renderer.domElement.addEventListener(
|
||||
"mouseup",
|
||||
this.onMouseUp.bind(this),
|
||||
false,
|
||||
);
|
||||
this.renderer.domElement.addEventListener(
|
||||
"onContextMenu",
|
||||
this.onContextMenu.bind(this),
|
||||
false,
|
||||
);
|
||||
|
||||
this.cam_obj = this.activeScene.get_object_by_name("::CAM::");
|
||||
if (this.cam_obj) {
|
||||
this.add_transform_clip_base("Camera Object", this.cam_obj, 0, 150);
|
||||
}
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR,
|
||||
payload: {
|
||||
showEditorLoadingBar: {
|
||||
progress: 100,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.HIDE_EDITOR_LOADINGBAR,
|
||||
});
|
||||
}
|
||||
|
||||
// Token comes in from the front end to load the scene from the site.
|
||||
public async loadScene(scene_token: string) {
|
||||
public async testBatchRequest() {
|
||||
const result = await this.api_manager.getMediaBatch(["m_8fmp9hrvsqcryzka1fra597kg42s50","m_z4jzbst3xfh64h0qn4bqh4afenfps9"]);
|
||||
console.log(result);
|
||||
}
|
||||
public async loadScene(scene_media_token: string) {
|
||||
this.dispatchAppUiState({
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADER
|
||||
type: APPUI_ACTION_TYPES.SHOW_EDITOR_LOADER,
|
||||
});
|
||||
|
||||
if (scene_token != null) {
|
||||
this.current_scene_media_token_id = scene_token;
|
||||
if (scene_media_token != null) {
|
||||
this.current_scene_media_token = scene_media_token;
|
||||
}
|
||||
|
||||
const load_scene_state_response = await this.api_manager.loadSceneState(
|
||||
this.current_scene_media_token_id,
|
||||
this.current_scene_media_token,
|
||||
);
|
||||
|
||||
console.log(load_scene_state_response)
|
||||
console.log(load_scene_state_response);
|
||||
if (load_scene_state_response.data == null) {
|
||||
console.log("load_scene_state_response Missing Data");
|
||||
return;
|
||||
}
|
||||
|
||||
const loaded_scene = load_scene_state_response.data["scene"];
|
||||
|
||||
if (load_scene_state_response.data == null) { return; }
|
||||
// Load these so you can rewrite the scene glb using it's token.
|
||||
this.current_scene_media_token =
|
||||
load_scene_state_response.data["scene_media_file_token"];
|
||||
this.current_scene_glb_media_token =
|
||||
load_scene_state_response.data["scene_glb_media_file_token"];
|
||||
|
||||
console.log(
|
||||
`loadScene => SceneMediaToken:${this.current_scene_media_token} SceneGLBMediaToken:${this.current_scene_glb_media_token}`,
|
||||
);
|
||||
|
||||
const loaded_scene = load_scene_state_response.data["scene"]
|
||||
this.current_scene_media_token_id = load_scene_state_response.data["media_file_token"]
|
||||
this.activeScene.scene.children = loaded_scene.children;
|
||||
|
||||
this.activeScene.scene.children.forEach((child: THREE.Object3D) => {
|
||||
@@ -252,45 +369,64 @@ class Editor {
|
||||
});
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: ACTION_TYPES.HIDE_EDITOR_LOADER
|
||||
type: APPUI_ACTION_TYPES.HIDE_EDITOR_LOADER,
|
||||
});
|
||||
}
|
||||
|
||||
public removeTransformControls() {
|
||||
if (this.control == undefined) { return };
|
||||
if (this.outlinePass == undefined) { return };
|
||||
|
||||
this.last_selected = this.selected;
|
||||
this.control.detach();
|
||||
this.activeScene.scene.remove(this.control);
|
||||
this.outlinePass.selectedObjects = [];
|
||||
}
|
||||
|
||||
public async saveScene(name: string) {
|
||||
// remove controls when saving scene.
|
||||
this.removeTransformControls();
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADER
|
||||
type: APPUI_ACTION_TYPES.SHOW_EDITOR_LOADER,
|
||||
});
|
||||
console.log(
|
||||
`saveScene => SceneMediaToken:${this.current_scene_media_token} SceneGLBMediaToken:${this.current_scene_glb_media_token}`,
|
||||
);
|
||||
|
||||
const result = await this.api_manager.saveSceneState(
|
||||
this.activeScene.scene,
|
||||
name,
|
||||
this.current_scene_media_token_id,
|
||||
this.current_scene_glb_media_token,
|
||||
this.current_scene_media_token,
|
||||
new TimelineDataState(),
|
||||
)
|
||||
);
|
||||
|
||||
const scene_media_token_id = result.data["scene_media_token_id"]
|
||||
if (scene_media_token_id != null) {
|
||||
this.current_scene_media_token_id = scene_media_token_id
|
||||
if (result.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scene_media_token = result.data["scene_media_file_token"];
|
||||
if (scene_media_token != null) {
|
||||
this.current_scene_media_token = scene_media_token;
|
||||
}
|
||||
const scene_glb_media_token = result.data["scene_glb_media_file_token"];
|
||||
if (scene_glb_media_token != null) {
|
||||
this.current_scene_glb_media_token = scene_glb_media_token;
|
||||
}
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: ACTION_TYPES.HIDE_EDITOR_LOADER
|
||||
type: APPUI_ACTION_TYPES.HIDE_EDITOR_LOADER,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This cleans up the transform controls
|
||||
* During saving it
|
||||
* Doesn't retain those controls.
|
||||
* @returns
|
||||
*/
|
||||
private removeTransformControls() {
|
||||
if (this.control == undefined) {
|
||||
return;
|
||||
}
|
||||
if (this.outlinePass == undefined) {
|
||||
return;
|
||||
}
|
||||
this.last_selected = this.selected;
|
||||
this.control.detach();
|
||||
this.activeScene.scene.remove(this.control);
|
||||
this.outlinePass.selectedObjects = [];
|
||||
}
|
||||
|
||||
async _serialize_timeline() {
|
||||
// note the database from the server is the source of truth for all the data.
|
||||
@@ -342,6 +478,71 @@ class Editor {
|
||||
);
|
||||
}
|
||||
|
||||
switchCameraView() {
|
||||
this.camera_person_mode = !this.camera_person_mode;
|
||||
if (this.cam_obj) {
|
||||
if (this.camera_person_mode) {
|
||||
this.last_cam_pos.copy(this.camera.position);
|
||||
this.last_cam_rot.copy(this.camera.rotation);
|
||||
|
||||
this.camera.position.copy(this.cam_obj.position);
|
||||
this.camera.rotation.copy(this.cam_obj.rotation);
|
||||
if (this.orbitControls) {
|
||||
this.orbitControls.enabled = false;
|
||||
}
|
||||
if (this.lockControls) {
|
||||
this.activeScene.scene.add(this.lockControls.getObject());
|
||||
}
|
||||
if (this.cameraViewControls) {
|
||||
this.cameraViewControls.enabled = true;
|
||||
}
|
||||
|
||||
if (this.activeScene.hot_items) {
|
||||
this.activeScene.hot_items.forEach((element) => {
|
||||
element.visible = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.camera.position.copy(this.last_cam_pos);
|
||||
this.camera.rotation.copy(this.last_cam_rot);
|
||||
if (this.orbitControls) {
|
||||
this.orbitControls.enabled = true;
|
||||
}
|
||||
if (this.lockControls) {
|
||||
this.activeScene.scene.remove(this.lockControls.getObject());
|
||||
}
|
||||
if (this.cameraViewControls) {
|
||||
this.cameraViewControls.enabled = false;
|
||||
}
|
||||
if (this.activeScene.hot_items) {
|
||||
this.activeScene.hot_items.forEach((element) => {
|
||||
element.visible = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async add_transform_clip_base(
|
||||
name: string = "New Clip",
|
||||
object: THREE.Object3D,
|
||||
start_offset: number,
|
||||
end_offset: number,
|
||||
) {
|
||||
this.timeline.addPlayableClip(
|
||||
new ClipUI(
|
||||
1.0,
|
||||
"transform",
|
||||
"clip2",
|
||||
object.uuid,
|
||||
object.uuid,
|
||||
start_offset,
|
||||
end_offset,
|
||||
),
|
||||
);
|
||||
this.transform_engine.loadObject(object.uuid, end_offset);
|
||||
}
|
||||
|
||||
async _test_demo() {
|
||||
// note the database from the server is the source of truth for all the data.
|
||||
// Test code here
|
||||
@@ -393,6 +594,18 @@ class Editor {
|
||||
400,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
this.audio_engine.loadClip("m_h33vytxs5eqqqf07nsy14qzrf9ww4v");
|
||||
|
||||
await this.timeline.addPlayableClip(new ClipUI(
|
||||
1.0,
|
||||
"audio",
|
||||
"AudioClip",
|
||||
"m_h33vytxs5eqqqf07nsy14qzrf9ww4v",
|
||||
"",
|
||||
0,
|
||||
50));
|
||||
}
|
||||
|
||||
// Configure post processing.
|
||||
@@ -455,8 +668,8 @@ class Editor {
|
||||
this.composer.addPass(this.smaaPass);
|
||||
this.composer.addPass(this.bokehPass);
|
||||
|
||||
this.outputPass = new OutputPass()
|
||||
this.composer.addPass(this.outputPass)
|
||||
this.outputPass = new OutputPass();
|
||||
this.composer.addPass(this.outputPass);
|
||||
}
|
||||
|
||||
create_parim(name: string) {
|
||||
@@ -466,6 +679,7 @@ class Editor {
|
||||
render_mode() {
|
||||
this.rendering = !this.rendering;
|
||||
this.activeScene.render_mode(this.rendering);
|
||||
console.log("works.")
|
||||
|
||||
//if (this.rendering) {
|
||||
// this._remove_post_processing()
|
||||
@@ -510,6 +724,21 @@ class Editor {
|
||||
//this.transform_engine.clips[this.test_box_uuid].reset(this.activeScene.get_object_by_uuid(this.test_box_uuid))
|
||||
}
|
||||
|
||||
take_timeline_cam_clip() {
|
||||
if (this.cam_obj == null) {
|
||||
return;
|
||||
}
|
||||
if (!this.camera_person_mode) {
|
||||
return;
|
||||
}
|
||||
this.transform_engine.addFrame(
|
||||
this.cam_obj,
|
||||
this.transform_engine.clips[this.cam_obj.uuid].length,
|
||||
);
|
||||
console.log("Camera frame taken.");
|
||||
this.activeScene.createPoint(this.cam_obj.position, false);
|
||||
}
|
||||
|
||||
// Basicly Unity 3D's update loop.
|
||||
update_loop(time: number) {
|
||||
// Updates debug stats.
|
||||
@@ -523,22 +752,27 @@ class Editor {
|
||||
|
||||
let delta_time = this.clock.getDelta();
|
||||
|
||||
// All calls that are not super important like timeline go here.
|
||||
//this.activeScene.update(delta_time)
|
||||
//this.orbit.update(0.1)
|
||||
if (this.cameraViewControls && this.camera_person_mode) {
|
||||
this.cameraViewControls.update(5 * delta_time);
|
||||
if (this.cam_obj) {
|
||||
if (this.timeline.isPlaying == false) {
|
||||
this.cam_obj.position.copy(this.camera.position);
|
||||
this.cam_obj.rotation.copy(this.camera.rotation);
|
||||
} else {
|
||||
this.camera.position.copy(this.cam_obj.position);
|
||||
this.camera.rotation.copy(this.cam_obj.rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//console.log(this.transform_engine.clips[this.test_box_uuid])
|
||||
if (this.render_camera && this.cam_obj) {
|
||||
this.render_camera.position.copy(this.cam_obj.position);
|
||||
this.render_camera.rotation.copy(this.cam_obj.rotation);
|
||||
}
|
||||
|
||||
this.timeline.update(delta_time);
|
||||
|
||||
this.render_scene();
|
||||
if (this.capturer != null) {
|
||||
this.capturer.capture(this.renderer.domElement);
|
||||
} // Record scene.
|
||||
}
|
||||
|
||||
start_playback() {
|
||||
this.timeline.isPlaying = true;
|
||||
this.renderScene();
|
||||
}
|
||||
|
||||
change_mode(type: any) {
|
||||
@@ -572,6 +806,56 @@ class Editor {
|
||||
return blob;
|
||||
}
|
||||
|
||||
async convertAudioClip(itteration: number, ffmpeg: FFmpeg, clip: ClipUI) {
|
||||
let video_og = itteration + 'tmp.mp4';
|
||||
let wav_name = itteration + 'tmp.wav';
|
||||
let new_video = (itteration + 1) + 'tmp.mp4';
|
||||
let startFrame = clip.start_offset;
|
||||
let endFrame = clip.ending_offset;
|
||||
|
||||
|
||||
if (endFrame > this.timeline.timeLineLimit) {
|
||||
endFrame = this.timeline.timeLineLimit;
|
||||
}
|
||||
if (startFrame > this.timeline.timeLineLimit) {
|
||||
startFrame = this.timeline.timeLineLimit-1;
|
||||
}
|
||||
|
||||
const startTime = startFrame / this.cap_fps;
|
||||
const endTime = endFrame / this.cap_fps;
|
||||
let end = endTime - startTime;
|
||||
|
||||
let duration = this.timeline.timeLineLimit / this.cap_fps;
|
||||
|
||||
let audioSegment = "as_" + wav_name;
|
||||
await ffmpeg.FS('writeFile', wav_name, await fetchFile(await this.api_manager.getMediaFile(clip.media_id)));
|
||||
await ffmpeg.run('-i',
|
||||
wav_name,
|
||||
'-ss', '0',
|
||||
'-to', '' + end,
|
||||
"-max_muxing_queue_size", "999999",
|
||||
audioSegment);
|
||||
|
||||
await ffmpeg.run('-i', video_og,"-max_muxing_queue_size", "999999", `${itteration}empty_tmp.wav`);
|
||||
|
||||
await ffmpeg.run(
|
||||
'-i', `${itteration}empty_tmp.wav`,
|
||||
'-i', audioSegment,
|
||||
'-filter_complex', "[1:a]adelay="+startTime*1000+"|"+startTime*1000+"[a1];[0:a][a1]amix=inputs=2[a]",
|
||||
"-map", "[a]",
|
||||
`${itteration}final_tmp.wav`)
|
||||
|
||||
await ffmpeg.run(
|
||||
'-i', video_og,
|
||||
'-i', `${itteration}final_tmp.wav`,
|
||||
'-c:v', 'copy',
|
||||
'-c:a', 'aac',
|
||||
'-map', '0:v:0',
|
||||
'-map', '1:a:0',
|
||||
'-strict', 'experimental',
|
||||
new_video)
|
||||
}
|
||||
|
||||
async stopPlayback() {
|
||||
this.render_mode();
|
||||
let ffmpeg = createFFmpeg({ log: true });
|
||||
@@ -589,40 +873,83 @@ class Editor {
|
||||
"" + this.cap_fps,
|
||||
"-i",
|
||||
"image%d.png",
|
||||
"output.mp4",
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"anullsrc", // This adds a silent audio track
|
||||
"-max_muxing_queue_size",
|
||||
"999999",
|
||||
"-c:v",
|
||||
"libx264", // Specify video codec (optional, but recommended for MP4)
|
||||
"-c:a",
|
||||
"aac", // Specify audio codec (optional, but recommended for MP4)
|
||||
"-shortest", // Ensure output duration matches the shortest stream (video or audio)
|
||||
"0tmp.mp4"
|
||||
);
|
||||
let output = await ffmpeg.FS("readFile", "output.mp4");
|
||||
|
||||
|
||||
let itteration = 0;
|
||||
|
||||
for (const clip of this.timeline.timelineItems) {
|
||||
if (clip.type == "lipsync" || clip.type == "audio") {
|
||||
await this.convertAudioClip(itteration, ffmpeg, clip);
|
||||
itteration += 1;
|
||||
}
|
||||
};
|
||||
|
||||
let output = await ffmpeg.FS("readFile", itteration + "tmp.mp4");
|
||||
// Create a Blob from the output file for downloading
|
||||
const blob = new Blob([output.buffer], { type: "video/mp4" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
await this.api_manager.uploadMedia(blob, "output.mp4");
|
||||
// Create a link to download the file
|
||||
const downloadLink = document.createElement("a");
|
||||
downloadLink.href = url;
|
||||
downloadLink.download = "output.mp4";
|
||||
downloadLink.download = "render.mp4";
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
// Clean up
|
||||
URL.revokeObjectURL(url);
|
||||
document.body.removeChild(downloadLink);
|
||||
|
||||
|
||||
let data = await this.api_manager.uploadMedia(blob, "tmp.wav");
|
||||
console.log(data);
|
||||
// Create a link to download the file
|
||||
|
||||
}
|
||||
|
||||
generateVideo() {
|
||||
console.log("Generating video...");
|
||||
if (this.rendering) { return; }
|
||||
this.startPlayback();
|
||||
this._initializeRecording();
|
||||
this.rendering = true;
|
||||
this.activeScene.render_mode(this.rendering);
|
||||
if (this.activeScene.hot_items) {
|
||||
this.activeScene.hot_items.forEach(element => {
|
||||
element.visible = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
startPlayback() {
|
||||
this.playback_location = 0;
|
||||
this._initialize_recording();
|
||||
this.timeline.isPlaying = true;
|
||||
this.timeline.scrubberPosition = 0;
|
||||
if (!this.camera_person_mode) {
|
||||
this.switchCameraView();
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes transform x y z changes.
|
||||
_initialize_control() {
|
||||
_initializeControl() {
|
||||
if (this.control == undefined) {
|
||||
return;
|
||||
}
|
||||
this.control.addEventListener("change", this.render_scene.bind(this));
|
||||
this.control.addEventListener("change", this.renderScene.bind(this));
|
||||
this.control.addEventListener("dragging-changed", (event: any) => {
|
||||
if (this.orbit == undefined) {
|
||||
if (this.orbitControls == undefined) {
|
||||
return;
|
||||
}
|
||||
this.orbit.enabled = !event.value;
|
||||
this.orbitControls.enabled = !event.value;
|
||||
// this.update_properties()
|
||||
});
|
||||
this.control.setSize(0.5); // Good default value for visuals.
|
||||
@@ -631,18 +958,18 @@ class Editor {
|
||||
this.activeScene.scene.add(this.control);
|
||||
}
|
||||
|
||||
// Initializes CCapture for capturing the scene to send over to backend.
|
||||
_initialize_recording() {
|
||||
_initializeRecording() {
|
||||
this.frame_buffer = [];
|
||||
this.render_timer = 0;
|
||||
}
|
||||
|
||||
// Render the scene to the camera.
|
||||
render_scene() {
|
||||
if (this.composer != null) {
|
||||
renderScene() {
|
||||
if (this.composer != null && !this.rendering) {
|
||||
this.composer.render();
|
||||
} else if (this.renderer && this.camera) {
|
||||
this.renderer.render(this.activeScene.scene, this.camera);
|
||||
} else if (this.renderer && this.render_camera) {
|
||||
this.renderer.setSize(this.render_width, this.render_height);
|
||||
this.renderer.render(this.activeScene.scene, this.render_camera);
|
||||
} else {
|
||||
console.error("Could not render to canvas no render or composer!");
|
||||
}
|
||||
@@ -652,10 +979,11 @@ class Editor {
|
||||
let imgData = this.renderer.domElement.toDataURL();
|
||||
this.frame_buffer.push(imgData);
|
||||
this.render_timer += this.clock.getDelta();
|
||||
if (this.playback_location >= this.fps_number * 3) {
|
||||
if (this.timeline.isPlaying == false) {
|
||||
this.stopPlayback();
|
||||
this.playback_location = 0;
|
||||
this.rendering = false;
|
||||
this.onWindowResize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -678,6 +1006,28 @@ class Editor {
|
||||
if (this.composer != null) {
|
||||
this.composer.setSize(width, height);
|
||||
}
|
||||
|
||||
if (this.render_camera == undefined) { return; }
|
||||
|
||||
//this.renderer.setSize(this.render_width, this.render_height);
|
||||
this.render_camera.aspect = this.render_width / this.render_height;
|
||||
this.render_camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
onContextMenu(event: any) {
|
||||
return false;
|
||||
}
|
||||
|
||||
onMouseDown(event: any) {
|
||||
if (event.button === 1 && this.camera_person_mode) {
|
||||
this.lockControls?.lock();
|
||||
}
|
||||
}
|
||||
|
||||
onMouseUp(event: any) {
|
||||
if (event.button === 1) {
|
||||
this.lockControls?.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Sets new mouse location usually used in raycasts.
|
||||
@@ -700,6 +1050,7 @@ class Editor {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.raycaster.setFromCamera(this.mouse, this.camera);
|
||||
let interactable: any[] = [];
|
||||
this.activeScene.scene.children.forEach((child: THREE.Object3D) => {
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import {
|
||||
EventDispatcher,
|
||||
Quaternion,
|
||||
Vector3,
|
||||
Vector2
|
||||
} from 'three';
|
||||
|
||||
const _changeEvent = { type: 'change' };
|
||||
|
||||
class FreeCam extends EventDispatcher {
|
||||
|
||||
constructor( object, domElement ) {
|
||||
|
||||
super();
|
||||
|
||||
this.object = object;
|
||||
this.domElement = domElement;
|
||||
|
||||
// API
|
||||
|
||||
// Set to false to disable this control
|
||||
this.enabled = true;
|
||||
|
||||
this.movementSpeed = 1.0;
|
||||
this.rollSpeed = 0.005;
|
||||
|
||||
this.dragToLook = false;
|
||||
this.autoForward = false;
|
||||
|
||||
this.lastMousePosition = new Vector2();
|
||||
this.mouseVelocity = new Vector2();
|
||||
|
||||
// disable default target object behavior
|
||||
|
||||
// internals
|
||||
|
||||
const scope = this;
|
||||
|
||||
this.tmpQuaternion = new Quaternion();
|
||||
|
||||
this.status = 0;
|
||||
|
||||
this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 };
|
||||
this.moveVector = new Vector3( 0, 0, 0 );
|
||||
this.rotationVector = new Vector3( 0, 0, 0 );
|
||||
|
||||
this.keydown = function ( event ) {
|
||||
if ( this.enabled === false ) return;
|
||||
if ( event.altKey || this.enabled === false ) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
switch ( event.code ) {
|
||||
case 'KeyW': this.moveState.forward = 1; break;
|
||||
case 'KeyS': this.moveState.back = 1; break;
|
||||
|
||||
case 'KeyA': this.moveState.left = 1; break;
|
||||
case 'KeyD': this.moveState.right = 1; break;
|
||||
|
||||
case 'Space': this.moveState.up = 1; break;
|
||||
case 'ShiftLeft': this.moveState.down = 1; break;
|
||||
|
||||
case 'ArrowUp': this.moveState.pitchUp = 1; break;
|
||||
case 'ArrowDown': this.moveState.pitchDown = 1; break;
|
||||
|
||||
case 'ArrowLeft': this.moveState.yawLeft = 1; break;
|
||||
case 'ArrowRight': this.moveState.yawRight = 1; break;
|
||||
|
||||
case 'KeyQ': this.moveState.rollLeft = 1; break;
|
||||
case 'KeyE': this.moveState.rollRight = 1; break;
|
||||
|
||||
|
||||
}
|
||||
|
||||
this.updateMovementVector();
|
||||
|
||||
};
|
||||
|
||||
this.keyup = function ( event ) {
|
||||
if ( this.enabled === false ) return;
|
||||
if ( this.enabled === false ) return;
|
||||
|
||||
switch ( event.code ) {
|
||||
case 'KeyW': this.moveState.forward = 0; break;
|
||||
case 'KeyS': this.moveState.back = 0; break;
|
||||
|
||||
case 'KeyA': this.moveState.left = 0; break;
|
||||
case 'KeyD': this.moveState.right = 0; break;
|
||||
|
||||
case 'Space': this.moveState.up = 0; break;
|
||||
case 'ShiftLeft': this.moveState.down = 0; break;
|
||||
|
||||
case 'ArrowUp': this.moveState.pitchUp = 0; break;
|
||||
case 'ArrowDown': this.moveState.pitchDown = 0; break;
|
||||
|
||||
case 'ArrowLeft': this.moveState.yawLeft = 0; break;
|
||||
case 'ArrowRight': this.moveState.yawRight = 0; break;
|
||||
|
||||
case 'KeyQ': this.moveState.rollLeft = 0; break;
|
||||
case 'KeyE': this.moveState.rollRight = 0; break;
|
||||
|
||||
}
|
||||
|
||||
this.updateMovementVector();
|
||||
|
||||
};
|
||||
|
||||
this.update = function ( delta ) {
|
||||
|
||||
if ( this.enabled === false ) return;
|
||||
|
||||
|
||||
const moveMult = delta * scope.movementSpeed;
|
||||
|
||||
scope.object.translateX( scope.moveVector.x * moveMult );
|
||||
scope.object.translateY( scope.moveVector.y * moveMult );
|
||||
scope.object.translateZ( scope.moveVector.z * moveMult );
|
||||
};
|
||||
|
||||
this.updateMovementVector = function () {
|
||||
|
||||
const forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0;
|
||||
|
||||
this.moveVector.x = ( - this.moveState.left + this.moveState.right );
|
||||
this.moveVector.y = ( - this.moveState.down + this.moveState.up );
|
||||
this.moveVector.z = ( - forward + this.moveState.back );
|
||||
};
|
||||
|
||||
this.getContainerDimensions = function () {
|
||||
|
||||
if ( this.domElement != document ) {
|
||||
|
||||
return {
|
||||
size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ],
|
||||
offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ]
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
return {
|
||||
size: [ window.innerWidth, window.innerHeight ],
|
||||
offset: [ 0, 0 ]
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.dispose = function () {
|
||||
|
||||
this.domElement.removeEventListener( 'contextmenu', _contextmenu );
|
||||
this.domElement.removeEventListener( 'pointerdown', _pointerdown );
|
||||
this.domElement.removeEventListener( 'pointermove', _pointermove );
|
||||
this.domElement.removeEventListener( 'pointerup', _pointerup );
|
||||
this.domElement.removeEventListener( 'pointercancel', _pointercancel );
|
||||
|
||||
window.removeEventListener( 'keydown', _keydown );
|
||||
window.removeEventListener( 'keyup', _keyup );
|
||||
|
||||
};
|
||||
|
||||
const _keydown = this.keydown.bind( this );
|
||||
const _keyup = this.keyup.bind( this );
|
||||
|
||||
window.addEventListener( 'keydown', _keydown );
|
||||
window.addEventListener( 'keyup', _keyup );
|
||||
|
||||
this.updateMovementVector();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { FreeCam };
|
||||
@@ -5,11 +5,13 @@ class Scene {
|
||||
name: string;
|
||||
gridHelper: THREE.GridHelper | undefined;
|
||||
scene: THREE.Scene;
|
||||
hot_items: THREE.Object3D[] | undefined;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
this.gridHelper;
|
||||
this.scene = new THREE.Scene();
|
||||
this.hot_items = [];
|
||||
}
|
||||
|
||||
initialize() {
|
||||
@@ -17,6 +19,7 @@ class Scene {
|
||||
this._createGrid();
|
||||
this._create_base_lighting();
|
||||
this._create_skybox();
|
||||
this._create_camera_obj();
|
||||
}
|
||||
|
||||
instantiate(name: string) {
|
||||
@@ -51,10 +54,33 @@ class Scene {
|
||||
return this.scene.getObjectByProperty('uuid', uuid);
|
||||
}
|
||||
|
||||
get_object_by_name(name: string) {
|
||||
return this.scene.getObjectByName(name);
|
||||
}
|
||||
|
||||
createPoint(pos: THREE.Vector3, visable: boolean = true) {
|
||||
let geometry = new THREE.SphereGeometry(0.1, 18, 12);
|
||||
let material = new THREE.MeshBasicMaterial({ color: 0x05C3DD });
|
||||
let obj = new THREE.Mesh(geometry, material);
|
||||
obj.position.copy(pos);
|
||||
obj.receiveShadow = visable;
|
||||
if(this.hot_items != undefined){
|
||||
this.hot_items.push(obj);
|
||||
}
|
||||
this.scene.add(obj);
|
||||
}
|
||||
|
||||
_disable_skybox() {
|
||||
this.scene.background = null;
|
||||
}
|
||||
|
||||
_create_camera_obj() {
|
||||
let cam_obj: THREE.Object3D = new THREE.Object3D();
|
||||
cam_obj.userData["name"] = "::CAM::";
|
||||
cam_obj.name = "::CAM::";
|
||||
this.scene.add(cam_obj);
|
||||
}
|
||||
|
||||
render_mode(enabled:boolean=true) {
|
||||
if(this.gridHelper == undefined) { return; }
|
||||
if(enabled) {
|
||||
|
||||
@@ -72,7 +72,7 @@ export class TimeLine {
|
||||
scene: Scene) {
|
||||
|
||||
this.timelineItems = []
|
||||
this.timeLineLimit = 60 * 10 // 10 seconds
|
||||
this.timeLineLimit = 60 * 5 // 5 seconds
|
||||
|
||||
this.isPlaying = false
|
||||
this.scrubberPosition = 0 // in frames into the tl
|
||||
@@ -151,7 +151,7 @@ export class TimeLine {
|
||||
else if (element.type == "animation") {
|
||||
}
|
||||
else if (element.type == "lipsync") {
|
||||
|
||||
this.lipSyncEngine.clips[element.object_uuid].reset();
|
||||
}
|
||||
else {
|
||||
this.stop()
|
||||
@@ -179,13 +179,17 @@ export class TimeLine {
|
||||
let object = this.scene.get_object_by_uuid(element.object_uuid)
|
||||
if (element.type == "transform") {
|
||||
if (object && this.transformEngine.clips[element.object_uuid]) {
|
||||
this.transformEngine.clips[element.object_uuid].length = (element.ending_offset - element.start_offset)
|
||||
this.transformEngine.clips[element.object_uuid].step(object)
|
||||
this.transformEngine.clips[element.object_uuid].length = (element.ending_offset - element.start_offset);
|
||||
this.transformEngine.clips[element.object_uuid].step(object, element.start_offset, this.scrubberPosition);
|
||||
}
|
||||
}
|
||||
else if (element.type == "audio") {
|
||||
// global audio track
|
||||
this.audioEngine.playClip(element.media_id)
|
||||
if(this.scrubberPosition+1 >= element.ending_offset){
|
||||
this.audioEngine.stopClip(element.media_id);
|
||||
}
|
||||
else {
|
||||
this.audioEngine.playClip(element.media_id);
|
||||
}
|
||||
}
|
||||
else if (element.type == "lipsync") {
|
||||
if(this.scrubberPosition+1 >= element.ending_offset){
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import {State, Action, ACTION_TYPES, VIEW_MODES} from './types';
|
||||
import {reducer} from './reducer';
|
||||
|
||||
const initialState:State = {
|
||||
timelineHeight: 260,
|
||||
viewMode: VIEW_MODES.EDITOR,
|
||||
showEditorLoader: {
|
||||
isShow:true,
|
||||
message: 'loading Editor Engine 🦊'
|
||||
}
|
||||
}
|
||||
|
||||
export type {State, Action};
|
||||
export {
|
||||
ACTION_TYPES,
|
||||
initialState,
|
||||
reducer,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {State, Action, ACTION_TYPES, VIEW_MODES} from './types';
|
||||
import {reducer} from './reducer';
|
||||
|
||||
import { initialStateValues } from './values';
|
||||
|
||||
export type {
|
||||
State as AppUiState,
|
||||
Action as AppUiAction,
|
||||
};
|
||||
|
||||
export {
|
||||
VIEW_MODES as APPUI_VIEW_MODES,
|
||||
ACTION_TYPES as APPUI_ACTION_TYPES,
|
||||
initialStateValues as appUiInitialStateValues,
|
||||
reducer as appUiReducer,
|
||||
}
|
||||
+30
-3
@@ -6,8 +6,8 @@ export function reducer(state: State, action: Action): State {
|
||||
return{
|
||||
...state,
|
||||
showEditorLoader: {
|
||||
isShow: true,
|
||||
message: action.payload?.showEditorLoader.message || undefined,
|
||||
isShowing: true,
|
||||
message: action.payload?.showEditorLoader.message,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,38 @@ export function reducer(state: State, action: Action): State {
|
||||
return{
|
||||
...state,
|
||||
showEditorLoader: {
|
||||
isShow: false,
|
||||
isShowing: false,
|
||||
message: state.showEditorLoader.message
|
||||
},
|
||||
}
|
||||
}
|
||||
case ACTION_TYPES.SHOW_EDITOR_LOADINGBAR:{
|
||||
return{
|
||||
...state,
|
||||
showEditorLoadingBar: {
|
||||
...action.payload?.showEditorLoadingBar,
|
||||
isShowing: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
case ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR:{
|
||||
return{
|
||||
...state,
|
||||
showEditorLoadingBar: {
|
||||
...state.showEditorLoadingBar,
|
||||
...action.payload?.showEditorLoadingBar,
|
||||
},
|
||||
}
|
||||
}
|
||||
case ACTION_TYPES.HIDE_EDITOR_LOADINGBAR:{
|
||||
return{
|
||||
...state,
|
||||
showEditorLoadingBar: {
|
||||
...state.showEditorLoadingBar,
|
||||
isShowing: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
case ACTION_TYPES.ON_TIMELINE_RESIZE:{
|
||||
return{
|
||||
...state,
|
||||
+34
-2
@@ -9,9 +9,16 @@ export type State = {
|
||||
viewMode: ViewModes;
|
||||
timelineHeight: number;
|
||||
showEditorLoader: {
|
||||
isShow:boolean
|
||||
message:string|undefined;
|
||||
isShowing:boolean;
|
||||
message?:string;
|
||||
};
|
||||
showEditorLoadingBar:{
|
||||
isShowing: boolean;
|
||||
label?: string;
|
||||
message?: string;
|
||||
progress?: number;
|
||||
useFakeTimer?: number;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +27,9 @@ export enum ACTION_TYPES {
|
||||
ON_CHANGE_VIEW_MODE = "on_change_view_mode",
|
||||
SHOW_EDITOR_LOADER = "show_editor_loader",
|
||||
HIDE_EDITOR_LOADER = "hide_editor_loader",
|
||||
SHOW_EDITOR_LOADINGBAR = "show_editor_loadingbar",
|
||||
UPDATE_EDITOR_LOADINGBAR = "update_editor_loadingbar",
|
||||
HIDE_EDITOR_LOADINGBAR = "hide_editor_loadingbar",
|
||||
};
|
||||
|
||||
export type Action =
|
||||
@@ -32,6 +42,28 @@ export type Action =
|
||||
}
|
||||
}
|
||||
}
|
||||
| {type: ACTION_TYPES.HIDE_EDITOR_LOADINGBAR,}
|
||||
| {
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADINGBAR,
|
||||
payload?:{
|
||||
showEditorLoadingBar: {
|
||||
label?: string;
|
||||
message?: string;
|
||||
progress?: number;
|
||||
useFakeTimer?: number;
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR,
|
||||
payload:{
|
||||
showEditorLoadingBar: {
|
||||
label?: string;
|
||||
message?: string;
|
||||
progress?: number;
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: ACTION_TYPES.ON_TIMELINE_RESIZE,
|
||||
payload: {
|
||||
@@ -0,0 +1,15 @@
|
||||
import {State, VIEW_MODES} from './types';
|
||||
|
||||
export const initialStateValues : State = {
|
||||
timelineHeight: 260,
|
||||
viewMode: VIEW_MODES.EDITOR,
|
||||
showEditorLoader: {
|
||||
isShowing:false,
|
||||
message: 'Loading Editor Engine 🦊'
|
||||
},
|
||||
showEditorLoadingBar: {
|
||||
isShowing:true,
|
||||
progress:5,
|
||||
message: 'Loading Editor Engine 🦊'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
AppUiState,
|
||||
AppUiAction,
|
||||
APPUI_ACTION_TYPES,
|
||||
APPUI_VIEW_MODES,
|
||||
appUiInitialStateValues,
|
||||
appUiReducer,
|
||||
} from './appUiReducer';
|
||||
|
||||
export type {
|
||||
AppUiState,
|
||||
AppUiAction
|
||||
};
|
||||
|
||||
export {
|
||||
APPUI_VIEW_MODES,
|
||||
APPUI_ACTION_TYPES,
|
||||
appUiInitialStateValues,
|
||||
appUiReducer,
|
||||
}
|
||||
+15
-18
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { LinksFunction } from "@remix-run/deno";
|
||||
import {
|
||||
Links,
|
||||
@@ -47,13 +48,11 @@ export const links: LinksFunction = () => [
|
||||
export default function App() {
|
||||
const [topBarInnerComponent, setTopBarInnerComponent] =
|
||||
useState<JSX.Element | null>(null);
|
||||
const [isLoaded, setIsLoaded] = useState<'loading'|'loaded'|'completed'>('loading');
|
||||
const [showLoader, setShowLoader] = useState<boolean>(true);
|
||||
useEffect(()=>{
|
||||
setIsLoaded('loaded');
|
||||
setTimeout(()=>setIsLoaded('completed'), 2000);
|
||||
setTimeout(()=>setShowLoader(false), 2500);
|
||||
},[]);
|
||||
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -63,15 +62,7 @@ export default function App() {
|
||||
<Links />
|
||||
</head>
|
||||
<body className="overflow-hidden bg-ui-background">
|
||||
{isLoaded !== 'completed' &&
|
||||
<CompleteTakeoverLoadingScreen
|
||||
className={
|
||||
isLoaded === 'loading'
|
||||
? "transition-opacity duration-1000 opacity-100"
|
||||
: "transition-opacity duration-1000 opacity-0"
|
||||
}
|
||||
/>
|
||||
}
|
||||
<CompleteTakeoverLoadingScreen isShowing={showLoader}/>
|
||||
<TopBarInnerContext.Provider
|
||||
value={{
|
||||
TopBarInner: topBarInnerComponent,
|
||||
@@ -89,11 +80,17 @@ export default function App() {
|
||||
);
|
||||
}
|
||||
|
||||
function CompleteTakeoverLoadingScreen({className}:{className:string}){
|
||||
function CompleteTakeoverLoadingScreen({isShowing}:{isShowing:boolean}){
|
||||
return(
|
||||
<div
|
||||
<Transition
|
||||
id='complete-takeover-loading-screen'
|
||||
className={className}
|
||||
show={isShowing}
|
||||
enter="transition-opacity duration-150"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-1000"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
style={{
|
||||
backgroundColor: '#1a1a27',
|
||||
position: 'fixed',
|
||||
@@ -107,7 +104,7 @@ function CompleteTakeoverLoadingScreen({className}:{className:string}){
|
||||
zIndex: 9999,
|
||||
}}
|
||||
>
|
||||
<LoadingDotsBricks />
|
||||
</div>
|
||||
<LoadingDotsBricks />
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
ButtonLink,
|
||||
H4,
|
||||
H1,H2,H3,H4,H5,H6,P,Label,
|
||||
LoadingBar,
|
||||
LoadingDotsTyping,
|
||||
LoadingDotsBricks,
|
||||
@@ -11,61 +11,108 @@ import {
|
||||
|
||||
export default function ComponentLibrary () {
|
||||
const [progress, setProgress] = useState(0);
|
||||
const loopRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const pushProgressBar: ()=>void = useCallback(()=>{
|
||||
loopRef.current = setInterval(function timer(){
|
||||
setProgress((curr)=>{
|
||||
if (curr < 100){
|
||||
return curr+10;
|
||||
}else{
|
||||
if(loopRef.current)
|
||||
clearInterval(loopRef.current);
|
||||
return curr;
|
||||
}
|
||||
})}
|
||||
, 3000);
|
||||
}, []);
|
||||
useEffect(()=>{
|
||||
if(progress === 0){
|
||||
const loop = setInterval(function timer(){
|
||||
setProgress((curr)=>{
|
||||
if (curr < 100){
|
||||
return curr+10;
|
||||
}else{
|
||||
clearTimeout(loop);
|
||||
return curr;
|
||||
}
|
||||
})}
|
||||
, 3000);
|
||||
pushProgressBar();
|
||||
return ()=>{
|
||||
if(loopRef.current)
|
||||
clearInterval(loopRef.current)
|
||||
}
|
||||
}, [progress]);
|
||||
},[progress]);
|
||||
|
||||
return(
|
||||
<div className='bg-ui-panel w-10/12 max-w-7xl h-full min-h-96 mx-auto my-6 rounded-lg p-6'>
|
||||
<div
|
||||
className="fixed w-full overflow-scroll"
|
||||
style={{height: "calc(100% - 72px)"}}
|
||||
>
|
||||
<div
|
||||
className='bg-ui-panel w-10/12 max-w-7xl mx-auto my-6 rounded-lg p-6'
|
||||
>
|
||||
|
||||
<div className="flex flex-col gap-2 mb-4">
|
||||
<H4>Buttons</H4>
|
||||
<div className="flex gap-2">
|
||||
<Button>Primary</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button disabled>Disabled</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 mb-4">
|
||||
<H4>ButtonLink</H4>
|
||||
<ButtonLink to="/">Back to /</ButtonLink>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 mb-4">
|
||||
<H4>Loading Dots</H4>
|
||||
<div className="flex gap-2">
|
||||
<div className="w-60 h-40 rounded-lg overflow-hidden">
|
||||
<LoadingDotsTyping />
|
||||
</div>
|
||||
<div className="w-60 h-40 rounded-lg overflow-hidden">
|
||||
<LoadingDotsBricks />
|
||||
<div className="flex flex-col gap-2 mb-4">
|
||||
<H1>Typography</H1>
|
||||
<H1>H1. This is an H1 Heading</H1>
|
||||
<H2>H2. This is an H2 Heading</H2>
|
||||
<H3>H3. This is an H3 Heading</H3>
|
||||
<H4>H4. This is an H4 Heading</H4>
|
||||
<Label>Label. Is essentially same as H4</Label>
|
||||
<P>P. Is an normal Paragraph</P>
|
||||
<H5>H5. This is an H5 Heading</H5>
|
||||
<H6>H6. This is an H6 Heading</H6>
|
||||
|
||||
<hr className="mt-2 mb-4"/>
|
||||
<H1>Components</H1>
|
||||
<H4>Buttons</H4>
|
||||
<div className="flex gap-2">
|
||||
<Button>Primary</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button className="bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary">
|
||||
Prelim Tertiary
|
||||
</Button>
|
||||
<Button disabled>Disabled</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 mb-4">
|
||||
<H4>Loading Bar</H4>
|
||||
<div className="flex gap-2">
|
||||
<LoadingBar progress={progress} wrapperClassName="rounded-lg"/>
|
||||
<Button onClick={()=>setProgress(0)}>Reset</Button>
|
||||
<div className="flex flex-col gap-2 mb-4">
|
||||
<H4>ButtonLink</H4>
|
||||
<ButtonLink to="/">Back to /</ButtonLink>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 mb-4">
|
||||
<H4>Loading Dots</H4>
|
||||
<div className="flex gap-2">
|
||||
<div className="w-60 h-40 rounded-lg overflow-hidden">
|
||||
<LoadingDotsTyping />
|
||||
</div>
|
||||
<div className="w-60 h-40 rounded-lg overflow-hidden">
|
||||
<LoadingDotsBricks />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 mb-4">
|
||||
<H4>Loading Bar</H4>
|
||||
<div className="flex gap-2 items-center">
|
||||
<LoadingBar
|
||||
progress={progress}
|
||||
isShowing={progress !== 100}
|
||||
wrapperClassName="rounded-lg"
|
||||
message="this takes progress from parent"
|
||||
/>
|
||||
<Button
|
||||
className="h-fit"
|
||||
onClick={()=>setProgress(0)}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
<LoadingBar
|
||||
label="labeling the bar"
|
||||
message="displaying a message"
|
||||
/>
|
||||
<LoadingBar
|
||||
label="useFakeTimer = 30000 (30secs)"
|
||||
useFakeTimer={30000}
|
||||
/>
|
||||
<LoadingBar
|
||||
label="useFakeTimer = 3000 (3secs)"
|
||||
useFakeTimer={3000}
|
||||
/>
|
||||
</div>
|
||||
<LoadingBar
|
||||
label="labeling the bar"
|
||||
message="displaying a message"
|
||||
pulsing
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -47,6 +47,21 @@ export default {
|
||||
"900": "#464762",
|
||||
"950": "#39394c",
|
||||
},
|
||||
"brand-tertiary": {
|
||||
//Aquamarine Blue
|
||||
DEFAULT: "#1cb6be", //=300
|
||||
'50': '#eefdfc',
|
||||
'100': '#d4f9f8',
|
||||
'200': '#aef3f3',
|
||||
'300': '#62e4e6',
|
||||
'400': '#38d3d8',
|
||||
'500': '#1cb6be',
|
||||
'600': '#1a94a0',
|
||||
'700': '#1c7682',
|
||||
'800': '#1f616b',
|
||||
'900': '#1e505b',
|
||||
'950': '#0e353e',
|
||||
},
|
||||
danger: {
|
||||
//copper rust
|
||||
DEFAULT: "#8f4951", //=700
|
||||
|
||||
Reference in new issue
Block a user