Begin work on dragging clips to add

This commit is contained in:
FromTheLOLAshes committed 2024-03-26 12:50:01 +07:00
1 parent 09e6e565d5
commit 188728693e
14 files changed
+322 -350

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
import { createContext } from "react";
import { AnimationClip, AudioClip } from "~/models/track";
export const ClipContext = createContext<{
animationClips: AnimationClip[];
audioClips: AudioClip[];
dragType: "animation" | "audio" | null;
dragId: string | null;
startDrag: (type: "animation" | "audio", id: string) => void;
endDrag: () => void;
}>({
animationClips: [],
audioClips: [],
dragType: null,
dragId: null,
startDrag: () => {},
endDrag: () => {},
});
+42
View File
@@ -0,0 +1,42 @@
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
import { ClipContext } from "./ClipContext";
import { AnimationClip, AudioClip } from "~/models/track";
interface Props {
children: ReactNode;
}
export const ClipProvider = ({ children }: Props) => {
const [state, setState] = useState<{
dragType: "animation" | "audio" | null;
dragId: string | null;
}>({ dragType: null, dragId: null });
const [animationClips, setAnimationClips] = useState<AnimationClip[]>([]);
const [audioClips, setAudioClips] = useState<AudioClip[]>([]);
useEffect(() => {
setAnimationClips([]);
setAudioClips([]);
}, []);
const startDrag = useCallback((type: "animation" | "audio", id: string) => {
setState({ dragId: id, dragType: type });
}, []);
const endDrag = useCallback(() => {
setState({ dragId: null, dragType: null });
}, []);
const values = useMemo(() => {
return {
dragType: state.dragType,
dragId: state.dragId,
animationClips,
audioClips,
startDrag,
endDrag,
};
}, [state, animationClips, audioClips, startDrag, endDrag]);
return <ClipContext.Provider value={values}>{children}</ClipContext.Provider>;
};
+4 -2
View File
@@ -1,10 +1,11 @@
import { useState } from "react";
interface LowerPanelPropsI {
timelineHeight: number;
children: React.ReactNode;
}
export const LowerPanel = ({ children }: LowerPanelPropsI) => {
export const LowerPanel = ({ children, timelineHeight }: LowerPanelPropsI) => {
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
@@ -14,10 +15,11 @@ export const LowerPanel = ({ children }: LowerPanelPropsI) => {
<div
className={[
"absolute bottom-0",
"h-[260px] w-screen overflow-auto",
"w-screen overflow-auto",
"border-t border-ui-panel-border",
"bg-ui-panel",
].join(" ")}
style={{ height: timelineHeight }}
>
{children}
</div>
+5 -99
View File
@@ -1,104 +1,10 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { faSparkles } from "@fortawesome/pro-solid-svg-icons";
import { Button, ButtonLink } from "~/components";
// import { ButtonDialogue } from "~/modules/ButtonDialogue";
import { TopBarHelmet } from "~/modules/TopBarHelmet/TopBarHelmet";
import { SidePanel } from "~/modules/SidePanel";
import { Controls3D } from "./comps/Controls3D";
import { ControlsTopButtons } from "./comps/ControlsTopButtons";
import { ControlsVideo } from "./comps/ControlsVideo";
import { PreviewWindow } from "./comps/PreviewWindow";
import { Timeline } from "./comps/Timeline";
import { SidePanelTabs } from "./comps/SidePanelTabs";
import { TrackProvider } from "~/contexts/TrackContext/TrackProvider";
import { EngineProvider } from "~/contexts/EngineProvider";
import { ClipProvider } from "~/contexts/ClipContext/ClipProvider";
import { PageEnigmaComponent } from "./PageEnigmaComponent";
export const PageEnigma = () => {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [timelineHeight, setTimelineHeight] = useState(0);
const timelineRef = useRef<HTMLDivElement | null>(null);
const updateTimelineHeight = useCallback(() => {
if (timelineRef.current) {
setTimelineHeight(timelineRef.current.offsetHeight);
}
}, []);
//for updating timeline/engine div height (for resizing)
useEffect(() => {
const observer = new ResizeObserver((entries) => {
for (let entry of entries) {
updateTimelineHeight();
}
});
if (timelineRef.current) {
observer.observe(timelineRef.current);
}
return () => observer.disconnect();
}, [updateTimelineHeight]);
return (
<div>
<TopBarHelmet>
<Button icon={faSparkles}>Generate Movie</Button>
</TopBarHelmet>
<EngineProvider>
<div style={{ height: "calc(100vh - 68px)" }}>
{/* Engine section/side panel */}
<div
id="CanvasUiWrapper"
className="flex"
// style={{ height: `calc(100% - ${timelineHeight}px)` }}
style={{ height: `calc(100% - 260px` }}
>
<div className="relative w-full overflow-hidden bg-gray-400">
<canvas
ref={canvasRef}
id="video-scene"
width="1280px"
height="720px"
/>
{/* Top controls */}
<div className="absolute left-0 top-0 w-full">
<div className="grid grid-cols-3 gap-4">
<ControlsTopButtons />
<Controls3D />
</div>
</div>
{/* Bottom controls */}
<div className="absolute bottom-0 left-0 w-full">
<PreviewWindow />
<ControlsVideo />
</div>
</div>
{/* Side panel */}
<SidePanel>
<SidePanelTabs />
</SidePanel>
</div>
{/* Timeline */}
<div className="min-h-[220px]" ref={timelineRef}>
<TrackProvider>
<Timeline
// editorCurrent={editorRef.current}
// time={20}
// length={12}
/>
</TrackProvider>
</div>
</div>
</EngineProvider>
</div>
<ClipProvider>
<PageEnigmaComponent />
</ClipProvider>
);
};
@@ -0,0 +1,98 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { faSparkles } from "@fortawesome/pro-solid-svg-icons";
import { Button } from "~/components";
import { TopBarHelmet } from "~/modules/TopBarHelmet/TopBarHelmet";
import { SidePanel } from "~/modules/SidePanel";
import { Controls3D } from "./comps/Controls3D";
import { ControlsTopButtons } from "./comps/ControlsTopButtons";
import { ControlsVideo } from "./comps/ControlsVideo";
import { PreviewWindow } from "./comps/PreviewWindow";
import { Timeline } from "./comps/Timeline";
import { SidePanelTabs } from "./comps/SidePanelTabs";
import { TrackProvider } from "~/contexts/TrackContext/TrackProvider";
import { EngineProvider } from "~/contexts/EngineProvider";
export const PageEnigmaComponent = () => {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [timelineHeight, setTimelineHeight] = useState(260);
const timelineRef = useRef<HTMLDivElement | null>(null);
const updateTimelineHeight = useCallback(() => {
if (timelineRef.current) {
setTimelineHeight(timelineRef.current.offsetHeight);
}
}, []);
//for updating timeline/engine div height (for resizing)
useEffect(() => {
const observer = new ResizeObserver((entries) => {
for (let entry of entries) {
updateTimelineHeight();
}
});
if (timelineRef.current) {
observer.observe(timelineRef.current);
}
return () => observer.disconnect();
}, [updateTimelineHeight]);
return (
<div>
<TopBarHelmet>
<Button icon={faSparkles}>Generate Movie</Button>
</TopBarHelmet>
<EngineProvider>
<div style={{ height: "calc(100vh - 68px)" }}>
{/* Engine section/side panel */}
<div
id="CanvasUiWrapper"
className="flex"
// style={{ height: `calc(100% - ${timelineHeight}px)` }}
style={{ height: `calc(100% - 260px` }}
>
<div className="relative w-full overflow-hidden bg-gray-400">
<canvas
ref={canvasRef}
id="video-scene"
width="1280px"
height="720px"
/>
{/* Top controls */}
<div className="absolute left-0 top-0 w-full">
<div className="grid grid-cols-3 gap-4">
<ControlsTopButtons />
<Controls3D />
</div>
</div>
{/* Bottom controls */}
<div className="absolute bottom-0 left-0 w-full">
<PreviewWindow />
<ControlsVideo />
</div>
</div>
{/* Side panel */}
<SidePanel>
<SidePanelTabs />
</SidePanel>
</div>
{/* Timeline */}
<div className="min-h-[220px]" ref={timelineRef}>
<TrackProvider>
<Timeline timelineHeight={timelineHeight} />
</TrackProvider>
</div>
</div>
</EngineProvider>
</div>
);
};
@@ -1,13 +1,20 @@
import { Tabs } from "~/modules/Tabs";
import { TabStyling } from "./TabStyling";
export const SidePanelTabs = ()=>{
return(
export const SidePanelTabs = () => {
return (
<Tabs
tabs={[
{
header: "Animation",
children: <p>Animation Tab</p>,
children: (
<div className="flex flex-wrap">
<div
id="ani-obj-1"
className="block h-16 w-16 bg-brand-secondary-700"
/>
</div>
),
},
{
header: "Camera",
@@ -24,5 +31,4 @@ export const SidePanelTabs = ()=>{
]}
/>
);
}
};
+9 -45
View File
@@ -1,8 +1,6 @@
import { TrackClip } from "./TrackClip";
import { useContext } from "react";
import { TrackContext } from "~/contexts/TrackContext/TrackContext";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faVolume, faVolumeSlash } from "@fortawesome/pro-solid-svg-icons";
import { Track } from "~/pages/PageEnigma/comps/Timeline/Track";
export const Audio = () => {
const { audio, updateAudio, length, scale, toggleAudioMute } =
@@ -17,48 +15,14 @@ export const Audio = () => {
>
<div className="mb-2 text-sm text-white">Global Audio</div>
<div className="flex flex-col gap-2">
<div className="pl-16">
<div className="bg-audio-unselected relative mt-4 block h-8 w-full rounded">
{clips.map((clip, index) => (
<TrackClip
key={clip.id}
min={
index > 0
? clips[index - 1].offset + clips[index - 1].length
: 0
}
max={
index < clips.length - 1
? clips[index + 1].offset
: length * 60
}
style="audio"
updateClip={updateAudio}
clip={clip}
/>
))}
<div
className="absolute text-xs text-white"
style={{ top: 6, left: 4 }}
>
Global Audio Track
</div>
<button
className="absolute text-xl text-white"
style={{ top: 2, left: -36 }}
onClick={() => toggleAudioMute()}
>
{audio?.muted ? (
<FontAwesomeIcon
icon={faVolumeSlash}
className="text-brand-primary"
/>
) : (
<FontAwesomeIcon icon={faVolume} />
)}
</button>
</div>
</div>
<Track
clips={clips}
title="Global Audio Track"
updateClip={updateAudio}
style="audio"
muted={audio?.muted}
toggleMute={toggleAudioMute}
/>
</div>
</div>
);
+7 -29
View File
@@ -1,6 +1,6 @@
import { TrackClip } from "./TrackClip";
import { useContext } from "react";
import { TrackContext } from "~/contexts/TrackContext/TrackContext";
import { Track } from "~/pages/PageEnigma/comps/Timeline/Track";
export const Camera = () => {
const { camera, updateCamera, length, scale } = useContext(TrackContext);
@@ -14,34 +14,12 @@ export const Camera = () => {
>
<div className="mb-2 text-sm text-white">Camera</div>
<div className="flex flex-col gap-2">
<div className="pl-16">
<div className="bg-camera-unselected relative mt-4 block h-8 w-full rounded">
{clips.map((clip, index) => (
<TrackClip
key={clip.id}
min={
index > 0
? clips[index - 1].offset + clips[index - 1].length
: 0
}
max={
index < clips.length - 1
? clips[index + 1].offset
: length * 60
}
style="camera"
updateClip={updateCamera}
clip={clip}
/>
))}
<div
className="absolute text-xs text-white"
style={{ top: 6, left: 4 }}
>
Camera Position/Rotation
</div>
</div>
</div>
<Track
clips={clips}
title="Camera Position/Rotation"
style="camera"
updateClip={updateCamera}
/>
</div>
</div>
);
+28 -105
View File
@@ -1,8 +1,6 @@
import { TrackClip } from "./TrackClip";
import { useContext, useMemo } from "react";
import { useCallback, useContext, useMemo } from "react";
import { TrackContext } from "~/contexts/TrackContext/TrackContext";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faVolumeSlash, faVolume } from "@fortawesome/pro-solid-svg-icons";
import { Track } from "~/pages/PageEnigma/comps/Timeline/Track";
function buildUpdaters(
updateCharacters: (options: {
@@ -47,6 +45,9 @@ export const Character = ({ characterId }: Props) => {
const { updateClipLipSync, updateClipPosition, updateClipAnimations } =
useMemo(() => buildUpdaters(updateCharacters), [updateCharacters]);
const toggleCharacterLipSyncMute = useCallback(() => {
toggleLipSyncMute(character?.id ?? "");
}, []);
if (!character) {
return false;
@@ -60,107 +61,29 @@ export const Character = ({ characterId }: Props) => {
>
<div className="mb-2 text-xs text-white">Character</div>
<div className="flex flex-col gap-2">
<div className="pl-16">
<div className="relative mt-4 block h-8 rounded bg-character-unselected">
{animationClips.map((clip, index) => (
<TrackClip
key={clip.id}
min={
index > 0
? animationClips[index - 1].offset +
animationClips[index - 1].length
: 0
}
max={
index < animationClips.length - 1
? animationClips[index + 1].offset
: length * 60
}
style="character"
updateClip={updateClipAnimations}
clip={clip}
/>
))}
<div
className="absolute text-xs text-white"
style={{ top: 6, left: 4 }}
>
Animation
</div>
</div>
</div>
<div className="pl-16">
<div className="relative mt-4 block h-8 w-full rounded bg-character-unselected">
{positionClips.map((clip, index) => (
<TrackClip
key={clip.id}
min={
index > 0
? positionClips[index - 1].offset +
positionClips[index - 1].length
: 0
}
max={
index < positionClips.length - 1
? positionClips[index + 1].offset
: length * 60
}
style="character"
updateClip={updateClipPosition}
clip={clip}
/>
))}
<div
className="absolute text-xs text-white"
style={{ top: 6, left: 4 }}
>
Character Position/Rotation
</div>
</div>
</div>
<div className="pl-16">
<div className="relative mt-4 block h-8 w-full rounded bg-character-unselected">
{lipSyncClips.map((clip, index) => (
<TrackClip
key={clip.id}
min={
index > 0
? lipSyncClips[index - 1].offset +
lipSyncClips[index - 1].length
: 0
}
max={
index < lipSyncClips.length - 1
? lipSyncClips[index + 1].offset
: length * 60
}
style="character"
updateClip={updateClipLipSync}
clip={clip}
/>
))}
<div
className="absolute text-xs text-white"
style={{ top: 6, left: 4 }}
>
Lipsync Audio Track
</div>
<button
className="absolute text-xl text-white"
style={{ top: 2, left: -36 }}
onClick={() => toggleLipSyncMute(character?.id)}
>
{character.muted ? (
<FontAwesomeIcon
icon={faVolumeSlash}
className="text-brand-primary"
/>
) : (
<FontAwesomeIcon icon={faVolume} />
)}
</button>
</div>
</div>
<Track
clips={animationClips}
title="Animation"
updateClip={updateClipAnimations}
style="character"
type="animations"
/>
<Track
clips={positionClips}
title="Character Position/Rotation"
updateClip={updateClipPosition}
style="character"
type="positions"
/>
<Track
clips={lipSyncClips}
title="Lipsync Audio Track"
updateClip={updateClipLipSync}
muted={character.muted}
toggleMute={toggleCharacterLipSyncMute}
style="character"
type="lipSync"
/>
</div>
</div>
);
@@ -1,6 +1,6 @@
import { TrackClip } from "./TrackClip";
import { useContext } from "react";
import { TrackContext } from "~/contexts/TrackContext/TrackContext";
import { Track } from "~/pages/PageEnigma/comps/Timeline/Track";
export const Objects = () => {
const { objects, updateObject, length, scale } = useContext(TrackContext);
@@ -14,35 +14,12 @@ export const Objects = () => {
<div className="mb-2 text-sm text-white">Objects</div>
{objects.objects.map((object) => (
<div key={object.id} className="flex flex-col gap-2">
<div className="pl-16">
<div className="bg-objects-unselected relative mt-4 block h-8 w-full rounded">
{object.clips.map((clip, index) => (
<TrackClip
key={clip.id}
min={
index > 0
? object.clips[index - 1].offset +
object.clips[index - 1].length
: 0
}
max={
index < object.clips.length - 1
? object.clips[index + 1].offset
: length * 60
}
style="objects"
updateClip={updateObject}
clip={clip}
/>
))}
<div
className="absolute text-xs text-white"
style={{ top: 6, left: 4 }}
>
Mask Position/Rotation
</div>
</div>
</div>
<Track
clips={object.clips}
title="Mask Position/Rotation"
updateClip={updateObject}
style="objects"
/>
</div>
))}
</div>
@@ -1,20 +1,7 @@
import {
Fragment,
// useCallback,
useContext
} from "react";
import { Button } from "~/components";
// import Editor from "../../js/editor";
import { EngineContext } from "~/contexts/EngineContext";
import { Fragment, useContext } from "react";
import { TrackContext } from "~/contexts/TrackContext/TrackContext";
import { LowerPanel } from "~/modules/LowerPanel";
import { Character } from "./Character";
import { Camera } from "./Camera";
import { Audio } from "./Audio";
import { Objects } from "./Objects";
@@ -22,16 +9,11 @@ import { useMouseEventsAnimation } from "./utils/useMouseEventsAnimation";
import { faSortDown } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export const Timeline = ({
// editorCurrent,
// time,
}: {
// editorCurrent: Editor | null;
// time: number;
}) => {
const editorEngine = useContext(EngineContext);
// editorEngine replaced the engineRef.current passed as a prop
// const { characters, updateCharacters } = useContext(TrackContext);
interface Props {
timelineHeight: number;
}
export const Timeline = ({ timelineHeight }: Props) => {
const { characters, objects, scale, length } = useContext(TrackContext);
const { onPointerDown, time } = useMouseEventsAnimation();
@@ -41,7 +23,7 @@ export const Timeline = ({
return (
<>
<LowerPanel>
<LowerPanel timelineHeight={timelineHeight}>
<div className="mt-4 flex h-3 text-sm text-white">
{Array(length)
.fill(0)
@@ -0,0 +1,78 @@
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 { TrackContext } from "~/contexts/TrackContext/TrackContext";
import { ClipContext } from "~/contexts/ClipContext/ClipContext";
interface Props {
clips: BaseClip[];
title: string;
style: "character" | "audio" | "camera" | "objects";
type?: "animations" | "positions" | "lipSync";
toggleMute?: () => void;
muted?: boolean;
updateClip: (options: { id: string; length: number; offset: number }) => void;
}
export const Track = ({
clips,
toggleMute,
updateClip,
muted,
title,
style,
type,
}: Props) => {
const { length } = useContext(TrackContext);
const { dragType } = useContext(ClipContext);
const trackType = type ?? style;
const className = dragType === trackType ? "highlight" : "";
return (
<div className="pl-16">
<div
className={`relative mt-4 block h-8 w-full rounded bg-${style}-unselected`}
>
{clips.map((clip, index) => (
<TrackClip
key={clip.id}
min={
index > 0 ? clips[index - 1].offset + clips[index - 1].length : 0
}
max={
index < clips.length - 1 ? clips[index + 1].offset : length * 60
}
style={style}
updateClip={updateClip}
clip={clip}
/>
))}
<div
className="absolute text-xs text-white"
style={{ top: 6, left: 4 }}
>
{title}
</div>
{!!toggleMute && (
<button
className="absolute text-xl text-white"
style={{ top: 2, left: -36 }}
onClick={toggleMute}
>
{muted ? (
<FontAwesomeIcon
icon={faVolumeSlash}
className="text-brand-primary"
/>
) : (
<FontAwesomeIcon icon={faVolume} />
)}
</button>
)}
</div>
</div>
);
};
@@ -59,7 +59,7 @@ export const useMouseEventsClip = (
) {
return;
}
currLength.current = initLength.current - delta;
currLength.current = initLength.current + delta;
}
setState({ length: currLength.current, offset: currOffset.current });
},
+7 -9
View File
@@ -1,9 +1,9 @@
import { useState } from 'react';
import { useState } from "react";
import { PageEnigma } from "../pages/PageEnigma";
import { Button } from '~/components/Button';
import { Button } from "~/components/Button";
export default function IdealEnigma(){
export default function IdealEnigma() {
//const [riddle, setRiddle] = useState<string>("");
//const [submitted, setSubmitted] = useState<boolean>(false);
@@ -16,14 +16,13 @@ export default function IdealEnigma(){
// setSubmitted(state);
//}
return <PageEnigma />
return <PageEnigma />;
// if(submitted){
// return <PageEnigma />
// return <PageEnigmaComponent />
// }else{
// return(
// <div className="w-full flex justify-center content-center">
// <form
// <form
// className="mt-20 flex flex-col content-center"
// onSubmit={handleSubmit}
// >
@@ -38,5 +37,4 @@ export default function IdealEnigma(){
// );
// }
}
}