fixes and timeline re-styling
This commit is contained in:
1 parent
0a0a2f1cab
commit
ef12e7375b
21 files changed
+363
-208
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, UIEvent } from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import { useMouseEventsTimeline } from "~/pages/PageEnigma/comps/Timeline/utils/useMouseEventsTimeline";
|
||||
import {
|
||||
currentScroll,
|
||||
timelineScrollX,
|
||||
currentTime,
|
||||
dndTimelineHeight,
|
||||
overTimeline,
|
||||
@@ -29,7 +29,7 @@ export const LowerPanel = ({ children, onStyle }: LowerPanelPropsI) => {
|
||||
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (event.button === 0) {
|
||||
const newTime = Math.round(
|
||||
(event.clientX + currentScroll.value - 92) / 4 / scale.value,
|
||||
(event.clientX + timelineScrollX.value - 92) / 4 / scale.value,
|
||||
);
|
||||
if (newTime < 0) {
|
||||
return;
|
||||
@@ -45,10 +45,6 @@ export const LowerPanel = ({ children, onStyle }: LowerPanelPropsI) => {
|
||||
[],
|
||||
);
|
||||
|
||||
const onScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
||||
currentScroll.value = event.currentTarget.scrollLeft;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!onStyle && (
|
||||
@@ -59,19 +55,13 @@ export const LowerPanel = ({ children, onStyle }: LowerPanelPropsI) => {
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={[
|
||||
"absolute bottom-0",
|
||||
"w-screen",
|
||||
onStyle ? "overflow-x-auto overflow-y-hidden" : "overflow-auto",
|
||||
"bg-ui-panel",
|
||||
].join(" ")}
|
||||
className={["absolute bottom-0", "w-screen", "bg-ui-panel"].join(" ")}
|
||||
style={{ height: onStyle ? 80 : displayHeight }}
|
||||
onPointerOver={() => {
|
||||
overTimeline.value = true;
|
||||
}}
|
||||
onPointerLeave={() => (overTimeline.value = false)}
|
||||
onPointerDown={onTimelineClick}
|
||||
onScroll={onScroll}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useContext } from "react";
|
||||
import React, { useContext, useRef } from "react";
|
||||
|
||||
import { LoadingBar, LoadingDots } from "~/components";
|
||||
import { SidePanel } from "~/modules/SidePanel";
|
||||
@@ -33,6 +33,11 @@ export const PageEditor = ({ setPage }: Props) => {
|
||||
useSignals();
|
||||
|
||||
const [appUiState] = useContext(AppUiContext);
|
||||
const r1 = useRef(appUiState);
|
||||
if (r1.current !== appUiState) {
|
||||
console.log("app");
|
||||
r1.current = appUiState;
|
||||
}
|
||||
|
||||
//To prevent the click event from propagating to the canvas: TODO: HANDLE THIS BETTER?
|
||||
const handleOverlayClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
@@ -51,6 +56,7 @@ export const PageEditor = ({ setPage }: Props) => {
|
||||
? pageHeight.value - dndTimelineHeight.value - 68
|
||||
: pageHeight.value - timelineHeight.value - 68;
|
||||
|
||||
console.log("page");
|
||||
return (
|
||||
<div className="w-screen">
|
||||
<TopBar pageName="Edit Scene" />
|
||||
|
||||
@@ -20,7 +20,7 @@ export const PageEnigmaComponent = () => {
|
||||
dispatchAppUiState,
|
||||
});
|
||||
}
|
||||
}, [editor, dispatchAppUiState]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faAngleLeft } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button, Link } from "~/components";
|
||||
import { Button } from "~/components";
|
||||
import { StyleSelection } from "~/pages/PageEnigma/comps/StyleSelection";
|
||||
import { LowerPanel } from "~/modules/LowerPanel";
|
||||
import { TimerGrid } from "~/pages/PageEnigma/comps/TimerGrid/TimerGrid";
|
||||
import { TimerGrid } from "~/pages/PageEnigma/comps/Timeline/TimerGrid";
|
||||
import { Scrubber } from "~/pages/PageEnigma/comps/Timeline/Scrubber";
|
||||
import { PreviewImages } from "~/pages/PageEnigma/comps/PreviewImages";
|
||||
import { TopBar } from "~/modules/TopBar";
|
||||
import { useContext } from "react";
|
||||
import React, { UIEvent, useCallback, useContext } from "react";
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
import {
|
||||
currentTime,
|
||||
filmLength,
|
||||
scale,
|
||||
timelineScrollX,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
|
||||
interface Props {
|
||||
setPage: (page: string) => void;
|
||||
@@ -19,6 +27,8 @@ export const PageStyling = ({ setPage }: Props) => {
|
||||
useSignals();
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
console.log(timelineScrollX.value);
|
||||
|
||||
const generateFrame = async () => {
|
||||
await editorEngine?.generateFrame();
|
||||
};
|
||||
@@ -30,7 +40,31 @@ export const PageStyling = ({ setPage }: Props) => {
|
||||
const switchEdit = () => {
|
||||
editorEngine?.switchEdit();
|
||||
setPage("edit");
|
||||
}
|
||||
};
|
||||
|
||||
const onScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
||||
timelineScrollX.value = event.currentTarget.scrollLeft;
|
||||
}, []);
|
||||
|
||||
const onTimelineClick = useCallback(
|
||||
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (event.button === 0) {
|
||||
const newTime = Math.round(
|
||||
(event.clientX + timelineScrollX.value - 92) / 4 / scale.value,
|
||||
);
|
||||
if (newTime < 0) {
|
||||
return;
|
||||
}
|
||||
currentTime.value = newTime;
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_TIME,
|
||||
data: { currentTime: Math.round(newTime) },
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-screen">
|
||||
@@ -50,17 +84,29 @@ export const PageStyling = ({ setPage }: Props) => {
|
||||
<div className="fixed bottom-0 left-0 w-full">
|
||||
<div className="flex h-[62px] w-full items-center justify-center gap-5 bg-ui-panel">
|
||||
<div>
|
||||
<Button variant="action" onClick={generateFrame}>Update Preview</Button>
|
||||
<Button variant="action" onClick={generateFrame}>
|
||||
Update Preview
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button variant="primary" onClick={generateMovie}>Generate Movie</Button>
|
||||
<Button variant="primary" onClick={generateMovie}>
|
||||
Generate Movie
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex h-[80px] w-full gap-5 border-t border-t-action-600 bg-ui-panel">
|
||||
<LowerPanel onStyle>
|
||||
<TimerGrid />
|
||||
<Scrubber />
|
||||
</LowerPanel>
|
||||
<div className="w-screen overflow-x-auto" onScroll={onScroll}>
|
||||
<div
|
||||
style={{
|
||||
width: filmLength.value * 60 * 4 * scale.value,
|
||||
height: 60,
|
||||
}}
|
||||
onClick={onTimelineClick}
|
||||
>
|
||||
<TimerGrid />
|
||||
<Scrubber />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { TrackClips } from "~/pages/PageEnigma/comps/Timeline/TrackClips";
|
||||
import {
|
||||
audioGroup,
|
||||
fullWidth,
|
||||
toggleAudioMute,
|
||||
updateAudio,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { audioGroup, fullWidth, updateAudio } from "~/pages/PageEnigma/store";
|
||||
import { ClipGroup } from "~/pages/PageEnigma/models";
|
||||
|
||||
export const Audio = () => {
|
||||
@@ -12,21 +7,16 @@ export const Audio = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-global_audio-groupBg block rounded-lg pb-5 pl-2 pr-4"
|
||||
style={{ width: fullWidth.value + 90 }}
|
||||
className="block rounded-r-lg bg-global_audio-groupBg pb-5 pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<div className="prevent-select mb-5 pt-2 text-xs font-medium text-white">
|
||||
Global Audio
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4 pt-4">
|
||||
<TrackClips
|
||||
id={audioGroup.value.id}
|
||||
clips={clips}
|
||||
title="Global Audio TrackClips"
|
||||
updateClip={updateAudio}
|
||||
group={ClipGroup.GLOBAL_AUDIO}
|
||||
muted={audioGroup.value.muted}
|
||||
toggleMute={toggleAudioMute}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,11 +9,10 @@ export const Camera = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="block rounded-lg bg-camera-groupBg pb-5 pl-2 pr-4"
|
||||
style={{ width: fullWidth.value + 90 }}
|
||||
className="block rounded-r-lg bg-camera-groupBg pb-5 pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<div className="mb-5 pt-2 text-xs font-medium text-white">Camera</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="pt-4">
|
||||
<TrackKeyFrames
|
||||
id={cameraGroup.value.id}
|
||||
keyframes={keyframes}
|
||||
|
||||
@@ -43,24 +43,14 @@ export const Character = ({ character }: Props) => {
|
||||
const { updateClipLipSync, updateClipPosition, updateClipAnimations } =
|
||||
useMemo(() => buildUpdaters(updateCharacters), []);
|
||||
|
||||
const toggleCharacterLipSyncMute = useCallback(() => {
|
||||
toggleLipSyncMute(character?.object_uuid ?? "");
|
||||
}, [character?.object_uuid]);
|
||||
|
||||
if (!character) {
|
||||
return false;
|
||||
}
|
||||
const { animationClips, positionKeyframes, lipSyncClips } = character;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="block rounded-lg bg-character-groupBg pb-5 pl-2 pr-4"
|
||||
style={{ width: fullWidth.value + 90 }}
|
||||
className="block rounded-r-lg bg-character-groupBg pb-5 pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<div className="prevent-select mb-5 pt-2 text-xs font-medium text-white">
|
||||
Character
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4 pt-4">
|
||||
<TrackClips
|
||||
id={character.object_uuid}
|
||||
clips={animationClips}
|
||||
@@ -81,8 +71,6 @@ export const Character = ({ character }: Props) => {
|
||||
clips={lipSyncClips}
|
||||
title="Lipsync Audio TrackClips"
|
||||
updateClip={updateClipLipSync}
|
||||
muted={character.muted}
|
||||
toggleMute={toggleCharacterLipSyncMute}
|
||||
group={ClipGroup.CHARACTER}
|
||||
type={ClipType.AUDIO}
|
||||
/>
|
||||
|
||||
@@ -7,7 +7,9 @@ export const Characters = () => {
|
||||
return (
|
||||
<>
|
||||
{characterGroup.value.characters.map((character) => (
|
||||
<Character key={character.object_uuid} character={character} />
|
||||
<div key={character.object_uuid} className="pb-4 pr-4">
|
||||
<Character character={character} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { objectGroup } from "~/pages/PageEnigma/store";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { ObjectTrack } from "~/pages/PageEnigma/comps/Timeline/ObjectTrack";
|
||||
import { ObjectTrackComponent } from "~/pages/PageEnigma/comps/Timeline/ObjectTrack";
|
||||
|
||||
export const ObjectGroups = () => {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<>
|
||||
{objectGroup.value.objects.length > 0 && (
|
||||
<div className="p-4">
|
||||
<ObjectTrack />
|
||||
{objectGroup.value.objects.map((object) => (
|
||||
<div key={object.object_uuid} className="pb-4 pr-4">
|
||||
<ObjectTrackComponent object={object} />
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,27 +1,26 @@
|
||||
import { fullWidth, objectGroup, updateObject } from "~/pages/PageEnigma/store";
|
||||
import { fullWidth, updateObject } from "~/pages/PageEnigma/store";
|
||||
import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames";
|
||||
import { ClipGroup } from "~/pages/PageEnigma/models";
|
||||
import { ClipGroup, ObjectTrack } from "~/pages/PageEnigma/models";
|
||||
|
||||
export const ObjectTrack = () => {
|
||||
interface Props {
|
||||
object: ObjectTrack;
|
||||
}
|
||||
|
||||
export const ObjectTrackComponent = ({ object }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className="bg-object-groupBg block rounded-lg pb-5 pl-2 pr-4"
|
||||
style={{ width: fullWidth.value + 90 }}
|
||||
className="mr-4 block rounded-r-lg bg-object-groupBg pb-5 pr-4"
|
||||
style={{ width: fullWidth.value + 32 }}
|
||||
>
|
||||
<div className="prevent-select mb-5 pt-2 text-xs font-medium text-white">
|
||||
Objects
|
||||
<div key={object.object_uuid} className="pt-4">
|
||||
<TrackKeyFrames
|
||||
id={object.object_uuid}
|
||||
keyframes={object.keyframes}
|
||||
title={`${object.name} Position/Rotation`}
|
||||
updateKeyframe={updateObject}
|
||||
group={ClipGroup.OBJECT}
|
||||
/>
|
||||
</div>
|
||||
{objectGroup.value.objects.map((object) => (
|
||||
<div key={object.object_uuid} className="flex flex-col gap-4">
|
||||
<TrackKeyFrames
|
||||
id={object.object_uuid}
|
||||
keyframes={object.keyframes}
|
||||
title={`${object.name} Position/Rotation`}
|
||||
updateKeyframe={updateObject}
|
||||
group={ClipGroup.OBJECT}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
audioGroup,
|
||||
characterGroup,
|
||||
objectGroup,
|
||||
timelineScrollY,
|
||||
toggleAudioMute,
|
||||
toggleLipSyncMute,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faVolume, faVolumeSlash } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const RowHeaders = () => {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
className="absolute mt-2"
|
||||
style={{ top: timelineScrollY.value * -1 - 8 }}
|
||||
>
|
||||
{characterGroup.value.characters.map((character) => (
|
||||
<div className="mb-4 ml-6" key={character.object_uuid}>
|
||||
<div className="relative block h-[224px] w-[64px] overflow-x-hidden rounded-l-lg bg-character-groupBg">
|
||||
<div className="absolute left-2 top-2 text-xs font-medium text-white">
|
||||
Character
|
||||
</div>
|
||||
<div className="absolute">
|
||||
<button
|
||||
className="text-md absolute text-white transition-colors duration-100 hover:text-white/80"
|
||||
style={{ top: 174, left: 34 }}
|
||||
onClick={() => toggleLipSyncMute(character.object_uuid)}
|
||||
>
|
||||
{character.muted ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faVolumeSlash}
|
||||
className="text-brand-primary transition-colors duration-100 hover:text-brand-primary/80"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faVolume} />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="mb-4 ml-6">
|
||||
<div className="relative block h-[88px] w-[64px] rounded-l-lg bg-camera-groupBg">
|
||||
<div className="absolute left-2 top-2 text-xs font-medium text-white">
|
||||
Camera
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4 ml-6">
|
||||
<div className="relative block h-[88px] w-[64px] rounded-l-lg bg-global_audio-groupBg">
|
||||
<div className="absolute left-2 top-2 text-xs font-medium text-white">
|
||||
Global Audio
|
||||
</div>
|
||||
<div className="absolute">
|
||||
<button
|
||||
className="text-md absolute text-white transition-colors duration-100 hover:text-white/80"
|
||||
style={{ top: 40, left: 34 }}
|
||||
onClick={() => toggleAudioMute()}
|
||||
>
|
||||
{audioGroup.value.muted ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faVolumeSlash}
|
||||
className="text-brand-primary transition-colors duration-100 hover:text-brand-primary/80"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faVolume} />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{objectGroup.value.objects.map((obj) => (
|
||||
<div key={obj.object_uuid} className="mb-4 ml-6">
|
||||
<div className="relative block h-[88px] w-[64px] rounded-l-lg bg-object-groupBg">
|
||||
<div className="absolute left-2 top-2 text-xs font-medium text-white">
|
||||
Object
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,9 @@
|
||||
import { currentTime, fullHeight, scale } from "~/pages/PageEnigma/store";
|
||||
import {
|
||||
timelineScrollX,
|
||||
currentTime,
|
||||
fullHeight,
|
||||
scale,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSortDown } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { useMouseEventsScrubber } from "~/pages/PageEnigma/comps/Timeline/utils/useMouseEventsScrubber";
|
||||
@@ -9,10 +14,16 @@ export const Scrubber = () => {
|
||||
const { onPointerDown, time } = useMouseEventsScrubber();
|
||||
const displayTime = time === -1 ? currentTime.value : time;
|
||||
|
||||
if (displayTime * 4 * scale.value + 84 - timelineScrollX.value < 84) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="absolute flex cursor-ew-resize flex-col items-center text-brand-primary"
|
||||
style={{ top: 8, left: displayTime * 4 * scale.value + 84 }}
|
||||
style={{
|
||||
top: 8,
|
||||
left: displayTime * 4 * scale.value + 84 - timelineScrollX.value,
|
||||
}}
|
||||
onPointerDown={onPointerDown}
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { UIEvent, useCallback, useEffect, useState } from "react";
|
||||
import { LowerPanel } from "~/modules/LowerPanel";
|
||||
|
||||
import { Camera } from "./Camera";
|
||||
@@ -10,15 +10,21 @@ import {
|
||||
deleteCharacterClip,
|
||||
selectedItem,
|
||||
ignoreDelete,
|
||||
timelineScrollX,
|
||||
timelineScrollY,
|
||||
filmLength,
|
||||
scale,
|
||||
} 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 { TimerGrid } from "~/pages/PageEnigma/comps/Timeline/TimerGrid";
|
||||
import { Scrubber } from "~/pages/PageEnigma/comps/Timeline/Scrubber";
|
||||
import { Characters } from "~/pages/PageEnigma/comps/Timeline/Characters";
|
||||
import { ObjectGroups } from "~/pages/PageEnigma/comps/Timeline/ObjectGroups";
|
||||
import useUpdateKeyframe from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe";
|
||||
import { Clip, Keyframe } from "~/pages/PageEnigma/models";
|
||||
import { RowHeaders } from "~/pages/PageEnigma/comps/Timeline/RowHeaders";
|
||||
import { pageWidth } from "~/store";
|
||||
|
||||
function getItemType(item: Clip | Keyframe | null) {
|
||||
if (!item) {
|
||||
@@ -32,6 +38,11 @@ export const Timeline = () => {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const { deleteKeyframe } = useUpdateKeyframe();
|
||||
|
||||
const onScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
||||
timelineScrollX.value = event.currentTarget.scrollLeft;
|
||||
timelineScrollY.value = event.currentTarget.scrollTop;
|
||||
}, []);
|
||||
|
||||
// implement the code to handle incoming messages from the Engine
|
||||
useQueueHandler();
|
||||
|
||||
@@ -75,17 +86,39 @@ export const Timeline = () => {
|
||||
<>
|
||||
<LowerPanel>
|
||||
<TimerGrid />
|
||||
<div className="p-4">
|
||||
<Characters />
|
||||
<div className="flex">
|
||||
<div
|
||||
className="mt-2 overflow-hidden"
|
||||
style={{
|
||||
width: 88,
|
||||
height: timelineHeight.value - 54,
|
||||
}}
|
||||
>
|
||||
<RowHeaders />
|
||||
</div>
|
||||
<div
|
||||
className="mt-2 overflow-auto"
|
||||
onScroll={onScroll}
|
||||
style={{
|
||||
width: pageWidth.value - 90,
|
||||
height: timelineHeight.value - 54,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ width: filmLength.value * 60 * 4 * scale.value + 32 }}
|
||||
>
|
||||
<Characters />
|
||||
<div className="pb-4 pr-8">
|
||||
<Camera />
|
||||
</div>
|
||||
<div className="pb-4 pr-8">
|
||||
<Audio />
|
||||
</div>
|
||||
<ObjectGroups />
|
||||
</div>
|
||||
</div>
|
||||
<Scrubber />
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Camera />
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Audio />
|
||||
</div>
|
||||
<ObjectGroups />
|
||||
<Scrubber />
|
||||
</LowerPanel>
|
||||
<ConfirmationModal
|
||||
title="Delete Clip"
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
timelineScrollX,
|
||||
filmLength,
|
||||
fullHeight,
|
||||
scale,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { Fragment } from "react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const TimerGrid = () => {
|
||||
useSignals();
|
||||
const sectionWidth = 60 * 4 * scale.value;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"prevent-select ml-[88px] mt-4",
|
||||
"relative flex h-7 overflow-hidden",
|
||||
"border-t border-t-ui-panel-border",
|
||||
"text-xs text-white opacity-75",
|
||||
].join(" ")}
|
||||
>
|
||||
<div className="absolute" style={{ left: timelineScrollX.value * -1 }}>
|
||||
{Array(filmLength.value)
|
||||
.fill(0)
|
||||
.map((_, index) => (
|
||||
<Fragment key={index}>
|
||||
<div
|
||||
className="absolute ps-1 pt-2"
|
||||
style={{ left: index * sectionWidth + 4 }}
|
||||
>
|
||||
00:{index < 10 ? "0" + index.toString() : index.toString()}
|
||||
</div>
|
||||
<div
|
||||
className="absolute block bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
left: index * sectionWidth,
|
||||
height: fullHeight.value,
|
||||
}}
|
||||
/>
|
||||
{Array(9)
|
||||
.fill(0)
|
||||
.map((_, ind) => (
|
||||
<div
|
||||
key={ind}
|
||||
className="absolute block h-2 bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
top: 0,
|
||||
left:
|
||||
index * sectionWidth + (sectionWidth / 10) * (ind + 1),
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
<div
|
||||
className="absolute pt-2"
|
||||
style={{ left: filmLength.value * sectionWidth + 4 }}
|
||||
>
|
||||
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,
|
||||
height: fullHeight.value,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -23,8 +23,6 @@ interface Props {
|
||||
title: string;
|
||||
group: ClipGroup;
|
||||
type?: ClipType;
|
||||
toggleMute?: () => void;
|
||||
muted?: boolean;
|
||||
updateClip: (options: { id: string; length: number; offset: number }) => void;
|
||||
}
|
||||
|
||||
@@ -56,9 +54,7 @@ function getCanBuild({
|
||||
export const TrackClips = ({
|
||||
id,
|
||||
clips,
|
||||
toggleMute,
|
||||
updateClip,
|
||||
muted,
|
||||
title,
|
||||
group,
|
||||
type,
|
||||
@@ -81,7 +77,7 @@ export const TrackClips = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Now check the the clip fits
|
||||
// Now check if the clip fits
|
||||
const position = track.getBoundingClientRect();
|
||||
const clipOffset = (event.clientX - position.x) / 4 / scale.value;
|
||||
|
||||
@@ -114,49 +110,31 @@ export const TrackClips = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pl-16">
|
||||
<div
|
||||
id={`track-${trackType}-${id}`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${group}-unselected`}
|
||||
onPointerOver={onPointerOver}
|
||||
onPointerLeave={onPointerLeave}
|
||||
onPointerMove={onPointerMove}
|
||||
>
|
||||
{clips.map((clip, index) => (
|
||||
<TrackClip
|
||||
key={clip.clip_uuid}
|
||||
min={
|
||||
index > 0 ? clips[index - 1].offset + clips[index - 1].length : 0
|
||||
}
|
||||
max={
|
||||
index < clips.length - 1
|
||||
? clips[index + 1].offset
|
||||
: filmLength.value * 60
|
||||
}
|
||||
group={group}
|
||||
updateClip={updateClip}
|
||||
clip={clip}
|
||||
/>
|
||||
))}
|
||||
<div className="prevent-select absolute ps-2 pt-1 text-xs font-medium text-white">
|
||||
{title}
|
||||
</div>
|
||||
{!!toggleMute && (
|
||||
<button
|
||||
className="text-md absolute text-white transition-colors duration-100 hover:text-white/80"
|
||||
style={{ top: 6, left: -28 }}
|
||||
onClick={toggleMute}
|
||||
>
|
||||
{muted ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faVolumeSlash}
|
||||
className="text-brand-primary transition-colors duration-100 hover:text-brand-primary/80"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faVolume} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
id={`track-${trackType}-${id}`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${group}-unselected`}
|
||||
onPointerOver={onPointerOver}
|
||||
onPointerLeave={onPointerLeave}
|
||||
onPointerMove={onPointerMove}
|
||||
>
|
||||
{clips.map((clip, index) => (
|
||||
<TrackClip
|
||||
key={clip.clip_uuid}
|
||||
min={
|
||||
index > 0 ? clips[index - 1].offset + clips[index - 1].length : 0
|
||||
}
|
||||
max={
|
||||
index < clips.length - 1
|
||||
? clips[index + 1].offset
|
||||
: filmLength.value * 60
|
||||
}
|
||||
group={group}
|
||||
updateClip={updateClip}
|
||||
clip={clip}
|
||||
/>
|
||||
))}
|
||||
<div className="prevent-select absolute ps-2 pt-1 text-xs font-medium text-white">
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ export const TrackKeyFrames = ({
|
||||
group,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className="pl-16">
|
||||
<div>
|
||||
<div
|
||||
id={`track-${group}-${id}`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${group}-unselected`}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
currentScroll,
|
||||
timelineScrollX,
|
||||
currentTime,
|
||||
filmLength,
|
||||
scale,
|
||||
@@ -26,7 +26,7 @@ export const useMouseEventsScrubber = () => {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_TIME,
|
||||
data: { currentTime: Math.round(time) + currentScroll.value },
|
||||
data: { currentTime: Math.round(time) + timelineScrollX.value },
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -46,7 +46,7 @@ export const useMouseEventsScrubber = () => {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_TIME,
|
||||
data: { currentTime: delta + currentScroll.value },
|
||||
data: { currentTime: delta + timelineScrollX.value },
|
||||
});
|
||||
}
|
||||
return delta;
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -73,7 +73,7 @@ export function deleteObjectKeyframe(keyframe: Keyframe) {
|
||||
const obj = oldObjectGroup.objects.find(
|
||||
(row) => row.object_uuid === keyframe.object_uuid,
|
||||
);
|
||||
console.log("delete", obj, keyframe.keyframe_uuid);
|
||||
|
||||
if (!obj) {
|
||||
return oldObjectGroup;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export const timelineHeight = signal(0);
|
||||
|
||||
export const fullHeight = computed(() => {
|
||||
return (
|
||||
characterGroup.value.length * 268 +
|
||||
characterGroup.value.characters.length * 268 +
|
||||
objectGroup.value.objects.length * 60 +
|
||||
300 +
|
||||
96
|
||||
|
||||
@@ -2,4 +2,5 @@ import { signal } from "@preact/signals-core";
|
||||
|
||||
export const currentTime = signal(0);
|
||||
|
||||
export const currentScroll = signal(0);
|
||||
export const timelineScrollX = signal(0);
|
||||
export const timelineScrollY = signal(0);
|
||||
Reference in new issue
Block a user