diff --git a/app/components/H4.tsx b/app/components/H4.tsx deleted file mode 100644 index bfbf79d630..0000000000 --- a/app/components/H4.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { twMerge } from "tailwind-merge" - -export const H4 = ({ - className, - children, -}:{ - className?: string, - children: React.ReactNode, -})=>{ - return( -

- {children} -

- ) -} \ No newline at end of file diff --git a/app/components/LoadingBar.tsx b/app/components/LoadingBar.tsx index 90a112df5c..2d2ab8177e 100644 --- a/app/components/LoadingBar.tsx +++ b/app/components/LoadingBar.tsx @@ -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(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 ( -
- {label && } -
-
+ +
+ {label && } +
+
+
+ {message &&

{message}

}
- {message &&

{message}

} -
+
); } \ No newline at end of file diff --git a/app/components/LoadingDots.tsx b/app/components/LoadingDots.tsx index 3abfa49eb6..2a1c53e4fa 100644 --- a/app/components/LoadingDots.tsx +++ b/app/components/LoadingDots.tsx @@ -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( + + { type==='typing' && +
} - }else{ - setState((curr) => { - if (curr!=='loading') return 'loading'; - else return curr; - }); - } - }, [show, transition]); - - if(state!== 'completed'){ - return( -
- { type==='typing' && -
- } - { type==='bricks' && -
- } - { - message && -

{message}

- } -
- ); - }else{ - return null; - } + { type==='bricks' && +
+ } + { + message && +

{message}

+ } +
+ ); }; diff --git a/app/components/Typography.tsx b/app/components/Typography.tsx new file mode 100644 index 0000000000..f2c0bda536 --- /dev/null +++ b/app/components/Typography.tsx @@ -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)=> +

{children}

; + +export const H2 = ({className,children,}:TypogProps)=> +

{children}

; + +export const H3 = ({className,children,}:TypogProps)=> +

{children}

; + +export const H4 = ({className,children,}:TypogProps)=> +

{children}

; + + export const Label = ({className,children,}:TypogProps)=> + ; + +export const H5 = ({className,children,}:TypogProps)=> +
{children}
; + +export const H6 = ({className,children,}:TypogProps)=> +
{children}
; + +export const P = ({className,children,}:TypogProps)=> +

{children}

