mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 01:18:34 +00:00
Merge branch 'test' into feature/login
This commit is contained in:
commit
11bf308bca
50 files changed
+2403
-2043
No files matched your search
@@ -1,8 +1,7 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Label } from ".";
|
||||
import { Label } from "./Typography";
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { H4 } from ".";
|
||||
|
||||
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
|
||||
|
||||
interface ItemPickerProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
label: string
|
||||
label: string;
|
||||
type: ArtStyle;
|
||||
selected: boolean;
|
||||
onSelected : (val:string)=>void;
|
||||
onSelected: (picked: ArtStyle) => void;
|
||||
}
|
||||
|
||||
export const ItemPicker = ({
|
||||
label,
|
||||
type,
|
||||
selected = false,
|
||||
onSelected,
|
||||
...imgProps
|
||||
}:ItemPickerProps) => {
|
||||
|
||||
const handleSelected = ()=>{
|
||||
onSelected(label);
|
||||
}: ItemPickerProps) => {
|
||||
const handleSelected = () => {
|
||||
onSelected(type);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge("relative border-2 rounded-lg overflow-hidden cursor-pointer transition-colors ease-in-out", selected ? "border-brand-primary" : "border-ui-border")}
|
||||
<button
|
||||
className={twMerge(
|
||||
"relative cursor-pointer overflow-hidden rounded-lg border-2 transition-colors ease-in-out",
|
||||
selected ? "border-brand-primary" : "border-ui-border",
|
||||
)}
|
||||
onClick={handleSelected}
|
||||
>
|
||||
<img className="aspect-square min-w-20" {...imgProps} />
|
||||
<div className="bg-gradient-to-t from-gray-700 absolute top-0 left-0 w-full h-full"/>
|
||||
<H4 className="absolute bottom-0 left-2 drop-shadow-md">{label}</H4>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<img className="aspect-square" {...imgProps} alt="style" />
|
||||
<div className="absolute left-0 top-0 h-full w-full bg-gradient-to-t from-gray-700" />
|
||||
<H4 className="absolute bottom-[1px] left-[6px] text-sm drop-shadow-md">
|
||||
{label}
|
||||
</H4>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
+36
-18
@@ -1,24 +1,42 @@
|
||||
import { TextareaHTMLAttributes } from "react"
|
||||
import { TextareaHTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Label } from "./Typography";
|
||||
|
||||
type ResizeType =
|
||||
| "none"
|
||||
| "both"
|
||||
| "horizontal"
|
||||
| "vertical"
|
||||
| "block"
|
||||
| "inline"
|
||||
| undefined;
|
||||
|
||||
export const Textarea = ({
|
||||
className,
|
||||
label,
|
||||
resize = "vertical",
|
||||
...rest
|
||||
}:TextareaHTMLAttributes<HTMLTextAreaElement>) => {
|
||||
return(
|
||||
<textarea
|
||||
|
||||
className={twMerge(
|
||||
"rounded-lg bg-ui-controls border border-ui-panel-border py-2 px-4"
|
||||
,className
|
||||
)}
|
||||
style={{
|
||||
outline: "2px solid transparent",
|
||||
transition: "outline-color 0.15s ease-in-out",
|
||||
}}
|
||||
onFocus={(e) => (e.currentTarget.style.outlineColor = "#e66462")}
|
||||
onBlur={(e) => (e.currentTarget.style.outlineColor = "transparent")}
|
||||
{...rest}
|
||||
/>
|
||||
}: TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
||||
label?: string;
|
||||
resize?: ResizeType;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{label && <Label>{label}</Label>}
|
||||
<textarea
|
||||
className={twMerge(
|
||||
"rounded-lg border border-ui-panel-border bg-ui-controls p-3",
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
outline: "2px solid transparent",
|
||||
transition: "outline-color 0.15s ease-in-out",
|
||||
resize: resize,
|
||||
}}
|
||||
onFocus={(e) => (e.currentTarget.style.outlineColor = "#e66462")}
|
||||
onBlur={(e) => (e.currentTarget.style.outlineColor = "transparent")}
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,32 +1,59 @@
|
||||
import { NavLink, NavLinkProps } from "@remix-run/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface TypogProps {
|
||||
interface TypoProps {
|
||||
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 H1 = ({ className, children }: TypoProps) => (
|
||||
<h1 className={twMerge(baseTypo, "text-2xl font-medium", className)}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
|
||||
export const H2 = ({className,children,}:TypogProps)=>
|
||||
<h2 className={twMerge(baseTypo, "font-medium text-xl", className)}> {children} </h2>;
|
||||
export const H2 = ({ className, children }: TypoProps) => (
|
||||
<h2 className={twMerge(baseTypo, "text-xl font-medium", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</h2>
|
||||
);
|
||||
|
||||
export const H3 = ({className,children,}:TypogProps)=>
|
||||
<h3 className={twMerge(baseTypo, "font-medium text-lg", className)}> {children} </h3>;
|
||||
export const H3 = ({ className, children }: TypoProps) => (
|
||||
<h3 className={twMerge(baseTypo, "text-lg font-medium", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</h3>
|
||||
);
|
||||
|
||||
export const H4 = ({className,children,}:TypogProps)=>
|
||||
<h4 className={twMerge(baseTypo, "font-medium text-base", className)}> {children} </h4>;
|
||||
export const H4 = ({ className, children }: TypoProps) => (
|
||||
<h4 className={twMerge(baseTypo, "text-base font-medium", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</h4>
|
||||
);
|
||||
|
||||
export const Label = ({className,children,}:TypogProps)=>
|
||||
<label className={twMerge(baseTypo, "font-medium text-base", className)}> {children} </label>;
|
||||
export const Label = ({ className, children }: TypoProps) => (
|
||||
<label className={twMerge(baseTypo, "mb-2 text-base font-medium", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</label>
|
||||
);
|
||||
|
||||
export const H5 = ({className,children,}:TypogProps)=>
|
||||
<h5 className={twMerge(baseTypo, "font-medium text-sm",className)}> {children} </h5>;
|
||||
export const H5 = ({ className, children }: TypoProps) => (
|
||||
<h5 className={twMerge(baseTypo, "text-sm font-medium", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</h5>
|
||||
);
|
||||
|
||||
export const H6 = ({className,children,}:TypogProps)=>
|
||||
<h6 className={twMerge(baseTypo, "font-light text-sm",className)}> {children} </h6>;
|
||||
export const H6 = ({ className, children }: TypoProps) => (
|
||||
<h6 className={twMerge(baseTypo, "text-sm font-light", className)}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</h6>
|
||||
);
|
||||
|
||||
export const Link = ({className, ...rest}:NavLinkProps)=>
|
||||
<NavLink
|
||||
@@ -37,5 +64,6 @@ export const Link = ({className, ...rest}:NavLinkProps)=>
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
export const P = ({className,children,}:TypogProps)=>
|
||||
<p className={twMerge(baseTypo, className)}> {children} </p>;
|
||||
export const P = ({ className, children }: TypoProps) => (
|
||||
<p className={twMerge(baseTypo, className)}> {children} </p>
|
||||
);
|
||||
@@ -23,6 +23,9 @@ export const LowerPanel = ({ children }: LowerPanelPropsI) => {
|
||||
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (event.button === 0) {
|
||||
const newTime = Math.round((event.clientX - 92) / 4 / scale.value);
|
||||
if (newTime < 0) {
|
||||
return;
|
||||
}
|
||||
currentTime.value = newTime;
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
@@ -35,25 +38,27 @@ export const LowerPanel = ({ children }: LowerPanelPropsI) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"absolute bottom-0",
|
||||
"w-screen overflow-auto",
|
||||
"bg-ui-panel",
|
||||
].join(" ")}
|
||||
style={{ height: displayHeight }}
|
||||
onPointerOver={() => {
|
||||
overTimeline.value = true;
|
||||
}}
|
||||
onPointerLeave={() => (overTimeline.value = false)}
|
||||
onPointerDown={onTimelineClick}
|
||||
>
|
||||
<>
|
||||
<div
|
||||
className="w-full cursor-ns-resize bg-ui-panel-border"
|
||||
style={{ height: 3, zIndex: 1000 }}
|
||||
onPointerDown={onPointerDown}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
className={[
|
||||
"absolute bottom-0",
|
||||
"w-screen overflow-auto",
|
||||
"bg-ui-panel",
|
||||
].join(" ")}
|
||||
style={{ height: displayHeight - 3 }}
|
||||
onPointerOver={() => {
|
||||
overTimeline.value = true;
|
||||
}}
|
||||
onPointerLeave={() => (overTimeline.value = false)}
|
||||
onPointerDown={onTimelineClick}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -15,7 +15,7 @@ export const SidePanel = ({ children }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"relative border-l border-l-ui-panel-border bg-ui-panel transition-all duration-300 ease-in-out" +
|
||||
"relative h-full border-l border-l-ui-panel-border bg-ui-panel transition-all duration-300 ease-in-out" +
|
||||
(isVisible ? " w-[23.5rem]" : " w-0")
|
||||
}
|
||||
>
|
||||
|
||||
@@ -19,7 +19,7 @@ export const Tabs = ({ tabs: tabProps }: { tabs: TabType[] }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full px-2 sm:px-0 flex flex-col">
|
||||
<div className="flex h-full w-full flex-col px-2 sm:px-0">
|
||||
<Tab.Group defaultIndex={3}>
|
||||
<Tab.List className="flex">
|
||||
{tabs.map((tab, idx) => (
|
||||
@@ -31,11 +31,9 @@ export const Tabs = ({ tabs: tabProps }: { tabs: TabType[] }) => {
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels className="grow p-4">
|
||||
<Tab.Panels className="grow overflow-auto p-4">
|
||||
{tabs.map((tab, idx) => (
|
||||
<Tab.Panel key={idx}>
|
||||
{tab.children}
|
||||
</Tab.Panel>
|
||||
<Tab.Panel key={idx}>{tab.children}</Tab.Panel>
|
||||
))}
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { PageEnigmaComponent } from "./PageEnigmaComponent";
|
||||
import { TrackProvider } from "~/pages/PageEnigma/contexts/TrackContext/TrackProvider";
|
||||
import { AppUIProvider } from "~/pages/PageEnigma/contexts/AppUiContext";
|
||||
import { EngineProvider } from "~/pages/PageEnigma/contexts/EngineProvider";
|
||||
import { DragComponent } from "~/pages/PageEnigma/comps/DragComponent/DragComponent";
|
||||
|
||||
export const PageEnigma = () => {
|
||||
return (
|
||||
<TrackProvider>
|
||||
<PageEnigmaComponent />
|
||||
<AppUIProvider>
|
||||
<EngineProvider>
|
||||
<PageEnigmaComponent />
|
||||
</EngineProvider>
|
||||
</AppUIProvider>
|
||||
<DragComponent />
|
||||
</TrackProvider>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useReducer } from "react";
|
||||
import { useContext } from "react";
|
||||
|
||||
import { faSparkles } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
@@ -15,32 +15,14 @@ import { ViewSideBySide } from "./comps/ViewSideBySide";
|
||||
import { SidePanelTabs } from "./comps/SidePanelTabs";
|
||||
import { Timeline } from "./comps/Timeline";
|
||||
|
||||
import { EngineProvider } from "./contexts/EngineProvider";
|
||||
import { AppUIProvider } from "./contexts/AppUiContext";
|
||||
import {
|
||||
appUiReducer,
|
||||
appUiInitialStateValues,
|
||||
APPUI_VIEW_MODES,
|
||||
} from "./reducers";
|
||||
import {
|
||||
canDrop,
|
||||
clipLength,
|
||||
currPosition,
|
||||
dragId,
|
||||
overTimeline,
|
||||
scale,
|
||||
timelineHeight,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { APPUI_VIEW_MODES } from "./reducers";
|
||||
import { timelineHeight } from "~/pages/PageEnigma/store";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { AppUiContext } from "~/pages/PageEnigma/contexts/AppUiContext";
|
||||
|
||||
export const PageEnigmaComponent = () => {
|
||||
useSignals();
|
||||
const { currX, currY } = currPosition.value;
|
||||
|
||||
const [appUiState, dispatchAppUiState] = useReducer(
|
||||
appUiReducer,
|
||||
appUiInitialStateValues,
|
||||
);
|
||||
const [appUiState] = useContext(AppUiContext);
|
||||
|
||||
//To prevent the click event from propagating to the canvas: TODO: HANDLE THIS BETTER?
|
||||
const handleOverlayClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
@@ -54,97 +36,77 @@ export const PageEnigmaComponent = () => {
|
||||
<Button icon={faSparkles}>Generate Movie</Button>
|
||||
</TopBarHelmet>
|
||||
|
||||
<AppUIProvider value={[appUiState, dispatchAppUiState]}>
|
||||
<EngineProvider>
|
||||
<div style={{ height: "calc(100vh - 68px)" }}>
|
||||
{/* Engine section/side panel */}
|
||||
<div style={{ height: "calc(100vh - 68px)" }}>
|
||||
{/* Engine section/side panel */}
|
||||
<div
|
||||
id="engine-n-panels-wrapper"
|
||||
className="flex"
|
||||
style={{ height: `calc(100% - ${lowerHeight}px)` }}
|
||||
>
|
||||
<div className="relative w-full overflow-hidden bg-transparent">
|
||||
<div
|
||||
id="engine-n-panels-wrapper"
|
||||
className="flex"
|
||||
style={{ height: `calc(100% - ${lowerHeight}px)` }}
|
||||
className={
|
||||
appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? "invisible"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<div className="relative w-full overflow-hidden bg-transparent">
|
||||
<div
|
||||
className={
|
||||
appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? "invisible"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<canvas id="video-scene" width="1280px" height="720px" />
|
||||
<canvas id="video-scene" width="1280px" height="720px" />
|
||||
|
||||
{/* Top controls */}
|
||||
<div
|
||||
className="absolute left-0 top-0 w-full"
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<ControlsTopButtons />
|
||||
<Controls3D />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom controls */}
|
||||
<div
|
||||
className="absolute bottom-0 left-0 w-full"
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<PreviewEngineCamera />
|
||||
<ControlsVideo />
|
||||
<ControlPanelSceneObject />
|
||||
</div>
|
||||
{/* Top controls */}
|
||||
<div
|
||||
className="absolute left-0 top-0 w-full"
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<ControlsTopButtons />
|
||||
<Controls3D />
|
||||
</div>
|
||||
{appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE && (
|
||||
<ViewSideBySide />
|
||||
)}
|
||||
<LoadingDots
|
||||
className="absolute left-0 top-0"
|
||||
isShowing={appUiState.showEditorLoader.isShowing}
|
||||
type="bricks"
|
||||
message={appUiState.showEditorLoader.message}
|
||||
/>
|
||||
<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>
|
||||
|
||||
{/* Side panel */}
|
||||
<div onClick={handleOverlayClick}>
|
||||
<SidePanel>
|
||||
<SidePanelTabs />
|
||||
</SidePanel>
|
||||
{/* Bottom controls */}
|
||||
<div
|
||||
className="absolute bottom-0 left-0 w-full"
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<PreviewEngineCamera />
|
||||
<ControlsVideo />
|
||||
<ControlPanelSceneObject />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
<div onClick={handleOverlayClick}>
|
||||
<Timeline />
|
||||
</div>
|
||||
{appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE && (
|
||||
<ViewSideBySide />
|
||||
)}
|
||||
<LoadingDots
|
||||
className="absolute left-0 top-0"
|
||||
isShowing={appUiState.showEditorLoader.isShowing}
|
||||
type="bricks"
|
||||
message={appUiState.showEditorLoader.message}
|
||||
/>
|
||||
<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>
|
||||
</EngineProvider>
|
||||
</AppUIProvider>
|
||||
<div
|
||||
id={`ani-dnd-${dragId.value}`}
|
||||
className={[
|
||||
"absolute p-2",
|
||||
"rounded-lg",
|
||||
!canDrop.value ? "bg-brand-primary" : "bg-brand-secondary-700",
|
||||
dragId.value ? "block" : "hidden",
|
||||
].join(" ")}
|
||||
style={{
|
||||
top: overTimeline.value ? currY - 16 : currY - 32,
|
||||
left: currX + 1,
|
||||
zIndex: 10000,
|
||||
width: overTimeline.value ? clipLength.value * 4 * scale.value : 64,
|
||||
height: overTimeline.value ? 32 : 64,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Side panel */}
|
||||
<div onClick={handleOverlayClick}>
|
||||
<SidePanel>
|
||||
<SidePanelTabs />
|
||||
</SidePanel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
<div onClick={handleOverlayClick}>
|
||||
<Timeline />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,10 @@
|
||||
import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import { QueueClip } from "~/pages/PageEnigma/models/track";
|
||||
import { XYZ } from "../datastructures/common";
|
||||
import {
|
||||
QueueClip,
|
||||
QueueKeyframe,
|
||||
UpdateTime,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import { toTimelineActions } from "./toTimelineActions";
|
||||
|
||||
class Queue {
|
||||
@@ -9,14 +12,14 @@ class Queue {
|
||||
string,
|
||||
{
|
||||
action: fromEngineActions | toEngineActions | toTimelineActions;
|
||||
data: QueueClip | { currentTime: number } | { position: XYZ, rotation: XYZ, scale: XYZ };
|
||||
data: QueueClip | UpdateTime | QueueKeyframe;
|
||||
}[]
|
||||
> = {};
|
||||
private _subscribers: Record<
|
||||
string,
|
||||
(entry: {
|
||||
action: fromEngineActions | toEngineActions | toTimelineActions;
|
||||
data: QueueClip | { currentTime: number } | { position: XYZ, rotation: XYZ, scale: XYZ };
|
||||
data: QueueClip | UpdateTime | QueueKeyframe;
|
||||
}) => void
|
||||
> = {};
|
||||
|
||||
@@ -26,8 +29,8 @@ class Queue {
|
||||
data,
|
||||
}: {
|
||||
queueName: string;
|
||||
action: fromEngineActions | toEngineActions | toTimelineActions ;
|
||||
data: QueueClip | { currentTime: number } | { position: XYZ, rotation: XYZ, scale: XYZ };
|
||||
action: fromEngineActions | toEngineActions | toTimelineActions;
|
||||
data: QueueClip | UpdateTime | QueueKeyframe;
|
||||
}) {
|
||||
if (!this._queue[queueName]) {
|
||||
this._queue[queueName] = [];
|
||||
@@ -44,7 +47,7 @@ class Queue {
|
||||
queueName: string,
|
||||
onMessage: (entry: {
|
||||
action: fromEngineActions | toEngineActions | toTimelineActions;
|
||||
data: QueueClip | { currentTime: number } | { position: XYZ, rotation: XYZ, scale: XYZ };
|
||||
data: QueueClip | UpdateTime | QueueKeyframe;
|
||||
}) => void,
|
||||
) {
|
||||
this._subscribers[queueName] = onMessage;
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
export enum toEngineActions {
|
||||
ADD_CLIP = "add_clip",
|
||||
// data=QueueClip
|
||||
ADD_KEYFRAME = "add_keyframe",
|
||||
// data= QueueKeyframe
|
||||
DELETE_CLIP = "delete_clip",
|
||||
// data=QueueClip
|
||||
DELETE_KEYFRAME = "delete_keyframe",
|
||||
// data=QueueKeyframe
|
||||
MUTE = "mute",
|
||||
// data={version, type, group, object_uuid} if lipsync
|
||||
// data={version, type, group} if global_audio
|
||||
@@ -13,8 +17,8 @@ export enum toEngineActions {
|
||||
// data={version, type, group} if global_audio
|
||||
UPDATE_CLIP = "update_clip",
|
||||
// data=QueueClip
|
||||
UPDATE_KEYFRAME = "update_keyframe",
|
||||
// data=QueueKeyframe
|
||||
UPDATE_TIME = "update_time",
|
||||
|
||||
ADD_KEYFRAME = "add_keyframe",
|
||||
// data={currentTime: number}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { AppUiContext } from "../../contexts/AppUiContext";
|
||||
import { Button, H4, H5, H6, InputVector } from "~/components";
|
||||
import { Button, H5, InputVector } from "~/components";
|
||||
|
||||
import { XYZ } from "../../datastructures/common";
|
||||
import { ACTION_TYPES } from "../../reducers/appUiReducer/types";
|
||||
@@ -16,86 +16,143 @@ import { ACTION_TYPES } from "../../reducers/appUiReducer/types";
|
||||
import { QueueNames } from "../../Queue/QueueNames";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { toTimelineActions } from "../../Queue/toTimelineActions";
|
||||
import { QueueKeyframe } from "~/pages/PageEnigma/models/track";
|
||||
export const ControlPanelSceneObject = () => {
|
||||
const [appUiState, dispatchAppUiState] = useContext(AppUiContext);
|
||||
|
||||
const position = appUiState?.currentSceneObject.objectVectors.position;
|
||||
const rotation = appUiState?.currentSceneObject.objectVectors.rotation;
|
||||
const scalar = appUiState?.currentSceneObject.objectVectors.scale;
|
||||
|
||||
const handlePositionChange = (xyz: XYZ) => {
|
||||
if (appUiState)
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
currentSceneObject: {
|
||||
objectVectors: {
|
||||
position: { ...xyz },
|
||||
rotation: appUiState.currentSceneObject.objectVectors.rotation,
|
||||
scale: appUiState.currentSceneObject.objectVectors.scale,
|
||||
},
|
||||
},
|
||||
if (!currentSceneObject) {
|
||||
console.log("Missing Scene Object Position");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
group: currentSceneObject.group,
|
||||
object_uuid: currentSceneObject.object_uuid,
|
||||
object_name: currentSceneObject.object_name,
|
||||
version: currentSceneObject.version,
|
||||
objectVectors: {
|
||||
position: { ...xyz },
|
||||
rotation: appUiState?.controlPanel.currentSceneObject?.objectVectors
|
||||
?.rotation ?? { x: 0, y: 0, z: 0 },
|
||||
scale: appUiState?.controlPanel.currentSceneObject?.objectVectors
|
||||
?.scale ?? { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleRotationChange = (xyz: XYZ) => {
|
||||
if (appUiState)
|
||||
if (appUiState) {
|
||||
if (currentSceneObject == null) {
|
||||
console.log("Missing Scene Object Rotation");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
currentSceneObject: {
|
||||
objectVectors: {
|
||||
position: appUiState.currentSceneObject.objectVectors.position,
|
||||
rotation: { ...xyz },
|
||||
scale: appUiState.currentSceneObject.objectVectors.scale,
|
||||
},
|
||||
group: currentSceneObject.group,
|
||||
object_uuid: currentSceneObject.object_uuid,
|
||||
object_name: currentSceneObject.object_name,
|
||||
version: currentSceneObject.version,
|
||||
objectVectors: {
|
||||
position:
|
||||
appUiState.controlPanel.currentSceneObject.objectVectors.position,
|
||||
rotation: { ...xyz },
|
||||
scale:
|
||||
appUiState.controlPanel.currentSceneObject.objectVectors.scale,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleScalarChange = (xyz: XYZ) => {
|
||||
if (appUiState)
|
||||
|
||||
const handleScaleChange = (xyz: XYZ) => {
|
||||
if (appUiState) {
|
||||
if (currentSceneObject == null) {
|
||||
console.log("Missing Scene Object Scale");
|
||||
return;
|
||||
}
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
currentSceneObject: {
|
||||
objectVectors: {
|
||||
position: appUiState.currentSceneObject.objectVectors.position,
|
||||
rotation: appUiState.currentSceneObject.objectVectors.rotation,
|
||||
scale: { ...xyz },
|
||||
},
|
||||
group: currentSceneObject.group,
|
||||
object_uuid: currentSceneObject.object_uuid,
|
||||
object_name: currentSceneObject.object_name,
|
||||
version: currentSceneObject.version,
|
||||
objectVectors: {
|
||||
position:
|
||||
appUiState.controlPanel.currentSceneObject.objectVectors.position,
|
||||
rotation:
|
||||
appUiState.controlPanel.currentSceneObject.objectVectors.rotation,
|
||||
scale: { ...xyz },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnAddKeyFrame = () => {
|
||||
if (appUiState) {
|
||||
console.log(`${appUiState.currentSceneObject.objectVectors.position.x}`)
|
||||
console.log(`${appUiState.currentSceneObject.objectVectors.position.y}`)
|
||||
console.log(`${appUiState.currentSceneObject.objectVectors.position.z}`)
|
||||
if (
|
||||
position == null ||
|
||||
rotation == null ||
|
||||
scale == null ||
|
||||
currentSceneObject == null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${appUiState.currentSceneObject.objectVectors.rotation.x}`)
|
||||
console.log(`${appUiState.currentSceneObject.objectVectors.rotation.y}`)
|
||||
console.log(`${appUiState.currentSceneObject.objectVectors.rotation.z}`)
|
||||
// console.log(`Position ${position.x}`)
|
||||
// console.log(`Position ${position.y}`)
|
||||
// console.log(`Position ${position.z}`)
|
||||
|
||||
console.log(`${appUiState.currentSceneObject.objectVectors.scale.x}`)
|
||||
console.log(`${appUiState.currentSceneObject.objectVectors.scale.y}`)
|
||||
console.log(`${appUiState.currentSceneObject.objectVectors.scale.z}`)
|
||||
// console.log(`Rotation ${rotation.x}`)
|
||||
// console.log(`Rotation ${rotation.y}`)
|
||||
// console.log(`Rotation ${rotation.z}`)
|
||||
|
||||
Queue.publish({queueName:QueueNames.TO_TIMELINE, action: toTimelineActions.ADD_KEYFRAME,
|
||||
data: {
|
||||
position: appUiState.currentSceneObject.objectVectors.position,
|
||||
rotation: appUiState.currentSceneObject.objectVectors.rotation,
|
||||
scale:appUiState.currentSceneObject.objectVectors.scale
|
||||
}
|
||||
})
|
||||
// console.log(`Scale ${scale.x}`)
|
||||
// console.log(`Scale ${scale.y}`)
|
||||
// console.log(`Scale ${scale.z}`)
|
||||
|
||||
console.log(`Group ${currentSceneObject.group}`);
|
||||
console.log(`Object UUID ${currentSceneObject.object_uuid}`);
|
||||
console.log(`Object Name ${currentSceneObject.object_name}`);
|
||||
console.log(`Object Version ${currentSceneObject.version}`);
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_TIMELINE,
|
||||
action: toTimelineActions.ADD_KEYFRAME,
|
||||
data: {
|
||||
group: currentSceneObject.group,
|
||||
object_uuid: currentSceneObject.object_uuid,
|
||||
object_name: currentSceneObject.object_name,
|
||||
version: 1,
|
||||
|
||||
position: currentSceneObject.objectVectors.position,
|
||||
rotation: currentSceneObject.objectVectors.rotation,
|
||||
scale: currentSceneObject.objectVectors.scale,
|
||||
} as QueueKeyframe,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!appUiState.controlPanel.currentSceneObject) {
|
||||
return null;
|
||||
}
|
||||
const position =
|
||||
appUiState.controlPanel.currentSceneObject.objectVectors.position;
|
||||
const rotation =
|
||||
appUiState.controlPanel.currentSceneObject.objectVectors.rotation;
|
||||
const scale = appUiState.controlPanel.currentSceneObject.objectVectors.scale;
|
||||
const currentSceneObject = appUiState.controlPanel.currentSceneObject;
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={appUiState?.currentSceneObject.isShowing}
|
||||
show={appUiState?.controlPanel.isShowing}
|
||||
className="absolute bottom-0 right-0 m-4 flex h-fit w-fit flex-col gap-2 rounded-lg border border-ui-panel-border bg-ui-panel p-4 text-white"
|
||||
enter="transition-opacity duration-100"
|
||||
enterFrom="opacity-0"
|
||||
@@ -139,18 +196,20 @@ export const ControlPanelSceneObject = () => {
|
||||
<div className="flex flex-col gap-1">
|
||||
<H5>Scale</H5>
|
||||
<InputVector
|
||||
x={scalar?.x || 0}
|
||||
y={scalar?.y || 0}
|
||||
z={scalar?.z || 0}
|
||||
onChange={handleScalarChange}
|
||||
x={scale?.x || 0}
|
||||
y={scale?.y || 0}
|
||||
z={scale?.z || 0}
|
||||
onChange={handleScaleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span className="h-2" />
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary"
|
||||
className="grow"
|
||||
onClick={handleOnAddKeyFrame}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="grow"
|
||||
onClick={handleOnAddKeyFrame}
|
||||
>
|
||||
Add Keyframe (K)
|
||||
</Button>
|
||||
<Button variant="secondary" icon={faTrash} />
|
||||
|
||||
@@ -4,10 +4,11 @@ import { ButtonDialogue } from "~/modules/ButtonDialogue";
|
||||
import { AppUiContext } from "../../contexts/AppUiContext";
|
||||
import { EngineContext } from "../../contexts/EngineContext";
|
||||
import { APPUI_ACTION_TYPES } from "../../reducers";
|
||||
import { ClipGroup } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const ControlsTopButtons = () => {
|
||||
const editorEngine = useContext(EngineContext);
|
||||
const [ appUiState, dispatchAppUiState ] = useContext(AppUiContext);
|
||||
const [appUiState, dispatchAppUiState] = useContext(AppUiContext);
|
||||
const [sceneName, setSceneName] = useState<string>("");
|
||||
const [sceneToken, setSceneToken] = useState<string>("");
|
||||
|
||||
@@ -22,8 +23,8 @@ export const ControlsTopButtons = () => {
|
||||
};
|
||||
|
||||
const handleButtonTest = () => {
|
||||
editorEngine?.testStylizeRequest()
|
||||
|
||||
editorEngine?.testStylizeRequest();
|
||||
|
||||
// if(appUiState?.showEditorLoadingBar.isShowing){
|
||||
//TO GO TO A HUNDRED AND DISAPPER
|
||||
// dispatchAppUiState({
|
||||
@@ -41,15 +42,15 @@ export const ControlsTopButtons = () => {
|
||||
// }, 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",
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//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,
|
||||
@@ -59,31 +60,33 @@ export const ControlsTopButtons = () => {
|
||||
// progress: 10,
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// });
|
||||
//}
|
||||
};
|
||||
|
||||
const handleObjectPanelTest = () => {
|
||||
if (appUiState?.currentSceneObject.isShowing) {
|
||||
if (appUiState?.controlPanel.isShowing) {
|
||||
dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT
|
||||
type: APPUI_ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT,
|
||||
});
|
||||
}else{
|
||||
} else {
|
||||
dispatchAppUiState({
|
||||
type:APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT,
|
||||
payload:{
|
||||
currentSceneObject:{
|
||||
objectVectors: {
|
||||
position: {x:1,y:2,z:3},
|
||||
rotation: {x:4,y:5,z:6},
|
||||
scale: {x:7,y:8,z:9},
|
||||
}
|
||||
}
|
||||
}
|
||||
type: APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
group: ClipGroup.OBJECT,
|
||||
object_name: "",
|
||||
object_uuid: "",
|
||||
version: "1",
|
||||
objectVectors: {
|
||||
position: { x: 1, y: 2, z: 3 },
|
||||
rotation: { x: 4, y: 5, z: 6 },
|
||||
scale: { x: 7, y: 8, z: 9 },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleButtonCameraView = () => {
|
||||
editorEngine?.switchCameraView();
|
||||
@@ -111,40 +114,44 @@ export const ControlsTopButtons = () => {
|
||||
<div className="flex flex-col gap-2 pl-3 pt-3">
|
||||
<div className="flex gap-2">
|
||||
<ButtonDialogue
|
||||
buttonProps={{
|
||||
variant: "secondary",
|
||||
label: "Load Scene",
|
||||
}}
|
||||
title="Load Scene"
|
||||
confirmButtonProps={{
|
||||
label: "Load",
|
||||
disabled: sceneToken === "" ? true : false,
|
||||
onClick: handleButtonLoadScene,
|
||||
}}
|
||||
>
|
||||
buttonProps={{
|
||||
variant: "secondary",
|
||||
label: "Load Scene",
|
||||
}}
|
||||
title="Load Scene"
|
||||
confirmButtonProps={{
|
||||
label: "Load",
|
||||
disabled: sceneToken === "",
|
||||
onClick: handleButtonLoadScene,
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
label="Please provide the Token of the scene you wished to load:"
|
||||
onChange={(e)=>{setSceneToken(e.target.value)}}
|
||||
onChange={(e) => {
|
||||
setSceneToken(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</ButtonDialogue>
|
||||
|
||||
<ButtonDialogue
|
||||
buttonProps={{
|
||||
variant: "secondary",
|
||||
label: "Save Scene",
|
||||
}}
|
||||
title="Save Scene"
|
||||
confirmButtonProps={{
|
||||
label: "Save",
|
||||
disabled: sceneName === "" ? true : false,
|
||||
onClick: handleButtonSave,
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
label="Please Enter a name for your scene"
|
||||
onChange={(e)=>{setSceneName(e.target.value)}}
|
||||
/>
|
||||
</ButtonDialogue>
|
||||
<ButtonDialogue
|
||||
buttonProps={{
|
||||
variant: "secondary",
|
||||
label: "Save Scene",
|
||||
}}
|
||||
title="Save Scene"
|
||||
confirmButtonProps={{
|
||||
label: "Save",
|
||||
disabled: sceneName === "",
|
||||
onClick: handleButtonSave,
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
label="Please Enter a name for your scene"
|
||||
onChange={(e) => {
|
||||
setSceneName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</ButtonDialogue>
|
||||
|
||||
<ButtonDialogue
|
||||
buttonProps={{
|
||||
@@ -172,7 +179,6 @@ export const ControlsTopButtons = () => {
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
|
||||
<Button onClick={handleButtonSingleFrame}>Render Single Frame</Button>
|
||||
<Button onClick={handleButtonTakeFrame}>Take Frame</Button>
|
||||
<Button onClick={handleButtonRender}>Render</Button>
|
||||
@@ -181,7 +187,7 @@ export const ControlsTopButtons = () => {
|
||||
<Button
|
||||
onClick={handleButtonTest}
|
||||
className="bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary"
|
||||
style={{zIndex:9001}}
|
||||
style={{ zIndex: 9001 }}
|
||||
>
|
||||
Test
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
canDrop,
|
||||
clipLength,
|
||||
currPosition,
|
||||
dragId,
|
||||
overTimeline,
|
||||
scale,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const DragComponent = () => {
|
||||
useSignals();
|
||||
const { currX, currY } = currPosition.value;
|
||||
|
||||
return (
|
||||
<div
|
||||
id={`ani-dnd-${dragId.value}`}
|
||||
className={[
|
||||
"absolute p-2",
|
||||
"rounded-lg",
|
||||
!canDrop.value ? "bg-brand-primary" : "bg-brand-secondary-700",
|
||||
dragId.value ? "block" : "hidden",
|
||||
].join(" ")}
|
||||
style={{
|
||||
top: overTimeline.value ? currY - 16 : currY - 32,
|
||||
left: currX + 1,
|
||||
zIndex: 10000,
|
||||
width: overTimeline.value ? clipLength.value * 4 * scale.value : 64,
|
||||
height: overTimeline.value ? 32 : 64,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { currentTime, fullHeight, scale } from "~/pages/PageEnigma/store";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSortDown } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { useMouseEventsAnimation } from "~/pages/PageEnigma/comps/Timeline/utils/useMouseEventsAnimation";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const Scrubber = () => {
|
||||
useSignals();
|
||||
const { onPointerDown, time } = useMouseEventsAnimation();
|
||||
const displayTime = time === -1 ? currentTime.value : time;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute flex cursor-ew-resize flex-col items-center text-brand-primary"
|
||||
style={{ top: 8, left: displayTime * 4 * scale.value + 84 }}
|
||||
onPointerDown={onPointerDown}
|
||||
>
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faSortDown} className="h-5 text-brand-primary" />
|
||||
</div>
|
||||
<div
|
||||
className="block bg-brand-primary"
|
||||
style={{
|
||||
width: 2,
|
||||
marginTop: -5,
|
||||
height: fullHeight.value,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,122 +1,121 @@
|
||||
import { useContext, useState } from 'react';
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
import {
|
||||
Button,
|
||||
H4,
|
||||
ItemPicker,
|
||||
Textarea
|
||||
} from "~/components"
|
||||
import { Button, ItemPicker, Label, Textarea } from "~/components";
|
||||
|
||||
import { AppUiContext } from '../../contexts/AppUiContext';
|
||||
import { APPUI_ACTION_TYPES, APPUI_VIEW_MODES } from '../../reducers';
|
||||
import { EngineContext } from '../../contexts/EngineContext';
|
||||
import { ArtStyle } from '../../js/api_manager';
|
||||
export const TabStylization = ()=>{
|
||||
import { AppUiContext } from "../../contexts/AppUiContext";
|
||||
import { APPUI_ACTION_TYPES, APPUI_VIEW_MODES } from "../../reducers";
|
||||
import { EngineContext } from "../../contexts/EngineContext";
|
||||
import { ArtStyle } from "../../js/api_manager";
|
||||
|
||||
export const TabStylization = () => {
|
||||
const [appUiState, dispatchAppUiState] = useContext(AppUiContext);
|
||||
const [ selection, setSelection ] = useState<string>("Anime");
|
||||
const [selection, setSelection] = useState<ArtStyle>(ArtStyle.Anime2DFlat);
|
||||
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const handlePickingStylizer = (picked:string)=>{
|
||||
const handlePickingStylizer = (picked: ArtStyle) => {
|
||||
console.log(`Picked style: ${picked}`);
|
||||
|
||||
let selected = ArtStyle.Anime2DFlat
|
||||
switch (picked) {
|
||||
case "Anime":
|
||||
selected = ArtStyle.Anime2DFlat
|
||||
break;
|
||||
case "Pixel":
|
||||
selected = ArtStyle.PixelArt
|
||||
break;
|
||||
case "Pixar":
|
||||
selected = ArtStyle.Cartoon3D
|
||||
break;
|
||||
case "Stylized":
|
||||
selected = ArtStyle.ComicBook
|
||||
break;
|
||||
if (editorEngine == null) {
|
||||
console.log("Editor is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (editorEngine == null) { console.log("Editor is null"); return;}
|
||||
editorEngine.art_style = selected
|
||||
console.log(`actual style: ${selected}`);
|
||||
editorEngine.art_style = picked;
|
||||
setSelection(picked);
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeHandlerNegative = (e:React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (editorEngine == null) { console.log("Editor is null"); return;}
|
||||
editorEngine.negative_prompt = e.target.value
|
||||
console.log(e.target.value)
|
||||
}
|
||||
const onChangeHandlerNegative = (
|
||||
e: React.ChangeEvent<HTMLTextAreaElement>,
|
||||
) => {
|
||||
if (editorEngine == null) {
|
||||
console.log("Editor is null");
|
||||
return;
|
||||
}
|
||||
editorEngine.negative_prompt = e.target.value;
|
||||
console.log(e.target.value);
|
||||
};
|
||||
|
||||
const onChangeHandlerPositive = (e:React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (editorEngine == null) { console.log("Editor is null"); return;}
|
||||
editorEngine.positive_prompt = e.target.value
|
||||
console.log(e.target.value)
|
||||
}
|
||||
const handleChangeViewMode = ()=>{
|
||||
if(dispatchAppUiState!==null){
|
||||
const onChangeHandlerPositive = (
|
||||
e: React.ChangeEvent<HTMLTextAreaElement>,
|
||||
) => {
|
||||
if (editorEngine == null) {
|
||||
console.log("Editor is null");
|
||||
return;
|
||||
}
|
||||
editorEngine.positive_prompt = e.target.value;
|
||||
console.log(e.target.value);
|
||||
};
|
||||
|
||||
const handleChangeViewMode = () => {
|
||||
if (dispatchAppUiState !== null) {
|
||||
dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.ON_CHANGE_VIEW_MODE,
|
||||
payload: {
|
||||
viewMode: appUiState?.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? APPUI_VIEW_MODES.EDITOR : APPUI_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
|
||||
label="Anime"
|
||||
selected={(selection === "Anime")}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/0.webp"
|
||||
/>
|
||||
<ItemPicker
|
||||
label="Pixel"
|
||||
selected={selection === "Pixel"}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/1.webp"
|
||||
/>
|
||||
<ItemPicker
|
||||
label="Pixar"
|
||||
selected={selection === "Pixar"}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/2.webp"
|
||||
/>
|
||||
<ItemPicker
|
||||
label="Stylized"
|
||||
selected={selection === "Stylized"}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/3.webp"
|
||||
/>
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col gap-3">
|
||||
<div className="flex flex-col">
|
||||
<Label>Select Base Style</Label>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
<ItemPicker
|
||||
label="Anime"
|
||||
type={ArtStyle.Anime2DFlat}
|
||||
selected={selection === ArtStyle.Anime2DFlat}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/0.webp"
|
||||
/>
|
||||
<ItemPicker
|
||||
label="Pixel"
|
||||
type={ArtStyle.PixelArt}
|
||||
selected={selection === ArtStyle.PixelArt}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/1.webp"
|
||||
/>
|
||||
<ItemPicker
|
||||
label="Pixar"
|
||||
type={ArtStyle.Cartoon3D}
|
||||
selected={selection === ArtStyle.Cartoon3D}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/2.webp"
|
||||
/>
|
||||
<ItemPicker
|
||||
label="Stylized"
|
||||
type={ArtStyle.ComicBook}
|
||||
selected={selection === ArtStyle.ComicBook}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/3.webp"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<H4>Enter a Prompt</H4>
|
||||
<div className="flex gap-2 my-2">
|
||||
<Textarea
|
||||
className="w-full h-32"
|
||||
placeholder="Type here to describe your scene"
|
||||
onChange={onChangeHandlerPositive}
|
||||
/>
|
||||
</div>
|
||||
<H4>Negative Prompt</H4>
|
||||
<div className="flex gap-2 my-2">
|
||||
<Textarea
|
||||
className="w-full h-32"
|
||||
placeholder="Type here to filter out the things you don't want in the scene"
|
||||
onChange={onChangeHandlerNegative}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-6 justify-center">
|
||||
|
||||
<Textarea
|
||||
label="Positive Prompt"
|
||||
className="h-32 w-full"
|
||||
placeholder="Type here to describe your scene"
|
||||
onChange={onChangeHandlerPositive}
|
||||
resize="none"
|
||||
/>
|
||||
<Textarea
|
||||
label="Negative Prompt"
|
||||
className="h-32 w-full"
|
||||
placeholder="Type here to filter out the things you don't want in the scene"
|
||||
onChange={onChangeHandlerNegative}
|
||||
resize="none"
|
||||
/>
|
||||
<div className="mt-3 flex justify-center gap-2">
|
||||
<Button onClick={handleChangeViewMode}>
|
||||
{
|
||||
appUiState?.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? "Back to Scene" : "Preview Side by Side"
|
||||
}
|
||||
{appUiState?.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? "Back to Scene"
|
||||
: "Preview Side by Side"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useContext } from "react";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { TrackClips } from "~/pages/PageEnigma/comps/Timeline/TrackClips";
|
||||
import { fullWidth } from "~/pages/PageEnigma/store";
|
||||
import {
|
||||
audioGroup,
|
||||
fullWidth,
|
||||
toggleAudioMute,
|
||||
updateAudio,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
|
||||
export const Audio = () => {
|
||||
const { audio, updateAudio, toggleAudioMute } = useContext(TrackContext);
|
||||
const { clips } = audio!;
|
||||
const { clips } = audioGroup.value;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -17,12 +19,12 @@ export const Audio = () => {
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<TrackClips
|
||||
id={audio!.id}
|
||||
id={audioGroup.value.id}
|
||||
clips={clips}
|
||||
title="Global Audio TrackClips"
|
||||
updateClip={updateAudio}
|
||||
style="audio"
|
||||
muted={audio!.muted}
|
||||
muted={audioGroup.value.muted}
|
||||
toggleMute={toggleAudioMute}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { useContext } from "react";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { TrackClips } from "~/pages/PageEnigma/comps/Timeline/TrackClips";
|
||||
import { fullWidth } from "~/pages/PageEnigma/store";
|
||||
import { cameraGroup, fullWidth, updateCamera } from "~/pages/PageEnigma/store";
|
||||
import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames";
|
||||
|
||||
export const Camera = () => {
|
||||
const { camera, updateCamera } = useContext(TrackContext);
|
||||
const { clips } = camera!;
|
||||
const { keyframes } = cameraGroup.value!;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -14,12 +11,12 @@ export const Camera = () => {
|
||||
>
|
||||
<div className="mb-5 pt-2 text-xs font-medium text-white">Camera</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<TrackClips
|
||||
id={camera!.id}
|
||||
clips={clips}
|
||||
<TrackKeyFrames
|
||||
id={cameraGroup.value.id}
|
||||
keyframes={keyframes}
|
||||
title="Camera Position/Rotation"
|
||||
style="camera"
|
||||
updateClip={updateCamera}
|
||||
updateKeyframe={updateCamera}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useCallback, useContext, useMemo } from "react";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { TrackClips } from "~/pages/PageEnigma/comps/Timeline/TrackClips";
|
||||
import { fullWidth } from "~/pages/PageEnigma/store";
|
||||
import {
|
||||
characterGroups,
|
||||
fullWidth,
|
||||
toggleLipSyncMute,
|
||||
updateCharacters,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames";
|
||||
|
||||
function buildUpdaters(
|
||||
updateCharacters: (options: {
|
||||
@@ -18,12 +23,8 @@ function buildUpdaters(
|
||||
}) {
|
||||
updateCharacters({ ...options, type: "animations" });
|
||||
}
|
||||
function updateClipPosition(options: {
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) {
|
||||
updateCharacters({ ...options, type: "positions" });
|
||||
function updateClipPosition(options: { id: string; offset: number }) {
|
||||
updateCharacters({ ...options, length: 0, type: "positions" });
|
||||
}
|
||||
function updateClipLipSync(options: {
|
||||
id: string;
|
||||
@@ -39,20 +40,19 @@ interface Props {
|
||||
}
|
||||
|
||||
export const Character = ({ characterId }: Props) => {
|
||||
const { characters, updateCharacters, toggleLipSyncMute } =
|
||||
useContext(TrackContext);
|
||||
const character = characters.find((row) => (row.id = characterId));
|
||||
const character = characterGroups.value.find((row) => (row.id = characterId));
|
||||
|
||||
const { updateClipLipSync, updateClipPosition, updateClipAnimations } =
|
||||
useMemo(() => buildUpdaters(updateCharacters), [updateCharacters]);
|
||||
useMemo(() => buildUpdaters(updateCharacters), []);
|
||||
|
||||
const toggleCharacterLipSyncMute = useCallback(() => {
|
||||
toggleLipSyncMute(character?.id ?? "");
|
||||
}, []);
|
||||
}, [character?.id]);
|
||||
|
||||
if (!character) {
|
||||
return false;
|
||||
}
|
||||
const { animationClips, positionClips, lipSyncClips } = character;
|
||||
const { animationClips, positionKeyframes, lipSyncClips } = character;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -71,13 +71,12 @@ export const Character = ({ characterId }: Props) => {
|
||||
style="character"
|
||||
type="animations"
|
||||
/>
|
||||
<TrackClips
|
||||
<TrackKeyFrames
|
||||
id={character.id}
|
||||
clips={positionClips}
|
||||
keyframes={positionKeyframes}
|
||||
title="Character Position/Rotation"
|
||||
updateClip={updateClipPosition}
|
||||
updateKeyframe={updateClipPosition}
|
||||
style="character"
|
||||
type="positions"
|
||||
/>
|
||||
<TrackClips
|
||||
id={character.id}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { characterGroups } from "~/pages/PageEnigma/store";
|
||||
import { Character } from "~/pages/PageEnigma/comps/Timeline/Character";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const Characters = () => {
|
||||
useSignals();
|
||||
return (
|
||||
<>
|
||||
{characterGroups.value.map((character) => (
|
||||
<Character key={character.id} characterId={character.id} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { objectGroup } from "~/pages/PageEnigma/store";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { ObjectTrack } from "~/pages/PageEnigma/comps/Timeline/ObjectTrack";
|
||||
|
||||
export const ObjectGroups = () => {
|
||||
useSignals();
|
||||
return (
|
||||
<>
|
||||
{objectGroup.value.objects.length > 0 && (
|
||||
<div className="p-4">
|
||||
<ObjectTrack />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+7
-11
@@ -1,11 +1,7 @@
|
||||
import { useContext } from "react";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { fullWidth } from "~/pages/PageEnigma/store";
|
||||
import { fullWidth, objectGroup, updateObject } from "~/pages/PageEnigma/store";
|
||||
import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames";
|
||||
|
||||
export const Objects = () => {
|
||||
const { objects, updateObject } = useContext(TrackContext);
|
||||
|
||||
export const ObjectTrack = () => {
|
||||
return (
|
||||
<div
|
||||
className="block rounded-lg bg-objects-groupBg pb-5 pl-2 pr-4"
|
||||
@@ -14,13 +10,13 @@ export const Objects = () => {
|
||||
<div className="prevent-select mb-5 pt-2 text-xs font-medium text-white">
|
||||
Objects
|
||||
</div>
|
||||
{objects.objects.map((object) => (
|
||||
<div key={object.id} className="flex flex-col gap-4">
|
||||
{objectGroup.value.objects.map((object) => (
|
||||
<div key={object.object_uuid} className="flex flex-col gap-4">
|
||||
<TrackKeyFrames
|
||||
id={object.id}
|
||||
keyFrames={object.keyFrames}
|
||||
id={object.object_uuid}
|
||||
keyframes={object.keyframes}
|
||||
title="Mask Position/Rotation"
|
||||
updateClip={updateObject}
|
||||
updateKeyframe={updateObject}
|
||||
style="objects"
|
||||
/>
|
||||
</div>
|
||||
@@ -1,62 +1,60 @@
|
||||
import { Fragment, useCallback, useContext, useEffect, useState } from "react";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { LowerPanel } from "~/modules/LowerPanel";
|
||||
import { Character } from "./Character";
|
||||
|
||||
import { Camera } from "./Camera";
|
||||
import { Audio } from "./Audio";
|
||||
import { Objects } from "./Objects";
|
||||
import { useMouseEventsAnimation } from "./utils/useMouseEventsAnimation";
|
||||
import { faSortDown } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ConfirmationModal } from "~/components/ConfirmationModal";
|
||||
import {
|
||||
scale,
|
||||
currentTime,
|
||||
filmLength,
|
||||
timelineHeight,
|
||||
deleteAudioClip,
|
||||
deleteCharacterClip,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { useQueueHandler } from "~/pages/PageEnigma/comps/Timeline/utils/useQueueHandler";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { TimerGrid } from "~/pages/PageEnigma/comps/TimerGrid/TimerGrid";
|
||||
import { Scrubber } from "~/pages/PageEnigma/comps/Scrubber/Scrubber";
|
||||
import { Characters } from "~/pages/PageEnigma/comps/Timeline/Characters";
|
||||
import { ObjectGroups } from "~/pages/PageEnigma/comps/Timeline/ObjectGroups";
|
||||
import useUpdateKeyframe from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe";
|
||||
import { Clip, Keyframe } from "~/pages/PageEnigma/models/track";
|
||||
import { selectedItem } from "~/pages/PageEnigma/store/selectedItem";
|
||||
|
||||
function getItemType(item: Clip | Keyframe | null) {
|
||||
if (!item) {
|
||||
return "";
|
||||
}
|
||||
return (item as Clip).clip_uuid ? "clip" : "keyframe";
|
||||
}
|
||||
|
||||
export const Timeline = () => {
|
||||
const {
|
||||
characters,
|
||||
objects,
|
||||
selectedClip,
|
||||
deleteCharacterClip,
|
||||
deleteAudioClip,
|
||||
deleteCameraClip,
|
||||
} = useContext(TrackContext);
|
||||
const { onPointerDown, time } = useMouseEventsAnimation();
|
||||
useSignals();
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const { deleteKeyframe } = useUpdateKeyframe();
|
||||
|
||||
// implement the code to handle incoming messages from the Engine
|
||||
useQueueHandler();
|
||||
|
||||
const sectionWidth = 60 * 4 * scale.value;
|
||||
const fullHeight =
|
||||
characters.length * 268 + objects.objects.length * 60 + 300 + 96;
|
||||
|
||||
useEffect(() => {
|
||||
timelineHeight.value = window.outerHeight * 0.25;
|
||||
}, []);
|
||||
|
||||
const onDeleteAsk = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (["Backspace", "Delete"].indexOf(event.key) > -1 && selectedClip) {
|
||||
setDialogOpen(true);
|
||||
}
|
||||
},
|
||||
[selectedClip],
|
||||
);
|
||||
|
||||
const displayTime = time === -1 ? currentTime.value : time;
|
||||
const onDeleteAsk = useCallback((event: KeyboardEvent) => {
|
||||
if (
|
||||
["Backspace", "Delete"].indexOf(event.key) > -1 &&
|
||||
selectedItem.value !== null
|
||||
) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
setDialogOpen(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
deleteCharacterClip(selectedClip!);
|
||||
deleteCameraClip(selectedClip!);
|
||||
deleteAudioClip(selectedClip!);
|
||||
}, [selectedClip, deleteAudioClip, deleteCharacterClip, deleteCameraClip]);
|
||||
deleteCharacterClip(selectedItem.value as Clip);
|
||||
deleteAudioClip(selectedItem.value as Clip);
|
||||
deleteKeyframe(selectedItem.value as Keyframe);
|
||||
selectedItem.value = null;
|
||||
}, [deleteKeyframe]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("keydown", onDeleteAsk);
|
||||
@@ -69,56 +67,9 @@ export const Timeline = () => {
|
||||
return (
|
||||
<>
|
||||
<LowerPanel>
|
||||
<div
|
||||
className={[
|
||||
"prevent-select mt-4",
|
||||
"flex h-3",
|
||||
"border-t border-t-ui-panel-border",
|
||||
"text-xs text-white opacity-75",
|
||||
].join(" ")}
|
||||
>
|
||||
{Array(filmLength.value)
|
||||
.fill(0)
|
||||
.map((_, index) => (
|
||||
<Fragment key={index}>
|
||||
<div
|
||||
className="absolute ps-1 pt-1"
|
||||
style={{ left: index * sectionWidth + 92 }}
|
||||
>
|
||||
00:{index < 10 ? "0" + index.toString() : index.toString()}
|
||||
</div>
|
||||
<div
|
||||
className="absolute block h-full bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
left: index * sectionWidth + 88,
|
||||
height: fullHeight,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{ left: filmLength.value * sectionWidth + 92 }}
|
||||
>
|
||||
00:
|
||||
{filmLength.value < 10
|
||||
? "0" + filmLength.value.toString()
|
||||
: filmLength.value.toString()}
|
||||
</div>
|
||||
<div
|
||||
className="absolute block h-full bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
left: filmLength.value * sectionWidth + 88,
|
||||
height: fullHeight,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TimerGrid />
|
||||
<div className="p-4">
|
||||
{characters.map((character) => (
|
||||
<Character key={character.id} characterId={character.id} />
|
||||
))}
|
||||
<Characters />
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Camera />
|
||||
@@ -126,35 +77,12 @@ export const Timeline = () => {
|
||||
<div className="p-4">
|
||||
<Audio />
|
||||
</div>
|
||||
{objects.objects.length > 0 && (
|
||||
<div className="p-4">
|
||||
<Objects />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="absolute flex cursor-ew-resize flex-col items-center text-brand-primary"
|
||||
style={{ top: 8, left: displayTime * 4 * scale.value + 84 }}
|
||||
onPointerDown={onPointerDown}
|
||||
>
|
||||
<div>
|
||||
<FontAwesomeIcon
|
||||
icon={faSortDown}
|
||||
className="h-5 text-brand-primary"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="block bg-brand-primary"
|
||||
style={{
|
||||
width: 2,
|
||||
marginTop: -5,
|
||||
height: fullHeight,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<ObjectGroups />
|
||||
<Scrubber />
|
||||
</LowerPanel>
|
||||
<ConfirmationModal
|
||||
title="Delete Clip"
|
||||
text="Are you sure you want to delete the selected clip?"
|
||||
text={`Are you sure you want to delete the selected ${getItemType(selectedItem.value)}?`}
|
||||
open={dialogOpen}
|
||||
onClose={() => setDialogOpen(false)}
|
||||
onOk={() => {
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { useMouseEventsClip } from "./utils/useMouseEventsClip";
|
||||
import { BaseClip } from "~/pages/PageEnigma/models/track";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { useContext, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { scale } from "~/pages/PageEnigma/store";
|
||||
import { Clip } from "~/pages/PageEnigma/models/track";
|
||||
import { selectedItem } from "~/pages/PageEnigma/store/selectedItem";
|
||||
|
||||
interface Props {
|
||||
min: number;
|
||||
max: number;
|
||||
style: "character" | "camera" | "audio" | "objects";
|
||||
clip: BaseClip;
|
||||
clip: Clip;
|
||||
updateClip: (options: { id: string; offset: number; length: number }) => void;
|
||||
}
|
||||
|
||||
export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => {
|
||||
const { selectClip, selectedClip } = useContext(TrackContext);
|
||||
const [state, setState] = useState({
|
||||
length: clip.length,
|
||||
offset: clip.offset,
|
||||
@@ -26,11 +25,15 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => {
|
||||
setState,
|
||||
);
|
||||
|
||||
const selectedClipId = (selectedItem.value as Clip | null)?.clip_uuid ?? "";
|
||||
|
||||
const { length, offset } = state;
|
||||
|
||||
const classes = [
|
||||
"absolute",
|
||||
clip.id === selectedClip ? `bg-${style}-selected` : `bg-${style}-clip`,
|
||||
clip.clip_uuid === selectedClipId
|
||||
? `bg-${style}-selected`
|
||||
: `bg-${style}-clip`,
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -51,7 +54,7 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => {
|
||||
...classes,
|
||||
"rounded-l-lg",
|
||||
"block h-full",
|
||||
clip.id === selectedClip
|
||||
clip.clip_uuid === selectedClipId
|
||||
? "border border-b-2 border-l-2 border-r-0 border-t-2 border-white focus-visible:outline-0"
|
||||
: "",
|
||||
].join(" ")}
|
||||
@@ -61,13 +64,13 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => {
|
||||
cursor: "w-resize",
|
||||
}}
|
||||
onPointerDown={(event) => onPointerDown(event, "left")}
|
||||
onClick={() => selectClip(clip.id)}
|
||||
onClick={() => (selectedItem.value = clip)}
|
||||
/>
|
||||
<button
|
||||
className={[
|
||||
...classes,
|
||||
"block h-full",
|
||||
clip.id === selectedClip
|
||||
clip.clip_uuid === selectedClipId
|
||||
? "border border-b-2 border-l-0 border-r-0 border-t-2 border-white focus-visible:outline-0"
|
||||
: "",
|
||||
].join(" ")}
|
||||
@@ -77,14 +80,14 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => {
|
||||
cursor: "move",
|
||||
}}
|
||||
onPointerDown={(event) => onPointerDown(event, "drag")}
|
||||
onClick={() => selectClip(clip.id)}
|
||||
onClick={() => (selectedItem.value = clip)}
|
||||
/>
|
||||
<button
|
||||
className={[
|
||||
...classes,
|
||||
"rounded-r-lg",
|
||||
"block h-full",
|
||||
clip.id === selectedClip
|
||||
clip.clip_uuid === selectedClipId
|
||||
? "border border-b-2 border-l-0 border-r-2 border-t-2 border-white focus-visible:outline-0"
|
||||
: "",
|
||||
].join(" ")}
|
||||
@@ -94,7 +97,7 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => {
|
||||
cursor: "e-resize",
|
||||
}}
|
||||
onPointerDown={(event) => onPointerDown(event, "right")}
|
||||
onClick={() => selectClip(clip.id)}
|
||||
onClick={() => (selectedItem.value = clip)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,106 +1,64 @@
|
||||
import { BaseKeyFrame } from "~/pages/PageEnigma/models/track";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { useContext, useState } from "react";
|
||||
import { Keyframe } from "~/pages/PageEnigma/models/track";
|
||||
import { useState } from "react";
|
||||
import { scale } from "~/pages/PageEnigma/store";
|
||||
import { useMouseEventsKeyframe } from "~/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSortUpDown } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { selectedItem } from "~/pages/PageEnigma/store/selectedItem";
|
||||
|
||||
interface Props {
|
||||
min: number;
|
||||
max: number;
|
||||
style: "character" | "camera" | "audio" | "objects";
|
||||
keyFrame: BaseKeyFrame;
|
||||
updateClip: (options: { id: string; offset: number; length: number }) => void;
|
||||
keyframe: Keyframe;
|
||||
updateKeyframe: (options: { id: string; offset: number }) => void;
|
||||
}
|
||||
|
||||
export const TrackKeyFrame = ({
|
||||
keyFrame,
|
||||
// min,
|
||||
// max,
|
||||
keyframe,
|
||||
min,
|
||||
max,
|
||||
style,
|
||||
// updateClip,
|
||||
updateKeyframe,
|
||||
}: Props) => {
|
||||
const { selectedClip } = useContext(TrackContext);
|
||||
// const { selectClip, selectedClip } = useContext(TrackContext);
|
||||
const [state, setState] = useState({
|
||||
offset: keyFrame.offset,
|
||||
const [offset, setOffset] = useState(keyframe.offset);
|
||||
const { onPointerDown } = useMouseEventsKeyframe({
|
||||
keyframe,
|
||||
max,
|
||||
min,
|
||||
updateKeyframe,
|
||||
setOffset,
|
||||
});
|
||||
// const { onPointerDown } = useMouseEventsClip(
|
||||
// keyFrame,
|
||||
// max,
|
||||
// min,
|
||||
// updateClip,
|
||||
// setState,
|
||||
// );
|
||||
|
||||
const { offset } = state;
|
||||
const selectedKeyframeId =
|
||||
(selectedItem.value as Keyframe | null)?.keyframe_uuid ?? "";
|
||||
|
||||
const classes = [
|
||||
"absolute",
|
||||
keyFrame.id === selectedClip ? `bg-${style}-selected` : `bg-${style}-clip`,
|
||||
keyframe.keyframe_uuid === selectedKeyframeId
|
||||
? `bg-${style}-selected`
|
||||
: `bg-${style}-clip`,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={[
|
||||
...classes,
|
||||
"px-[6px] py-[3px]",
|
||||
"rounded",
|
||||
"prevent-select text-xs font-medium text-white",
|
||||
].join(" ")}
|
||||
style={{ top: -28, left: offset * 4 * scale.value + 2 }}
|
||||
>
|
||||
{keyFrame.name}
|
||||
</div>
|
||||
<button
|
||||
className={[
|
||||
...classes,
|
||||
"rounded-l-lg",
|
||||
"block h-full",
|
||||
keyFrame.id === selectedClip
|
||||
? "border border-b-2 border-l-2 border-r-0 border-t-2 border-white focus-visible:outline-0"
|
||||
: "",
|
||||
].join(" ")}
|
||||
style={{
|
||||
width: 15,
|
||||
left: offset * 4 * scale.value,
|
||||
cursor: "w-resize",
|
||||
}}
|
||||
// onPointerDown={(event) => onPointerDown(event, "left")}
|
||||
// onClick={() => selectClip(clip.id)}
|
||||
/>
|
||||
<button
|
||||
className={[
|
||||
...classes,
|
||||
"block h-full",
|
||||
keyFrame.id === selectedClip
|
||||
? "border border-b-2 border-l-0 border-r-0 border-t-2 border-white focus-visible:outline-0"
|
||||
: "",
|
||||
].join(" ")}
|
||||
style={{
|
||||
width: length * 4 * scale.value - 30,
|
||||
left: offset * 4 * scale.value + 15,
|
||||
cursor: "move",
|
||||
}}
|
||||
// onPointerDown={(event) => onPointerDown(event, "drag")}
|
||||
// onClick={() => selectClip(clip.id)}
|
||||
/>
|
||||
<button
|
||||
className={[
|
||||
...classes,
|
||||
"rounded-r-lg",
|
||||
"block h-full",
|
||||
keyFrame.id === selectedClip
|
||||
? "border border-b-2 border-l-0 border-r-2 border-t-2 border-white focus-visible:outline-0"
|
||||
: "",
|
||||
].join(" ")}
|
||||
style={{
|
||||
width: 15,
|
||||
left: offset * 4 * scale.value + length * 4 * scale.value - 15,
|
||||
cursor: "e-resize",
|
||||
}}
|
||||
// onPointerDown={(event) => onPointerDown(event, "right")}
|
||||
// onClick={() => selectClip(clip.id)}
|
||||
/>
|
||||
</>
|
||||
<button
|
||||
className={[
|
||||
...classes,
|
||||
"rounded-l-lg",
|
||||
"block h-full",
|
||||
keyframe.keyframe_uuid === selectedKeyframeId
|
||||
? "border-2 border-white focus-visible:outline-0"
|
||||
: "",
|
||||
].join(" ")}
|
||||
style={{
|
||||
width: 15,
|
||||
left: offset * 4 * scale.value,
|
||||
cursor: "w-resize",
|
||||
}}
|
||||
onPointerDown={(event) => onPointerDown(event, "left")}
|
||||
onClick={() => (selectedItem.value = keyframe)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSortUpDown} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -1,118 +1,47 @@
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faVolume, faVolumeSlash } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { BaseKeyFrame } from "~/pages/PageEnigma/models/track";
|
||||
import { PointerEvent } from "react";
|
||||
import {
|
||||
canDrop,
|
||||
clipLength,
|
||||
dragType,
|
||||
dropId,
|
||||
dropOffset,
|
||||
filmLength,
|
||||
scale,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { Keyframe } from "~/pages/PageEnigma/models/track";
|
||||
import { filmLength } from "~/pages/PageEnigma/store";
|
||||
import { TrackKeyFrame } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrame";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
keyFrames: BaseKeyFrame[];
|
||||
keyframes: Keyframe[];
|
||||
title: string;
|
||||
style: "character" | "audio" | "camera" | "objects";
|
||||
type?: "animations" | "positions" | "lipSync";
|
||||
toggleMute?: () => void;
|
||||
muted?: boolean;
|
||||
updateClip: (options: { id: string; length: number; offset: number }) => void;
|
||||
updateKeyframe: (options: { id: string; offset: number }) => void;
|
||||
}
|
||||
|
||||
export const TrackKeyFrames = ({
|
||||
id,
|
||||
keyFrames,
|
||||
toggleMute,
|
||||
updateClip,
|
||||
muted,
|
||||
keyframes,
|
||||
updateKeyframe,
|
||||
title,
|
||||
style,
|
||||
type,
|
||||
}: Props) => {
|
||||
const trackType = type ?? style;
|
||||
|
||||
function onPointerOver() {
|
||||
if (dragType.value !== trackType) {
|
||||
return;
|
||||
}
|
||||
dropId.value = id;
|
||||
}
|
||||
|
||||
function onPointerMove(event: PointerEvent<HTMLDivElement>) {
|
||||
if (dragType.value !== trackType) {
|
||||
return;
|
||||
}
|
||||
const track = document.getElementById(`track-${trackType}-${id}`);
|
||||
if (!track) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now check the the clip fits
|
||||
const position = track.getBoundingClientRect();
|
||||
const clipOffset = (event.clientX - position.x) / 4 / scale.value;
|
||||
|
||||
if (clipOffset + clipLength.value > filmLength.value * 60) {
|
||||
canDrop.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
canDrop.value = true;
|
||||
dropOffset.value = clipOffset;
|
||||
}
|
||||
function onPointerLeave() {
|
||||
if (dragType.value !== trackType) {
|
||||
return;
|
||||
}
|
||||
canDrop.value = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pl-16">
|
||||
<div
|
||||
id={`track-${trackType}-${id}`}
|
||||
id={`track-${style}-${id}`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${style}-unselected`}
|
||||
onPointerOver={onPointerOver}
|
||||
onPointerLeave={onPointerLeave}
|
||||
onPointerMove={onPointerMove}
|
||||
>
|
||||
{keyFrames.map((keyFrame, index) => (
|
||||
{keyframes.map((keyframe, index) => (
|
||||
<TrackKeyFrame
|
||||
key={keyFrame.id}
|
||||
min={index > 0 ? keyFrames[index - 1].offset + 1 : 0}
|
||||
key={keyframe.keyframe_uuid}
|
||||
min={index > 0 ? keyframes[index - 1].offset + 1 : 0}
|
||||
max={
|
||||
index < keyFrames.length - 1
|
||||
? keyFrames[index + 1].offset
|
||||
index < keyframes.length - 1
|
||||
? keyframes[index + 1].offset
|
||||
: filmLength.value * 60
|
||||
}
|
||||
style={style}
|
||||
updateClip={updateClip}
|
||||
keyFrame={keyFrame}
|
||||
updateKeyframe={updateKeyframe}
|
||||
keyframe={keyframe}
|
||||
/>
|
||||
))}
|
||||
<div className="prevent-select absolute ps-2 pt-1 text-xs font-medium text-white">
|
||||
{title}
|
||||
</div>
|
||||
{!!toggleMute && (
|
||||
<button
|
||||
className="text-md absolute text-white transition-colors duration-100 hover:text-white/80"
|
||||
style={{ top: 6, left: -28 }}
|
||||
onClick={toggleMute}
|
||||
>
|
||||
{muted ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faVolumeSlash}
|
||||
className="text-brand-primary transition-colors duration-100 hover:text-brand-primary/80"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faVolume} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import React, { Dispatch, useCallback, useEffect, useRef } from "react";
|
||||
import { Keyframe } from "~/pages/PageEnigma/models/track";
|
||||
import { canDrop, scale } from "~/pages/PageEnigma/store";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const useMouseEventsKeyframe = ({
|
||||
keyframe,
|
||||
max,
|
||||
min,
|
||||
updateKeyframe,
|
||||
setOffset,
|
||||
}: {
|
||||
keyframe: Keyframe;
|
||||
max: number;
|
||||
min: number;
|
||||
updateKeyframe: (args: { id: string; offset: number }) => void;
|
||||
setOffset: Dispatch<number>;
|
||||
}) => {
|
||||
useSignals();
|
||||
const currOffset = useRef(keyframe.offset);
|
||||
const initOffset = useRef(keyframe.offset);
|
||||
const isActive = useRef("");
|
||||
const clientX = useRef(0);
|
||||
|
||||
const onPointerUp = useCallback(() => {
|
||||
if (isActive.current) {
|
||||
updateKeyframe({
|
||||
id: keyframe.keyframe_uuid,
|
||||
offset: Math.round(currOffset.current),
|
||||
});
|
||||
isActive.current = "";
|
||||
canDrop.value = false;
|
||||
}
|
||||
}, [updateKeyframe, keyframe.keyframe_uuid]);
|
||||
|
||||
const onMouseMove = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
const delta = (event.clientX - clientX.current) / 4 / scale.value;
|
||||
const deltaOffset = delta + initOffset.current;
|
||||
if (isActive.current === "drag") {
|
||||
if (deltaOffset < min) {
|
||||
currOffset.current = min;
|
||||
return;
|
||||
}
|
||||
if (deltaOffset > max) {
|
||||
currOffset.current = max;
|
||||
return;
|
||||
}
|
||||
currOffset.current = deltaOffset;
|
||||
}
|
||||
setOffset(currOffset.current);
|
||||
},
|
||||
[max, min, setOffset],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
currOffset.current = keyframe.offset;
|
||||
initOffset.current = keyframe.offset;
|
||||
}, [keyframe.offset]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("pointerup", onPointerUp);
|
||||
window.addEventListener("pointermove", onMouseMove);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("pointerup", onPointerUp);
|
||||
window.removeEventListener("pointermove", onMouseMove);
|
||||
};
|
||||
}, [onPointerUp, onMouseMove]);
|
||||
|
||||
return {
|
||||
onPointerDown: (
|
||||
event: React.PointerEvent<HTMLButtonElement>,
|
||||
type: string,
|
||||
) => {
|
||||
if (event.button === 0) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
clientX.current = event.clientX;
|
||||
isActive.current = type;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,25 +1,29 @@
|
||||
import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useCallback, useContext, useEffect } from "react";
|
||||
import { currentTime } from "~/pages/PageEnigma/store";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import { QueueClip } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
interface UpdateTime {
|
||||
currentTime: number;
|
||||
}
|
||||
import {
|
||||
QueueClip,
|
||||
QueueKeyframe,
|
||||
UpdateTime,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { toTimelineActions } from "~/pages/PageEnigma/Queue/toTimelineActions";
|
||||
|
||||
interface Arguments {
|
||||
action: fromEngineActions | toEngineActions;
|
||||
data: QueueClip | UpdateTime;
|
||||
action: fromEngineActions | toEngineActions | toTimelineActions;
|
||||
data: QueueClip | UpdateTime | QueueKeyframe;
|
||||
}
|
||||
|
||||
export function useQueueHandler() {
|
||||
useSignals();
|
||||
const handleActions = useCallback(({ action, data }: Arguments) => {
|
||||
console.log(`FROM ENGINE:${action} ${data}`)
|
||||
const { addKeyframe } = useContext(TrackContext);
|
||||
|
||||
const handleFromEngineActions = useCallback(({ action, data }: Arguments) => {
|
||||
console.log("FROM ENGINE", action, data);
|
||||
switch (action) {
|
||||
case fromEngineActions.UPDATE_TIME:
|
||||
currentTime.value = (data as UpdateTime).currentTime;
|
||||
@@ -29,7 +33,19 @@ export function useQueueHandler() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleToTimelineActions = useCallback(({ action, data }: Arguments) => {
|
||||
console.log("TO TIMELINE", action, data);
|
||||
switch (action) {
|
||||
case toTimelineActions.ADD_KEYFRAME:
|
||||
addKeyframe(data as QueueKeyframe, currentTime.value);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown action ${action}`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
Queue.subscribe(QueueNames.FROM_ENGINE, handleActions);
|
||||
}, [handleActions]);
|
||||
Queue.subscribe(QueueNames.FROM_ENGINE, handleFromEngineActions);
|
||||
Queue.subscribe(QueueNames.TO_TIMELINE, handleToTimelineActions);
|
||||
}, [handleFromEngineActions, handleToTimelineActions]);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { filmLength, fullHeight, scale } from "~/pages/PageEnigma/store";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export const TimerGrid = () => {
|
||||
const sectionWidth = 60 * 4 * scale.value;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"prevent-select mt-4",
|
||||
"flex h-3",
|
||||
"border-t border-t-ui-panel-border",
|
||||
"text-xs text-white opacity-75",
|
||||
].join(" ")}
|
||||
>
|
||||
{Array(filmLength.value)
|
||||
.fill(0)
|
||||
.map((_, index) => (
|
||||
<Fragment key={index}>
|
||||
<div
|
||||
className="absolute ps-1 pt-1"
|
||||
style={{ left: index * sectionWidth + 92 }}
|
||||
>
|
||||
00:{index < 10 ? "0" + index.toString() : index.toString()}
|
||||
</div>
|
||||
<div
|
||||
className="absolute block h-full bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
left: index * sectionWidth + 88,
|
||||
height: fullHeight.value,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{ left: filmLength.value * sectionWidth + 92 }}
|
||||
>
|
||||
00:
|
||||
{filmLength.value < 10
|
||||
? "0" + filmLength.value.toString()
|
||||
: filmLength.value.toString()}
|
||||
</div>
|
||||
<div
|
||||
className="absolute block h-full bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
left: filmLength.value * sectionWidth + 88,
|
||||
height: fullHeight.value,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,19 +1,20 @@
|
||||
import { createContext, Dispatch, ReactNode } from "react";
|
||||
import { AppUiState, AppUiAction } from "../reducers";
|
||||
import { createContext, Dispatch, ReactNode, useReducer } from "react";
|
||||
import {
|
||||
AppUiState,
|
||||
AppUiAction,
|
||||
appUiReducer,
|
||||
appUiInitialStateValues,
|
||||
} from "../reducers";
|
||||
|
||||
export const AppUiContext = createContext<[
|
||||
AppUiState | null,
|
||||
Dispatch<AppUiAction>
|
||||
]>([ null ,()=>{}]);
|
||||
export const AppUiContext = createContext<[AppUiState, Dispatch<AppUiAction>]>([
|
||||
appUiInitialStateValues,
|
||||
() => {},
|
||||
]);
|
||||
|
||||
export const AppUIProvider = ({ children }: { children: ReactNode }) => {
|
||||
const value = useReducer(appUiReducer, appUiInitialStateValues);
|
||||
|
||||
export const AppUIProvider = ({
|
||||
value,
|
||||
children,
|
||||
}:{
|
||||
value:[AppUiState, Dispatch<AppUiAction>],
|
||||
children: ReactNode
|
||||
})=>{
|
||||
return (
|
||||
<AppUiContext.Provider value={value}>{children}</AppUiContext.Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,72 +1,15 @@
|
||||
import { createContext } from "react";
|
||||
import {
|
||||
AudioGroup,
|
||||
CameraGroup,
|
||||
CharacterGroup,
|
||||
ObjectGroup,
|
||||
ObjectTrack,
|
||||
MediaClip,
|
||||
Keyframe,
|
||||
QueueKeyframe,
|
||||
Clip,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const TrackContext = createContext<{
|
||||
// timeline characters and it's functions
|
||||
characters: CharacterGroup[];
|
||||
updateCharacters: (options: {
|
||||
type: "animations" | "positions" | "lipSync";
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) => void;
|
||||
toggleLipSyncMute: (characterId: string) => void;
|
||||
addCharacterAnimation: (options: {
|
||||
clipId: string;
|
||||
characterId: string;
|
||||
animationClips: MediaClip[];
|
||||
offset: number;
|
||||
}) => void;
|
||||
addCharacterAudio: (options: {
|
||||
clipId: string;
|
||||
characterId: string;
|
||||
audioClips: MediaClip[];
|
||||
offset: number;
|
||||
}) => void;
|
||||
selectCharacterClip: (clipId: string) => void;
|
||||
deleteCharacterClip: (clipId: string) => void;
|
||||
|
||||
// timeline camera group
|
||||
camera: CameraGroup | null;
|
||||
updateCamera: (options: {
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) => void;
|
||||
selectCameraClip: (clipId: string) => void;
|
||||
deleteCameraClip: (clipId: string) => void;
|
||||
|
||||
// timeline global audio group
|
||||
audio: AudioGroup | null;
|
||||
updateAudio: (options: {
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) => void;
|
||||
toggleAudioMute: () => void;
|
||||
addGlobalAudio: (
|
||||
clipId: string,
|
||||
animationClips: MediaClip[],
|
||||
offset: number,
|
||||
) => void;
|
||||
selectAudioClip: (clipId: string) => void;
|
||||
deleteAudioClip: (clipId: string) => void;
|
||||
|
||||
// timeline objects group
|
||||
objects: ObjectGroup;
|
||||
updateObject: (options: { id: string; offset: number }) => void;
|
||||
addObject: (options: ObjectTrack) => void;
|
||||
|
||||
// current - only select one item - will be replaced
|
||||
selectedClip: string | null;
|
||||
selectClip: (clipId: string) => void;
|
||||
// keyframes
|
||||
addKeyframe: (keyframe: QueueKeyframe, offset: number) => void;
|
||||
deleteKeyframe: (keyframe: Keyframe) => void;
|
||||
|
||||
// sidebar clips
|
||||
animationClips: MediaClip[];
|
||||
@@ -80,32 +23,8 @@ export const TrackContext = createContext<{
|
||||
) => void;
|
||||
endDrag: () => void;
|
||||
}>({
|
||||
characters: [],
|
||||
updateCharacters: () => {},
|
||||
toggleLipSyncMute: () => {},
|
||||
addCharacterAnimation: () => {},
|
||||
addCharacterAudio: () => {},
|
||||
selectCharacterClip: () => {},
|
||||
deleteCharacterClip: () => {},
|
||||
|
||||
camera: null,
|
||||
updateCamera: () => {},
|
||||
selectCameraClip: () => {},
|
||||
deleteCameraClip: () => {},
|
||||
|
||||
audio: null,
|
||||
updateAudio: () => {},
|
||||
toggleAudioMute: () => {},
|
||||
selectAudioClip: () => {},
|
||||
addGlobalAudio: () => {},
|
||||
deleteAudioClip: () => {},
|
||||
|
||||
objects: { id: "", objects: [] },
|
||||
updateObject: () => {},
|
||||
addObject: () => {},
|
||||
|
||||
selectedClip: null,
|
||||
selectClip: () => {},
|
||||
addKeyframe: () => {},
|
||||
deleteKeyframe: () => {},
|
||||
|
||||
animationClips: [],
|
||||
audioClips: [],
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import useUpdateCharacters from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters";
|
||||
import useUpdateCamera from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCamera";
|
||||
import useUpdateAudio from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateAudio";
|
||||
import useUpdateObject from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject";
|
||||
import { ClipType, MediaClip } from "~/pages/PageEnigma/models/track";
|
||||
import {
|
||||
ClipType,
|
||||
Keyframe,
|
||||
Clip,
|
||||
MediaClip,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import useUpdateDragDrop from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateDragDrop";
|
||||
import {
|
||||
addCharacterAnimation,
|
||||
canDrop,
|
||||
dragId,
|
||||
dragType,
|
||||
@@ -14,24 +16,17 @@ import {
|
||||
dropOffset,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import * as uuid from "uuid";
|
||||
import useUpdateKeyframe from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe";
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const TrackProvider = ({ children }: Props) => {
|
||||
const characters = useUpdateCharacters();
|
||||
const camera = useUpdateCamera();
|
||||
const audio = useUpdateAudio();
|
||||
const objects = useUpdateObject();
|
||||
const keyframes = useUpdateKeyframe();
|
||||
|
||||
const { endDrag, ...dragDrop } = useUpdateDragDrop();
|
||||
|
||||
const [selectedClip, setSelectedClip] = useState<string | null>(null);
|
||||
const selectClip = useCallback((clipId: string | null) => {
|
||||
setSelectedClip(clipId);
|
||||
}, []);
|
||||
|
||||
const [animationClips, setAnimationClips] = useState<MediaClip[]>([]);
|
||||
const [audioClips, setAudioClips] = useState<MediaClip[]>([]);
|
||||
|
||||
@@ -57,7 +52,7 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
const dropClip = useCallback(() => {
|
||||
if (canDrop.value) {
|
||||
if (dragType.value === "animations") {
|
||||
characters.addCharacterAnimation({
|
||||
addCharacterAnimation({
|
||||
clipId: dragId.value!,
|
||||
characterId: dropId.value,
|
||||
animationClips,
|
||||
@@ -66,7 +61,7 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
}
|
||||
}
|
||||
endDrag();
|
||||
}, [animationClips, characters, endDrag]);
|
||||
}, [animationClips, endDrag]);
|
||||
|
||||
const fullWidth = useMemo(() => {
|
||||
return length * 60 * 4 * scale;
|
||||
@@ -103,20 +98,43 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
name: "Walk",
|
||||
},
|
||||
]);
|
||||
setAudioClips([]);
|
||||
setAudioClips([
|
||||
{
|
||||
version: 1,
|
||||
media_id: uuid.v4(),
|
||||
type: ClipType.ANIMATION,
|
||||
length: 25,
|
||||
name: "Sing",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: uuid.v4(),
|
||||
type: ClipType.AUDIO,
|
||||
length: 25,
|
||||
name: "Chatter",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: uuid.v4(),
|
||||
type: ClipType.AUDIO,
|
||||
length: 25,
|
||||
name: "Talk",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: uuid.v4(),
|
||||
type: ClipType.AUDIO,
|
||||
length: 25,
|
||||
name: "Yell",
|
||||
},
|
||||
]);
|
||||
setScale(1);
|
||||
setLength(12);
|
||||
}, []);
|
||||
|
||||
const values = useMemo(() => {
|
||||
return {
|
||||
...characters,
|
||||
...camera,
|
||||
...audio,
|
||||
...objects,
|
||||
|
||||
selectClip,
|
||||
selectedClip,
|
||||
...keyframes,
|
||||
|
||||
...dragDrop,
|
||||
endDrag: dropClip,
|
||||
@@ -133,13 +151,7 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
setTimelineHeight,
|
||||
};
|
||||
}, [
|
||||
characters,
|
||||
camera,
|
||||
audio,
|
||||
objects,
|
||||
|
||||
selectClip,
|
||||
selectedClip,
|
||||
keyframes,
|
||||
|
||||
dragDrop,
|
||||
dropClip,
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import {
|
||||
AudioGroup,
|
||||
Clip,
|
||||
ClipGroup,
|
||||
ClipType,
|
||||
MediaClip,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
export default function useUpdateAudio() {
|
||||
const [audio, setAudio] = useState<AudioGroup>({
|
||||
id: "AU1",
|
||||
muted: false,
|
||||
clips: [],
|
||||
});
|
||||
|
||||
const updateAudio = useCallback(
|
||||
({
|
||||
id,
|
||||
offset,
|
||||
length,
|
||||
}: {
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) => {
|
||||
setAudio((oldAudio) => {
|
||||
const newClips = [...oldAudio.clips];
|
||||
const clipIndex = newClips.findIndex((row) => row.clip_uuid === id);
|
||||
if (clipIndex === -1) {
|
||||
return { ...oldAudio };
|
||||
}
|
||||
const clip = newClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...oldAudio,
|
||||
clips: newClips,
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const addGlobalAudio = useCallback(
|
||||
(dragId: string, audioClips: MediaClip[], offset: number) => {
|
||||
const clip = audioClips.find((row) => row.media_id === dragId);
|
||||
if (!clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clip_uuid = uuid.v4();
|
||||
const newClip = {
|
||||
...clip,
|
||||
type: ClipType.AUDIO,
|
||||
group: ClipGroup.GLOBAL_AUDIO,
|
||||
offset,
|
||||
clip_uuid,
|
||||
} as Clip;
|
||||
|
||||
setAudio((oldAudio) => {
|
||||
return {
|
||||
...oldAudio,
|
||||
clips: [...oldAudio.clips, newClip],
|
||||
};
|
||||
});
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newClip,
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const toggleAudioMute = useCallback(() => {
|
||||
setAudio((oldAudio) => {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: oldAudio?.muted ? toEngineActions.UNMUTE : toEngineActions.MUTE,
|
||||
data: {
|
||||
version: 1,
|
||||
type: ClipType.AUDIO,
|
||||
group: ClipGroup.GLOBAL_AUDIO,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...oldAudio,
|
||||
muted: !oldAudio.muted,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const selectAudioClip = useCallback((clipId: string) => {
|
||||
setAudio((oldAudio) => {
|
||||
return {
|
||||
...oldAudio,
|
||||
clips: [
|
||||
...oldAudio.clips.map((clip) => {
|
||||
return {
|
||||
...clip,
|
||||
selected:
|
||||
clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
};
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const deleteAudioClip = useCallback((clipId: string) => {
|
||||
setAudio((oldAudio) => {
|
||||
return {
|
||||
...oldAudio,
|
||||
clips: [
|
||||
...oldAudio.clips.filter((clip) => {
|
||||
if (clip.clip_uuid === clipId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: clip!,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
audio,
|
||||
updateAudio,
|
||||
toggleAudioMute,
|
||||
selectAudioClip,
|
||||
addGlobalAudio,
|
||||
deleteAudioClip,
|
||||
};
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { CameraGroup } from "~/pages/PageEnigma/models/track";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
|
||||
export default function useUpdateCamera() {
|
||||
const [camera, setCamera] = useState<CameraGroup>({
|
||||
id: "CA1",
|
||||
clips: [],
|
||||
});
|
||||
|
||||
const updateCamera = useCallback(
|
||||
({
|
||||
id,
|
||||
offset,
|
||||
length,
|
||||
}: {
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) => {
|
||||
setCamera((oldCamera) => {
|
||||
const newClips = [...oldCamera.clips];
|
||||
const clipIndex = newClips.findIndex((row) => row.clip_uuid === id);
|
||||
if (clipIndex === -1) {
|
||||
return { ...oldCamera };
|
||||
}
|
||||
const clip = newClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...oldCamera,
|
||||
clips: newClips,
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const selectCameraClip = useCallback((clipId: string) => {
|
||||
setCamera((oldCamera) => {
|
||||
return {
|
||||
...oldCamera,
|
||||
clips: [
|
||||
...oldCamera.clips.map((clip) => {
|
||||
return {
|
||||
...clip,
|
||||
selected:
|
||||
clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
};
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const deleteCameraClip = useCallback((clipId: string) => {
|
||||
setCamera((oldCamera) => {
|
||||
return {
|
||||
...oldCamera,
|
||||
clips: [
|
||||
...oldCamera.clips.filter((clip) => {
|
||||
if (clip.clip_uuid === clipId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: clip!,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
camera,
|
||||
updateCamera,
|
||||
selectCameraClip,
|
||||
deleteCameraClip,
|
||||
};
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import {
|
||||
Clip,
|
||||
CharacterGroup,
|
||||
ClipGroup,
|
||||
ClipType,
|
||||
MediaClip,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import * as uuid from "uuid";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
|
||||
export default function useUpdateCharacters() {
|
||||
const [characters, setCharacters] = useState<CharacterGroup[]>([
|
||||
{
|
||||
id: "CH1",
|
||||
muted: false,
|
||||
animationClips: [],
|
||||
positionClips: [],
|
||||
lipSyncClips: [],
|
||||
},
|
||||
]);
|
||||
|
||||
const updateCharacters = useCallback(
|
||||
({
|
||||
type,
|
||||
id,
|
||||
offset,
|
||||
length,
|
||||
}: {
|
||||
type: "animations" | "positions" | "lipSync";
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) => {
|
||||
if (type === "animations") {
|
||||
setCharacters((oldCharacters) => {
|
||||
return oldCharacters.map((character) => {
|
||||
const newAnimationClips = [...character.animationClips];
|
||||
const clipIndex = newAnimationClips.findIndex(
|
||||
(row) => row.clip_uuid === id,
|
||||
);
|
||||
if (clipIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const clip = newAnimationClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
animationClips: newAnimationClips,
|
||||
};
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "positions") {
|
||||
setCharacters((oldCharacters) => {
|
||||
return oldCharacters.map((character) => {
|
||||
const newPositionClips = [...character.positionClips];
|
||||
const clipIndex = newPositionClips.findIndex(
|
||||
(row) => row.clip_uuid === id,
|
||||
);
|
||||
if (clipIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const clip = newPositionClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
positionClips: newPositionClips,
|
||||
};
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (type === "lipSync") {
|
||||
setCharacters((oldCharacters) => {
|
||||
return oldCharacters.map((character) => {
|
||||
const newLipSyncClips = [...character.lipSyncClips];
|
||||
const clipIndex = newLipSyncClips.findIndex(
|
||||
(row) => row.clip_uuid === id,
|
||||
);
|
||||
if (clipIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const clip = newLipSyncClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: newLipSyncClips,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const addCharacterAnimation = useCallback(
|
||||
({
|
||||
clipId,
|
||||
characterId,
|
||||
animationClips,
|
||||
offset,
|
||||
}: {
|
||||
clipId: string;
|
||||
characterId: string;
|
||||
animationClips: MediaClip[];
|
||||
offset: number;
|
||||
}) => {
|
||||
const clip = animationClips.find((row) => row.media_id === clipId);
|
||||
if (!clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clip_uuid = uuid.v4();
|
||||
const newClip = {
|
||||
...clip,
|
||||
group: ClipGroup.CHARACTER,
|
||||
offset,
|
||||
clip_uuid,
|
||||
object_uuid: characterId,
|
||||
} as Clip;
|
||||
|
||||
setCharacters((oldCharacters) => {
|
||||
return oldCharacters.map((character) => {
|
||||
if (character.id !== characterId) {
|
||||
return { ...character };
|
||||
}
|
||||
return {
|
||||
...character,
|
||||
animationClips: [...character.animationClips, newClip].sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newClip,
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const addCharacterAudio = useCallback(
|
||||
({
|
||||
clipId,
|
||||
characterId,
|
||||
audioClips,
|
||||
offset,
|
||||
}: {
|
||||
clipId: string;
|
||||
characterId: string;
|
||||
audioClips: MediaClip[];
|
||||
offset: number;
|
||||
}) => {
|
||||
const clip = audioClips.find((row) => row.media_id === clipId);
|
||||
if (!clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clip_uuid = uuid.v4();
|
||||
const newClip = {
|
||||
...clip,
|
||||
type: ClipType.AUDIO,
|
||||
group: ClipGroup.CHARACTER,
|
||||
offset,
|
||||
clip_uuid,
|
||||
object_uuid: characterId,
|
||||
} as Clip;
|
||||
|
||||
setCharacters((oldCharacters) => {
|
||||
return oldCharacters.map((character) => {
|
||||
if (character.id !== characterId) {
|
||||
return { ...character };
|
||||
}
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: [...character.lipSyncClips, newClip].sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newClip,
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const toggleLipSyncMute = useCallback((characterId: string) => {
|
||||
setCharacters((oldCharacters) => {
|
||||
return oldCharacters.map((character) => {
|
||||
if (character.id === characterId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: character?.muted
|
||||
? toEngineActions.UNMUTE
|
||||
: toEngineActions.MUTE,
|
||||
data: {
|
||||
version: 1,
|
||||
type: ClipType.AUDIO,
|
||||
group: ClipGroup.CHARACTER,
|
||||
object_uuid: characterId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...character,
|
||||
muted:
|
||||
character.id === characterId ? !character.muted : character.muted,
|
||||
};
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
const selectCharacterClip = useCallback((clipId: string) => {
|
||||
setCharacters((oldCharacters) => {
|
||||
return [
|
||||
...oldCharacters.map((character) => ({
|
||||
...character,
|
||||
animationClips: character.animationClips.map((clip) => ({
|
||||
...clip,
|
||||
selected:
|
||||
clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
positionClips: character.positionClips.map((clip) => ({
|
||||
...clip,
|
||||
selected:
|
||||
clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
lipSyncClips: character.lipSyncClips.map((clip) => ({
|
||||
...clip,
|
||||
selected:
|
||||
clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
})),
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
const deleteCharacterClip = useCallback((clipId: string) => {
|
||||
setCharacters((oldCharacters) => {
|
||||
return [
|
||||
...oldCharacters.map((character) => ({
|
||||
...character,
|
||||
animationClips: character.animationClips.filter((clip) => {
|
||||
if (clip.clip_uuid === clipId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: clip!,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
positionClips: character.positionClips.filter((clip) => {
|
||||
if (clip.clip_uuid === clipId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: clip!,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
lipSyncClips: character.lipSyncClips.filter((clip) => {
|
||||
if (clip.clip_uuid === clipId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: clip!,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
})),
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
characters,
|
||||
updateCharacters,
|
||||
toggleLipSyncMute,
|
||||
addCharacterAnimation,
|
||||
addCharacterAudio,
|
||||
selectCharacterClip,
|
||||
deleteCharacterClip,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useCallback } from "react";
|
||||
import {
|
||||
ClipGroup,
|
||||
QueueKeyframe,
|
||||
Keyframe,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import {
|
||||
addCameraKeyframe,
|
||||
addCharacterKeyframe,
|
||||
addObjectKeyframe,
|
||||
deleteCameraKeyframe,
|
||||
deleteCharacterKeyframe,
|
||||
deleteObjectKeyframe,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
|
||||
const ADD_KEYFRAME: Record<
|
||||
ClipGroup,
|
||||
(keyframe: QueueKeyframe, offset: number) => void
|
||||
> = {
|
||||
[ClipGroup.CAMERA]: addCameraKeyframe,
|
||||
[ClipGroup.CHARACTER]: addCharacterKeyframe,
|
||||
[ClipGroup.OBJECT]: addObjectKeyframe,
|
||||
[ClipGroup.GLOBAL_AUDIO]: () => {},
|
||||
};
|
||||
|
||||
const DELETE_KEYFRAME: Record<ClipGroup, (keyframe: Keyframe) => void> = {
|
||||
[ClipGroup.CAMERA]: deleteCameraKeyframe,
|
||||
[ClipGroup.CHARACTER]: deleteCharacterKeyframe,
|
||||
[ClipGroup.OBJECT]: deleteObjectKeyframe,
|
||||
[ClipGroup.GLOBAL_AUDIO]: () => {},
|
||||
};
|
||||
|
||||
export default function useUpdateKeyframe() {
|
||||
const addKeyframe = useCallback((keyframe: QueueKeyframe, offset: number) => {
|
||||
ADD_KEYFRAME[keyframe.group](keyframe, offset);
|
||||
}, []);
|
||||
|
||||
const deleteKeyframe = useCallback((keyframe: Keyframe) => {
|
||||
console.log("del", keyframe);
|
||||
DELETE_KEYFRAME[keyframe.group](keyframe);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
addKeyframe,
|
||||
deleteKeyframe,
|
||||
};
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { ObjectGroup, ObjectTrack } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export default function useUpdateObject() {
|
||||
const [objects, setObjects] = useState<ObjectGroup>({
|
||||
id: "OB1",
|
||||
objects: [
|
||||
// {
|
||||
// id: "OB1-O1",
|
||||
// keyFrames: [
|
||||
// {
|
||||
// id: "OB1-O1-1",
|
||||
// offset: 0,
|
||||
// name: "obj 1",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
],
|
||||
});
|
||||
|
||||
const updateObject = useCallback(
|
||||
({ id, offset }: { id: string; offset: number }) => {
|
||||
setObjects((oldObject) => {
|
||||
return {
|
||||
id: oldObject.id,
|
||||
objects: oldObject.objects.map((object) => ({
|
||||
id: object.id,
|
||||
keyFrames: object.keyFrames.map((keyFrame) => ({
|
||||
...keyFrame,
|
||||
offset: keyFrame.id === id ? offset : keyFrame.offset,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const addObject = useCallback((obj: ObjectTrack) => {
|
||||
setObjects((oldObjectGroup) => {
|
||||
return {
|
||||
...oldObjectGroup,
|
||||
objects: [...oldObjectGroup.objects, obj],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
objects,
|
||||
updateObject,
|
||||
addObject,
|
||||
};
|
||||
}
|
||||
+214
-123
@@ -3,7 +3,7 @@ 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, ArtStyle, Visibility } from "./api_manager.js";
|
||||
import { APIManager, ArtStyle, Visibility } from "./api_manager.js";
|
||||
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
|
||||
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
|
||||
import { OutlinePass } from "three/addons/postprocessing/OutlinePass.js";
|
||||
@@ -101,7 +101,7 @@ class Editor {
|
||||
|
||||
positive_prompt: string;
|
||||
negative_prompt: string;
|
||||
art_style:ArtStyle;
|
||||
art_style: ArtStyle;
|
||||
// Default params.
|
||||
constructor() {
|
||||
console.log(
|
||||
@@ -111,9 +111,8 @@ class Editor {
|
||||
// Special async react lifecycle fix
|
||||
// For making sure the editor only gets created onece.
|
||||
this.can_initialize = false;
|
||||
let one_element = document.getElementById("created-one-element");
|
||||
this.can_initialize = true;
|
||||
let newElement = document.createElement("div");
|
||||
const newElement = document.createElement("div");
|
||||
newElement.id = "created-one-element";
|
||||
document.body.appendChild(newElement);
|
||||
// life cycle fix
|
||||
@@ -198,9 +197,10 @@ class Editor {
|
||||
this.current_scene_glb_media_token = null;
|
||||
|
||||
// stylization parameters
|
||||
this.positive_prompt = "((masterpiece, best quality, 8K, detailed)), colorful, epic, fantasy, (fox, red fox:1.2), no humans, 1other, ((koi pond)), outdoors, pond, rocks, stones, koi fish, ((watercolor))), lilypad, fish swimming around."
|
||||
this.negative_prompt = ""
|
||||
this.art_style = ArtStyle.Anime2DFlat
|
||||
this.positive_prompt =
|
||||
"((masterpiece, best quality, 8K, detailed)), colorful, epic, fantasy, (fox, red fox:1.2), no humans, 1other, ((koi pond)), outdoors, pond, rocks, stones, koi fish, ((watercolor))), lilypad, fish swimming around.";
|
||||
this.negative_prompt = "";
|
||||
this.art_style = ArtStyle.Anime2DFlat;
|
||||
}
|
||||
|
||||
initialize(config: any) {
|
||||
@@ -212,7 +212,6 @@ class Editor {
|
||||
// type: APPUI_ACTION_TYPES.SHOW_EDITOR_LOADER
|
||||
// });
|
||||
|
||||
|
||||
if (this.can_initialize == false) {
|
||||
console.log("Editor Already Initialized");
|
||||
return;
|
||||
@@ -224,15 +223,20 @@ class Editor {
|
||||
this.canvasRenderCamReference = document.getElementById("camera-view");
|
||||
|
||||
// Base width and height.
|
||||
let width = this.canvReference.width;
|
||||
let height = this.canvReference.height;
|
||||
const width = this.canvReference.width;
|
||||
const height = this.canvReference.height;
|
||||
// Sets up camera and base position.
|
||||
this.camera = new THREE.PerspectiveCamera(70, width / height, 0.15, 30);
|
||||
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.15, 30);
|
||||
this.render_camera = new THREE.PerspectiveCamera(
|
||||
70,
|
||||
width / height,
|
||||
0.15,
|
||||
30,
|
||||
);
|
||||
|
||||
// Base WebGL render and clock for delta time.
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
@@ -291,9 +295,21 @@ 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.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,
|
||||
);
|
||||
|
||||
// saving state of the scene
|
||||
this.current_scene_media_token = null;
|
||||
@@ -317,7 +333,13 @@ class Editor {
|
||||
|
||||
this.cam_obj = this.activeScene.get_object_by_name("::CAM::");
|
||||
if (this.cam_obj) {
|
||||
this.addTransformClipBase("Camera Object", "camera", this.cam_obj, 0, 150);
|
||||
this.addTransformClipBase(
|
||||
"Camera Object",
|
||||
"camera",
|
||||
this.cam_obj,
|
||||
0,
|
||||
150,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(this.cam_obj);
|
||||
@@ -338,22 +360,27 @@ class Editor {
|
||||
|
||||
// Token comes in from the front end to load the scene from the site.
|
||||
public async testBatchRequest() {
|
||||
const result = await this.api_manager.getMediaBatch(["m_8fmp9hrvsqcryzka1fra597kg42s50", "m_z4jzbst3xfh64h0qn4bqh4afenfps9"]);
|
||||
const result = await this.api_manager.getMediaBatch([
|
||||
"m_8fmp9hrvsqcryzka1fra597kg42s50",
|
||||
"m_z4jzbst3xfh64h0qn4bqh4afenfps9",
|
||||
]);
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
public async testTestTimelineEvents() {
|
||||
}
|
||||
public async testTestTimelineEvents() {}
|
||||
|
||||
public async testStylizeRequest() {
|
||||
const result = await this.api_manager.stylizeVideo(
|
||||
"mu_6wy1570a0c3c0tpkkncf4tsvb5234",
|
||||
this.art_style,
|
||||
this.positive_prompt,
|
||||
this.negative_prompt,
|
||||
Visibility.Public).catch(error=> {
|
||||
console.log(error);
|
||||
});
|
||||
const result = await this.api_manager
|
||||
.stylizeVideo(
|
||||
"mu_6wy1570a0c3c0tpkkncf4tsvb5234",
|
||||
this.art_style,
|
||||
this.positive_prompt,
|
||||
this.negative_prompt,
|
||||
Visibility.Public,
|
||||
)
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
@@ -378,7 +405,9 @@ class Editor {
|
||||
|
||||
const loaded_scene = load_scene_state_response.data["scene"];
|
||||
|
||||
if (load_scene_state_response.data == null) { return; }
|
||||
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"];
|
||||
@@ -395,9 +424,9 @@ class Editor {
|
||||
child.parent = this.activeScene.scene;
|
||||
|
||||
if (child.type == "DirectionalLight") {
|
||||
let pos = child.position;
|
||||
let rot = child.rotation;
|
||||
let light = this.activeScene._create_base_lighting();
|
||||
const pos = child.position;
|
||||
const rot = child.rotation;
|
||||
const light = this.activeScene._create_base_lighting();
|
||||
light.position.set(pos.x, pos.y, pos.z);
|
||||
light.rotation.set(rot.x, rot.y, rot.z);
|
||||
this.activeScene.scene.remove(child);
|
||||
@@ -468,7 +497,7 @@ class Editor {
|
||||
async _serialize_timeline() {
|
||||
// note the database from the server is the source of truth for all the data.
|
||||
// Test code here
|
||||
let object: any = await this.activeScene.load_glb(
|
||||
const object: any = await this.activeScene.load_glb(
|
||||
"./resources/models/fox/fox.glb",
|
||||
);
|
||||
|
||||
@@ -494,7 +523,16 @@ class Editor {
|
||||
|
||||
// media id for this is up in the air but when a path is created you should be able to store and delete it
|
||||
this.timeline.addPlayableClip(
|
||||
new ClipUI(1.0, "transform", "character", "clip2", object.uuid, object.uuid, 0, 150),
|
||||
new ClipUI(
|
||||
1.0,
|
||||
"transform",
|
||||
"character",
|
||||
"clip2",
|
||||
object.uuid,
|
||||
object.uuid,
|
||||
0,
|
||||
150,
|
||||
),
|
||||
);
|
||||
|
||||
// media id for this as well it can be downloaded
|
||||
@@ -519,7 +557,7 @@ class Editor {
|
||||
|
||||
switchCameraView() {
|
||||
this.camera_person_mode = !this.camera_person_mode;
|
||||
console.log("camera")
|
||||
console.log("camera");
|
||||
if (this.cam_obj) {
|
||||
if (this.camera_person_mode) {
|
||||
this.last_cam_pos.copy(this.camera.position);
|
||||
@@ -536,12 +574,11 @@ class Editor {
|
||||
if (this.cameraViewControls) {
|
||||
this.cameraViewControls.enabled = true;
|
||||
}
|
||||
this.cam_obj.scale.set(0,0,0);
|
||||
this.cam_obj.scale.set(0, 0, 0);
|
||||
if (this.activeScene.hot_items) {
|
||||
this.activeScene.hot_items.forEach((element) => {
|
||||
element.visible = false;
|
||||
});
|
||||
|
||||
}
|
||||
} else {
|
||||
this.camera.position.copy(this.last_cam_pos);
|
||||
@@ -555,7 +592,7 @@ class Editor {
|
||||
if (this.cameraViewControls) {
|
||||
this.cameraViewControls.enabled = false;
|
||||
}
|
||||
this.cam_obj.scale.set(1,1,1);
|
||||
this.cam_obj.scale.set(1, 1, 1);
|
||||
if (this.activeScene.hot_items) {
|
||||
this.activeScene.hot_items.forEach((element) => {
|
||||
element.visible = true;
|
||||
@@ -579,7 +616,7 @@ class Editor {
|
||||
group,
|
||||
name,
|
||||
object.uuid, // object id here ...
|
||||
object.uuid,// Need to change to media id ....
|
||||
object.uuid, // Need to change to media id ....
|
||||
offset,
|
||||
length,
|
||||
),
|
||||
@@ -590,7 +627,7 @@ class Editor {
|
||||
async _test_demo() {
|
||||
// note the database from the server is the source of truth for all the data.
|
||||
// Test code here
|
||||
let object: any = await this.activeScene.load_glb(
|
||||
const object: any = await this.activeScene.load_glb(
|
||||
"./resources/models/fox/fox.glb",
|
||||
);
|
||||
|
||||
@@ -651,8 +688,8 @@ class Editor {
|
||||
|
||||
// Configure post processing.
|
||||
_configurePostProcessing() {
|
||||
let width = this.canvReference.width;
|
||||
let height = this.canvReference.height;
|
||||
const width = this.canvReference.width;
|
||||
const height = this.canvReference.height;
|
||||
|
||||
if (this.renderer == undefined || this.camera == undefined) {
|
||||
return;
|
||||
@@ -714,7 +751,7 @@ class Editor {
|
||||
}
|
||||
|
||||
create_parim(name: string) {
|
||||
let uuid = this.activeScene.instantiate(name);
|
||||
const uuid = this.activeScene.instantiate(name);
|
||||
}
|
||||
|
||||
renderMode() {
|
||||
@@ -756,11 +793,16 @@ class Editor {
|
||||
|
||||
// Basicly Unity 3D's update loop.
|
||||
updateLoop(time: number) {
|
||||
|
||||
if (this.cam_obj == undefined) {
|
||||
this.cam_obj = this.activeScene.get_object_by_name("::CAM::");
|
||||
if (this.cam_obj) {
|
||||
this.addTransformClipBase("Camera Object", "camera", this.cam_obj, 0, 150)
|
||||
this.addTransformClipBase(
|
||||
"Camera Object",
|
||||
"camera",
|
||||
this.cam_obj,
|
||||
0,
|
||||
150,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,7 +815,7 @@ class Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let delta_time = this.clock.getDelta();
|
||||
const delta_time = this.clock.getDelta();
|
||||
|
||||
if (this.cameraViewControls && this.camera_person_mode) {
|
||||
this.cameraViewControls.update(5 * delta_time);
|
||||
@@ -813,13 +855,12 @@ class Editor {
|
||||
}
|
||||
|
||||
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';
|
||||
const video_og = itteration + "tmp.mp4";
|
||||
const wav_name = itteration + "tmp.wav";
|
||||
const new_video = itteration + 1 + "tmp.mp4";
|
||||
let startFrame = clip.offset;
|
||||
let endFrame = clip.length;
|
||||
|
||||
|
||||
if (endFrame > this.timeline.timeline_limit) {
|
||||
endFrame = this.timeline.timeline_limit;
|
||||
}
|
||||
@@ -829,42 +870,72 @@ class Editor {
|
||||
|
||||
const startTime = startFrame / this.cap_fps;
|
||||
const endTime = endFrame / this.cap_fps;
|
||||
let end = endTime - startTime;
|
||||
const end = endTime - startTime;
|
||||
|
||||
let duration = this.timeline.timeline_limit / 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',
|
||||
const audioSegment = "as_" + wav_name;
|
||||
await ffmpeg.FS(
|
||||
"writeFile",
|
||||
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 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', `${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`)
|
||||
"-i",
|
||||
video_og,
|
||||
"-max_muxing_queue_size",
|
||||
"999999",
|
||||
`${itteration}empty_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)
|
||||
"-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(compile_audio: boolean = true) {
|
||||
this.renderMode();
|
||||
let ffmpeg = createFFmpeg({ log: true });
|
||||
const ffmpeg = createFFmpeg({ log: true });
|
||||
await ffmpeg.load();
|
||||
for (let index = 0; index < this.frame_buffer.length; index++) {
|
||||
const element = this.frame_buffer[index];
|
||||
@@ -890,10 +961,9 @@ class Editor {
|
||||
"-c:a",
|
||||
"aac", // Specify audio codec (optional, but recommended for MP4)
|
||||
"-shortest", // Ensure output duration matches the shortest stream (video or audio)
|
||||
"0tmp.mp4"
|
||||
"0tmp.mp4",
|
||||
);
|
||||
|
||||
|
||||
let itteration = 0;
|
||||
|
||||
if (compile_audio) {
|
||||
@@ -902,10 +972,10 @@ class Editor {
|
||||
await this.convertAudioClip(itteration, ffmpeg, clip);
|
||||
itteration += 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let output = await ffmpeg.FS("readFile", itteration + "tmp.mp4");
|
||||
const 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);
|
||||
@@ -918,11 +988,9 @@ class Editor {
|
||||
URL.revokeObjectURL(url);
|
||||
document.body.removeChild(downloadLink);
|
||||
|
||||
|
||||
let data = await this.api_manager.uploadMedia(blob, "tmp.wav");
|
||||
const data = await this.api_manager.uploadMedia(blob, "tmp.wav");
|
||||
console.log(data);
|
||||
// Create a link to download the file
|
||||
|
||||
}
|
||||
|
||||
async generateFrame() {
|
||||
@@ -931,7 +999,7 @@ class Editor {
|
||||
this.generating_preview = true;
|
||||
this.activeScene.renderMode(true);
|
||||
if (this.activeScene.hot_items) {
|
||||
this.activeScene.hot_items.forEach(element => {
|
||||
this.activeScene.hot_items.forEach((element) => {
|
||||
element.visible = false;
|
||||
});
|
||||
}
|
||||
@@ -943,37 +1011,41 @@ class Editor {
|
||||
|
||||
this.renderer.setSize(this.render_width, this.render_height);
|
||||
this.renderer.render(this.activeScene.scene, this.render_camera);
|
||||
let imgData = this.renderer.domElement.toDataURL();
|
||||
const imgData = this.renderer.domElement.toDataURL();
|
||||
this.activeScene.renderMode(false);
|
||||
this.onWindowResize();
|
||||
|
||||
const rawPreview: HTMLVideoElement | null = document.getElementById("raw-preview") as HTMLVideoElement;
|
||||
const rawPreview: HTMLVideoElement | null = document.getElementById(
|
||||
"raw-preview",
|
||||
) as HTMLVideoElement;
|
||||
if (rawPreview) {
|
||||
rawPreview.src = imgData;
|
||||
|
||||
|
||||
let ffmpeg = createFFmpeg({ log: false });
|
||||
const ffmpeg = createFFmpeg({ log: false });
|
||||
await ffmpeg.load();
|
||||
await ffmpeg.FS(
|
||||
"writeFile",
|
||||
`render.png`,
|
||||
await fetchFile(imgData),
|
||||
);
|
||||
await ffmpeg.run('-i', `render.png`, 'render.mp4');
|
||||
let output = await ffmpeg.FS("readFile", 'render.mp4');
|
||||
await ffmpeg.FS("writeFile", `render.png`, await fetchFile(imgData));
|
||||
await ffmpeg.run("-i", `render.png`, "render.mp4");
|
||||
const output = await ffmpeg.FS("readFile", "render.mp4");
|
||||
const blob = new Blob([output.buffer], { type: "video/mp4" });
|
||||
|
||||
let url = await this.api_manager.uploadMediaFrameGeneration(blob, "render.mp4", "anime_ghibli", "((masterpiece, best quality, 8K, detailed)), colorful, epic, fantasy, (fox, red fox:1.2), no humans, 1other, ((koi pond)), outdoors, pond, rocks, stones, koi fish, ((watercolor))), lilypad, fish swimming around.", "");
|
||||
const url = await this.api_manager.uploadMediaFrameGeneration(
|
||||
blob,
|
||||
"render.mp4",
|
||||
"anime_ghibli",
|
||||
"((masterpiece, best quality, 8K, detailed)), colorful, epic, fantasy, (fox, red fox:1.2), no humans, 1other, ((koi pond)), outdoors, pond, rocks, stones, koi fish, ((watercolor))), lilypad, fish swimming around.",
|
||||
"",
|
||||
);
|
||||
console.log(url);
|
||||
|
||||
const stylePreview: HTMLVideoElement | null = document.getElementById("stylized-preview") as HTMLVideoElement;
|
||||
const stylePreview: HTMLVideoElement | null = document.getElementById(
|
||||
"stylized-preview",
|
||||
) as HTMLVideoElement;
|
||||
if (stylePreview) {
|
||||
stylePreview.src = url;
|
||||
stylePreview.width = rawPreview.width;
|
||||
stylePreview.height = rawPreview.height;
|
||||
}
|
||||
else {
|
||||
console.log("No style preview window.")
|
||||
} else {
|
||||
console.log("No style preview window.");
|
||||
}
|
||||
|
||||
this.generating_preview = false;
|
||||
@@ -981,9 +1053,8 @@ class Editor {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(url);
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.log("No raw preview window.")
|
||||
} else {
|
||||
console.log("No raw preview window.");
|
||||
}
|
||||
this.generating_preview = false;
|
||||
}
|
||||
@@ -991,14 +1062,16 @@ class Editor {
|
||||
|
||||
generateVideo() {
|
||||
console.log("Generating video...");
|
||||
if (this.rendering) { return; }
|
||||
if (this.rendering) {
|
||||
return;
|
||||
}
|
||||
this.startPlayback();
|
||||
this.frame_buffer = [];
|
||||
this.render_timer = 0;
|
||||
this.rendering = true;
|
||||
this.activeScene.renderMode(this.rendering);
|
||||
if (this.activeScene.hot_items) {
|
||||
this.activeScene.hot_items.forEach(element => {
|
||||
this.activeScene.hot_items.forEach((element) => {
|
||||
element.visible = false;
|
||||
});
|
||||
}
|
||||
@@ -1046,7 +1119,7 @@ class Editor {
|
||||
|
||||
if (this.rendering && this.renderer && this.clock) {
|
||||
this.playback_location++;
|
||||
let imgData = this.renderer.domElement.toDataURL();
|
||||
const imgData = this.renderer.domElement.toDataURL();
|
||||
this.frame_buffer.push(imgData);
|
||||
this.render_timer += this.clock.getDelta();
|
||||
if (this.timeline.is_playing == false) {
|
||||
@@ -1060,31 +1133,47 @@ class Editor {
|
||||
}
|
||||
|
||||
updateSelectedUI() {
|
||||
if (this.selected == undefined) { return; }
|
||||
let pos = this.selected.position;
|
||||
let rot = this.selected.rotation;
|
||||
let scale = this.selected.scale;
|
||||
if (this.selected == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pos = this.selected.position;
|
||||
const rot = this.selected.rotation;
|
||||
const scale = this.selected.scale;
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
currentSceneObject: {
|
||||
objectVectors: {
|
||||
position: { x: parseFloat(pos.x.toFixed(2)), y: parseFloat(pos.y.toFixed(2)), z: parseFloat(pos.z.toFixed(2)) },
|
||||
rotation: { x: parseFloat(rot.x.toFixed(2)), y: parseFloat(rot.y.toFixed(2)), z: parseFloat(rot.z.toFixed(2)) },
|
||||
scalar: { x: parseFloat(scale.x.toFixed(2)), y: parseFloat(scale.y.toFixed(2)), z: parseFloat(scale.z.toFixed(2)) },
|
||||
}
|
||||
}
|
||||
}
|
||||
group: "object", // TODO: add meta data to determine what it is a camera or a object or a character into prefab clips
|
||||
object_uuid: this.selected.uuid,
|
||||
object_name: this.selected.name,
|
||||
version: this.version,
|
||||
objectVectors: {
|
||||
position: {
|
||||
x: parseFloat(pos.x.toFixed(2)),
|
||||
y: parseFloat(pos.y.toFixed(2)),
|
||||
z: parseFloat(pos.z.toFixed(2)),
|
||||
},
|
||||
rotation: {
|
||||
x: parseFloat(rot.x.toFixed(2)),
|
||||
y: parseFloat(rot.y.toFixed(2)),
|
||||
z: parseFloat(rot.z.toFixed(2)),
|
||||
},
|
||||
scale: {
|
||||
x: parseFloat(scale.x.toFixed(2)),
|
||||
y: parseFloat(scale.y.toFixed(2)),
|
||||
z: parseFloat(scale.z.toFixed(2)),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Automaticly resize scene.
|
||||
onWindowResize() {
|
||||
// Calculate the maximum possible dimensions while maintaining the aspect ratio
|
||||
let width = window.innerWidth; // / aspect_adjust
|
||||
let height = window.innerHeight; // / aspectRatio
|
||||
const width = window.innerWidth; // / aspect_adjust
|
||||
const height = window.innerHeight; // / aspectRatio
|
||||
|
||||
if (this.camera == undefined || this.renderer == undefined) {
|
||||
return;
|
||||
@@ -1099,7 +1188,9 @@ class Editor {
|
||||
this.composer.setSize(width, height);
|
||||
}
|
||||
|
||||
if (this.render_camera == undefined) { return; }
|
||||
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;
|
||||
@@ -1144,7 +1235,7 @@ class Editor {
|
||||
}
|
||||
|
||||
this.raycaster.setFromCamera(this.mouse, this.camera);
|
||||
let interactable: any[] = [];
|
||||
const interactable: any[] = [];
|
||||
this.activeScene.scene.children.forEach((child: THREE.Object3D) => {
|
||||
if (child.name != "") {
|
||||
if (child.type == "Mesh" || child.type == "Object3D") {
|
||||
@@ -1152,7 +1243,7 @@ class Editor {
|
||||
}
|
||||
}
|
||||
});
|
||||
let intersects = this.raycaster.intersectObjects(interactable, true);
|
||||
const intersects = this.raycaster.intersectObjects(interactable, true);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
if (intersects[0].object.type != "GridHelper") {
|
||||
@@ -1176,7 +1267,7 @@ class Editor {
|
||||
} else if (this.transform_interaction == false) {
|
||||
this.removeTransformControls();
|
||||
this.dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT
|
||||
type: APPUI_ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT,
|
||||
});
|
||||
} else {
|
||||
this.transform_interaction = false;
|
||||
|
||||
+307
-287
@@ -10,323 +10,344 @@ import AnimationEngine from "./animation_engine";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "../Queue/QueueNames";
|
||||
import { toEngineActions } from "../Queue/toEngineActions";
|
||||
import { Action } from "@remix-run/router";
|
||||
import { fromEngineActions } from "../Queue/fromEngineActions";
|
||||
import { currentTime } from "../store";
|
||||
|
||||
// Every object uuid / entity has a track.
|
||||
export class TimelineCurrentState {
|
||||
is_editable: boolean
|
||||
selected_object_ID: number
|
||||
constructor() {
|
||||
this.is_editable = true // can add clips to it
|
||||
this.selected_object_ID = 0
|
||||
}
|
||||
is_editable: boolean;
|
||||
selected_object_ID: number;
|
||||
constructor() {
|
||||
this.is_editable = true; // can add clips to it
|
||||
this.selected_object_ID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class TimelineDataState {
|
||||
timeline_items: ClipUI[]
|
||||
scrubber_frame_position: number
|
||||
constructor(timeline_items: ClipUI[] = [],
|
||||
scrubber_frame_position: number = 0) {
|
||||
this.timeline_items = timeline_items
|
||||
this.scrubber_frame_position = scrubber_frame_position
|
||||
}
|
||||
timeline_items: ClipUI[];
|
||||
scrubber_frame_position: number;
|
||||
constructor(
|
||||
timeline_items: ClipUI[] = [],
|
||||
scrubber_frame_position: number = 0,
|
||||
) {
|
||||
this.timeline_items = timeline_items;
|
||||
this.scrubber_frame_position = scrubber_frame_position;
|
||||
}
|
||||
}
|
||||
|
||||
export class TimeLine {
|
||||
timeline_items: ClipUI[];
|
||||
|
||||
timeline_items: ClipUI[]
|
||||
timeline_limit: number;
|
||||
scrubber_frame_position: number;
|
||||
is_playing: boolean;
|
||||
|
||||
timeline_limit: number
|
||||
scrubber_frame_position: number
|
||||
is_playing: boolean
|
||||
// plays audio
|
||||
audio_engine: AudioEngine;
|
||||
// key framing
|
||||
transform_engine: TransformEngine;
|
||||
// animation engine
|
||||
animation_engine: AnimationEngine;
|
||||
// lip sync engine
|
||||
lipSync_engine: LipSyncEngine;
|
||||
|
||||
// plays audio
|
||||
audio_engine: AudioEngine
|
||||
// key framing
|
||||
transform_engine: TransformEngine
|
||||
// animation engine
|
||||
animation_engine: AnimationEngine
|
||||
// lip sync engine
|
||||
lipSync_engine: LipSyncEngine
|
||||
scene: Scene;
|
||||
|
||||
scene: Scene
|
||||
current_time: number;
|
||||
// ensure that the elements are loaded first.
|
||||
constructor(
|
||||
audio_engine: AudioEngine,
|
||||
transform_engine: TransformEngine,
|
||||
lipsync_engine: LipSyncEngine,
|
||||
animation_engine: AnimationEngine,
|
||||
scene: Scene,
|
||||
) {
|
||||
this.timeline_items = [];
|
||||
this.timeline_limit = 60 * 5; // 5 seconds
|
||||
|
||||
current_time: number
|
||||
// ensure that the elements are loaded first.
|
||||
constructor(audio_engine: AudioEngine,
|
||||
transform_engine: TransformEngine,
|
||||
lipsync_engine: LipSyncEngine,
|
||||
animation_engine: AnimationEngine,
|
||||
scene: Scene) {
|
||||
this.is_playing = false;
|
||||
this.scrubber_frame_position = 0; // in frames into the tl
|
||||
|
||||
this.timeline_items = []
|
||||
this.timeline_limit = 60 * 5 // 5 seconds
|
||||
// this will be used to play the audio clips
|
||||
this.audio_engine = audio_engine;
|
||||
this.transform_engine = transform_engine;
|
||||
this.lipSync_engine = lipsync_engine;
|
||||
this.animation_engine = animation_engine;
|
||||
|
||||
this.is_playing = false
|
||||
this.scrubber_frame_position = 0 // in frames into the tl
|
||||
this.scene = scene;
|
||||
|
||||
// this will be used to play the audio clips
|
||||
this.audio_engine = audio_engine
|
||||
this.transform_engine = transform_engine
|
||||
this.lipSync_engine = lipsync_engine
|
||||
this.animation_engine = animation_engine
|
||||
Queue.subscribe(
|
||||
QueueNames.TO_ENGINE,
|
||||
this.handleTimelineActions.bind(this),
|
||||
);
|
||||
|
||||
this.scene = scene;
|
||||
this.current_time = 0;
|
||||
// TODO: How to move the timeline should put in update.
|
||||
// setInterval(()=> {
|
||||
// this.current_time +=1
|
||||
// this.pushEvent(fromEngineActions.UPDATE_TIME, { currentTime: this.current_time })
|
||||
// },50)
|
||||
}
|
||||
|
||||
Queue.subscribe(QueueNames.TO_ENGINE, this.handleTimelineActions.bind(this));
|
||||
public async pushEvent(action: fromEngineActions, data: any) {
|
||||
this.current_time += 1;
|
||||
Queue.publish({
|
||||
queueName: QueueNames.FROM_ENGINE,
|
||||
action: fromEngineActions.UPDATE_TIME,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
this.current_time = 0
|
||||
// TODO: How to move the timeline should put in update.
|
||||
// setInterval(()=> {
|
||||
// this.current_time +=1
|
||||
// this.pushEvent(fromEngineActions.UPDATE_TIME, { currentTime: this.current_time })
|
||||
// },50)
|
||||
public async handleTimelineActions(data: any) {
|
||||
const action = data["action"];
|
||||
switch (action) {
|
||||
case toEngineActions.ADD_KEYFRAME:
|
||||
await this.addKeyFrame(data);
|
||||
break;
|
||||
case toEngineActions.ADD_CLIP:
|
||||
await this.addClip(data);
|
||||
break;
|
||||
case toEngineActions.DELETE_CLIP:
|
||||
await this.deleteClip(data);
|
||||
break;
|
||||
case toEngineActions.UPDATE_CLIP:
|
||||
await this.updateClip(data);
|
||||
break;
|
||||
case toEngineActions.UPDATE_TIME:
|
||||
await this.scrub(data);
|
||||
break;
|
||||
case toEngineActions.MUTE:
|
||||
await this.mute(data);
|
||||
break;
|
||||
default:
|
||||
console.log("Action Not Wired");
|
||||
}
|
||||
}
|
||||
|
||||
public async addKeyFrame(data: any) {
|
||||
// KeyFrame Object
|
||||
// version: number;
|
||||
// clip_uuid: string;
|
||||
// group: ClipGroup;
|
||||
// object_uuid?: string;
|
||||
// offset: number;
|
||||
// position: XYZ
|
||||
// rotation: XYZ;
|
||||
// scale: XYZ;
|
||||
// selected?: boolean;
|
||||
}
|
||||
|
||||
public async addClip(data: any) {
|
||||
let object_uuid = data["data"]["object_uuid"];
|
||||
|
||||
if (object_uuid.length < 6) {
|
||||
object_uuid = this.scene.selected?.uuid; //data['data']['object_uuid'];
|
||||
if (object_uuid == undefined) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public async pushEvent(action: fromEngineActions, data: any) {
|
||||
this.current_time += 1
|
||||
Queue.publish({
|
||||
queueName: QueueNames.FROM_ENGINE,
|
||||
action: fromEngineActions.UPDATE_TIME,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
const media_id = data["data"]["media_id"];
|
||||
const name = data["data"]["name"];
|
||||
const group = data["data"]["group"];
|
||||
const version = data["data"]["group"];
|
||||
const type = data["data"]["type"];
|
||||
const offset = data["data"]["offset"];
|
||||
const end_offset = data["data"]["length"] + offset;
|
||||
|
||||
public async handleTimelineActions(data: any) {
|
||||
const action = data["action"]
|
||||
switch (action) {
|
||||
case toEngineActions.ADD_KEYFRAME:
|
||||
await this.addKeyFrame(data)
|
||||
break
|
||||
case toEngineActions.ADD_CLIP:
|
||||
await this.addClip(data)
|
||||
break
|
||||
case toEngineActions.DELETE_CLIP:
|
||||
await this.deleteClip(data)
|
||||
break
|
||||
case toEngineActions.UPDATE_CLIP:
|
||||
await this.updateClip(data)
|
||||
break
|
||||
case toEngineActions.UPDATE_TIME:
|
||||
await this.scrub(data)
|
||||
break
|
||||
case toEngineActions.MUTE:
|
||||
await this.mute(data)
|
||||
break
|
||||
default:
|
||||
console.log("Action Not Wired")
|
||||
// media id for this as well it can be downloaded
|
||||
this.addPlayableClip(
|
||||
new ClipUI(
|
||||
version,
|
||||
type,
|
||||
group,
|
||||
name,
|
||||
media_id,
|
||||
object_uuid,
|
||||
offset,
|
||||
end_offset,
|
||||
),
|
||||
);
|
||||
|
||||
switch (type) {
|
||||
case "animation":
|
||||
this.animation_engine.load_object(object_uuid, media_id, name);
|
||||
break;
|
||||
case "transform":
|
||||
this.transform_engine.loadObject(object_uuid, data["data"]["length"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
public async updateClip(data: any) {
|
||||
// only length and offset changes here.
|
||||
console.log(data);
|
||||
}
|
||||
public async deleteClip(data: any) {
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
public async scrubberUpdate(data: any) {
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
public async mute(data: any) {
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
public async addPlayableClip(clip: ClipUI): Promise<void> {
|
||||
this.timeline_items.push(clip);
|
||||
}
|
||||
|
||||
// when given a media id item it will create the clip.
|
||||
// Then the clip will be loaded by the engines, if they come from outside of the loaded scene.
|
||||
public async createClipOffset(
|
||||
media_id: string,
|
||||
object_uuid: string,
|
||||
type: string,
|
||||
): Promise<void> {
|
||||
// use engine to load based off media id and type animation | transform |
|
||||
}
|
||||
|
||||
// this will update the state of the clips based off uuid easing?
|
||||
public async updatePlayableClip(
|
||||
clip_uuid: string,
|
||||
updates: AnyJson,
|
||||
): Promise<void> {}
|
||||
|
||||
public async deletePlayableClip(clip_uuid: string): Promise<void> {}
|
||||
|
||||
public async scrub(data: any): Promise<void> {
|
||||
if (this.is_playing) {
|
||||
return;
|
||||
}
|
||||
this.setScrubberPosition(data["data"]["currentTime"]);
|
||||
}
|
||||
|
||||
public async scrubberDidStop(offset_frame: number) {}
|
||||
// public streaming events into the timeline from
|
||||
public async setScrubberPosition(offset: number) {
|
||||
this.scrubber_frame_position = offset; // in ms
|
||||
}
|
||||
|
||||
// should play from the clip that is closest to the to scrubber
|
||||
public async play(): Promise<void> {
|
||||
console.log(`Starting Timeline`);
|
||||
this.is_playing = true;
|
||||
}
|
||||
|
||||
private async resetScene() {
|
||||
this.timeline_limit = this.getEndPoint();
|
||||
for (const element of this.timeline_items) {
|
||||
if (element.type == "transform") {
|
||||
const object = this.scene.get_object_by_uuid(element.object_uuid);
|
||||
if (object && this.transform_engine.clips[element.object_uuid]) {
|
||||
this.transform_engine.clips[element.object_uuid].reset(object);
|
||||
}
|
||||
} else if (element.type == "audio") {
|
||||
this.audio_engine.loadClip(element.media_id);
|
||||
} else if (element.type == "animation") {
|
||||
this.animation_engine.clips[element.object_uuid].stop();
|
||||
} else if (element.type == "lipsync") {
|
||||
this.lipSync_engine.clips[element.object_uuid].reset();
|
||||
} else {
|
||||
this.stop();
|
||||
throw "Error New Type of element in the timeline";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getEndPoint(): number {
|
||||
let longest = 0;
|
||||
for (const element of this.timeline_items) {
|
||||
if (longest < element.length) {
|
||||
longest = element.length;
|
||||
}
|
||||
}
|
||||
return longest;
|
||||
}
|
||||
|
||||
// called by the editor update loop on each frame
|
||||
public async update() {
|
||||
//if (this.is_playing == false) return; // start and stop
|
||||
|
||||
if (this.is_playing) {
|
||||
this.current_time += 1;
|
||||
this.pushEvent(fromEngineActions.UPDATE_TIME, {
|
||||
currentTime: this.current_time,
|
||||
});
|
||||
this.scrubber_frame_position = this.current_time;
|
||||
}
|
||||
|
||||
public async addKeyFrame(data: any) {
|
||||
// KeyFrame Object
|
||||
// version: number;
|
||||
// clip_uuid: string;
|
||||
// group: ClipGroup;
|
||||
// object_uuid?: string;
|
||||
// offset: number;
|
||||
// position: XYZ
|
||||
// rotation: XYZ;
|
||||
// scale: XYZ;
|
||||
// selected?: boolean;
|
||||
|
||||
if (this.scrubber_frame_position <= 0) {
|
||||
await this.resetScene();
|
||||
}
|
||||
|
||||
public async addClip(data: any) {
|
||||
let object_uuid = data['data']['object_uuid'];
|
||||
|
||||
if (object_uuid.length < 6){
|
||||
object_uuid = this.scene.selected?.uuid;//data['data']['object_uuid'];
|
||||
if(object_uuid == undefined) { return; }
|
||||
//this.scrubber_frame_position += 1;
|
||||
//2. allow stopping.
|
||||
//3. smallest unit is a frame and it is set by the scene and is in fps, our videos will be 60fps but we can reprocess them using the pipeline.
|
||||
for (const element of this.timeline_items) {
|
||||
if (
|
||||
element.offset <= this.scrubber_frame_position &&
|
||||
this.scrubber_frame_position <= element.length
|
||||
) {
|
||||
// run async
|
||||
// element.play()
|
||||
// remove the element from the list
|
||||
const object = this.scene.get_object_by_uuid(element.object_uuid);
|
||||
if (element.type == "transform") {
|
||||
if (object && this.transform_engine.clips[element.object_uuid]) {
|
||||
this.transform_engine.clips[element.object_uuid].length =
|
||||
element.length - element.offset;
|
||||
this.transform_engine.clips[element.object_uuid].step(
|
||||
object,
|
||||
element.offset,
|
||||
this.scrubber_frame_position,
|
||||
);
|
||||
}
|
||||
} else if (element.type == "audio") {
|
||||
if (this.scrubber_frame_position + 1 >= element.length) {
|
||||
this.audio_engine.stopClip(element.media_id);
|
||||
} else {
|
||||
this.audio_engine.playClip(element.media_id);
|
||||
}
|
||||
} else if (element.type == "lipsync") {
|
||||
if (this.scrubber_frame_position + 1 >= element.length) {
|
||||
this.lipSync_engine.clips[element.object_uuid].stop();
|
||||
} else if (object) {
|
||||
await this.lipSync_engine.clips[element.object_uuid].play(object);
|
||||
this.lipSync_engine.clips[element.object_uuid].step();
|
||||
}
|
||||
} else if (element.type == "animation") {
|
||||
if (object) {
|
||||
await this.animation_engine.clips[object.uuid].play(object);
|
||||
this.animation_engine.clips[object.uuid].step(
|
||||
this.scrubber_frame_position / 60,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.stop();
|
||||
throw "Error New Type of element in the timeline";
|
||||
}
|
||||
|
||||
let media_id = data['data']['media_id'];
|
||||
let name = data['data']['name'];
|
||||
let group = data['data']['group'];
|
||||
let version = data['data']['group'];
|
||||
let type = data['data']['type'];
|
||||
let offset = data['data']['offset'];
|
||||
let end_offset = data['data']['length']+offset;
|
||||
|
||||
|
||||
// media id for this as well it can be downloaded
|
||||
this.addPlayableClip(
|
||||
new ClipUI(version, type,
|
||||
group, name,
|
||||
media_id, object_uuid,
|
||||
offset, end_offset));
|
||||
|
||||
switch(type) {
|
||||
case "animation":
|
||||
this.animation_engine.load_object(object_uuid, media_id, name);
|
||||
break
|
||||
case "transform":
|
||||
this.transform_engine.loadObject(object_uuid, data['data']['length']);
|
||||
break
|
||||
}
|
||||
}
|
||||
public async updateClip(data: any) {
|
||||
// only length and offset changes here.
|
||||
console.log(data)
|
||||
}
|
||||
public async deleteClip(data: any) {
|
||||
console.log(data)
|
||||
//this.timelineItems = this.timelineItems.filter(item => item !== element)
|
||||
}
|
||||
}
|
||||
|
||||
public async scrubberUpdate(data: any) {
|
||||
console.log(data)
|
||||
if (
|
||||
this.scrubber_frame_position >= this.timeline_limit &&
|
||||
this.is_playing
|
||||
) {
|
||||
await this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public async mute(data: any) {
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
public async addPlayableClip(clip: ClipUI): Promise<void> {
|
||||
this.timeline_items.push(clip)
|
||||
}
|
||||
|
||||
// when given a media id item it will create the clip.
|
||||
// Then the clip will be loaded by the engines, if they come from outside of the loaded scene.
|
||||
public async createClipOffset(media_id: string, object_uuid: string, type: string): Promise<void> {
|
||||
// use engine to load based off media id and type animation | transform |
|
||||
}
|
||||
|
||||
// this will update the state of the clips based off uuid easing?
|
||||
public async updatePlayableClip(clip_uuid: string, updates: AnyJson): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
public async deletePlayableClip(clip_uuid: string): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
public async scrub(data: any): Promise<void> {
|
||||
if (this.is_playing) { return }
|
||||
this.setScrubberPosition(data['data']['currentTime']);
|
||||
}
|
||||
|
||||
public async scrubberDidStop(offset_frame: number) {
|
||||
|
||||
}
|
||||
// public streaming events into the timeline from
|
||||
public async setScrubberPosition(offset: number) {
|
||||
this.scrubber_frame_position = offset // in ms
|
||||
}
|
||||
|
||||
// should play from the clip that is closest to the to scrubber
|
||||
public async play(): Promise<void> {
|
||||
console.log(`Starting Timeline`)
|
||||
this.is_playing = true
|
||||
}
|
||||
|
||||
private async resetScene() {
|
||||
this.timeline_limit = this.getEndPoint();
|
||||
for (const element of this.timeline_items) {
|
||||
if (element.type == "transform") {
|
||||
let object = this.scene.get_object_by_uuid(element.object_uuid);
|
||||
if (object && this.transform_engine.clips[element.object_uuid]) { this.transform_engine.clips[element.object_uuid].reset(object); }
|
||||
}
|
||||
else if (element.type == "audio") {
|
||||
this.audio_engine.loadClip(element.media_id);
|
||||
}
|
||||
else if (element.type == "animation") {
|
||||
this.animation_engine.clips[element.object_uuid].stop();
|
||||
}
|
||||
else if (element.type == "lipsync") {
|
||||
this.lipSync_engine.clips[element.object_uuid].reset();
|
||||
}
|
||||
else {
|
||||
this.stop()
|
||||
throw "Error New Type of element in the timeline"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getEndPoint(): number {
|
||||
let longest = 0;
|
||||
for (const element of this.timeline_items) {
|
||||
if(longest < element.length) {
|
||||
longest = element.length;
|
||||
}
|
||||
}
|
||||
return longest;
|
||||
}
|
||||
|
||||
// called by the editor update loop on each frame
|
||||
public async update(deltatime: number) {
|
||||
//if (this.is_playing == false) return; // start and stop
|
||||
|
||||
|
||||
if(this.is_playing) {
|
||||
this.current_time += 1;
|
||||
this.pushEvent(fromEngineActions.UPDATE_TIME, { currentTime: this.current_time })
|
||||
this.scrubber_frame_position = this.current_time;
|
||||
}
|
||||
|
||||
if (this.scrubber_frame_position <= 0) {
|
||||
await this.resetScene();
|
||||
}
|
||||
|
||||
//this.scrubber_frame_position += 1;
|
||||
//2. allow stopping.
|
||||
//3. smallest unit is a frame and it is set by the scene and is in fps, our videos will be 60fps but we can reprocess them using the pipeline.
|
||||
for (const element of this.timeline_items) {
|
||||
if (element.offset <= this.scrubber_frame_position && this.scrubber_frame_position <= element.length) {
|
||||
// run async
|
||||
// element.play()
|
||||
// remove the element from the list
|
||||
let object = this.scene.get_object_by_uuid(element.object_uuid)
|
||||
if (element.type == "transform") {
|
||||
if (object && this.transform_engine.clips[element.object_uuid]) {
|
||||
this.transform_engine.clips[element.object_uuid].length = (element.length - element.offset);
|
||||
this.transform_engine.clips[element.object_uuid].step(object, element.offset, this.scrubber_frame_position);
|
||||
}
|
||||
}
|
||||
else if (element.type == "audio") {
|
||||
if (this.scrubber_frame_position + 1 >= element.length) {
|
||||
this.audio_engine.stopClip(element.media_id);
|
||||
}
|
||||
else {
|
||||
this.audio_engine.playClip(element.media_id);
|
||||
}
|
||||
}
|
||||
else if (element.type == "lipsync") {
|
||||
if (this.scrubber_frame_position + 1 >= element.length) {
|
||||
this.lipSync_engine.clips[element.object_uuid].stop();
|
||||
}
|
||||
else if (object) {
|
||||
await this.lipSync_engine.clips[element.object_uuid].play(object);
|
||||
this.lipSync_engine.clips[element.object_uuid].step();
|
||||
}
|
||||
}
|
||||
else if (element.type == "animation") {
|
||||
if (object) {
|
||||
await this.animation_engine.clips[object.uuid].play(object);
|
||||
this.animation_engine.clips[object.uuid].step(this.scrubber_frame_position/60);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.stop()
|
||||
throw "Error New Type of element in the timeline"
|
||||
}
|
||||
//this.timelineItems = this.timelineItems.filter(item => item !== element)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.scrubber_frame_position >= this.timeline_limit && this.is_playing) {
|
||||
await this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private async stop(): Promise<void> {
|
||||
await this.resetScene();
|
||||
this.is_playing = false
|
||||
console.log(`Stopping Timeline`)
|
||||
this.current_time = 0;
|
||||
this.pushEvent(fromEngineActions.UPDATE_TIME, { currentTime: this.current_time })
|
||||
}
|
||||
private async stop(): Promise<void> {
|
||||
await this.resetScene();
|
||||
this.is_playing = false;
|
||||
console.log(`Stopping Timeline`);
|
||||
this.current_time = 0;
|
||||
this.pushEvent(fromEngineActions.UPDATE_TIME, {
|
||||
currentTime: this.current_time,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// How much timeline precision we have
|
||||
@@ -387,7 +408,7 @@ export class TimeLine {
|
||||
// }
|
||||
|
||||
// function CheckIfTimeLineStartAfterClipPlays() {
|
||||
// const timeline = new TimeLine()
|
||||
// const timeline = new TimeLine()
|
||||
// timeline.addPlayableClip(new ClipUI("clip3",0,1000))
|
||||
// timeline.play()
|
||||
// setInterval(async ()=> {
|
||||
@@ -397,7 +418,7 @@ export class TimeLine {
|
||||
// }
|
||||
|
||||
// function CheckIfClipsPlayAllTogetherConcurrently() {
|
||||
// const timeline = new TimeLine()
|
||||
// const timeline = new TimeLine()
|
||||
// timeline.addPlayableClip(new ClipUI("clip1",0,1000))
|
||||
// timeline.addPlayableClip(new ClipUI("clip2",0,1000))
|
||||
// timeline.addPlayableClip(new ClipUI("clip3",0,1000))
|
||||
@@ -406,7 +427,7 @@ export class TimeLine {
|
||||
// }
|
||||
|
||||
// function CheckIfClipsPlayAllTogether() {
|
||||
// const timeline = new TimeLine()
|
||||
// const timeline = new TimeLine()
|
||||
// timeline.addPlayableClip(new TrackClip("clip1",0,1000))
|
||||
// timeline.addPlayableClip(new TrackClip("clip2",0,1000))
|
||||
// timeline.addPlayableClip(new TrackClip("clip3",0,1000))
|
||||
@@ -422,4 +443,3 @@ export class TimeLine {
|
||||
// CheckIfClipsPlayAllTogetherConcurrently()
|
||||
|
||||
// CheckIfClipsPlayAllTogether()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { XYZ } from "../datastructures/common";
|
||||
|
||||
export interface QueueClip {
|
||||
version: number;
|
||||
type: ClipType;
|
||||
@@ -45,10 +47,28 @@ export enum ClipGroup {
|
||||
OBJECT = "object",
|
||||
}
|
||||
|
||||
export interface BaseKeyFrame {
|
||||
id: string;
|
||||
name: string;
|
||||
export interface Keyframe {
|
||||
version: number;
|
||||
keyframe_uuid: string;
|
||||
group: ClipGroup;
|
||||
object_uuid: string;
|
||||
offset: number;
|
||||
position: XYZ;
|
||||
rotation: XYZ;
|
||||
scale: XYZ;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
export interface QueueKeyframe {
|
||||
version: number;
|
||||
keyframe_uuid?: string;
|
||||
group: ClipGroup;
|
||||
object_uuid: string;
|
||||
object_name?: string;
|
||||
offset?: number;
|
||||
position: XYZ;
|
||||
rotation: XYZ;
|
||||
scale: XYZ;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
@@ -56,13 +76,13 @@ export interface CharacterGroup {
|
||||
id: string;
|
||||
muted: boolean;
|
||||
animationClips: Clip[];
|
||||
positionClips: Clip[];
|
||||
positionKeyframes: Keyframe[];
|
||||
lipSyncClips: Clip[];
|
||||
}
|
||||
|
||||
export interface CameraGroup {
|
||||
id: string;
|
||||
clips: Clip[];
|
||||
keyframes: Keyframe[];
|
||||
}
|
||||
|
||||
export interface AudioGroup {
|
||||
@@ -77,8 +97,11 @@ export interface ObjectGroup {
|
||||
}
|
||||
|
||||
export interface ObjectTrack {
|
||||
id: string;
|
||||
keyFrames: ObjectKeyFrame[];
|
||||
object_uuid: string;
|
||||
name: string;
|
||||
keyframes: Keyframe[];
|
||||
}
|
||||
|
||||
export interface ObjectKeyFrame extends BaseKeyFrame {}
|
||||
export interface UpdateTime {
|
||||
currentTime: number;
|
||||
}
|
||||
@@ -1,94 +1,95 @@
|
||||
import { State, Action, ACTION_TYPES} from './types';
|
||||
import { State, Action, ACTION_TYPES } from "./types";
|
||||
|
||||
export function reducer(state: State, action: Action): State {
|
||||
switch(action.type){
|
||||
case ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT:{
|
||||
return{
|
||||
switch (action.type) {
|
||||
case ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT: {
|
||||
console.log("pay", action.payload);
|
||||
return {
|
||||
...state,
|
||||
currentSceneObject: {
|
||||
controlPanel: {
|
||||
isShowing: true,
|
||||
objectVectors: {...action.payload.currentSceneObject.objectVectors},
|
||||
currentSceneObject: action.payload,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT:{
|
||||
return{
|
||||
case ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT: {
|
||||
return {
|
||||
...state,
|
||||
currentSceneObject: {
|
||||
isShowing: state.currentSceneObject.isShowing,
|
||||
objectVectors: {...action.payload.currentSceneObject.objectVectors},
|
||||
controlPanel: {
|
||||
isShowing: state.controlPanel.isShowing,
|
||||
currentSceneObject: action.payload,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT:{
|
||||
return{
|
||||
case ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT: {
|
||||
return {
|
||||
...state,
|
||||
currentSceneObject: {
|
||||
controlPanel: {
|
||||
...state.controlPanel,
|
||||
isShowing: false,
|
||||
objectVectors: state.currentSceneObject.objectVectors,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.SHOW_EDITOR_LOADER:{
|
||||
return{
|
||||
case ACTION_TYPES.SHOW_EDITOR_LOADER: {
|
||||
return {
|
||||
...state,
|
||||
showEditorLoader: {
|
||||
isShowing: true,
|
||||
message: action.payload?.showEditorLoader.message,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.HIDE_EDITOR_LOADER:{
|
||||
return{
|
||||
case ACTION_TYPES.HIDE_EDITOR_LOADER: {
|
||||
return {
|
||||
...state,
|
||||
showEditorLoader: {
|
||||
isShowing: false,
|
||||
message: state.showEditorLoader.message
|
||||
message: state.showEditorLoader.message,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.SHOW_EDITOR_LOADINGBAR:{
|
||||
return{
|
||||
case ACTION_TYPES.SHOW_EDITOR_LOADINGBAR: {
|
||||
return {
|
||||
...state,
|
||||
showEditorLoadingBar: {
|
||||
...action.payload?.showEditorLoadingBar,
|
||||
isShowing: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR:{
|
||||
return{
|
||||
case ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR: {
|
||||
return {
|
||||
...state,
|
||||
showEditorLoadingBar: {
|
||||
...state.showEditorLoadingBar,
|
||||
...action.payload?.showEditorLoadingBar,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.HIDE_EDITOR_LOADINGBAR:{
|
||||
return{
|
||||
case ACTION_TYPES.HIDE_EDITOR_LOADINGBAR: {
|
||||
return {
|
||||
...state,
|
||||
showEditorLoadingBar: {
|
||||
...state.showEditorLoadingBar,
|
||||
isShowing: false,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.ON_TIMELINE_RESIZE:{
|
||||
return{
|
||||
case ACTION_TYPES.ON_TIMELINE_RESIZE: {
|
||||
return {
|
||||
...state,
|
||||
timelineHeight: action.payload.timelineHeight,
|
||||
}
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.ON_CHANGE_VIEW_MODE:{
|
||||
case ACTION_TYPES.ON_CHANGE_VIEW_MODE: {
|
||||
console.log(action);
|
||||
return{
|
||||
return {
|
||||
...state,
|
||||
viewMode: action.payload.viewMode
|
||||
}
|
||||
viewMode: action.payload.viewMode,
|
||||
};
|
||||
}
|
||||
default:{
|
||||
default: {
|
||||
return state; //no change
|
||||
}
|
||||
}// end switch
|
||||
} // end switch
|
||||
}
|
||||
@@ -1,32 +1,41 @@
|
||||
import { Simple3DVector } from "../../datastructures/common";
|
||||
import { ClipGroup } from "../../models/track";
|
||||
export enum VIEW_MODES {
|
||||
EDITOR = 'editor',
|
||||
SIDE_BY_SIDE = 'side-by-side'
|
||||
};
|
||||
EDITOR = "editor",
|
||||
SIDE_BY_SIDE = "side-by-side",
|
||||
}
|
||||
|
||||
type ViewModes = VIEW_MODES.EDITOR | VIEW_MODES.SIDE_BY_SIDE;
|
||||
|
||||
export type ControlPanel = {
|
||||
isShowing: boolean;
|
||||
currentSceneObject: SceneObject;
|
||||
};
|
||||
export type SceneObject = {
|
||||
group: ClipGroup; // TODO: add meta data to determine what it is a camera or a object or a character into prefab clips
|
||||
object_uuid: string;
|
||||
object_name: string;
|
||||
version: string;
|
||||
objectVectors: Simple3DVector;
|
||||
};
|
||||
|
||||
export type State = {
|
||||
viewMode: ViewModes;
|
||||
timelineHeight: number;
|
||||
showEditorLoader: {
|
||||
isShowing:boolean;
|
||||
message?:string;
|
||||
isShowing: boolean;
|
||||
message?: string;
|
||||
};
|
||||
showEditorLoadingBar:{
|
||||
showEditorLoadingBar: {
|
||||
isShowing: boolean;
|
||||
label?: string;
|
||||
message?: string;
|
||||
progress?: number;
|
||||
useFakeTimer?: number;
|
||||
}
|
||||
currentSceneObject:{
|
||||
isShowing: boolean;
|
||||
objectVectors: Simple3DVector;
|
||||
}
|
||||
};
|
||||
controlPanel: ControlPanel;
|
||||
};
|
||||
|
||||
|
||||
export enum ACTION_TYPES {
|
||||
ON_TIMELINE_RESIZE = "on_timeline_resize",
|
||||
ON_CHANGE_VIEW_MODE = "on_change_view_mode",
|
||||
@@ -38,67 +47,61 @@ export enum ACTION_TYPES {
|
||||
SHOW_CONTROLPANELS_SCENEOBJECT = "show_controlpanels_sceneobject",
|
||||
UPDATE_CONTROLPANELS_SCENEOBJECT = "update_controlpanels_sceneobject",
|
||||
HIDE_CONTROLPANELS_SCENEOBJECT = "hide_controlpanels_sceneobject",
|
||||
};
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| {type: ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT}
|
||||
| {
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload:{
|
||||
currentSceneObject: {
|
||||
objectVectors: Simple3DVector;
|
||||
};
|
||||
}
|
||||
// Not sure why we need this but type script won't stop complaning fix wil.
|
||||
type: ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT;
|
||||
}
|
||||
| {
|
||||
type: ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT,
|
||||
payload:{
|
||||
currentSceneObject: {
|
||||
objectVectors: Simple3DVector;
|
||||
};
|
||||
}
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT;
|
||||
payload: SceneObject;
|
||||
}
|
||||
| {type: ACTION_TYPES.HIDE_EDITOR_LOADER,}
|
||||
| {
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADER,
|
||||
payload?:{
|
||||
type: ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT;
|
||||
payload: SceneObject;
|
||||
}
|
||||
| { type: ACTION_TYPES.HIDE_EDITOR_LOADER }
|
||||
| {
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADER;
|
||||
payload?: {
|
||||
showEditorLoader: {
|
||||
message:string|undefined;
|
||||
}
|
||||
}
|
||||
message: string | undefined;
|
||||
};
|
||||
};
|
||||
}
|
||||
| {type: ACTION_TYPES.HIDE_EDITOR_LOADINGBAR,}
|
||||
| { type: ACTION_TYPES.HIDE_EDITOR_LOADINGBAR }
|
||||
| {
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADINGBAR,
|
||||
payload?:{
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADINGBAR;
|
||||
payload?: {
|
||||
showEditorLoadingBar: {
|
||||
label?: string;
|
||||
message?: string;
|
||||
progress?: number;
|
||||
useFakeTimer?: number;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR,
|
||||
payload:{
|
||||
type: ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR;
|
||||
payload: {
|
||||
showEditorLoadingBar: {
|
||||
label?: string;
|
||||
message?: string;
|
||||
progress?: number;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: ACTION_TYPES.ON_TIMELINE_RESIZE,
|
||||
type: ACTION_TYPES.ON_TIMELINE_RESIZE;
|
||||
payload: {
|
||||
timelineHeight: number
|
||||
}
|
||||
timelineHeight: number;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: ACTION_TYPES.ON_CHANGE_VIEW_MODE,
|
||||
payload: {
|
||||
viewMode: ViewModes
|
||||
}
|
||||
}
|
||||
;
|
||||
type: ACTION_TYPES.ON_CHANGE_VIEW_MODE;
|
||||
payload: {
|
||||
viewMode: ViewModes;
|
||||
};
|
||||
};
|
||||
@@ -1,23 +1,30 @@
|
||||
import {State, VIEW_MODES} from './types';
|
||||
import { State, VIEW_MODES } from "./types";
|
||||
import { ClipGroup } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const initialStateValues : State = {
|
||||
export const initialStateValues: State = {
|
||||
timelineHeight: 260,
|
||||
viewMode: VIEW_MODES.EDITOR,
|
||||
showEditorLoader: {
|
||||
isShowing:false,
|
||||
message: 'Loading Editor Engine 🦊'
|
||||
isShowing: false,
|
||||
message: "Loading Editor Engine 🦊",
|
||||
},
|
||||
showEditorLoadingBar: {
|
||||
isShowing:true,
|
||||
progress:5,
|
||||
message: 'Loading Editor Engine 🦊'
|
||||
isShowing: true,
|
||||
progress: 5,
|
||||
message: "Loading Editor Engine 🦊",
|
||||
},
|
||||
currentSceneObject:{
|
||||
isShowing:false,
|
||||
objectVectors:{
|
||||
position:{x:0,y:0,z:0},
|
||||
rotation:{x:0,y:0,z:0},
|
||||
scale:{x:0,y:0,z:0},
|
||||
}
|
||||
}
|
||||
}
|
||||
controlPanel: {
|
||||
isShowing: false,
|
||||
currentSceneObject: {
|
||||
group: ClipGroup.OBJECT,
|
||||
object_name: "",
|
||||
object_uuid: "",
|
||||
version: "1",
|
||||
objectVectors: {
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
rotation: { x: 0, y: 0, z: 0 },
|
||||
scale: { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
AudioGroup,
|
||||
Clip,
|
||||
ClipGroup,
|
||||
ClipType,
|
||||
MediaClip,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import * as uuid from "uuid";
|
||||
import { signal } from "@preact/signals-core";
|
||||
|
||||
export const audioGroup = signal<AudioGroup>({
|
||||
id: "AG-1",
|
||||
clips: [],
|
||||
muted: false,
|
||||
});
|
||||
|
||||
export function updateAudio({
|
||||
id,
|
||||
offset,
|
||||
length,
|
||||
}: {
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) {
|
||||
const oldAudioGroup = audioGroup.value;
|
||||
const newClips = [...oldAudioGroup.clips];
|
||||
const clipIndex = newClips.findIndex((row) => row.clip_uuid === id);
|
||||
if (clipIndex === -1) {
|
||||
return { ...oldAudioGroup };
|
||||
}
|
||||
const clip = newClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
audioGroup.value = {
|
||||
...oldAudioGroup,
|
||||
clips: newClips,
|
||||
};
|
||||
}
|
||||
|
||||
export function addGlobalAudio(
|
||||
dragId: string,
|
||||
audioClips: MediaClip[],
|
||||
offset: number,
|
||||
) {
|
||||
const clip = audioClips.find((row) => row.media_id === dragId);
|
||||
if (!clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clip_uuid = uuid.v4();
|
||||
const newClip = {
|
||||
...clip,
|
||||
type: ClipType.AUDIO,
|
||||
group: ClipGroup.GLOBAL_AUDIO,
|
||||
offset,
|
||||
clip_uuid,
|
||||
} as Clip;
|
||||
|
||||
const oldAudioGroup = audioGroup.value;
|
||||
audioGroup.value = {
|
||||
...oldAudioGroup,
|
||||
clips: [...oldAudioGroup.clips, newClip],
|
||||
};
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newClip,
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleAudioMute() {
|
||||
const oldAudioGroup = audioGroup.value;
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: oldAudioGroup?.muted
|
||||
? toEngineActions.UNMUTE
|
||||
: toEngineActions.MUTE,
|
||||
data: {
|
||||
version: 1,
|
||||
type: ClipType.AUDIO,
|
||||
group: ClipGroup.GLOBAL_AUDIO,
|
||||
},
|
||||
});
|
||||
|
||||
audioGroup.value = {
|
||||
...oldAudioGroup,
|
||||
muted: !oldAudioGroup.muted,
|
||||
};
|
||||
}
|
||||
|
||||
export function selectAudioClip(clipId: string) {
|
||||
const oldAudioGroup = audioGroup.value;
|
||||
audioGroup.value = {
|
||||
...oldAudioGroup,
|
||||
clips: [
|
||||
...oldAudioGroup.clips.map((clip) => {
|
||||
return {
|
||||
...clip,
|
||||
selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
};
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteAudioClip(clip: Clip) {
|
||||
const oldAudioGroup = audioGroup.value;
|
||||
audioGroup.value = {
|
||||
...oldAudioGroup,
|
||||
clips: [
|
||||
...oldAudioGroup.clips.filter((row) => {
|
||||
if (row.clip_uuid === clip.clip_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
CameraGroup,
|
||||
Keyframe,
|
||||
QueueKeyframe,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import * as uuid from "uuid";
|
||||
import { signal } from "@preact/signals-core";
|
||||
|
||||
export const cameraGroup = signal<CameraGroup>({ id: "CG1", keyframes: [] });
|
||||
|
||||
export function updateCamera({ id, offset }: { id: string; offset: number }) {
|
||||
const oldCameraGroup = cameraGroup.value;
|
||||
const newKeyframes = [...oldCameraGroup.keyframes];
|
||||
const keyframe = newKeyframes.find((row) => row.keyframe_uuid === id);
|
||||
if (!keyframe) {
|
||||
return;
|
||||
}
|
||||
keyframe.offset = offset;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_KEYFRAME,
|
||||
data: keyframe,
|
||||
});
|
||||
|
||||
cameraGroup.value = {
|
||||
...oldCameraGroup,
|
||||
keyframes: newKeyframes,
|
||||
};
|
||||
}
|
||||
|
||||
export function addCameraKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
const newKeyframe = {
|
||||
version: keyframe.version,
|
||||
keyframe_uuid: uuid.v4(),
|
||||
group: keyframe.group,
|
||||
object_uuid: keyframe.object_uuid,
|
||||
offset,
|
||||
position: keyframe.position,
|
||||
rotation: keyframe.rotation,
|
||||
scale: keyframe.scale,
|
||||
selected: false,
|
||||
} as Keyframe;
|
||||
|
||||
const oldCameraGroup = cameraGroup.value;
|
||||
cameraGroup.value = {
|
||||
...oldCameraGroup,
|
||||
keyframes: [...oldCameraGroup.keyframes, newKeyframe].sort(
|
||||
(keyFrameA, keyframeB) => keyFrameA.offset - keyframeB.offset,
|
||||
),
|
||||
};
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_KEYFRAME,
|
||||
data: newKeyframe,
|
||||
});
|
||||
}
|
||||
|
||||
export function selectCameraKeyframe(keyframeId: string) {
|
||||
const oldCameraGroup = cameraGroup.value;
|
||||
cameraGroup.value = {
|
||||
...oldCameraGroup,
|
||||
keyframes: [
|
||||
...oldCameraGroup.keyframes.map((keyframe) => {
|
||||
return {
|
||||
...keyframe,
|
||||
selected:
|
||||
keyframe.keyframe_uuid === keyframeId
|
||||
? !keyframe.selected
|
||||
: keyframe.selected,
|
||||
};
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteCameraKeyframe(deleteKeyframe: Keyframe) {
|
||||
const oldCameraGroup = cameraGroup.value;
|
||||
cameraGroup.value = {
|
||||
...oldCameraGroup,
|
||||
keyframes: [
|
||||
...oldCameraGroup.keyframes.filter((keyframe) => {
|
||||
if (keyframe.keyframe_uuid === deleteKeyframe.keyframe_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_KEYFRAME,
|
||||
data: keyframe,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
import {
|
||||
CharacterGroup,
|
||||
Clip,
|
||||
ClipGroup,
|
||||
ClipType,
|
||||
Keyframe,
|
||||
MediaClip,
|
||||
QueueKeyframe,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import { signal } from "@preact/signals-core";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
export const characterGroups = signal<CharacterGroup[]>([
|
||||
{
|
||||
id: "CH1",
|
||||
muted: false,
|
||||
animationClips: [],
|
||||
positionKeyframes: [],
|
||||
lipSyncClips: [],
|
||||
},
|
||||
]);
|
||||
|
||||
export function updateCharacters({
|
||||
type,
|
||||
id,
|
||||
offset,
|
||||
length,
|
||||
}: {
|
||||
type: "animations" | "positions" | "lipSync";
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
if (type === "animations") {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newAnimationClips = [...character.animationClips];
|
||||
const clipIndex = newAnimationClips.findIndex(
|
||||
(row) => row.clip_uuid === id,
|
||||
);
|
||||
if (clipIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const clip = newAnimationClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
animationClips: newAnimationClips,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "positions") {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newPositionKeyframes = [...character.positionKeyframes];
|
||||
const keyframeIndex = newPositionKeyframes.findIndex(
|
||||
(row) => row.keyframe_uuid === id,
|
||||
);
|
||||
if (keyframeIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const keyframe = newPositionKeyframes[keyframeIndex];
|
||||
keyframe.offset = offset;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: keyframe,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
positionClips: newPositionKeyframes,
|
||||
};
|
||||
});
|
||||
}
|
||||
if (type === "lipSync") {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newLipSyncClips = [...character.lipSyncClips];
|
||||
const clipIndex = newLipSyncClips.findIndex(
|
||||
(row) => row.clip_uuid === id,
|
||||
);
|
||||
if (clipIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const clip = newLipSyncClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: newLipSyncClips,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function addCharacterAnimation({
|
||||
clipId,
|
||||
characterId,
|
||||
animationClips,
|
||||
offset,
|
||||
}: {
|
||||
clipId: string;
|
||||
characterId: string;
|
||||
animationClips: MediaClip[];
|
||||
offset: number;
|
||||
}) {
|
||||
const clip = animationClips.find((row) => row.media_id === clipId);
|
||||
if (!clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clip_uuid = uuid.v4();
|
||||
const newClip = {
|
||||
...clip,
|
||||
group: ClipGroup.CHARACTER,
|
||||
offset,
|
||||
clip_uuid,
|
||||
object_uuid: characterId,
|
||||
} as Clip;
|
||||
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
if (character.id !== characterId) {
|
||||
return { ...character };
|
||||
}
|
||||
return {
|
||||
...character,
|
||||
animationClips: [...character.animationClips, newClip].sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newClip,
|
||||
});
|
||||
}
|
||||
|
||||
export function addCharacterAudio({
|
||||
clipId,
|
||||
characterId,
|
||||
audioClips,
|
||||
offset,
|
||||
}: {
|
||||
clipId: string;
|
||||
characterId: string;
|
||||
audioClips: MediaClip[];
|
||||
offset: number;
|
||||
}) {
|
||||
const clip = audioClips.find((row) => row.media_id === clipId);
|
||||
if (!clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clip_uuid = uuid.v4();
|
||||
const newClip = {
|
||||
...clip,
|
||||
type: ClipType.AUDIO,
|
||||
group: ClipGroup.CHARACTER,
|
||||
offset,
|
||||
clip_uuid,
|
||||
object_uuid: characterId,
|
||||
} as Clip;
|
||||
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
if (character.id !== characterId) {
|
||||
return { ...character };
|
||||
}
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: [...character.lipSyncClips, newClip].sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newClip,
|
||||
});
|
||||
}
|
||||
|
||||
export function addCharacterKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
const newKeyframe = {
|
||||
version: keyframe.version,
|
||||
keyframe_uuid: uuid.v4(),
|
||||
group: keyframe.group,
|
||||
object_uuid: keyframe.object_uuid,
|
||||
offset,
|
||||
position: keyframe.position,
|
||||
rotation: keyframe.rotation,
|
||||
scale: keyframe.scale,
|
||||
selected: false,
|
||||
} as Keyframe;
|
||||
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
if (character.id !== keyframe.object_uuid) {
|
||||
return { ...character };
|
||||
}
|
||||
return {
|
||||
...character,
|
||||
positionKeyframes: [...character.positionKeyframes, newKeyframe].sort(
|
||||
(keyFrameA, keyframeB) => keyFrameA.offset - keyframeB.offset,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_KEYFRAME,
|
||||
data: newKeyframe,
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleLipSyncMute(characterId: string) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
if (character.id === characterId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: character?.muted
|
||||
? toEngineActions.UNMUTE
|
||||
: toEngineActions.MUTE,
|
||||
data: {
|
||||
version: 1,
|
||||
type: ClipType.AUDIO,
|
||||
group: ClipGroup.CHARACTER,
|
||||
object_uuid: characterId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...character,
|
||||
muted: character.id === characterId ? !character.muted : character.muted,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function selectCharacterClip(clipId: string) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => ({
|
||||
...character,
|
||||
animationClips: character.animationClips.map((clip) => ({
|
||||
...clip,
|
||||
selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
positionClips: character.positionKeyframes.map((keyframe) => ({
|
||||
...keyframe,
|
||||
selected:
|
||||
keyframe.keyframe_uuid === clipId
|
||||
? !keyframe.selected
|
||||
: keyframe.selected,
|
||||
})),
|
||||
lipSyncClips: character.lipSyncClips.map((clip) => ({
|
||||
...clip,
|
||||
selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
export function deleteCharacterClip(clip: Clip) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => ({
|
||||
...character,
|
||||
animationClips: character.animationClips.filter((row) => {
|
||||
if (row.clip_uuid === clip.clip_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
lipSyncClips: character.lipSyncClips.filter((row) => {
|
||||
if (row.clip_uuid === clip.clip_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
export function deleteCharacterKeyframe(keyframe: Keyframe) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => ({
|
||||
...character,
|
||||
positionClips: character.positionKeyframes.filter((row) => {
|
||||
if (row.keyframe_uuid === keyframe.keyframe_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_KEYFRAME,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
}));
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
export * from "./audioGroup";
|
||||
export * from "./cameraGroup";
|
||||
export * from "./characterGroups";
|
||||
export * from "./dragAndDrop";
|
||||
export * from "./objectGroup";
|
||||
export * from "./sizing";
|
||||
export * from "./timing";
|
||||
@@ -0,0 +1,121 @@
|
||||
import { signal } from "@preact/signals-core";
|
||||
import {
|
||||
Keyframe,
|
||||
ObjectGroup,
|
||||
QueueKeyframe,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import * as uuid from "uuid";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
|
||||
export const objectGroup = signal<ObjectGroup>({
|
||||
id: "OB1",
|
||||
objects: [],
|
||||
});
|
||||
|
||||
export function updateObject({ id, offset }: { id: string; offset: number }) {
|
||||
const oldObject = objectGroup.value;
|
||||
objectGroup.value = {
|
||||
id: oldObject.id,
|
||||
objects: oldObject.objects.map((object) => ({
|
||||
object_uuid: object.object_uuid,
|
||||
name: object.name,
|
||||
keyframes: object.keyframes.map((keyFrame) => ({
|
||||
...keyFrame,
|
||||
offset: keyFrame.keyframe_uuid === id ? offset : keyFrame.offset,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function addObjectKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
const oldObjectGroup = objectGroup.value;
|
||||
const obj = oldObjectGroup.objects.find(
|
||||
(row) => row.object_uuid === keyframe.object_uuid,
|
||||
);
|
||||
|
||||
const newObject = obj ?? {
|
||||
object_uuid: keyframe.object_uuid,
|
||||
name: keyframe.object_name ?? "unknown",
|
||||
keyframes: [] as Keyframe[],
|
||||
};
|
||||
const newKeyframe = {
|
||||
version: keyframe.version,
|
||||
keyframe_uuid: uuid.v4(),
|
||||
group: keyframe.group,
|
||||
object_uuid: keyframe.object_uuid,
|
||||
offset,
|
||||
position: keyframe.position,
|
||||
rotation: keyframe.rotation,
|
||||
scale: keyframe.scale,
|
||||
selected: false,
|
||||
} as Keyframe;
|
||||
newObject.keyframes.push(newKeyframe);
|
||||
console.log(newObject);
|
||||
newObject.keyframes.sort(
|
||||
(keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset,
|
||||
);
|
||||
|
||||
objectGroup.value = {
|
||||
...oldObjectGroup,
|
||||
objects: [
|
||||
...oldObjectGroup.objects.filter(
|
||||
(object) => object.object_uuid !== keyframe.object_uuid,
|
||||
),
|
||||
newObject,
|
||||
].sort((objA, objB) => (objA.object_uuid < objB.object_uuid ? -1 : 1)),
|
||||
};
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_KEYFRAME,
|
||||
data: newKeyframe,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteObjectKeyframe(keyframe: Keyframe) {
|
||||
const oldObjectGroup = objectGroup.value;
|
||||
const obj = oldObjectGroup.objects.find(
|
||||
(row) => row.object_uuid === keyframe.object_uuid,
|
||||
);
|
||||
console.log("delete", obj);
|
||||
if (!obj) {
|
||||
return oldObjectGroup;
|
||||
}
|
||||
|
||||
const newKeyframes = [
|
||||
...obj.keyframes.filter((row) => {
|
||||
if (row.keyframe_uuid === keyframe.keyframe_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_KEYFRAME,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
];
|
||||
|
||||
if (newKeyframes.length) {
|
||||
return {
|
||||
...oldObjectGroup,
|
||||
objects: [
|
||||
...oldObjectGroup.objects.map((object) => ({
|
||||
...object,
|
||||
...(object.object_uuid === keyframe.object_uuid
|
||||
? { keyframes: newKeyframes }
|
||||
: {}),
|
||||
})),
|
||||
],
|
||||
};
|
||||
}
|
||||
objectGroup.value = {
|
||||
...oldObjectGroup,
|
||||
objects: [
|
||||
...oldObjectGroup.objects.filter(
|
||||
(object) => object.object_uuid !== keyframe.object_uuid,
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { signal } from "@preact/signals-core";
|
||||
import { Clip, Keyframe } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const selectedItem = signal<Clip | Keyframe | null>(null);
|
||||
@@ -1,4 +1,6 @@
|
||||
import { signal, computed } from "@preact/signals-core";
|
||||
import { characterGroups } from "~/pages/PageEnigma/store/characterGroups";
|
||||
import { objectGroup } from "~/pages/PageEnigma/store/objectGroup";
|
||||
|
||||
export const scale = signal(1);
|
||||
export const filmLength = signal(12);
|
||||
@@ -6,3 +8,12 @@ export const timelineHeight = signal(0);
|
||||
export const fullWidth = computed(() => {
|
||||
return filmLength.value * 60 * 4 * scale.value;
|
||||
});
|
||||
|
||||
export const fullHeight = computed(() => {
|
||||
return (
|
||||
characterGroups.value.length * 268 +
|
||||
objectGroup.value.objects.length * 60 +
|
||||
300 +
|
||||
96
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user