mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
Merge pull request #84 from storytold/feature/keyframes-continued
Feature/keyframes continued
This commit is contained in:
35 files changed
+391
-335
No files matched your search
@@ -3,12 +3,65 @@ import { TrackProvider } from "~/pages/PageEnigma/contexts/TrackContext/TrackPro
|
||||
import { AppUIProvider } from "~/pages/PageEnigma/contexts/AppUiContext";
|
||||
import { EngineProvider } from "~/pages/PageEnigma/contexts/EngineProvider";
|
||||
import { DragComponent } from "~/pages/PageEnigma/comps/DragComponent/DragComponent";
|
||||
import { toast, ToastBar, Toaster } from "react-hot-toast";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faTriangleExclamation,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
export const PageEnigma = () => {
|
||||
return (
|
||||
<TrackProvider>
|
||||
<AppUIProvider>
|
||||
<EngineProvider>
|
||||
<Toaster
|
||||
position="top-right"
|
||||
toastOptions={{
|
||||
style: {
|
||||
border: "1px solid #39394D",
|
||||
backgroundColor: "#39394D",
|
||||
},
|
||||
duration: 99999,
|
||||
}}
|
||||
containerStyle={{
|
||||
top: 84,
|
||||
right: 20,
|
||||
}}
|
||||
>
|
||||
{(thisToast) => (
|
||||
<ToastBar toast={thisToast}>
|
||||
{({ message }) => {
|
||||
return (
|
||||
<div className="mr-3 flex justify-between rounded-lg">
|
||||
<div>
|
||||
{thisToast.type === "error" && (
|
||||
<div className="ml-3 flex items-center gap-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faTriangleExclamation}
|
||||
className="text-keyframe-selected"
|
||||
/>
|
||||
<div className="text-base font-bold text-white">
|
||||
Warning!
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-left text-sm text-white opacity-50">
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => toast.dismiss(thisToast.id)}
|
||||
className="text-sm font-bold text-white opacity-50"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</ToastBar>
|
||||
)}
|
||||
</Toaster>
|
||||
<PageEnigmaComponent />
|
||||
</EngineProvider>
|
||||
</AppUIProvider>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext } from "react";
|
||||
|
||||
import { faSparkles } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { Button, LoadingBar, LoadingDots } from "~/components";
|
||||
@@ -19,9 +18,12 @@ 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";
|
||||
import { EngineContext } from "./contexts/EngineContext";
|
||||
|
||||
export const PageEnigmaComponent = () => {
|
||||
useSignals();
|
||||
|
||||
const editorEngine = useContext(EngineContext);
|
||||
const [appUiState] = useContext(AppUiContext);
|
||||
|
||||
//To prevent the click event from propagating to the canvas: TODO: HANDLE THIS BETTER?
|
||||
@@ -29,13 +31,20 @@ export const PageEnigmaComponent = () => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const handleGenerateMovieClick=()=> {
|
||||
if ( editorEngine != null) {
|
||||
editorEngine?.generateVideo()
|
||||
} else {
|
||||
console.log("Tried to generate movie but editor was null")
|
||||
}
|
||||
};
|
||||
|
||||
const lowerHeight = timelineHeight.value;
|
||||
return (
|
||||
<div>
|
||||
<TopBarHelmet>
|
||||
<Button icon={faSparkles}>Generate Movie</Button>
|
||||
<Button icon={faSparkles} onClick={handleGenerateMovieClick}>Generate Movie</Button>
|
||||
</TopBarHelmet>
|
||||
|
||||
<div style={{ height: "calc(100vh - 68px)" }}>
|
||||
{/* Engine section/side panel */}
|
||||
<div
|
||||
|
||||
@@ -24,68 +24,10 @@ export const ControlsTopButtons = () => {
|
||||
|
||||
const handleButtonTest = () => {
|
||||
editorEngine?.testStylizeRequest();
|
||||
|
||||
// if(appUiState?.showEditorLoadingBar.isShowing){
|
||||
//TO GO TO A HUNDRED AND DISAPPER
|
||||
// dispatchAppUiState({
|
||||
// type: APPUI_ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR,
|
||||
// payload:{
|
||||
// showEditorLoadingBar: {
|
||||
// progress: 100,
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// setTimeout(() => {
|
||||
// dispatchAppUiState({
|
||||
// type: APPUI_ACTION_TYPES.HIDE_EDITOR_LOADINGBAR,
|
||||
// });
|
||||
// }, 1000);
|
||||
//END :TO GO TO A HUNDRED AND DISAPPER
|
||||
|
||||
//EAMPLE OF CHANING LOADING MESSAGE
|
||||
// dispatchAppUiState({
|
||||
// type: APPUI_ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR,
|
||||
// payload:{
|
||||
// showEditorLoadingBar: {
|
||||
// message: "new message",
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }else{
|
||||
// dispatchAppUiState({
|
||||
// type: APPUI_ACTION_TYPES.SHOW_EDITOR_LOADINGBAR,
|
||||
// payload:{
|
||||
// showEditorLoadingBar: {
|
||||
// message: 'display of LoadingBar triggered by Test Button',
|
||||
// progress: 10,
|
||||
// }
|
||||
// }
|
||||
|
||||
// });
|
||||
//}
|
||||
};
|
||||
|
||||
const handleObjectPanelTest = () => {
|
||||
if (appUiState?.controlPanel.isShowing) {
|
||||
dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT,
|
||||
});
|
||||
} else {
|
||||
dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
group: ClipGroup.OBJECT,
|
||||
object_name: "TEST BUTTON",
|
||||
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 handleTestButton2 = () => {
|
||||
console.log("Test Button 2")
|
||||
};
|
||||
|
||||
const handleButtonCameraView = () => {
|
||||
@@ -94,18 +36,9 @@ export const ControlsTopButtons = () => {
|
||||
const handleButtonPlayBack = () => {
|
||||
editorEngine?.startPlayback();
|
||||
};
|
||||
const handleButtonRender = () => {
|
||||
editorEngine?.generateVideo();
|
||||
};
|
||||
|
||||
const handleButtonTakeFrame = () => {
|
||||
editorEngine?.take_timeline_cam_clip();
|
||||
};
|
||||
|
||||
const handleButtonLoad = () => {
|
||||
console.log("LOADING");
|
||||
};
|
||||
|
||||
const handleButtonSingleFrame = () => {
|
||||
editorEngine?.generateFrame();
|
||||
};
|
||||
@@ -172,18 +105,15 @@ export const ControlsTopButtons = () => {
|
||||
Toggle Camera View
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleObjectPanelTest}
|
||||
onClick={handleTestButton2}
|
||||
className="bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary"
|
||||
>
|
||||
Show Object Panel
|
||||
Test Button 2
|
||||
</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>
|
||||
{/* <Button onClick={handleButtonLoad}>Load</Button>
|
||||
<Button onClick={handleButtonRender}>Render</Button> */}
|
||||
<Button
|
||||
onClick={handleButtonTest}
|
||||
className="bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useContext, useEffect } from "react";
|
||||
import { MediaClip } from "~/pages/PageEnigma/models/track";
|
||||
import { ClipType, MediaClip } from "~/pages/PageEnigma/models/track";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import {
|
||||
canDrop,
|
||||
@@ -11,7 +11,7 @@ import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
interface Props {
|
||||
clip: MediaClip;
|
||||
type: "animations" | "audio";
|
||||
type: ClipType;
|
||||
}
|
||||
|
||||
export const AnimationElement = ({ clip, type }: Props) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AnimationElement } from "./AnimationElement";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { timelineHeight } from "~/pages/PageEnigma/store";
|
||||
import { ClipType } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const AnimationElements = () => {
|
||||
const { animationClips } = useContext(TrackContext);
|
||||
@@ -17,7 +18,11 @@ export const AnimationElements = () => {
|
||||
<div className="flex flex-wrap gap-3 overflow-y-auto" style={{ height }}>
|
||||
{animationClips.map((clip) => {
|
||||
return (
|
||||
<AnimationElement key={clip.media_id} clip={clip} type="animations" />
|
||||
<AnimationElement
|
||||
key={clip.media_id}
|
||||
clip={clip}
|
||||
type={ClipType.ANIMATION}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useContext, useEffect } from "react";
|
||||
import { MediaClip } from "~/pages/PageEnigma/models/track";
|
||||
import { ClipType, MediaClip } from "~/pages/PageEnigma/models/track";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import {
|
||||
canDrop,
|
||||
@@ -11,7 +11,7 @@ import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
interface Props {
|
||||
clip: MediaClip;
|
||||
type: "animations" | "audio";
|
||||
type: ClipType;
|
||||
}
|
||||
|
||||
export const AudioElement = ({ clip, type }: Props) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AnimationElement } from "./AnimationElement";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { timelineHeight } from "~/pages/PageEnigma/store";
|
||||
import { ClipType } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const AudioElements = () => {
|
||||
const { audioClips } = useContext(TrackContext);
|
||||
@@ -17,7 +18,11 @@ export const AudioElements = () => {
|
||||
<div className="flex flex-wrap gap-3 overflow-y-auto" style={{ height }}>
|
||||
{audioClips.map((clip) => {
|
||||
return (
|
||||
<AnimationElement key={clip.media_id} clip={clip} type="audio" />
|
||||
<AnimationElement
|
||||
key={clip.media_id}
|
||||
clip={clip}
|
||||
type={ClipType.AUDIO}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -5,13 +5,14 @@ import {
|
||||
toggleAudioMute,
|
||||
updateAudio,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { ClipGroup } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const Audio = () => {
|
||||
const { clips } = audioGroup.value;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="block rounded-lg bg-audio-groupBg pb-5 pl-2 pr-4"
|
||||
className="bg-global_audio-groupBg block rounded-lg pb-5 pl-2 pr-4"
|
||||
style={{ width: fullWidth.value + 90 }}
|
||||
>
|
||||
<div className="prevent-select mb-5 pt-2 text-xs font-medium text-white">
|
||||
@@ -23,7 +24,7 @@ export const Audio = () => {
|
||||
clips={clips}
|
||||
title="Global Audio TrackClips"
|
||||
updateClip={updateAudio}
|
||||
style="audio"
|
||||
group={ClipGroup.GLOBAL_AUDIO}
|
||||
muted={audioGroup.value.muted}
|
||||
toggleMute={toggleAudioMute}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { cameraGroup, fullWidth, updateCamera } from "~/pages/PageEnigma/store";
|
||||
import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { ClipGroup } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const Camera = () => {
|
||||
useSignals();
|
||||
@@ -17,7 +18,7 @@ export const Camera = () => {
|
||||
id={cameraGroup.value.id}
|
||||
keyframes={keyframes}
|
||||
title="Camera Position/Rotation"
|
||||
style="camera"
|
||||
group={ClipGroup.CAMERA}
|
||||
updateKeyframe={updateCamera}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,11 @@ import {
|
||||
updateCharacters,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames";
|
||||
import { ClipGroup, ClipType } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
function buildUpdaters(
|
||||
updateCharacters: (options: {
|
||||
type: "animations" | "positions" | "lipSync";
|
||||
type: ClipType;
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
@@ -21,17 +22,17 @@ function buildUpdaters(
|
||||
length: number;
|
||||
offset: number;
|
||||
}) {
|
||||
updateCharacters({ ...options, type: "animations" });
|
||||
updateCharacters({ ...options, type: ClipType.ANIMATION });
|
||||
}
|
||||
function updateClipPosition(options: { id: string; offset: number }) {
|
||||
updateCharacters({ ...options, length: 0, type: "positions" });
|
||||
updateCharacters({ ...options, length: 0, type: ClipType.TRANSFORM });
|
||||
}
|
||||
function updateClipLipSync(options: {
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) {
|
||||
updateCharacters({ ...options, type: "lipSync" });
|
||||
updateCharacters({ ...options, type: ClipType.AUDIO });
|
||||
}
|
||||
return { updateClipLipSync, updateClipPosition, updateClipAnimations };
|
||||
}
|
||||
@@ -68,15 +69,15 @@ export const Character = ({ characterId }: Props) => {
|
||||
clips={animationClips}
|
||||
title="Animation"
|
||||
updateClip={updateClipAnimations}
|
||||
style="character"
|
||||
type="animations"
|
||||
group={ClipGroup.CHARACTER}
|
||||
type={ClipType.ANIMATION}
|
||||
/>
|
||||
<TrackKeyFrames
|
||||
id={character.id}
|
||||
keyframes={positionKeyframes}
|
||||
title="Character Position/Rotation"
|
||||
updateKeyframe={updateClipPosition}
|
||||
style="character"
|
||||
group={ClipGroup.CHARACTER}
|
||||
/>
|
||||
<TrackClips
|
||||
id={character.id}
|
||||
@@ -85,8 +86,8 @@ export const Character = ({ characterId }: Props) => {
|
||||
updateClip={updateClipLipSync}
|
||||
muted={character.muted}
|
||||
toggleMute={toggleCharacterLipSyncMute}
|
||||
style="character"
|
||||
type="lipSync"
|
||||
group={ClipGroup.CHARACTER}
|
||||
type={ClipType.AUDIO}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ObjectTrack } from "~/pages/PageEnigma/comps/Timeline/ObjectTrack";
|
||||
|
||||
export const ObjectGroups = () => {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<>
|
||||
{objectGroup.value.objects.length > 0 && (
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { fullWidth, objectGroup, updateObject } from "~/pages/PageEnigma/store";
|
||||
import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames";
|
||||
import { ClipGroup } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const ObjectTrack = () => {
|
||||
return (
|
||||
<div
|
||||
className="block rounded-lg bg-objects-groupBg pb-5 pl-2 pr-4"
|
||||
className="bg-object-groupBg block rounded-lg pb-5 pl-2 pr-4"
|
||||
style={{ width: fullWidth.value + 90 }}
|
||||
>
|
||||
<div className="prevent-select mb-5 pt-2 text-xs font-medium text-white">
|
||||
@@ -17,7 +18,7 @@ export const ObjectTrack = () => {
|
||||
keyframes={object.keyframes}
|
||||
title={`${object.name} Position/Rotation`}
|
||||
updateKeyframe={updateObject}
|
||||
style="objects"
|
||||
group={ClipGroup.OBJECT}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
File renamed without changes.
@@ -13,7 +13,7 @@ import {
|
||||
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 { Scrubber } from "~/pages/PageEnigma/comps/Timeline/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";
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { useMouseEventsClip } from "./utils/useMouseEventsClip";
|
||||
import { useState } from "react";
|
||||
import { scale, selectedItem } from "~/pages/PageEnigma/store";
|
||||
import { Clip } from "~/pages/PageEnigma/models/track";
|
||||
import { Clip, ClipGroup } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
interface Props {
|
||||
min: number;
|
||||
max: number;
|
||||
style: "character" | "camera" | "audio" | "objects";
|
||||
group: ClipGroup;
|
||||
clip: Clip;
|
||||
updateClip: (options: { id: string; offset: number; length: number }) => void;
|
||||
}
|
||||
|
||||
export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => {
|
||||
export const TrackClip = ({ clip, min, max, group, updateClip }: Props) => {
|
||||
const [state, setState] = useState({
|
||||
length: clip.length,
|
||||
offset: clip.offset,
|
||||
@@ -31,8 +31,8 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => {
|
||||
const classes = [
|
||||
"absolute",
|
||||
clip.clip_uuid === selectedClipId
|
||||
? `bg-${style}-selected`
|
||||
: `bg-${style}-clip`,
|
||||
? `bg-${group}-selected`
|
||||
: `bg-${group}-clip`,
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TrackClip } from "~/pages/PageEnigma/comps/Timeline/TrackClip";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faVolume, faVolumeSlash } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Clip } from "~/pages/PageEnigma/models/track";
|
||||
import { Clip, ClipGroup, ClipType } from "~/pages/PageEnigma/models/track";
|
||||
import { PointerEvent } from "react";
|
||||
import {
|
||||
canDrop,
|
||||
@@ -17,13 +17,38 @@ interface Props {
|
||||
id: string;
|
||||
clips: Clip[];
|
||||
title: string;
|
||||
style: "character" | "audio" | "camera" | "objects";
|
||||
type?: "animations" | "positions" | "lipSync";
|
||||
group: ClipGroup;
|
||||
type?: ClipType;
|
||||
toggleMute?: () => void;
|
||||
muted?: boolean;
|
||||
updateClip: (options: { id: string; length: number; offset: number }) => void;
|
||||
}
|
||||
|
||||
function getCanBuild({
|
||||
dragType,
|
||||
type,
|
||||
group,
|
||||
}: {
|
||||
dragType: ClipType | null;
|
||||
type?: ClipType;
|
||||
group: ClipGroup;
|
||||
}) {
|
||||
if (dragType === ClipType.ANIMATION) {
|
||||
if (dragType === type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (dragType === ClipType.AUDIO) {
|
||||
if (group === ClipGroup.CHARACTER && type === ClipType.AUDIO) {
|
||||
return true;
|
||||
}
|
||||
if (group === ClipGroup.GLOBAL_AUDIO) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const TrackClips = ({
|
||||
id,
|
||||
clips,
|
||||
@@ -31,36 +56,22 @@ export const TrackClips = ({
|
||||
updateClip,
|
||||
muted,
|
||||
title,
|
||||
style,
|
||||
group,
|
||||
type,
|
||||
}: Props) => {
|
||||
const trackType = type ?? style;
|
||||
const trackType = (type ?? group) as ClipType;
|
||||
|
||||
function onPointerOver() {
|
||||
if (dragType.value === "animations") {
|
||||
if (dragType.value !== trackType) {
|
||||
return;
|
||||
}
|
||||
if (getCanBuild({ dragType: dragType.value, type, group })) {
|
||||
dropId.value = id;
|
||||
}
|
||||
if (dragType.value === "audio") {
|
||||
if (["lipSync", "audio"].indexOf(trackType) === -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
dropId.value = id;
|
||||
}
|
||||
|
||||
function onPointerMove(event: PointerEvent<HTMLDivElement>) {
|
||||
if (dragType.value === "animations") {
|
||||
if (dragType.value !== trackType) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (dragType.value === "audio") {
|
||||
if (["lipSync", "audio"].indexOf(trackType) === -1) {
|
||||
return;
|
||||
}
|
||||
if (!getCanBuild({ dragType: dragType.value, type, group })) {
|
||||
return;
|
||||
}
|
||||
|
||||
const track = document.getElementById(`track-${trackType}-${id}`);
|
||||
if (!track) {
|
||||
return;
|
||||
@@ -101,7 +112,7 @@ export const TrackClips = ({
|
||||
<div className="pl-16">
|
||||
<div
|
||||
id={`track-${trackType}-${id}`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${style}-unselected`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${group}-unselected`}
|
||||
onPointerOver={onPointerOver}
|
||||
onPointerLeave={onPointerLeave}
|
||||
onPointerMove={onPointerMove}
|
||||
@@ -117,7 +128,7 @@ export const TrackClips = ({
|
||||
? clips[index + 1].offset
|
||||
: filmLength.value * 60
|
||||
}
|
||||
style={style}
|
||||
group={group}
|
||||
updateClip={updateClip}
|
||||
clip={clip}
|
||||
/>
|
||||
|
||||
@@ -1,44 +1,40 @@
|
||||
import { Keyframe } from "~/pages/PageEnigma/models/track";
|
||||
import { useState } from "react";
|
||||
import { scale, selectedItem } from "~/pages/PageEnigma/store";
|
||||
import { filmLength, scale, selectedItem } 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 { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
interface Props {
|
||||
min: number;
|
||||
max: number;
|
||||
keyframe: Keyframe;
|
||||
updateKeyframe: (options: { id: string; offset: number }) => void;
|
||||
}
|
||||
|
||||
export const TrackKeyFrame = ({
|
||||
keyframe,
|
||||
min,
|
||||
max,
|
||||
updateKeyframe,
|
||||
}: Props) => {
|
||||
const [offset, setOffset] = useState(keyframe.offset);
|
||||
const { onPointerDown } = useMouseEventsKeyframe({
|
||||
export const TrackKeyFrame = ({ keyframe, updateKeyframe }: Props) => {
|
||||
useSignals();
|
||||
const { onPointerDown, offset } = useMouseEventsKeyframe({
|
||||
keyframe,
|
||||
max,
|
||||
min,
|
||||
max: filmLength.value * 60,
|
||||
min: 0,
|
||||
updateKeyframe,
|
||||
setOffset,
|
||||
});
|
||||
|
||||
const displayOffset = offset > -1 ? offset : keyframe.offset;
|
||||
|
||||
const selectedKeyframeId =
|
||||
(selectedItem.value as Keyframe | null)?.keyframe_uuid ?? "";
|
||||
|
||||
return (
|
||||
<button
|
||||
className={["block rotate-45 cursor-ew-resize", "absolute"].join(" ")}
|
||||
className={[
|
||||
"block rotate-45 cursor-ew-resize",
|
||||
"absolute",
|
||||
keyframe.keyframe_uuid === selectedKeyframeId
|
||||
? "bg-keyframe-selected"
|
||||
: "bg-keyframe-unselected",
|
||||
].join(" ")}
|
||||
style={{
|
||||
width: 18,
|
||||
height: 18,
|
||||
backgroundColor:
|
||||
keyframe.keyframe_uuid === selectedKeyframeId ? "#FFDE67" : "#EEEEEE",
|
||||
left: offset * 4 * scale.value - 6,
|
||||
left: displayOffset * 4 * scale.value - 6,
|
||||
top: 8,
|
||||
}}
|
||||
onPointerDown={(event) => onPointerDown(event, "drag")}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Keyframe } from "~/pages/PageEnigma/models/track";
|
||||
import { filmLength } from "~/pages/PageEnigma/store";
|
||||
import { ClipGroup, Keyframe } from "~/pages/PageEnigma/models/track";
|
||||
import { TrackKeyFrame } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrame";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
keyframes: Keyframe[];
|
||||
title: string;
|
||||
style: "character" | "audio" | "camera" | "objects";
|
||||
group: ClipGroup;
|
||||
toggleMute?: () => void;
|
||||
muted?: boolean;
|
||||
updateKeyframe: (options: { id: string; offset: number }) => void;
|
||||
updateKeyframe: (options: {
|
||||
id: string;
|
||||
offset: number;
|
||||
force?: boolean;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export const TrackKeyFrames = ({
|
||||
@@ -17,23 +20,17 @@ export const TrackKeyFrames = ({
|
||||
keyframes,
|
||||
updateKeyframe,
|
||||
title,
|
||||
style,
|
||||
group,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className="pl-16">
|
||||
<div
|
||||
id={`track-${style}-${id}`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${style}-unselected`}
|
||||
id={`track-${group}-${id}`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${group}-unselected`}
|
||||
>
|
||||
{keyframes.map((keyframe, index) => (
|
||||
{keyframes.map((keyframe) => (
|
||||
<TrackKeyFrame
|
||||
key={keyframe.keyframe_uuid}
|
||||
min={index > 0 ? keyframes[index - 1].offset + 1 : 0}
|
||||
max={
|
||||
index < keyframes.length - 1
|
||||
? keyframes[index + 1].offset
|
||||
: filmLength.value * 60
|
||||
}
|
||||
updateKeyframe={updateKeyframe}
|
||||
keyframe={keyframe}
|
||||
/>
|
||||
|
||||
@@ -37,13 +37,11 @@ export const useMouseEventsClip = (
|
||||
if (isActive.current === "drag") {
|
||||
if (deltaOffset < min) {
|
||||
currOffset.current = min;
|
||||
return;
|
||||
} else if (deltaOffset + currLength.current > max) {
|
||||
currOffset.current = max;
|
||||
} else {
|
||||
currOffset.current = deltaOffset;
|
||||
}
|
||||
if (deltaOffset + currLength.current > max) {
|
||||
currOffset.current = max - currLength.current;
|
||||
return;
|
||||
}
|
||||
currOffset.current = deltaOffset;
|
||||
}
|
||||
if (isActive.current === "left") {
|
||||
if (initLength.current - delta < 30 || deltaOffset < min) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Dispatch, useCallback, useEffect, useRef } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Keyframe } from "~/pages/PageEnigma/models/track";
|
||||
import { canDrop, scale } from "~/pages/PageEnigma/store";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
@@ -8,15 +8,14 @@ export const useMouseEventsKeyframe = ({
|
||||
max,
|
||||
min,
|
||||
updateKeyframe,
|
||||
setOffset,
|
||||
}: {
|
||||
keyframe: Keyframe;
|
||||
max: number;
|
||||
min: number;
|
||||
updateKeyframe: (args: { id: string; offset: number }) => void;
|
||||
setOffset: Dispatch<number>;
|
||||
}) => {
|
||||
useSignals();
|
||||
const [offset, setOffset] = useState(-1);
|
||||
const currOffset = useRef(keyframe.offset);
|
||||
const initOffset = useRef(keyframe.offset);
|
||||
const isActive = useRef("");
|
||||
@@ -30,8 +29,9 @@ export const useMouseEventsKeyframe = ({
|
||||
});
|
||||
isActive.current = "";
|
||||
canDrop.value = false;
|
||||
setOffset(-1);
|
||||
}
|
||||
}, [updateKeyframe, keyframe.keyframe_uuid]);
|
||||
}, [updateKeyframe, keyframe.keyframe_uuid, setOffset]);
|
||||
|
||||
const onMouseMove = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
@@ -40,15 +40,13 @@ export const useMouseEventsKeyframe = ({
|
||||
if (isActive.current === "drag") {
|
||||
if (deltaOffset < min) {
|
||||
currOffset.current = min;
|
||||
return;
|
||||
}
|
||||
if (deltaOffset > max) {
|
||||
} else if (deltaOffset > max) {
|
||||
currOffset.current = max;
|
||||
return;
|
||||
} else {
|
||||
currOffset.current = deltaOffset;
|
||||
}
|
||||
currOffset.current = deltaOffset;
|
||||
setOffset(currOffset.current);
|
||||
}
|
||||
setOffset(currOffset.current);
|
||||
},
|
||||
[max, min, setOffset],
|
||||
);
|
||||
@@ -78,7 +76,9 @@ export const useMouseEventsKeyframe = ({
|
||||
event.preventDefault();
|
||||
clientX.current = event.clientX;
|
||||
isActive.current = type;
|
||||
setOffset(keyframe.offset);
|
||||
}
|
||||
},
|
||||
offset,
|
||||
};
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
MediaClip,
|
||||
Keyframe,
|
||||
QueueKeyframe,
|
||||
ClipType,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const TrackContext = createContext<{
|
||||
@@ -15,7 +16,7 @@ export const TrackContext = createContext<{
|
||||
audioClips: MediaClip[];
|
||||
|
||||
// drag and drop
|
||||
startDrag: (type: "animations" | "audio", id: string, length: number) => void;
|
||||
startDrag: (type: ClipType, id: string, length: number) => void;
|
||||
endDrag: () => void;
|
||||
}>({
|
||||
addKeyframe: () => {},
|
||||
|
||||
@@ -48,7 +48,7 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
// cross group functions
|
||||
const dropClip = useCallback(() => {
|
||||
if (canDrop.value) {
|
||||
if (dragType.value === "animations") {
|
||||
if (dragType.value === ClipType.ANIMATION) {
|
||||
addCharacterAnimation({
|
||||
clipId: dragId.value!,
|
||||
characterId: dropId.value,
|
||||
@@ -56,7 +56,7 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
offset: dropOffset.value,
|
||||
});
|
||||
}
|
||||
if (dragType.value === "audio") {
|
||||
if (dragType.value === ClipType.AUDIO) {
|
||||
addCharacterAudio({
|
||||
clipId: dragId.value!,
|
||||
characterId: dropId.value,
|
||||
@@ -72,36 +72,14 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
}
|
||||
}
|
||||
endDrag();
|
||||
}, [animationClips, endDrag]);
|
||||
}, [animationClips, endDrag, audioClips]);
|
||||
|
||||
const fullWidth = useMemo(() => {
|
||||
return length * 60 * 4 * scale;
|
||||
}, [length, scale]);
|
||||
|
||||
useEffect(() => {
|
||||
setAnimationClips([
|
||||
{
|
||||
version: 1,
|
||||
media_id: "m_5q9s6esz8ymjqz0bheh8nf4crtj2kx",
|
||||
type: ClipType.ANIMATION,
|
||||
length: 25,
|
||||
name: "Sit",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: uuid.v4(),
|
||||
type: ClipType.ANIMATION,
|
||||
length: 25,
|
||||
name: "Idle",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: uuid.v4(),
|
||||
type: ClipType.ANIMATION,
|
||||
length: 25,
|
||||
name: "Stand",
|
||||
},
|
||||
{
|
||||
setAnimationClips([{
|
||||
version: 1,
|
||||
media_id: "m_w5t517krrf63f3tj2288vsffmj87zw",
|
||||
type: ClipType.ANIMATION,
|
||||
@@ -110,34 +88,13 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
},
|
||||
]);
|
||||
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: "m_403phjvjkbbaxxbz8y7r6qjay07mfd",
|
||||
type: ClipType.AUDIO,
|
||||
length: 25,
|
||||
name: "Talk",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: uuid.v4(),
|
||||
type: ClipType.AUDIO,
|
||||
length: 25,
|
||||
name: "Yell",
|
||||
},
|
||||
}
|
||||
]);
|
||||
setScale(1);
|
||||
setLength(12);
|
||||
|
||||
@@ -5,10 +5,11 @@ import {
|
||||
dragId,
|
||||
dragType,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { ClipType } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export default function useUpdateDragDrop() {
|
||||
const startDrag = useCallback(
|
||||
(type: "animations" | "audio", id: string, length: number) => {
|
||||
(type: ClipType, id: string, length: number) => {
|
||||
dragId.value = id;
|
||||
dragType.value = type;
|
||||
clipLength.value = length;
|
||||
|
||||
@@ -36,7 +36,6 @@ export default function useUpdateKeyframe() {
|
||||
}, []);
|
||||
|
||||
const deleteKeyframe = useCallback((keyframe: Keyframe) => {
|
||||
console.log("del", keyframe);
|
||||
DELETE_KEYFRAME[keyframe.group](keyframe);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,47 +1,49 @@
|
||||
// Clip offsets represent the state of the clip on the timeline as well as what type of clip it is.
|
||||
// it is created from a media id.
|
||||
import { ClipGroup, ClipType } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export class ClipUI {
|
||||
version: number
|
||||
type: "transform" | "audio" | "animation" | "lipsync"
|
||||
group: "character" | "object" | "camera" | "global_audio"
|
||||
name: string
|
||||
media_id: string
|
||||
object_uuid: string
|
||||
offset: number // in frames
|
||||
length: number // in frames
|
||||
version: number;
|
||||
type: ClipType;
|
||||
group: ClipGroup;
|
||||
name: string;
|
||||
media_id: string;
|
||||
object_uuid: string;
|
||||
offset: number; // in frames
|
||||
length: number; // in frames
|
||||
|
||||
constructor(
|
||||
version: number,
|
||||
type: "transform" | "audio" | "animation" | "lipsync",
|
||||
group: "character" | "object" | "camera" | "global_audio",
|
||||
type: ClipType,
|
||||
group: ClipGroup,
|
||||
name: string,
|
||||
media_id: string,
|
||||
object_uuid: string,
|
||||
offset: number,
|
||||
length: number,
|
||||
) {
|
||||
this.version = version
|
||||
this.group = group // Only needed for UI
|
||||
this.version = version;
|
||||
this.group = group; // Only needed for UI
|
||||
|
||||
this.name = name // UI
|
||||
this.type = type // UI and Animation / Audio / Lipsync / : Engine
|
||||
this.name = name; // UI
|
||||
this.type = type; // UI and Animation / Audio / Lipsync / : Engine
|
||||
|
||||
this.object_uuid = object_uuid // Animation / Audio / Lipsync / : Engine
|
||||
this.media_id = media_id // Animation / Audio / Lipsync / : Engine
|
||||
this.offset = offset
|
||||
this.length = length
|
||||
this.object_uuid = object_uuid; // Animation / Audio / Lipsync / : Engine
|
||||
this.media_id = media_id; // Animation / Audio / Lipsync / : Engine
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
return JSON.stringify({
|
||||
version: this.version,
|
||||
group:this.group,
|
||||
group: this.group,
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
object_uuid: this.object_uuid,
|
||||
media_id: this.media_id,
|
||||
start_offset: this.offset,
|
||||
ending_offset: this.length
|
||||
})
|
||||
ending_offset: this.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -54,9 +54,9 @@ export enum Visibility {
|
||||
*/
|
||||
type Data = { [key: string]: any };
|
||||
class APIManagerResponseSuccess {
|
||||
public user_message: String;
|
||||
public user_message: string;
|
||||
public data: Data | null;
|
||||
constructor(user_message: String = "", data: Data | null = null) {
|
||||
constructor(user_message: string = "", data: Data | null = null) {
|
||||
this.data = data;
|
||||
this.user_message = user_message;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class APIManagerResponseError extends Error {
|
||||
}
|
||||
|
||||
export class APIManager {
|
||||
baseUrl: String;
|
||||
baseUrl: string;
|
||||
|
||||
constructor() {
|
||||
this.baseUrl = "https://api.fakeyou.com";
|
||||
@@ -139,9 +139,9 @@ export class APIManager {
|
||||
throw new APIManagerResponseError("Failed to download file");
|
||||
}
|
||||
// Convert the response from a blob to json text
|
||||
let blob = await file_response.blob();
|
||||
const blob = await file_response.blob();
|
||||
const json_result: string = await new Promise((resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => resolve(JSON.parse(reader.result as string));
|
||||
reader.onerror = reject;
|
||||
reader.readAsText(blob);
|
||||
@@ -156,7 +156,7 @@ export class APIManager {
|
||||
scene_glb_media_file_token,
|
||||
);
|
||||
console.log(`GLB ${media_bucket_path}`);
|
||||
let glbLoader = new GLTFLoader();
|
||||
const glbLoader = new GLTFLoader();
|
||||
// promisify this
|
||||
const loadGlb = (
|
||||
bucket_path: string,
|
||||
@@ -188,13 +188,13 @@ export class APIManager {
|
||||
* @returns
|
||||
*/
|
||||
private async getMediaFile(media_file_token: string): Promise<string> {
|
||||
let api_base_url = "https://api.fakeyou.com";
|
||||
let url = `${api_base_url}/v1/media_files/file/${media_file_token}`;
|
||||
let response = await fetch(url);
|
||||
let json = await JSON.parse(await response.text());
|
||||
let bucketPath = json["media_file"]["public_bucket_path"];
|
||||
let media_base_url = "https://storage.googleapis.com/vocodes-public";
|
||||
let media_url = `${media_base_url}${bucketPath}`; // gets you a bucket path
|
||||
const api_base_url = "https://api.fakeyou.com";
|
||||
const url = `${api_base_url}/v1/media_files/file/${media_file_token}`;
|
||||
const response = await fetch(url);
|
||||
const json = await JSON.parse(await response.text());
|
||||
const bucketPath = json["media_file"]["public_bucket_path"];
|
||||
const media_base_url = "https://storage.googleapis.com/vocodes-public";
|
||||
const media_url = `${media_base_url}${bucketPath}`; // gets you a bucket path
|
||||
return media_url;
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ export class APIManager {
|
||||
scene_glb_media_file_token: string | null,
|
||||
): Promise<string> {
|
||||
const url = `${this.baseUrl}/v1/media_files/write/engine_asset`;
|
||||
let uuid = uuidv4();
|
||||
const uuid = uuidv4();
|
||||
const form_data = new FormData();
|
||||
form_data.append("uuid_idempotency_token", uuid);
|
||||
|
||||
@@ -257,7 +257,7 @@ export class APIManager {
|
||||
timeline_state: TimelineDataState | null, // only for now.
|
||||
): Promise<string> {
|
||||
const url = `${this.baseUrl}/v1/media_files/write/scene_file`;
|
||||
let uuid = uuidv4();
|
||||
const uuid = uuidv4();
|
||||
|
||||
console.log(
|
||||
`Saving Scene scene_media_file_token:${scene_media_file_token} | scene_glb_media_file_token:${scene_glb_media_file_token}`,
|
||||
@@ -311,7 +311,7 @@ export class APIManager {
|
||||
fileName: string,
|
||||
): Promise<APIManagerResponseSuccess> {
|
||||
const url = `${this.baseUrl}/v1/media_uploads/upload`;
|
||||
let uuid = uuidv4();
|
||||
const uuid = uuidv4();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("uuid_idempotency_token", uuid);
|
||||
@@ -346,11 +346,11 @@ export class APIManager {
|
||||
negative_prompt: string,
|
||||
): Promise<string> {
|
||||
const url = `https://funnel.tailce84f.ts.net/preview/`;
|
||||
|
||||
|
||||
const payload = {
|
||||
style: style,
|
||||
positive_prompt: positive_prompt,
|
||||
negative_prompt: negative_prompt
|
||||
negative_prompt: negative_prompt,
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
@@ -393,14 +393,15 @@ export class APIManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public async stylizeVideo(media_token: string,
|
||||
style: ArtStyle,
|
||||
positive_prompt:string,
|
||||
negative_prompt:string,
|
||||
visibility:Visibility) {
|
||||
let uuid = uuidv4();
|
||||
|
||||
public async stylizeVideo(
|
||||
media_token: string,
|
||||
style: ArtStyle,
|
||||
positive_prompt: string,
|
||||
negative_prompt: string,
|
||||
visibility: Visibility,
|
||||
) {
|
||||
const uuid = uuidv4();
|
||||
|
||||
const data = {
|
||||
uuid_idempotency_token: uuid,
|
||||
style: style,
|
||||
@@ -410,10 +411,10 @@ export class APIManager {
|
||||
trim_start_millis: 0,
|
||||
trim_end_millis: 3000,
|
||||
enable_lipsync: true,
|
||||
creator_set_visibility: visibility
|
||||
}
|
||||
creator_set_visibility: visibility,
|
||||
};
|
||||
|
||||
const json_data = JSON.stringify(data)
|
||||
const json_data = JSON.stringify(data);
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/v1/video/enqueue_vst`, {
|
||||
method: "POST",
|
||||
@@ -435,4 +436,3 @@ export class APIManager {
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "../Queue/QueueNames";
|
||||
import { toEngineActions } from "../Queue/toEngineActions";
|
||||
import { fromEngineActions } from "../Queue/fromEngineActions";
|
||||
|
||||
import { ClipGroup, ClipType } from "~/pages/PageEnigma/models/track";
|
||||
// Every object uuid / entity has a track.
|
||||
export class TimelineCurrentState {
|
||||
is_editable: boolean;
|
||||
@@ -160,7 +160,7 @@ export class TimeLine {
|
||||
if (new_item) {
|
||||
await this.addPlayableClip(new ClipUI(
|
||||
data_json['version'],
|
||||
"transform",
|
||||
ClipType.TRANSFORM,
|
||||
data_json['group'],
|
||||
object_name,
|
||||
"",
|
||||
@@ -171,7 +171,7 @@ export class TimeLine {
|
||||
}
|
||||
|
||||
public async addClip(data: any) {
|
||||
let object_uuid = data["data"]["object_uuid"];
|
||||
const object_uuid = data["data"]["object_uuid"];
|
||||
const media_id = data["data"]["media_id"];
|
||||
const name = data["data"]["name"];
|
||||
const group = data["data"]["group"];
|
||||
@@ -194,7 +194,7 @@ export class TimeLine {
|
||||
this.addPlayableClip(
|
||||
new ClipUI(
|
||||
version,
|
||||
"lipsync",
|
||||
"lipsync",// TODO potiential bug ..
|
||||
group,
|
||||
name,
|
||||
media_id,
|
||||
@@ -366,7 +366,7 @@ export class TimeLine {
|
||||
// element.play()
|
||||
// remove the element from the list
|
||||
const object = this.scene.get_object_by_uuid(element.object_uuid);
|
||||
if (element.type == "transform") {
|
||||
if (element.type === ClipType.TRANSFORM) {
|
||||
if (object && this.transform_engine.clips[element.object_uuid]) {
|
||||
this.transform_engine.clips[element.object_uuid].step(
|
||||
object,
|
||||
@@ -381,14 +381,14 @@ export class TimeLine {
|
||||
} else {
|
||||
this.audio_engine.playClip(element.media_id);
|
||||
}
|
||||
} else if (element.type == "lipsync") {
|
||||
} else if (element.type == "lipsync") { // we will remove this when we know which group it will come from character + audio == lip sync audio.
|
||||
if (this.scrubber_frame_position + 1 >= element.length) {
|
||||
this.lipSync_engine.clips[element.object_uuid].stop();
|
||||
} else if (object) {
|
||||
await this.lipSync_engine.clips[element.object_uuid].play(object);
|
||||
this.lipSync_engine.clips[element.object_uuid].step();
|
||||
}
|
||||
} else if (element.type == "animation") {
|
||||
} else if (element.type === ClipType.ANIMATION) {
|
||||
if (object) {
|
||||
await this.animation_engine.clips[object.uuid].play(object);
|
||||
this.animation_engine.clips[object.uuid].step(
|
||||
|
||||
@@ -38,6 +38,7 @@ export enum ClipType {
|
||||
TRANSFORM = "transform",
|
||||
AUDIO = "audio",
|
||||
ANIMATION = "animation",
|
||||
LIPSYNC = "lipsync"
|
||||
}
|
||||
|
||||
export enum ClipGroup {
|
||||
|
||||
@@ -8,11 +8,22 @@ 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";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
export const cameraGroup = signal<CameraGroup>({ id: "CG1", keyframes: [] });
|
||||
|
||||
export function updateCamera({ id, offset }: { id: string; offset: number }) {
|
||||
const oldCameraGroup = cameraGroup.value;
|
||||
|
||||
const existingKeyframe = oldCameraGroup.keyframes.some((row) => {
|
||||
return row.offset === offset && row.keyframe_uuid !== id;
|
||||
});
|
||||
|
||||
if (existingKeyframe) {
|
||||
toast.error("There can only be one keyframe at this offset.");
|
||||
return;
|
||||
}
|
||||
|
||||
const newKeyframes = [...oldCameraGroup.keyframes];
|
||||
const keyframe = newKeyframes.find((row) => row.keyframe_uuid === id);
|
||||
if (!keyframe) {
|
||||
@@ -33,7 +44,11 @@ export function updateCamera({ id, offset }: { id: string; offset: number }) {
|
||||
}
|
||||
|
||||
export function addCameraKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
console.log("cam", keyframe);
|
||||
const oldCameraGroup = cameraGroup.value;
|
||||
if (oldCameraGroup.keyframes.some((row) => row.offset === offset)) {
|
||||
toast.error("There can only be one keyframe at this offset.");
|
||||
return;
|
||||
}
|
||||
const newKeyframe = {
|
||||
version: keyframe.version,
|
||||
keyframe_uuid: uuid.v4(),
|
||||
@@ -46,7 +61,6 @@ export function addCameraKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
selected: false,
|
||||
} as Keyframe;
|
||||
|
||||
const oldCameraGroup = cameraGroup.value;
|
||||
cameraGroup.value = {
|
||||
...oldCameraGroup,
|
||||
keyframes: [...oldCameraGroup.keyframes, newKeyframe].sort(
|
||||
|
||||
@@ -28,14 +28,16 @@ export function updateCharacters({
|
||||
id,
|
||||
offset,
|
||||
length,
|
||||
force,
|
||||
}: {
|
||||
type: "animations" | "positions" | "lipSync";
|
||||
type: ClipType;
|
||||
id: string;
|
||||
length: number;
|
||||
length?: number;
|
||||
offset: number;
|
||||
force?: boolean;
|
||||
}) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
if (type === "animations") {
|
||||
if (type === ClipType.ANIMATION) {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newAnimationClips = [...character.animationClips];
|
||||
const clipIndex = newAnimationClips.findIndex(
|
||||
@@ -46,7 +48,7 @@ export function updateCharacters({
|
||||
}
|
||||
const clip = newAnimationClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length;
|
||||
clip.length = length!;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
@@ -61,7 +63,7 @@ export function updateCharacters({
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "positions") {
|
||||
if (type === ClipType.TRANSFORM) {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newPositionKeyframes = [...character.positionKeyframes];
|
||||
const keyframeIndex = newPositionKeyframes.findIndex(
|
||||
@@ -85,7 +87,7 @@ export function updateCharacters({
|
||||
};
|
||||
});
|
||||
}
|
||||
if (type === "lipSync") {
|
||||
if (type === ClipType.AUDIO) {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newLipSyncClips = [...character.lipSyncClips];
|
||||
const clipIndex = newLipSyncClips.findIndex(
|
||||
@@ -96,7 +98,7 @@ export function updateCharacters({
|
||||
}
|
||||
const clip = newLipSyncClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length;
|
||||
clip.length = length!;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
@@ -204,6 +206,18 @@ export function addCharacterAudio({
|
||||
}
|
||||
|
||||
export function addCharacterKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
|
||||
if (
|
||||
oldCharacterGroups.some((characterGroup) => {
|
||||
return characterGroup.positionKeyframes.some(
|
||||
(row) => row.offset === offset,
|
||||
);
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newKeyframe = {
|
||||
version: keyframe.version,
|
||||
keyframe_uuid: uuid.v4(),
|
||||
@@ -216,7 +230,6 @@ export function addCharacterKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
selected: false,
|
||||
} as Keyframe;
|
||||
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
if (character.id !== keyframe.object_uuid) {
|
||||
return { ...character };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { signal } from "@preact/signals-core";
|
||||
import { ClipType } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const dragType = signal<"animations" | "audio" | null>(null);
|
||||
export const dragType = signal<ClipType | null>(null);
|
||||
export const dragId = signal<string | null>(null);
|
||||
export const canDrop = signal(false);
|
||||
export const overTimeline = signal(false);
|
||||
|
||||
@@ -8,23 +8,47 @@ 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";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
export const objectGroup = signal<ObjectGroup>({
|
||||
id: "OB1",
|
||||
objects: [],
|
||||
});
|
||||
|
||||
export function updateObject({ id, offset }: { id: string; offset: number }) {
|
||||
const oldObject = objectGroup.value;
|
||||
export function updateObject({
|
||||
id,
|
||||
offset,
|
||||
}: {
|
||||
id: string;
|
||||
offset: number;
|
||||
}): void {
|
||||
const oldObjectGroup = objectGroup.value;
|
||||
const obj = oldObjectGroup.objects.find((objectTrack) =>
|
||||
objectTrack.keyframes.some((row) => row.keyframe_uuid === id),
|
||||
);
|
||||
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingKeyframe = obj.keyframes.some((row) => {
|
||||
return row.offset === offset && row.keyframe_uuid !== id;
|
||||
});
|
||||
|
||||
if (existingKeyframe) {
|
||||
toast.error("There can only be one keyframe at this offset.");
|
||||
return;
|
||||
}
|
||||
|
||||
objectGroup.value = {
|
||||
id: oldObject.id,
|
||||
objects: oldObject.objects.map((object) => ({
|
||||
id: oldObjectGroup.id,
|
||||
objects: oldObjectGroup.objects.map((object) => ({
|
||||
object_uuid: object.object_uuid,
|
||||
name: object.name,
|
||||
keyframes: object.keyframes.map((keyFrame) => {
|
||||
if (keyFrame.keyframe_uuid !== id) {
|
||||
keyframes: object.keyframes.map((keyframe) => {
|
||||
if (keyframe.keyframe_uuid !== id) {
|
||||
return {
|
||||
...keyFrame,
|
||||
...keyframe,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,13 +56,13 @@ export function updateObject({ id, offset }: { id: string; offset: number }) {
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_KEYFRAME,
|
||||
data: {
|
||||
...keyFrame,
|
||||
...keyframe,
|
||||
offset,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...keyFrame,
|
||||
...keyframe,
|
||||
offset,
|
||||
};
|
||||
}),
|
||||
@@ -52,6 +76,11 @@ export function addObjectKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
(row) => row.object_uuid === keyframe.object_uuid,
|
||||
);
|
||||
|
||||
if (obj && obj.keyframes.some((row) => row.offset === offset)) {
|
||||
toast.error("There can only be one keyframe at this offset.");
|
||||
return;
|
||||
}
|
||||
|
||||
const newObject = obj ?? {
|
||||
object_uuid: keyframe.object_uuid,
|
||||
name: keyframe.object_name ?? "unknown",
|
||||
@@ -69,7 +98,7 @@ export function addObjectKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
selected: false,
|
||||
} as Keyframe;
|
||||
newObject.keyframes.push(newKeyframe);
|
||||
console.log(newObject);
|
||||
|
||||
newObject.keyframes.sort(
|
||||
(keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset,
|
||||
);
|
||||
@@ -78,7 +107,7 @@ export function addObjectKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
...oldObjectGroup,
|
||||
objects: [
|
||||
...oldObjectGroup.objects.filter(
|
||||
(object) => object.object_uuid !== keyframe.object_uuid,
|
||||
(row) => row.object_uuid !== obj?.object_uuid,
|
||||
),
|
||||
newObject,
|
||||
].sort((objA, objB) => (objA.object_uuid < objB.object_uuid ? -1 : 1)),
|
||||
|
||||
Generated
+24
@@ -29,6 +29,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-cookie": "^7.1.4",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"three": "^0.162.0",
|
||||
"uuid": "^9.0.1",
|
||||
@@ -5950,6 +5951,14 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/goober": {
|
||||
"version": "2.1.14",
|
||||
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz",
|
||||
"integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==",
|
||||
"peerDependencies": {
|
||||
"csstype": "^3.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
@@ -10416,6 +10425,21 @@
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hot-toast": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz",
|
||||
"integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==",
|
||||
"dependencies": {
|
||||
"goober": "^2.1.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16",
|
||||
"react-dom": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-cookie": "^7.1.4",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"three": "^0.162.0",
|
||||
"uuid": "^9.0.1",
|
||||
|
||||
+28
-24
@@ -50,17 +50,17 @@ export default {
|
||||
"brand-tertiary": {
|
||||
//Aquamarine Blue
|
||||
DEFAULT: "#1cb6be", //=300
|
||||
'50': '#eefdfc',
|
||||
'100': '#d4f9f8',
|
||||
'200': '#aef3f3',
|
||||
'300': '#62e4e6',
|
||||
'400': '#38d3d8',
|
||||
'500': '#1cb6be',
|
||||
'600': '#1a94a0',
|
||||
'700': '#1c7682',
|
||||
'800': '#1f616b',
|
||||
'900': '#1e505b',
|
||||
'950': '#0e353e',
|
||||
"50": "#eefdfc",
|
||||
"100": "#d4f9f8",
|
||||
"200": "#aef3f3",
|
||||
"300": "#62e4e6",
|
||||
"400": "#38d3d8",
|
||||
"500": "#1cb6be",
|
||||
"600": "#1a94a0",
|
||||
"700": "#1c7682",
|
||||
"800": "#1f616b",
|
||||
"900": "#1e505b",
|
||||
"950": "#0e353e",
|
||||
},
|
||||
danger: {
|
||||
//copper rust
|
||||
@@ -106,18 +106,22 @@ export default {
|
||||
clip: "#5F949F",
|
||||
groupBg: "#2B393E",
|
||||
},
|
||||
audio: {
|
||||
global_audio: {
|
||||
unselected: "#864C68",
|
||||
selected: "#D460A6",
|
||||
clip: "#E37BAD",
|
||||
groupBg: "#382940",
|
||||
},
|
||||
objects: {
|
||||
object: {
|
||||
unselected: "#7C5646",
|
||||
selected: "#EA8E5A",
|
||||
clip: "#D49D75",
|
||||
groupBg: "#372E32",
|
||||
},
|
||||
keyframe: {
|
||||
unselected: "#EEEEEE",
|
||||
selected: "#FFDE67",
|
||||
},
|
||||
|
||||
"ui-background": "#1a1a27",
|
||||
"ui-panel": "#242433",
|
||||
@@ -126,11 +130,11 @@ export default {
|
||||
"ui-controls-button": "#676781",
|
||||
"ui-divider": "#515168",
|
||||
|
||||
"axis-x" :"#D33242",
|
||||
"axis-y" :"#308752",
|
||||
"axis-z" :"#2E70FF",
|
||||
red: "#D33242",
|
||||
green:"#308752",
|
||||
"axis-x": "#D33242",
|
||||
"axis-y": "#308752",
|
||||
"axis-z": "#2E70FF",
|
||||
red: "#D33242",
|
||||
green: "#308752",
|
||||
blue: "#2E70FF",
|
||||
|
||||
white: colors.white,
|
||||
@@ -146,11 +150,11 @@ export default {
|
||||
"bg-camera-selected",
|
||||
"bg-camera-unselected",
|
||||
"bg-camera-clip",
|
||||
"bg-audio-selected",
|
||||
"bg-audio-unselected",
|
||||
"bg-audio-clip",
|
||||
"bg-objects-selected",
|
||||
"bg-objects-unselected",
|
||||
"bg-objects-clip",
|
||||
"bg-global_audio-selected",
|
||||
"bg-global_audio-unselected",
|
||||
"bg-global_audio-clip",
|
||||
"bg-object-selected",
|
||||
"bg-object-unselected",
|
||||
"bg-object-clip",
|
||||
],
|
||||
} satisfies Config;
|
||||
Reference in new issue
Block a user