mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 17:58:55 +00:00
Refactor TrackContext, add drop of animation clip
This commit is contained in:
1 parent
141bb0c417
commit
e36ec9317f
18 files changed
+401
-116
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useContext, useState } from "react";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
|
||||
interface LowerPanelPropsI {
|
||||
timelineHeight: number;
|
||||
@@ -7,6 +8,7 @@ interface LowerPanelPropsI {
|
||||
|
||||
export const LowerPanel = ({ children, timelineHeight }: LowerPanelPropsI) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { setOverTimeline } = useContext(TrackContext);
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
@@ -20,6 +22,11 @@ export const LowerPanel = ({ children, timelineHeight }: LowerPanelPropsI) => {
|
||||
"bg-ui-panel",
|
||||
].join(" ")}
|
||||
style={{ height: timelineHeight }}
|
||||
onPointerOver={() => {
|
||||
console.log("over");
|
||||
setOverTimeline(true);
|
||||
}}
|
||||
onPointerLeave={() => setOverTimeline(false)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { BaseClip } from "~/models/track";
|
||||
import { BaseClip } from "~/pages/PageEnigma/models/track";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
|
||||
interface Props {
|
||||
@@ -8,8 +8,16 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ClipElement = ({ clip, type }: Props) => {
|
||||
const { startDrag, dragId, endDrag, scale, canDrop, setCanDrop } =
|
||||
useContext(TrackContext);
|
||||
const {
|
||||
startDrag,
|
||||
dragId,
|
||||
endDrag,
|
||||
scale,
|
||||
canDrop,
|
||||
setCanDrop,
|
||||
overTimeline,
|
||||
dropId,
|
||||
} = useContext(TrackContext);
|
||||
const [initPosition, setInitPosition] = useState<{
|
||||
initX: number;
|
||||
initY: number;
|
||||
@@ -36,6 +44,8 @@ export const ClipElement = ({ clip, type }: Props) => {
|
||||
|
||||
const onMouseMove = (event: MouseEvent) => {
|
||||
if (dragId) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
const deltaX = event.clientX - initX;
|
||||
const deltaY = event.clientY - initY;
|
||||
setCurrPosition({ x: deltaX, y: deltaY });
|
||||
@@ -55,20 +65,24 @@ export const ClipElement = ({ clip, type }: Props) => {
|
||||
const onPointerDown = useCallback(
|
||||
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (event.button === 0) {
|
||||
const box = document.getElementById(`ani-clip-${clip.id}`);
|
||||
const position = box!.getBoundingClientRect();
|
||||
|
||||
startDrag(type, clip.id);
|
||||
setCurrPosition({
|
||||
x: 0,
|
||||
y: 0,
|
||||
x: position.x,
|
||||
y: position.y + position.height + 1,
|
||||
});
|
||||
setInitPosition({
|
||||
initX: position.x,
|
||||
initY: position.y + position.height + 1,
|
||||
});
|
||||
setInitPosition({ initY: event.clientY, initX: event.clientX });
|
||||
setCanDrop(false);
|
||||
}
|
||||
},
|
||||
[type, clip.id, startDrag, setCanDrop],
|
||||
);
|
||||
|
||||
// console.log("clip", canDrop);
|
||||
|
||||
return (
|
||||
<div key={clip.id} className="relative h-16 w-16">
|
||||
<div
|
||||
@@ -82,16 +96,17 @@ export const ClipElement = ({ clip, type }: Props) => {
|
||||
<div
|
||||
id={`ani-dnd-${clip.id}`}
|
||||
className={[
|
||||
"absolute h-8 p-2",
|
||||
"absolute p-2",
|
||||
"rounded-lg",
|
||||
!canDrop ? "bg-brand-primary" : "bg-brand-secondary-700",
|
||||
dragId ? "block" : "hidden",
|
||||
].join(" ")}
|
||||
style={{
|
||||
top: y,
|
||||
left: x,
|
||||
top: overTimeline ? y + 24 : y,
|
||||
left: overTimeline ? x : x,
|
||||
zIndex: 5000,
|
||||
width: clip.length * 4 * scale,
|
||||
width: overTimeline ? clip.length * 4 * scale : 64,
|
||||
height: overTimeline ? 32 : 64,
|
||||
}}
|
||||
onPointerDown={onPointerDown}
|
||||
>
|
||||
|
||||
@@ -17,11 +17,12 @@ export const Audio = () => {
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Track
|
||||
id={audio!.id}
|
||||
clips={clips}
|
||||
title="Global Audio Track"
|
||||
updateClip={updateAudio}
|
||||
style="audio"
|
||||
muted={audio?.muted}
|
||||
muted={audio!.muted}
|
||||
toggleMute={toggleAudioMute}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ export const Camera = () => {
|
||||
<div className="mb-5 pt-2 text-xs font-medium text-white">Camera</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Track
|
||||
id={camera!.id}
|
||||
clips={clips}
|
||||
title="Camera Position/Rotation"
|
||||
style="camera"
|
||||
|
||||
@@ -63,6 +63,7 @@ export const Character = ({ characterId }: Props) => {
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Track
|
||||
id={character.id}
|
||||
clips={animationClips}
|
||||
title="Animation"
|
||||
updateClip={updateClipAnimations}
|
||||
@@ -70,6 +71,7 @@ export const Character = ({ characterId }: Props) => {
|
||||
type="animations"
|
||||
/>
|
||||
<Track
|
||||
id={character.id}
|
||||
clips={positionClips}
|
||||
title="Character Position/Rotation"
|
||||
updateClip={updateClipPosition}
|
||||
@@ -77,6 +79,7 @@ export const Character = ({ characterId }: Props) => {
|
||||
type="positions"
|
||||
/>
|
||||
<Track
|
||||
id={character.id}
|
||||
clips={lipSyncClips}
|
||||
title="Lipsync Audio Track"
|
||||
updateClip={updateClipLipSync}
|
||||
|
||||
@@ -16,6 +16,7 @@ export const Objects = () => {
|
||||
{objects.objects.map((object) => (
|
||||
<div key={object.id} className="flex flex-col gap-2">
|
||||
<Track
|
||||
id={object.id}
|
||||
clips={object.clips}
|
||||
title="Mask Position/Rotation"
|
||||
updateClip={updateObject}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { TrackClip } from "~/pages/PageEnigma/comps/Timeline/TrackClip";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faVolume, faVolumeSlash } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { BaseClip } from "~/models/track";
|
||||
import { useContext } from "react";
|
||||
import { BaseClip } from "~/pages/PageEnigma/models/track";
|
||||
import { PointerEvent, useContext } from "react";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
clips: BaseClip[];
|
||||
title: string;
|
||||
style: "character" | "audio" | "camera" | "objects";
|
||||
@@ -16,6 +17,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const Track = ({
|
||||
id,
|
||||
clips,
|
||||
toggleMute,
|
||||
updateClip,
|
||||
@@ -24,7 +26,8 @@ export const Track = ({
|
||||
style,
|
||||
type,
|
||||
}: Props) => {
|
||||
const { length, setCanDrop, dragType } = useContext(TrackContext);
|
||||
const { length, scale, setCanDrop, dragType, setDropId, setDropOffset } =
|
||||
useContext(TrackContext);
|
||||
const trackType = type ?? style;
|
||||
|
||||
function onPointerOver() {
|
||||
@@ -32,6 +35,19 @@ export const Track = ({
|
||||
return;
|
||||
}
|
||||
setCanDrop(true);
|
||||
setDropId(id);
|
||||
}
|
||||
|
||||
function onPointerMove(event: PointerEvent<HTMLDivElement>) {
|
||||
if (dragType !== trackType) {
|
||||
return;
|
||||
}
|
||||
const track = document.getElementById(`track-${trackType}-${id}`);
|
||||
if (!track) {
|
||||
return;
|
||||
}
|
||||
const position = track.getBoundingClientRect();
|
||||
setDropOffset((event.clientX - position.x) / 4 / scale);
|
||||
}
|
||||
function onPointerLeave() {
|
||||
if (dragType !== trackType) {
|
||||
@@ -43,9 +59,11 @@ export const Track = ({
|
||||
return (
|
||||
<div className="pl-16">
|
||||
<div
|
||||
id={`track-${trackType}-${id}`}
|
||||
className={`rounded=lg relative mt-4 block h-9 w-full bg-${style}-unselected`}
|
||||
onPointerOver={onPointerOver}
|
||||
onPointerLeave={onPointerLeave}
|
||||
onPointerMove={onPointerMove}
|
||||
>
|
||||
{clips.map((clip, index) => (
|
||||
<TrackClip
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMouseEventsClip } from "./utils/useMouseEventsClip";
|
||||
import { BaseClip } from "~/models/track";
|
||||
import { BaseClip } from "~/pages/PageEnigma/models/track";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ export const useMouseEventsAnimation = () => {
|
||||
const onMouseMove = (event: MouseEvent) => {
|
||||
const delta = (event.clientX - clientX) / 4 / scale + currentTime;
|
||||
if (isActive === "drag") {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (delta < 0 || delta > max) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, {
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { BaseClip } from "~/models/track";
|
||||
import { BaseClip } from "~/pages/PageEnigma/models/track";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
|
||||
export const useMouseEventsClip = (
|
||||
|
||||
@@ -3,78 +3,143 @@ import {
|
||||
AnimationClip,
|
||||
AudioClip,
|
||||
AudioGroup,
|
||||
BaseClip,
|
||||
CameraGroup,
|
||||
CharacterGroup,
|
||||
ObjectGroup,
|
||||
} from "~/models/track";
|
||||
} from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export const TrackContext = createContext<{
|
||||
// timeline characters and it's functions
|
||||
characters: CharacterGroup[];
|
||||
camera: CameraGroup | null;
|
||||
audio: AudioGroup | null;
|
||||
objects: ObjectGroup;
|
||||
selectedClip: string | null;
|
||||
updateCharacters: (options: {
|
||||
type: "animations" | "positions" | "lipSync";
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) => void;
|
||||
toggleLipSyncMute: (characterId: string) => void;
|
||||
addCharacterAnimation: (options: {
|
||||
clipId: string;
|
||||
characterId: string;
|
||||
animationClips: BaseClip[];
|
||||
offset: number;
|
||||
}) => void;
|
||||
addCharacterAudio: (options: {
|
||||
clipId: string;
|
||||
characterId: string;
|
||||
audioClips: BaseClip[];
|
||||
offset: number;
|
||||
}) => void;
|
||||
selectCharacterClip: (clipId: string) => void;
|
||||
|
||||
// timeline camera group
|
||||
camera: CameraGroup | null;
|
||||
updateCamera: (options: {
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) => void;
|
||||
selectCameraClip: (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: BaseClip[]) => void;
|
||||
selectAudioClip: (clipId: string) => void;
|
||||
|
||||
// timeline objects group
|
||||
objects: ObjectGroup;
|
||||
updateObject: (options: {
|
||||
id: string;
|
||||
length: number;
|
||||
offset: number;
|
||||
}) => void;
|
||||
selectObjectClip: (clipId: string) => void;
|
||||
|
||||
// current - only select one item - will be replaced
|
||||
selectedClip: string | null;
|
||||
selectClip: (clipId: string) => void;
|
||||
toggleLipSyncMute: (characterId: string) => void;
|
||||
toggleAudioMute: () => void;
|
||||
|
||||
// sidebar clips
|
||||
animationClips: AnimationClip[];
|
||||
audioClips: AudioClip[];
|
||||
|
||||
// drag and drop
|
||||
dragType: "animations" | "lipSync" | null;
|
||||
dragId: string | null;
|
||||
startDrag: (type: "animations" | "lipSync", id: string) => void;
|
||||
endDrag: () => void;
|
||||
scale: number;
|
||||
currentTime: number;
|
||||
length: number;
|
||||
updateCurrentTime: (time: number) => void;
|
||||
canDrop: boolean;
|
||||
setCanDrop: (can: boolean) => void;
|
||||
overTimeline: boolean;
|
||||
setOverTimeline: (over: boolean) => void;
|
||||
dropId: string;
|
||||
setDropId: (id: string) => void;
|
||||
dropOffset: number;
|
||||
setDropOffset: (offset: number) => void;
|
||||
|
||||
// scale of timeline displa
|
||||
scale: number;
|
||||
|
||||
// current time position
|
||||
currentTime: number;
|
||||
updateCurrentTime: (time: number) => void;
|
||||
|
||||
// total length of the film clip
|
||||
length: number;
|
||||
|
||||
// computed width length * 60 * 4 * scale
|
||||
fullWidth: number;
|
||||
}>({
|
||||
characters: [],
|
||||
camera: null,
|
||||
audio: null,
|
||||
objects: { id: "", objects: [] },
|
||||
selectedClip: null,
|
||||
updateCharacters: () => {},
|
||||
updateCamera: () => {},
|
||||
updateAudio: () => {},
|
||||
updateObject: () => {},
|
||||
selectClip: () => {},
|
||||
toggleLipSyncMute: () => {},
|
||||
addCharacterAnimation: () => {},
|
||||
addCharacterAudio: () => {},
|
||||
selectCharacterClip: () => {},
|
||||
|
||||
camera: null,
|
||||
updateCamera: () => {},
|
||||
selectCameraClip: () => {},
|
||||
|
||||
audio: null,
|
||||
updateAudio: () => {},
|
||||
toggleAudioMute: () => {},
|
||||
selectAudioClip: () => {},
|
||||
addGlobalAudio: () => {},
|
||||
|
||||
objects: { id: "", objects: [] },
|
||||
updateObject: () => {},
|
||||
selectObjectClip: () => {},
|
||||
|
||||
selectedClip: null,
|
||||
selectClip: () => {},
|
||||
|
||||
animationClips: [],
|
||||
audioClips: [],
|
||||
|
||||
dragType: null,
|
||||
dragId: null,
|
||||
startDrag: () => {},
|
||||
endDrag: () => {},
|
||||
scale: 1,
|
||||
currentTime: 0,
|
||||
length: 12,
|
||||
updateCurrentTime: () => {},
|
||||
dropId: "",
|
||||
setDropId: () => {},
|
||||
canDrop: false,
|
||||
setCanDrop: () => {},
|
||||
overTimeline: false,
|
||||
setOverTimeline: () => {},
|
||||
dropOffset: 0,
|
||||
setDropOffset: () => {},
|
||||
|
||||
scale: 1,
|
||||
currentTime: 0,
|
||||
updateCurrentTime: () => {},
|
||||
length: 12,
|
||||
fullWidth: 0,
|
||||
});
|
||||
@@ -4,50 +4,51 @@ import useUpdateCharacters from "~/pages/PageEnigma/contexts/TrackContext/utils/
|
||||
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 { AnimationClip, AudioClip } from "~/models/track";
|
||||
import { AnimationClip, AudioClip } from "~/pages/PageEnigma/models/track";
|
||||
import useUpdateDragDrop from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateDragDrop";
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const TrackProvider = ({ children }: Props) => {
|
||||
const { characters, updateCharacters, toggleLipSyncMute } =
|
||||
useUpdateCharacters();
|
||||
const { camera, updateCamera } = useUpdateCamera();
|
||||
const { audio, updateAudio, toggleAudioMute } = useUpdateAudio();
|
||||
const { objects, updateObject } = useUpdateObject();
|
||||
const [selectedClip, setSelectedClip] = useState<string | null>(null);
|
||||
const characters = useUpdateCharacters();
|
||||
const camera = useUpdateCamera();
|
||||
const audio = useUpdateAudio();
|
||||
const objects = useUpdateObject();
|
||||
|
||||
const { endDrag, ...dragDrop } = useUpdateDragDrop();
|
||||
|
||||
const [selectedClip, setSelectedClip] = useState<string | null>(null);
|
||||
const selectClip = useCallback((clipId: string | null) => {
|
||||
setSelectedClip(clipId);
|
||||
}, []);
|
||||
|
||||
const [state, setState] = useState<{
|
||||
dragType: "animations" | "lipSync" | null;
|
||||
dragId: string | null;
|
||||
}>({ dragType: null, dragId: null });
|
||||
const [animationClips, setAnimationClips] = useState<AnimationClip[]>([]);
|
||||
const [audioClips, setAudioClips] = useState<AudioClip[]>([]);
|
||||
|
||||
const startDrag = useCallback(
|
||||
(type: "animations" | "lipSync", id: string) => {
|
||||
setState({ dragId: id, dragType: type });
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const endDrag = useCallback(() => {
|
||||
setState({ dragId: null, dragType: null });
|
||||
}, []);
|
||||
|
||||
const [scale, setScale] = useState(1);
|
||||
const [length, setLength] = useState(1);
|
||||
const [time, setTime] = useState(0);
|
||||
const updateCurrentTime = useCallback((newTime: number) => {
|
||||
setTime(newTime);
|
||||
}, []);
|
||||
|
||||
const [canDrop, setCanDrop] = useState(false);
|
||||
const [scale, setScale] = useState(1);
|
||||
const [length, setLength] = useState(1);
|
||||
// cross group functions
|
||||
const dropClip = useCallback(() => {
|
||||
const { canDrop, dragType, dropId, dragId, dropOffset } = dragDrop;
|
||||
if (canDrop) {
|
||||
if (dragType === "animations") {
|
||||
characters.addCharacterAnimation({
|
||||
clipId: dragId!,
|
||||
characterId: dropId,
|
||||
animationClips,
|
||||
offset: dropOffset,
|
||||
});
|
||||
}
|
||||
}
|
||||
endDrag();
|
||||
}, [dragDrop, animationClips, characters, endDrag]);
|
||||
|
||||
const fullWidth = useMemo(() => {
|
||||
return length * 60 * 4 * scale;
|
||||
@@ -69,54 +70,42 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
|
||||
const values = useMemo(() => {
|
||||
return {
|
||||
characters,
|
||||
camera,
|
||||
audio,
|
||||
objects,
|
||||
updateCharacters,
|
||||
updateCamera,
|
||||
updateAudio,
|
||||
updateObject,
|
||||
...characters,
|
||||
...camera,
|
||||
...audio,
|
||||
...objects,
|
||||
|
||||
selectClip,
|
||||
selectedClip,
|
||||
toggleLipSyncMute,
|
||||
toggleAudioMute,
|
||||
dragType: state.dragType,
|
||||
dragId: state.dragId,
|
||||
|
||||
...dragDrop,
|
||||
endDrag: dropClip,
|
||||
|
||||
animationClips,
|
||||
audioClips,
|
||||
startDrag,
|
||||
endDrag,
|
||||
|
||||
scale,
|
||||
currentTime: time,
|
||||
length,
|
||||
updateCurrentTime,
|
||||
canDrop,
|
||||
setCanDrop,
|
||||
fullWidth,
|
||||
};
|
||||
}, [
|
||||
characters,
|
||||
updateCharacters,
|
||||
camera,
|
||||
audio,
|
||||
objects,
|
||||
|
||||
selectClip,
|
||||
selectedClip,
|
||||
camera,
|
||||
updateCamera,
|
||||
audio,
|
||||
updateAudio,
|
||||
objects,
|
||||
updateObject,
|
||||
toggleLipSyncMute,
|
||||
toggleAudioMute,
|
||||
state.dragId,
|
||||
state.dragType,
|
||||
|
||||
dragDrop,
|
||||
dropClip,
|
||||
|
||||
animationClips,
|
||||
audioClips,
|
||||
startDrag,
|
||||
endDrag,
|
||||
canDrop,
|
||||
|
||||
updateCurrentTime,
|
||||
setCanDrop,
|
||||
time,
|
||||
fullWidth,
|
||||
length,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { AudioGroup } from "~/models/track";
|
||||
import { AudioGroup, BaseClip } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export default function useUpdateAudio() {
|
||||
const [audio, setAudio] = useState<AudioGroup>({
|
||||
@@ -49,6 +49,23 @@ export default function useUpdateAudio() {
|
||||
[],
|
||||
);
|
||||
|
||||
const addGlobalAudio = useCallback(
|
||||
(dragId: string, audioClips: BaseClip[]) => {
|
||||
const clip = audioClips.find((row) => row.id === dragId);
|
||||
if (!clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
setAudio((oldAudio) => {
|
||||
return {
|
||||
...oldAudio,
|
||||
clips: [...oldAudio.clips, clip],
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const toggleAudioMute = useCallback(() => {
|
||||
setAudio((oldAudio) => {
|
||||
return {
|
||||
@@ -58,9 +75,27 @@ export default function useUpdateAudio() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const selectAudioClip = useCallback((clipId: string) => {
|
||||
setAudio((oldAudio) => {
|
||||
return {
|
||||
...oldAudio,
|
||||
clips: [
|
||||
...oldAudio.clips.map((clip) => {
|
||||
return {
|
||||
...clip,
|
||||
selected: clip.id === clipId ? !clip.selected : clip.selected,
|
||||
};
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
audio,
|
||||
updateAudio,
|
||||
toggleAudioMute,
|
||||
selectAudioClip,
|
||||
addGlobalAudio,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { CameraGroup } from "~/models/track";
|
||||
import { CameraGroup } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export default function useUpdateCamera() {
|
||||
const [camera, setCamera] = useState<CameraGroup>({
|
||||
@@ -48,8 +48,25 @@ export default function useUpdateCamera() {
|
||||
[],
|
||||
);
|
||||
|
||||
const selectCameraClip = useCallback((clipId: string) => {
|
||||
setCamera((oldCamera) => {
|
||||
return {
|
||||
...oldCamera,
|
||||
clips: [
|
||||
...oldCamera.clips.map((clip) => {
|
||||
return {
|
||||
...clip,
|
||||
selected: clip.id === clipId ? !clip.selected : clip.selected,
|
||||
};
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
camera,
|
||||
updateCamera,
|
||||
selectCameraClip,
|
||||
};
|
||||
}
|
||||
@@ -1,25 +1,12 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { CharacterGroup } from "~/models/track";
|
||||
import { BaseClip, CharacterGroup } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export default function useUpdateCharacters() {
|
||||
const [characters, setCharacters] = useState<CharacterGroup[]>([
|
||||
{
|
||||
id: "CH1",
|
||||
muted: false,
|
||||
animationClips: [
|
||||
{
|
||||
id: "CH1-A1",
|
||||
length: 200,
|
||||
offset: 0,
|
||||
name: "ani 1",
|
||||
},
|
||||
{
|
||||
id: "CH1-A2",
|
||||
length: 180,
|
||||
offset: 300,
|
||||
name: "ani 2",
|
||||
},
|
||||
],
|
||||
animationClips: [],
|
||||
positionClips: [
|
||||
{
|
||||
id: "CH1-P1",
|
||||
@@ -120,6 +107,70 @@ export default function useUpdateCharacters() {
|
||||
[],
|
||||
);
|
||||
|
||||
const addCharacterAnimation = useCallback(
|
||||
({
|
||||
clipId,
|
||||
characterId,
|
||||
animationClips,
|
||||
offset,
|
||||
}: {
|
||||
clipId: string;
|
||||
characterId: string;
|
||||
animationClips: BaseClip[];
|
||||
offset: number;
|
||||
}) => {
|
||||
const clip = animationClips.find((row) => row.id === clipId);
|
||||
if (!clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCharacters((oldCharacters) => {
|
||||
return oldCharacters.map((character) => {
|
||||
if (character.id !== characterId) {
|
||||
return { ...character };
|
||||
}
|
||||
return {
|
||||
...character,
|
||||
animationClips: [...character.animationClips, { ...clip, offset }],
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const addCharacterAudio = useCallback(
|
||||
({
|
||||
clipId,
|
||||
characterId,
|
||||
audioClips,
|
||||
offset,
|
||||
}: {
|
||||
clipId: string;
|
||||
characterId: string;
|
||||
audioClips: BaseClip[];
|
||||
offset: number;
|
||||
}) => {
|
||||
const clip = audioClips.find((row) => row.id === clipId);
|
||||
if (!clip) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCharacters((oldCharacters) => {
|
||||
return oldCharacters.map((character) => {
|
||||
if (character.id !== characterId) {
|
||||
return { ...character };
|
||||
}
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: [...character.lipSyncClips, { ...clip, offset }],
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const toggleLipSyncMute = useCallback((characterId: string) => {
|
||||
setCharacters((oldCharacters) => {
|
||||
return oldCharacters.map((character) => ({
|
||||
@@ -130,9 +181,34 @@ export default function useUpdateCharacters() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const selectCharacterClip = useCallback((clipId: string) => {
|
||||
setCharacters((oldCharacters) => {
|
||||
return [
|
||||
...oldCharacters.map((character) => ({
|
||||
...character,
|
||||
animationClips: character.animationClips.map((clip) => ({
|
||||
...clip,
|
||||
selected: clip.id === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
positionClips: character.positionClips.map((clip) => ({
|
||||
...clip,
|
||||
selected: clip.id === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
lipSyncClips: character.lipSyncClips.map((clip) => ({
|
||||
...clip,
|
||||
selected: clip.id === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
})),
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
characters,
|
||||
updateCharacters,
|
||||
toggleLipSyncMute,
|
||||
addCharacterAnimation,
|
||||
addCharacterAudio,
|
||||
selectCharacterClip,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function useUpdateDragDrop() {
|
||||
const [state, setState] = useState<{
|
||||
dragType: "animations" | "lipSync" | null;
|
||||
dragId: string | null;
|
||||
}>({ dragType: null, dragId: null });
|
||||
const startDrag = useCallback(
|
||||
(type: "animations" | "lipSync", id: string) => {
|
||||
setState({ dragId: id, dragType: type });
|
||||
},
|
||||
[],
|
||||
);
|
||||
const [canDrop, setCanDrop] = useState(false);
|
||||
const [overTimeline, setOverTimeline] = useState(false);
|
||||
|
||||
const endDrag = useCallback(() => {
|
||||
setState({ dragId: null, dragType: null });
|
||||
}, []);
|
||||
|
||||
const [dropId, setDropId] = useState("");
|
||||
const [dropOffset, setDropOffset] = useState(0);
|
||||
|
||||
return {
|
||||
dragId: state.dragId,
|
||||
dragType: state.dragType,
|
||||
startDrag,
|
||||
endDrag,
|
||||
canDrop,
|
||||
setCanDrop,
|
||||
overTimeline,
|
||||
setOverTimeline,
|
||||
dropId,
|
||||
setDropId,
|
||||
dropOffset,
|
||||
setDropOffset,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { ObjectGroup } from "~/models/track";
|
||||
import { ObjectGroup } from "~/pages/PageEnigma/models/track";
|
||||
|
||||
export default function useUpdateObject() {
|
||||
const [objects, setObjects] = useState<ObjectGroup>({
|
||||
@@ -46,8 +46,24 @@ export default function useUpdateObject() {
|
||||
[],
|
||||
);
|
||||
|
||||
const selectObjectClip = useCallback((clipId: string) => {
|
||||
setObjects((oldObjectGroup) => {
|
||||
return {
|
||||
...oldObjectGroup,
|
||||
objects: oldObjectGroup.objects.map((object) => ({
|
||||
...object,
|
||||
clips: object.clips.map((clip) => ({
|
||||
...clip,
|
||||
selected: clip.id === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
objects,
|
||||
updateObject,
|
||||
selectObjectClip,
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,7 @@ export interface BaseClip {
|
||||
name: string;
|
||||
offset: number;
|
||||
length: number;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
export interface AnimationClip extends BaseClip {}
|
||||
Reference in new issue
Block a user