; \ No newline at end of file diff --git a/app/components/index.tsx b/app/components/index.tsx index 5dabccba07..323db7b12c 100644 --- a/app/components/index.tsx +++ b/app/components/index.tsx @@ -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, diff --git a/app/modules/SidePanel/SidePanel.tsx b/app/modules/SidePanel/SidePanel.tsx index fbb07a8b56..1181302cb7 100644 --- a/app/modules/SidePanel/SidePanel.tsx +++ b/app/modules/SidePanel/SidePanel.tsx @@ -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 ( -
+
- +
{children} - +
); }; diff --git a/app/modules/SidePanelOld/SidePanelInnerOld.tsx b/app/modules/SidePanelOld/SidePanelInnerOld.tsx deleted file mode 100644 index 3112e81c5d..0000000000 --- a/app/modules/SidePanelOld/SidePanelInnerOld.tsx +++ /dev/null @@ -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 ( -
-
-
- {!title && } - {title && ( - - {title} - - )} -
- -
-
-
-
{children}
-
- ); -}; diff --git a/app/modules/SidePanelOld/SidePanelOld.tsx b/app/modules/SidePanelOld/SidePanelOld.tsx deleted file mode 100644 index b46e9841c7..0000000000 --- a/app/modules/SidePanelOld/SidePanelOld.tsx +++ /dev/null @@ -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(false); - const handleOpen = () => setOpen(true); - const handleClose = () => setOpen(false); - - return ( - <> - - - -
- -
-
-
- - - - {children} - - - -
-
-
-
-
- - ); -}; diff --git a/app/modules/SidePanelOld/index.tsx b/app/modules/SidePanelOld/index.tsx deleted file mode 100644 index 5f4f8914f6..0000000000 --- a/app/modules/SidePanelOld/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { SidePanel } from "./SidePanelOld"; diff --git a/app/modules/Tabs/Tabs.tsx b/app/modules/Tabs/Tabs.tsx index 3a83dfbd65..9b32021140 100644 --- a/app/modules/Tabs/Tabs.tsx +++ b/app/modules/Tabs/Tabs.tsx @@ -19,7 +19,7 @@ export const Tabs = ({ tabs: tabProps }: { tabs: TabType[] }) => { ); return ( -
+
{tabs.map((tab, idx) => ( @@ -31,9 +31,9 @@ export const Tabs = ({ tabs: tabProps }: { tabs: TabType[] }) => { ))} - + {tabs.map((tab, idx) => ( - + {tab.children} ))} diff --git a/app/pages/PageCube/PageCube.tsx b/app/pages/PageCube/PageCube.tsx index 54faf66a60..f6d2062e5a 100644 --- a/app/pages/PageCube/PageCube.tsx +++ b/app/pages/PageCube/PageCube.tsx @@ -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 = () => {

Ration X: {rotationX ? rotationX : ""}

Ration Y: {rotationY ? rotationY : ""}

- +

Side Panel Context

diff --git a/app/pages/PageEnigma/PageEnigmaComponent.tsx b/app/pages/PageEnigma/PageEnigmaComponent.tsx index 7d442cd9d6..0c94744c75 100644 --- a/app/pages/PageEnigma/PageEnigmaComponent.tsx +++ b/app/pages/PageEnigma/PageEnigmaComponent.tsx @@ -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 = () => {
{
- {appUiState.viewMode === VIEW_MODES.SIDE_BY_SIDE && ( + {appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE && ( )} +
diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx index 3ed0d03d8d..01670a0220 100644 --- a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx @@ -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(""); const [sceneToken, setSceneToken] = useState(""); @@ -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 (
@@ -88,14 +144,24 @@ export const ControlsTopButtons = () => {
-
- + + + - + {/* + */} +
); diff --git a/app/pages/PageEnigma/comps/ControlsVideo/ControlsVideo.tsx b/app/pages/PageEnigma/comps/ControlsVideo/ControlsVideo.tsx index 5b1ad21ffe..07a7fc0eaa 100644 --- a/app/pages/PageEnigma/comps/ControlsVideo/ControlsVideo.tsx +++ b/app/pages/PageEnigma/comps/ControlsVideo/ControlsVideo.tsx @@ -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"); diff --git a/app/pages/PageEnigma/comps/Helmet.tsx b/app/pages/PageEnigma/comps/Helmet.tsx new file mode 100644 index 0000000000..a14e44a4de --- /dev/null +++ b/app/pages/PageEnigma/comps/Helmet.tsx @@ -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(Date.now()); + const engineContext = useContext(EngineContext); + + const handleGenerateVideo = () => { + engineContext?.generateVideo(); + }; + + useEffect(()=>{ + setHelmetKey(Date.now()); + },[engineContext]); + + return ( + + + + ); +} \ No newline at end of file diff --git a/app/pages/PageEnigma/comps/PreviewEngineCamera/index.tsx b/app/pages/PageEnigma/comps/PreviewEngineCamera/index.tsx index 324a2bcad9..fb8e695aee 100644 --- a/app/pages/PageEnigma/comps/PreviewEngineCamera/index.tsx +++ b/app/pages/PageEnigma/comps/PreviewEngineCamera/index.tsx @@ -8,7 +8,7 @@ export const PreviewEngineCamera = ()=>{ const [showLoader, setShowLoader] = useState(true); useEffect(()=>{ - setTimeout(()=>setShowLoader(false), 5000); + setTimeout(()=>setShowLoader(false), 500); },[]); return (
{ src="/resources/uiAssets/video_player_placeholder.gif" />
- +
diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx index a356416e55..85d1fc5dea 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx @@ -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( - <> +

Select Base Stylizer

{
- +
); } \ No newline at end of file diff --git a/app/pages/PageEnigma/comps/Timeline/Timeline.tsx b/app/pages/PageEnigma/comps/Timeline/Timeline.tsx index 126c0d680a..4c61defb6c 100644 --- a/app/pages/PageEnigma/comps/Timeline/Timeline.tsx +++ b/app/pages/PageEnigma/comps/Timeline/Timeline.tsx @@ -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, diff --git a/app/pages/PageEnigma/comps/ViewSideBySide/index.tsx b/app/pages/PageEnigma/comps/ViewSideBySide/index.tsx index 788f31d297..bcad3ada4c 100644 --- a/app/pages/PageEnigma/comps/ViewSideBySide/index.tsx +++ b/app/pages/PageEnigma/comps/ViewSideBySide/index.tsx @@ -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 = ()=>{
- +
) diff --git a/app/pages/PageEnigma/contexts/AppUiContext.tsx b/app/pages/PageEnigma/contexts/AppUiContext.tsx index 0544b3e844..f46d63e884 100644 --- a/app/pages/PageEnigma/contexts/AppUiContext.tsx +++ b/app/pages/PageEnigma/contexts/AppUiContext.tsx @@ -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|null]>([null,null]); +export const AppUiContext = createContext<[ + AppUiState | null, + Dispatch +]>([ null ,()=>{}]); export const AppUIProvider = ({ value, children, }:{ - value:[State, Dispatch], + value:[AppUiState, Dispatch], children: ReactNode })=>{ return ( diff --git a/app/pages/PageEnigma/contexts/EngineProvider.tsx b/app/pages/PageEnigma/contexts/EngineProvider.tsx index 899de9ff72..fdaa9982ef 100644 --- a/app/pages/PageEnigma/contexts/EngineProvider.tsx +++ b/app/pages/PageEnigma/contexts/EngineProvider.tsx @@ -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(null); const [appUiState, dispatchAppUiState] = useContext(AppUiContext); + // To talk to react land. + const trackContext = useContext(TrackContext) useEffect(() => { //componentDidMount diff --git a/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx b/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx index 2559041981..413b947e99 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx +++ b/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx @@ -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([]); diff --git a/app/pages/PageEnigma/datastructures/clips/animation_clip.ts b/app/pages/PageEnigma/datastructures/clips/animation_clip.ts index 50110653d4..30d9bf91e1 100644 --- a/app/pages/PageEnigma/datastructures/clips/animation_clip.ts +++ b/app/pages/PageEnigma/datastructures/clips/animation_clip.ts @@ -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 { diff --git a/app/pages/PageEnigma/datastructures/clips/transform_clip.ts b/app/pages/PageEnigma/datastructures/clips/transform_clip.ts index f167843319..66659207d7 100644 --- a/app/pages/PageEnigma/datastructures/clips/transform_clip.ts +++ b/app/pages/PageEnigma/datastructures/clips/transform_clip.ts @@ -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); diff --git a/app/pages/PageEnigma/js/api_manager.ts b/app/pages/PageEnigma/js/api_manager.ts index f1ffe4b311..98f83e62ea 100644 --- a/app/pages/PageEnigma/js/api_manager.ts +++ b/app/pages/PageEnigma/js/api_manager.ts @@ -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 { 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 { + public async loadSceneState( + scene_media_file_token: string | null, + ): Promise { 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 => { + const loadGlb = ( + bucket_path: string, + ): Promise => { 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 { + public async getMediaFile(media_file_token: string): Promise { 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 { 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 { - return ""; + public async getMediaBatch(media_tokens: string[]): Promise { + 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; diff --git a/app/pages/PageEnigma/js/editor.ts b/app/pages/PageEnigma/js/editor.ts index e10f37f004..0a3c768ac8 100644 --- a/app/pages/PageEnigma/js/editor.ts +++ b/app/pages/PageEnigma/js/editor.ts @@ -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) => { diff --git a/app/pages/PageEnigma/js/free_cam.js b/app/pages/PageEnigma/js/free_cam.js new file mode 100644 index 0000000000..c9ba6ec140 --- /dev/null +++ b/app/pages/PageEnigma/js/free_cam.js @@ -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 }; \ No newline at end of file diff --git a/app/pages/PageEnigma/js/scene.ts b/app/pages/PageEnigma/js/scene.ts index 52693599cf..05ac860d18 100644 --- a/app/pages/PageEnigma/js/scene.ts +++ b/app/pages/PageEnigma/js/scene.ts @@ -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) { diff --git a/app/pages/PageEnigma/js/timeline.ts b/app/pages/PageEnigma/js/timeline.ts index 208a7e20e9..267d23ba63 100644 --- a/app/pages/PageEnigma/js/timeline.ts +++ b/app/pages/PageEnigma/js/timeline.ts @@ -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){ diff --git a/app/pages/PageEnigma/reducer/index.tsx b/app/pages/PageEnigma/reducer/index.tsx deleted file mode 100644 index e44636d058..0000000000 --- a/app/pages/PageEnigma/reducer/index.tsx +++ /dev/null @@ -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, -} \ No newline at end of file diff --git a/app/pages/PageEnigma/reducers/appUiReducer/index.tsx b/app/pages/PageEnigma/reducers/appUiReducer/index.tsx new file mode 100644 index 0000000000..3c94ae01c3 --- /dev/null +++ b/app/pages/PageEnigma/reducers/appUiReducer/index.tsx @@ -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, +} \ No newline at end of file diff --git a/app/pages/PageEnigma/reducer/reducer.tsx b/app/pages/PageEnigma/reducers/appUiReducer/reducer.tsx similarity index 51% rename from app/pages/PageEnigma/reducer/reducer.tsx rename to app/pages/PageEnigma/reducers/appUiReducer/reducer.tsx index 7062c2b4b5..c632ebfef2 100644 --- a/app/pages/PageEnigma/reducer/reducer.tsx +++ b/app/pages/PageEnigma/reducers/appUiReducer/reducer.tsx @@ -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, diff --git a/app/pages/PageEnigma/reducer/types.tsx b/app/pages/PageEnigma/reducers/appUiReducer/types.tsx similarity index 51% rename from app/pages/PageEnigma/reducer/types.tsx rename to app/pages/PageEnigma/reducers/appUiReducer/types.tsx index a9d259ac08..5e07b24b3f 100644 --- a/app/pages/PageEnigma/reducer/types.tsx +++ b/app/pages/PageEnigma/reducers/appUiReducer/types.tsx @@ -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: { diff --git a/app/pages/PageEnigma/reducers/appUiReducer/values.tsx b/app/pages/PageEnigma/reducers/appUiReducer/values.tsx new file mode 100644 index 0000000000..b62b819a6f --- /dev/null +++ b/app/pages/PageEnigma/reducers/appUiReducer/values.tsx @@ -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 🦊' + } +} \ No newline at end of file diff --git a/app/pages/PageEnigma/reducers/index.tsx b/app/pages/PageEnigma/reducers/index.tsx new file mode 100644 index 0000000000..c670c51556 --- /dev/null +++ b/app/pages/PageEnigma/reducers/index.tsx @@ -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, +} \ No newline at end of file diff --git a/app/root.tsx b/app/root.tsx index e6504470a3..855484c581 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -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(null); - const [isLoaded, setIsLoaded] = useState<'loading'|'loaded'|'completed'>('loading'); + const [showLoader, setShowLoader] = useState(true); useEffect(()=>{ - setIsLoaded('loaded'); - setTimeout(()=>setIsLoaded('completed'), 2000); + setTimeout(()=>setShowLoader(false), 2500); },[]); - return ( @@ -63,15 +62,7 @@ export default function App() { - {isLoaded !== 'completed' && - - } + - - + + ); }; diff --git a/app/routes/ComponentLibrary.tsx b/app/routes/ComponentLibrary.tsx index acc2188c52..c0e6719021 100644 --- a/app/routes/ComponentLibrary.tsx +++ b/app/routes/ComponentLibrary.tsx @@ -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(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( -
+
+
-
-

Buttons

-
- - - -
-
- -
-

ButtonLink

- Back to / -
- -
-

Loading Dots

-
-
- -
-
- +
+

Typography

+

H1. This is an H1 Heading

+

H2. This is an H2 Heading

+

H3. This is an H3 Heading

+

H4. This is an H4 Heading

+ +

P. Is an normal Paragraph

+
H5. This is an H5 Heading
+
H6. This is an H6 Heading
+ +
+

Components

+

Buttons

+
+ + + +
-
-
-

Loading Bar

-
- - +
+

ButtonLink

+ Back to / +
+ +
+

Loading Dots

+
+
+ +
+
+ +
+
+
+ +
+

Loading Bar

+
+ + +
+ + +
-
); diff --git a/tailwind.config.ts b/tailwind.config.ts index 7eb8a62827..482b42e119 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -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