mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-08 02:52:12 +00:00
Keyframe and speed ups.
This commit is contained in:
1 parent
c7849f9a3e
commit
9347782812
27 files changed
+989
-1143
No files matched your search
@@ -22,21 +22,12 @@ import {
|
||||
appUiInitialStateValues,
|
||||
APPUI_VIEW_MODES,
|
||||
} from "./reducers";
|
||||
import {
|
||||
canDrop,
|
||||
clipLength,
|
||||
currPosition,
|
||||
dragId,
|
||||
overTimeline,
|
||||
scale,
|
||||
timelineHeight,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { timelineHeight } from "~/pages/PageEnigma/store";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { DragComponent } from "~/pages/PageEnigma/comps/DragComponent/DragComponent";
|
||||
|
||||
export const PageEnigmaComponent = () => {
|
||||
useSignals();
|
||||
const { currX, currY } = currPosition.value;
|
||||
|
||||
const [appUiState, dispatchAppUiState] = useReducer(
|
||||
appUiReducer,
|
||||
appUiInitialStateValues,
|
||||
@@ -129,22 +120,7 @@ export const PageEnigmaComponent = () => {
|
||||
</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,
|
||||
}}
|
||||
/>
|
||||
<DragComponent />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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,6 +16,7 @@ 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);
|
||||
|
||||
@@ -24,24 +25,21 @@ export const ControlPanelSceneObject = () => {
|
||||
const scale = appUiState?.currentSceneObject.objectVectors.scale;
|
||||
const currentSceneObject = appUiState?.currentSceneObject;
|
||||
|
||||
|
||||
const handlePositionChange = (xyz: XYZ) => {
|
||||
if (appUiState) {
|
||||
|
||||
|
||||
if (currentSceneObject == null) {
|
||||
console.log("Missing Scene Object Position");
|
||||
return;
|
||||
console.log("Missing Scene Object Position");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
currentSceneObject: {
|
||||
group:currentSceneObject.group,
|
||||
object_uuid:currentSceneObject.object_uuid,
|
||||
object_name:currentSceneObject.object_name,
|
||||
version:currentSceneObject.version,
|
||||
group: currentSceneObject.group,
|
||||
object_uuid: currentSceneObject.object_uuid,
|
||||
object_name: currentSceneObject.object_name,
|
||||
version: currentSceneObject.version,
|
||||
objectVectors: {
|
||||
position: { ...xyz },
|
||||
rotation: appUiState.currentSceneObject.objectVectors.rotation,
|
||||
@@ -54,20 +52,19 @@ export const ControlPanelSceneObject = () => {
|
||||
};
|
||||
const handleRotationChange = (xyz: XYZ) => {
|
||||
if (appUiState) {
|
||||
|
||||
if (currentSceneObject == null) {
|
||||
console.log("Missing Scene Object Rotation");
|
||||
return;
|
||||
}
|
||||
if (currentSceneObject == null) {
|
||||
console.log("Missing Scene Object Rotation");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
currentSceneObject: {
|
||||
group:currentSceneObject.group,
|
||||
object_uuid:currentSceneObject.object_uuid,
|
||||
object_name:currentSceneObject.object_name,
|
||||
version:currentSceneObject.version,
|
||||
group: currentSceneObject.group,
|
||||
object_uuid: currentSceneObject.object_uuid,
|
||||
object_name: currentSceneObject.object_name,
|
||||
version: currentSceneObject.version,
|
||||
objectVectors: {
|
||||
position: appUiState.currentSceneObject.objectVectors.position,
|
||||
rotation: { ...xyz },
|
||||
@@ -80,19 +77,18 @@ export const ControlPanelSceneObject = () => {
|
||||
};
|
||||
const handleScaleChange = (xyz: XYZ) => {
|
||||
if (appUiState) {
|
||||
|
||||
if (currentSceneObject == null) {
|
||||
console.log("Missing Scene Object Scale");
|
||||
return;
|
||||
console.log("Missing Scene Object Scale");
|
||||
return;
|
||||
}
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload: {
|
||||
currentSceneObject: {
|
||||
group:currentSceneObject.group,
|
||||
object_uuid:currentSceneObject.object_uuid,
|
||||
object_name:currentSceneObject.object_name,
|
||||
version:currentSceneObject.version,
|
||||
group: currentSceneObject.group,
|
||||
object_uuid: currentSceneObject.object_uuid,
|
||||
object_name: currentSceneObject.object_name,
|
||||
version: currentSceneObject.version,
|
||||
objectVectors: {
|
||||
position: appUiState.currentSceneObject.objectVectors.position,
|
||||
rotation: appUiState.currentSceneObject.objectVectors.rotation,
|
||||
@@ -101,52 +97,53 @@ export const ControlPanelSceneObject = () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnAddKeyFrame = () => {
|
||||
if (appUiState) {
|
||||
if (
|
||||
position == null ||
|
||||
rotation == null ||
|
||||
scale == null ||
|
||||
currentSceneObject == null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (position == null ||
|
||||
rotation == null ||
|
||||
scale == null ||
|
||||
currentSceneObject == null)
|
||||
{ return; }
|
||||
// console.log(`Position ${position.x}`)
|
||||
// console.log(`Position ${position.y}`)
|
||||
// console.log(`Position ${position.z}`)
|
||||
|
||||
// console.log(`Position ${position.x}`)
|
||||
// console.log(`Position ${position.y}`)
|
||||
// console.log(`Position ${position.z}`)
|
||||
// console.log(`Rotation ${rotation.x}`)
|
||||
// console.log(`Rotation ${rotation.y}`)
|
||||
// console.log(`Rotation ${rotation.z}`)
|
||||
|
||||
// console.log(`Rotation ${rotation.x}`)
|
||||
// console.log(`Rotation ${rotation.y}`)
|
||||
// console.log(`Rotation ${rotation.z}`)
|
||||
|
||||
// console.log(`Scale ${scale.x}`)
|
||||
// console.log(`Scale ${scale.y}`)
|
||||
// console.log(`Scale ${scale.z}`)
|
||||
// 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}`)
|
||||
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: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
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
position: currentSceneObject.objectVectors.position,
|
||||
rotation: currentSceneObject.objectVectors.rotation,
|
||||
scale: currentSceneObject.objectVectors.scale,
|
||||
} as QueueKeyframe,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition
|
||||
@@ -203,9 +200,11 @@ export const ControlPanelSceneObject = () => {
|
||||
|
||||
<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} />
|
||||
|
||||
@@ -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,29 @@
|
||||
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";
|
||||
|
||||
export const Scrubber = () => {
|
||||
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,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 { 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 { keyframes } = camera!;
|
||||
const { keyframes } = cameraGroup.value!;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -15,7 +12,7 @@ export const Camera = () => {
|
||||
<div className="mb-5 pt-2 text-xs font-medium text-white">Camera</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<TrackKeyFrames
|
||||
id={camera!.id}
|
||||
id={cameraGroup.value.id}
|
||||
keyframes={keyframes}
|
||||
title="Camera Position/Rotation"
|
||||
style="camera"
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
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(
|
||||
@@ -36,16 +40,14 @@ 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, toggleLipSyncMute]);
|
||||
}, [character?.id]);
|
||||
|
||||
if (!character) {
|
||||
return false;
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+3
-7
@@ -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,7 +10,7 @@ export const Objects = () => {
|
||||
<div className="prevent-select mb-5 pt-2 text-xs font-medium text-white">
|
||||
Objects
|
||||
</div>
|
||||
{objects.objects.map((object) => (
|
||||
{objectGroup.value.objects.map((object) => (
|
||||
<div key={object.object_uuid} className="flex flex-col gap-4">
|
||||
<TrackKeyFrames
|
||||
id={object.object_uuid}
|
||||
@@ -1,42 +1,30 @@
|
||||
import { Fragment, useCallback, useContext, useEffect, useState } from "react";
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
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";
|
||||
|
||||
export const Timeline = () => {
|
||||
const {
|
||||
characters,
|
||||
objects,
|
||||
selectedItem,
|
||||
deleteCharacterClip,
|
||||
deleteAudioClip,
|
||||
deleteCameraClip,
|
||||
} = useContext(TrackContext);
|
||||
const { onPointerDown, time } = useMouseEventsAnimation();
|
||||
useSignals();
|
||||
const { selectedItem } = useContext(TrackContext);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
|
||||
// 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;
|
||||
}, []);
|
||||
@@ -50,13 +38,10 @@ export const Timeline = () => {
|
||||
[selectedItem],
|
||||
);
|
||||
|
||||
const displayTime = time === -1 ? currentTime.value : time;
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
deleteCharacterClip(selectedItem!);
|
||||
deleteCameraClip(selectedItem!);
|
||||
deleteAudioClip(selectedItem!);
|
||||
}, [selectedItem, deleteAudioClip, deleteCharacterClip, deleteCameraClip]);
|
||||
}, [selectedItem]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("keydown", onDeleteAsk);
|
||||
@@ -66,59 +51,13 @@ export const Timeline = () => {
|
||||
};
|
||||
}, [onDeleteAsk]);
|
||||
|
||||
console.log("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,31 +65,8 @@ 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"
|
||||
|
||||
@@ -116,19 +116,19 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => {
|
||||
onPointerDown={(event) => onPointerDown(event, "right")}
|
||||
onClick={() => selectItem(clip.clip_uuid)}
|
||||
/>
|
||||
{selectedItem === clip.clip_uuid && (
|
||||
<button
|
||||
className="absolute flex h-full items-center justify-center"
|
||||
style={{
|
||||
width: length * 4 * scale.value,
|
||||
left: offset * 4 * scale.value,
|
||||
zIndex: 2000,
|
||||
}}
|
||||
onPointerDown={onPlayClick}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlay} />
|
||||
</button>
|
||||
)}
|
||||
{/*{selectedItem === clip.clip_uuid && (*/}
|
||||
{/* <button*/}
|
||||
{/* className="absolute flex h-full items-center justify-center"*/}
|
||||
{/* style={{*/}
|
||||
{/* width: length * 4 * scale.value,*/}
|
||||
{/* left: offset * 4 * scale.value,*/}
|
||||
{/* zIndex: 2000,*/}
|
||||
{/* }}*/}
|
||||
{/* onPointerDown={onPlayClick}*/}
|
||||
{/* >*/}
|
||||
{/* <FontAwesomeIcon icon={faPlay} />*/}
|
||||
{/* </button>*/}
|
||||
{/*)}*/}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -23,7 +23,7 @@ export function useQueueHandler() {
|
||||
const { addKeyframe } = useContext(TrackContext);
|
||||
|
||||
const handleFromEngineActions = useCallback(({ action, data }: Arguments) => {
|
||||
console.log(`FROM ENGINE:${action} ${data}`);
|
||||
console.log("FROM ENGINE", action, data);
|
||||
switch (action) {
|
||||
case fromEngineActions.UPDATE_TIME:
|
||||
currentTime.value = (data as UpdateTime).currentTime;
|
||||
@@ -34,10 +34,11 @@ export function useQueueHandler() {
|
||||
}, []);
|
||||
|
||||
const handleToTimelineActions = useCallback(({ action, data }: Arguments) => {
|
||||
console.log(`FROM ENGINE:${action} ${data}`);
|
||||
console.log("TO TIMELINE", action, data);
|
||||
switch (action) {
|
||||
case toTimelineActions.ADD_KEYFRAME:
|
||||
addKeyframe(data as QueueKeyframe);
|
||||
console.log(1);
|
||||
addKeyframe(data as QueueKeyframe, currentTime.value);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown action ${action}`);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -10,63 +10,14 @@ import {
|
||||
} 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; offset: number }) => void;
|
||||
selectCameraKeyframe: (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;
|
||||
// keyframes
|
||||
addKeyframe: (keyframe: QueueKeyframe, offset: number) => void;
|
||||
deleteKeyframe: (keyframe: Keyframe) => void;
|
||||
|
||||
// current - only select one item - will be replaced
|
||||
selectedItem: string | null;
|
||||
selectItem: (itemId: string) => void;
|
||||
|
||||
// keyframes
|
||||
addKeyframe: (keyframe: QueueKeyframe, offset: number) => void;
|
||||
deleteKeyframe: (keyframe: Keyframe) => void;
|
||||
|
||||
// sidebar clips
|
||||
animationClips: MediaClip[];
|
||||
audioClips: MediaClip[];
|
||||
@@ -79,28 +30,6 @@ export const TrackContext = createContext<{
|
||||
) => void;
|
||||
endDrag: () => void;
|
||||
}>({
|
||||
characters: [],
|
||||
updateCharacters: () => {},
|
||||
toggleLipSyncMute: () => {},
|
||||
addCharacterAnimation: () => {},
|
||||
addCharacterAudio: () => {},
|
||||
selectCharacterClip: () => {},
|
||||
deleteCharacterClip: () => {},
|
||||
|
||||
camera: null,
|
||||
updateCamera: () => {},
|
||||
selectCameraKeyframe: () => {},
|
||||
|
||||
audio: null,
|
||||
updateAudio: () => {},
|
||||
toggleAudioMute: () => {},
|
||||
selectAudioClip: () => {},
|
||||
addGlobalAudio: () => {},
|
||||
deleteAudioClip: () => {},
|
||||
|
||||
objects: { id: "", objects: [] },
|
||||
updateObject: () => {},
|
||||
|
||||
addKeyframe: () => {},
|
||||
deleteKeyframe: () => {},
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
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 useUpdateDragDrop from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateDragDrop";
|
||||
import {
|
||||
addCharacterAnimation,
|
||||
canDrop,
|
||||
dragId,
|
||||
dragType,
|
||||
@@ -21,16 +18,6 @@ interface Props {
|
||||
}
|
||||
|
||||
export const TrackProvider = ({ children }: Props) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { addCharacterKeyframe, deleteCharacterKeyframe, ...characters } =
|
||||
useUpdateCharacters();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { addCameraKeyframe, deleteCameraKeyframe, ...camera } =
|
||||
useUpdateCamera();
|
||||
const audio = useUpdateAudio();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { addObjectKeyframe, deleteObjectKeyframe, ...objects } =
|
||||
useUpdateObject();
|
||||
const keyframes = useUpdateKeyframe();
|
||||
|
||||
const { endDrag, ...dragDrop } = useUpdateDragDrop();
|
||||
@@ -65,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,
|
||||
@@ -74,7 +61,7 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
}
|
||||
}
|
||||
endDrag();
|
||||
}, [animationClips, characters, endDrag]);
|
||||
}, [animationClips, endDrag]);
|
||||
|
||||
const fullWidth = useMemo(() => {
|
||||
return length * 60 * 4 * scale;
|
||||
@@ -118,11 +105,6 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
|
||||
const values = useMemo(() => {
|
||||
return {
|
||||
...characters,
|
||||
...camera,
|
||||
...audio,
|
||||
...objects,
|
||||
|
||||
...keyframes,
|
||||
|
||||
selectItem,
|
||||
@@ -143,11 +125,6 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
setTimelineHeight,
|
||||
};
|
||||
}, [
|
||||
characters,
|
||||
camera,
|
||||
audio,
|
||||
objects,
|
||||
|
||||
keyframes,
|
||||
|
||||
selectItem,
|
||||
|
||||
@@ -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,122 +0,0 @@
|
||||
import { useCallback, useState } from "react";
|
||||
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";
|
||||
|
||||
export default function useUpdateCamera() {
|
||||
const [camera, setCamera] = useState<CameraGroup>({
|
||||
id: "CA1",
|
||||
keyframes: [],
|
||||
});
|
||||
|
||||
const updateCamera = useCallback(
|
||||
({ id, offset }: { id: string; offset: number }) => {
|
||||
setCamera((oldCamera) => {
|
||||
const newKeyframes = [...oldCamera.keyframes];
|
||||
const keyframe = newKeyframes.find((row) => row.keyframe_uuid === id);
|
||||
if (!keyframe) {
|
||||
return { ...oldCamera };
|
||||
}
|
||||
keyframe.offset = offset;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_KEYFRAME,
|
||||
data: keyframe,
|
||||
});
|
||||
|
||||
return {
|
||||
...oldCamera,
|
||||
clips: newKeyframes,
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const addCameraKeyframe = useCallback(
|
||||
(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;
|
||||
|
||||
setCamera((oldCamera) => {
|
||||
return {
|
||||
...oldCamera,
|
||||
keyframes: [...oldCamera.keyframes, newKeyframe].sort(
|
||||
(keyFrameA, keyframeB) => keyFrameA.offset - keyframeB.offset,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_KEYFRAME,
|
||||
data: newKeyframe,
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const selectCameraKeyframe = useCallback((keyframeId: string) => {
|
||||
setCamera((oldCamera) => {
|
||||
return {
|
||||
...oldCamera,
|
||||
clips: [
|
||||
...oldCamera.keyframes.map((keyframe) => {
|
||||
return {
|
||||
...keyframe,
|
||||
selected:
|
||||
keyframe.keyframe_uuid === keyframeId
|
||||
? !keyframe.selected
|
||||
: keyframe.selected,
|
||||
};
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const deleteCameraKeyframe = useCallback((keyframeId: string) => {
|
||||
setCamera((oldCamera) => {
|
||||
return {
|
||||
...oldCamera,
|
||||
keyframes: [
|
||||
...oldCamera.keyframes.filter((keyframe) => {
|
||||
if (keyframe.keyframe_uuid === keyframeId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_KEYFRAME,
|
||||
data: keyframe,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
camera,
|
||||
updateCamera,
|
||||
selectCameraKeyframe,
|
||||
addCameraKeyframe,
|
||||
deleteCameraKeyframe,
|
||||
};
|
||||
}
|
||||
@@ -1,383 +0,0 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import {
|
||||
Clip,
|
||||
CharacterGroup,
|
||||
ClipGroup,
|
||||
ClipType,
|
||||
MediaClip,
|
||||
QueueKeyframe,
|
||||
Keyframe,
|
||||
} 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: [],
|
||||
positionKeyframes: [],
|
||||
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 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,
|
||||
};
|
||||
});
|
||||
});
|
||||
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 addCharacterKeyframe = useCallback(
|
||||
(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;
|
||||
|
||||
setCharacters((oldCharacters) => {
|
||||
return oldCharacters.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,
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
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.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,
|
||||
})),
|
||||
})),
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
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;
|
||||
}),
|
||||
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;
|
||||
}),
|
||||
})),
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
const deleteCharacterKeyframe = useCallback((keyframe: Keyframe) => {
|
||||
setCharacters((oldCharacters) => {
|
||||
return [
|
||||
...oldCharacters.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;
|
||||
}),
|
||||
})),
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
characters,
|
||||
updateCharacters,
|
||||
toggleLipSyncMute,
|
||||
addCharacterAnimation,
|
||||
addCharacterAudio,
|
||||
selectCharacterClip,
|
||||
deleteCharacterClip,
|
||||
addCharacterKeyframe,
|
||||
deleteCharacterKeyframe,
|
||||
};
|
||||
}
|
||||
@@ -4,37 +4,33 @@ import {
|
||||
QueueKeyframe,
|
||||
Keyframe,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import useUpdateObject from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject";
|
||||
import useUpdateCharacters from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters";
|
||||
import {
|
||||
addCharacterKeyframe,
|
||||
addObjectKeyframe,
|
||||
deleteCharacterKeyframe,
|
||||
deleteObjectKeyframe,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
|
||||
export default function useUpdateKeyframe() {
|
||||
const { addObjectKeyframe, deleteObjectKeyframe } = useUpdateObject();
|
||||
const { addCharacterKeyframe, deleteCharacterKeyframe } =
|
||||
useUpdateCharacters();
|
||||
const addKeyframe = useCallback((keyframe: QueueKeyframe, offset: number) => {
|
||||
console.log(keyframe, offset);
|
||||
if (keyframe.group === ClipGroup.OBJECT) {
|
||||
console.log(2);
|
||||
addObjectKeyframe(keyframe, offset);
|
||||
}
|
||||
if (keyframe.group === ClipGroup.CHARACTER) {
|
||||
addCharacterKeyframe(keyframe, offset);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const addKeyframe = useCallback(
|
||||
(keyframe: QueueKeyframe, offset: number) => {
|
||||
if (keyframe.group === ClipGroup.OBJECT) {
|
||||
addObjectKeyframe(keyframe, offset);
|
||||
}
|
||||
if (keyframe.group === ClipGroup.CHARACTER) {
|
||||
addCharacterKeyframe(keyframe, offset);
|
||||
}
|
||||
},
|
||||
[addCharacterKeyframe, addObjectKeyframe],
|
||||
);
|
||||
|
||||
const deleteKeyframe = useCallback(
|
||||
(keyframe: Keyframe) => {
|
||||
if (keyframe.group === ClipGroup.OBJECT) {
|
||||
deleteObjectKeyframe(keyframe);
|
||||
}
|
||||
if (keyframe.group === ClipGroup.CHARACTER) {
|
||||
deleteCharacterKeyframe(keyframe);
|
||||
}
|
||||
},
|
||||
[deleteCharacterKeyframe, deleteObjectKeyframe],
|
||||
);
|
||||
const deleteKeyframe = useCallback((keyframe: Keyframe) => {
|
||||
if (keyframe.group === ClipGroup.OBJECT) {
|
||||
deleteObjectKeyframe(keyframe);
|
||||
}
|
||||
if (keyframe.group === ClipGroup.CHARACTER) {
|
||||
deleteCharacterKeyframe(keyframe);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
addKeyframe,
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import {
|
||||
ObjectGroup,
|
||||
QueueKeyframe,
|
||||
Keyframe,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
export default function useUpdateObject() {
|
||||
const [objects, setObjects] = useState<ObjectGroup>({
|
||||
id: "OB1",
|
||||
objects: [],
|
||||
});
|
||||
|
||||
const updateObject = useCallback(
|
||||
({ id, offset }: { id: string; offset: number }) => {
|
||||
setObjects((oldObject) => {
|
||||
return {
|
||||
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,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const addObjectKeyframe = useCallback(
|
||||
(keyframe: QueueKeyframe, offset: number) => {
|
||||
setObjects((oldObjectGroup) => {
|
||||
const obj = objects.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[],
|
||||
};
|
||||
newObject.keyframes.push({
|
||||
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.sort(
|
||||
(keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset,
|
||||
);
|
||||
|
||||
return {
|
||||
...oldObjectGroup,
|
||||
objects: [
|
||||
...oldObjectGroup.objects.filter(
|
||||
(object) => object.object_uuid !== keyframe.object_uuid,
|
||||
),
|
||||
newObject,
|
||||
].sort((objA, objB) =>
|
||||
objA.object_uuid < objB.object_uuid ? -1 : 1,
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
[objects],
|
||||
);
|
||||
|
||||
const deleteObjectKeyframe = useCallback((keyframe: Keyframe) => {
|
||||
setObjects((oldObjectGroup) => {
|
||||
const obj = oldObjectGroup.objects.find(
|
||||
(row) => row.object_uuid === keyframe.object_uuid,
|
||||
);
|
||||
if (!obj) {
|
||||
return oldObjectGroup;
|
||||
}
|
||||
|
||||
const newKeyframes = [
|
||||
...obj.keyframes.filter(
|
||||
(row) => row.keyframe_uuid !== keyframe.keyframe_uuid,
|
||||
),
|
||||
];
|
||||
|
||||
if (newKeyframes.length) {
|
||||
return {
|
||||
...oldObjectGroup,
|
||||
objects: [
|
||||
...oldObjectGroup.objects.map((object) => ({
|
||||
...object,
|
||||
...(object.object_uuid === keyframe.object_uuid
|
||||
? { keyframes: newKeyframes }
|
||||
: {}),
|
||||
})),
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
...oldObjectGroup,
|
||||
objects: [
|
||||
...oldObjectGroup.objects.filter(
|
||||
(object) => object.object_uuid !== keyframe.object_uuid,
|
||||
),
|
||||
],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
objects,
|
||||
updateObject,
|
||||
addObjectKeyframe,
|
||||
deleteObjectKeyframe,
|
||||
};
|
||||
}
|
||||
@@ -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(clipId: string) {
|
||||
const oldAudioGroup = audioGroup.value;
|
||||
audioGroup.value = {
|
||||
...oldAudioGroup,
|
||||
clips: [
|
||||
...oldAudioGroup.clips.filter((clip) => {
|
||||
if (clip.clip_uuid === clipId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: clip!,
|
||||
});
|
||||
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(keyframeId: string) {
|
||||
const oldCameraGroup = cameraGroup.value;
|
||||
cameraGroup.value = {
|
||||
...oldCameraGroup,
|
||||
keyframes: [
|
||||
...oldCameraGroup.keyframes.filter((keyframe) => {
|
||||
if (keyframe.keyframe_uuid === keyframeId) {
|
||||
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(clipId: string) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.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;
|
||||
}),
|
||||
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;
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
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,107 @@
|
||||
import { signal } from "@preact/signals-core";
|
||||
import {
|
||||
Keyframe,
|
||||
ObjectGroup,
|
||||
QueueKeyframe,
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
import { useCallback } from "react";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
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) {
|
||||
console.log(keyframe, offset);
|
||||
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[],
|
||||
};
|
||||
newObject.keyframes.push({
|
||||
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);
|
||||
console.log(newObject);
|
||||
newObject.keyframes.sort(
|
||||
(keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset,
|
||||
);
|
||||
|
||||
console.log(4);
|
||||
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)),
|
||||
};
|
||||
console.log("T", objectGroup.value);
|
||||
}
|
||||
|
||||
export function deleteObjectKeyframe(keyframe: Keyframe) {
|
||||
const oldObjectGroup = objectGroup.value;
|
||||
const obj = oldObjectGroup.objects.find(
|
||||
(row) => row.object_uuid === keyframe.object_uuid,
|
||||
);
|
||||
if (!obj) {
|
||||
return oldObjectGroup;
|
||||
}
|
||||
|
||||
const newKeyframes = [
|
||||
...obj.keyframes.filter(
|
||||
(row) => row.keyframe_uuid !== keyframe.keyframe_uuid,
|
||||
),
|
||||
];
|
||||
|
||||
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,
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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