Merge branch 'test' into feature/resize-timeline

This commit is contained in:
FromTheLOLAshes committed 2024-03-29 23:02:22 +07:00
commit 71dde2af2f
38 files changed
+1314 -485

No files matched your search

-19
View File
@@ -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>
)
}
+92 -15
View File
@@ -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>
);
}
+27 -50
View File
@@ -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>
);
};
+31
View File
@@ -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>;
+2 -2
View File
@@ -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,
+8 -13
View File
@@ -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>
);
};
-54
View File
@@ -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
View File
@@ -1 +0,0 @@
export { SidePanel } from "./SidePanelOld";
+3 -3
View File
@@ -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>
))}
+3 -3
View File
@@ -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>
+25 -18
View File
@@ -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");
+24
View File
@@ -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);
+81 -24
View File
@@ -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;
+430 -79
View File
@@ -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) => {
+176
View File
@@ -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 };
+26
View File
@@ -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) {
+10 -6
View File
@@ -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){
-18
View File
@@ -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,
}
@@ -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,
@@ -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 🦊'
}
}
+20
View File
@@ -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
View File
@@ -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>
);
};
+95 -48
View File
@@ -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>
);
+15
View File
@@ -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