mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 01:18:34 +00:00
Add delete clip with modal
This commit is contained in:
1 parent
e36ec9317f
commit
985de175a5
8 files changed
+172
-3
No files matched your search
@@ -0,0 +1,54 @@
|
||||
import { Dialog } from "@headlessui/react";
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
title: string;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onOk?: () => void;
|
||||
okText?: string;
|
||||
okColor?: string;
|
||||
onCancel?: () => void;
|
||||
cancelText?: string;
|
||||
}
|
||||
|
||||
export const ConfirmationModal = ({
|
||||
text,
|
||||
title,
|
||||
open,
|
||||
onClose,
|
||||
onOk,
|
||||
okText = "OK",
|
||||
okColor = "bg-brand-success",
|
||||
onCancel,
|
||||
cancelText = "Cancel",
|
||||
}: Props) => {
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose}>
|
||||
<div className="fixed inset-0 flex w-screen items-center justify-center bg-white p-4 opacity-35" />
|
||||
<div className="fixed inset-0 flex w-screen items-center justify-center p-4">
|
||||
<Dialog.Panel className="bg-black p-4">
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
|
||||
<p className="my-6">{text}</p>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
{!!onCancel && (
|
||||
<button onClick={onCancel} className="rounded-lg px-3 py-2">
|
||||
{cancelText}
|
||||
</button>
|
||||
)}
|
||||
{!!onOk && (
|
||||
<button
|
||||
onClick={onOk}
|
||||
className={[okColor, "rounded-lg px-3 py-2"].join(" ")}
|
||||
>
|
||||
{okText}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -23,7 +23,6 @@ export const LowerPanel = ({ children, timelineHeight }: LowerPanelPropsI) => {
|
||||
].join(" ")}
|
||||
style={{ height: timelineHeight }}
|
||||
onPointerOver={() => {
|
||||
console.log("over");
|
||||
setOverTimeline(true);
|
||||
}}
|
||||
onPointerLeave={() => setOverTimeline(false)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Fragment, useContext } from "react";
|
||||
import { Fragment, useCallback, useContext, useEffect, useState } from "react";
|
||||
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
|
||||
import { LowerPanel } from "~/modules/LowerPanel";
|
||||
import { Character } from "./Character";
|
||||
@@ -9,19 +9,61 @@ 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";
|
||||
|
||||
interface Props {
|
||||
timelineHeight: number;
|
||||
}
|
||||
|
||||
export const Timeline = ({ timelineHeight }: Props) => {
|
||||
const { characters, objects, scale, length } = useContext(TrackContext);
|
||||
const {
|
||||
characters,
|
||||
objects,
|
||||
scale,
|
||||
length,
|
||||
selectedClip,
|
||||
deleteCharacterClip,
|
||||
deleteAudioClip,
|
||||
deleteCameraClip,
|
||||
deleteObjectClip,
|
||||
} = useContext(TrackContext);
|
||||
const { onPointerDown, time } = useMouseEventsAnimation();
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
|
||||
const sectionWidth = 60 * 4 * scale;
|
||||
const fullHeight =
|
||||
characters.length * 200 + objects.objects.length * 80 + 248 + 96;
|
||||
|
||||
const onDeleteAsk = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (["Backspace", "Delete"].indexOf(event.key) > -1 && selectedClip) {
|
||||
setDialogOpen(true);
|
||||
}
|
||||
},
|
||||
[selectedClip],
|
||||
);
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
deleteCharacterClip(selectedClip!);
|
||||
deleteCameraClip(selectedClip!);
|
||||
deleteAudioClip(selectedClip!);
|
||||
deleteObjectClip(selectedClip!);
|
||||
}, [
|
||||
selectedClip,
|
||||
deleteAudioClip,
|
||||
deleteCharacterClip,
|
||||
deleteCameraClip,
|
||||
deleteObjectClip,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("keydown", onDeleteAsk);
|
||||
|
||||
return () => {
|
||||
document.addEventListener("keydown", onDeleteAsk);
|
||||
};
|
||||
}, [onDeleteAsk]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<LowerPanel timelineHeight={timelineHeight}>
|
||||
@@ -102,6 +144,19 @@ export const Timeline = ({ timelineHeight }: Props) => {
|
||||
/>
|
||||
</div>
|
||||
</LowerPanel>
|
||||
<ConfirmationModal
|
||||
title="Delete Clip"
|
||||
text="Are you sure you want to delete the selected clip?"
|
||||
open={dialogOpen}
|
||||
onClose={() => setDialogOpen(false)}
|
||||
onOk={() => {
|
||||
onDelete();
|
||||
setDialogOpen(false);
|
||||
}}
|
||||
okText="Delete"
|
||||
okColor="bg-brand-primary"
|
||||
onCancel={() => setDialogOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -32,6 +32,7 @@ export const TrackContext = createContext<{
|
||||
offset: number;
|
||||
}) => void;
|
||||
selectCharacterClip: (clipId: string) => void;
|
||||
deleteCharacterClip: (clipId: string) => void;
|
||||
|
||||
// timeline camera group
|
||||
camera: CameraGroup | null;
|
||||
@@ -41,6 +42,7 @@ export const TrackContext = createContext<{
|
||||
offset: number;
|
||||
}) => void;
|
||||
selectCameraClip: (clipId: string) => void;
|
||||
deleteCameraClip: (clipId: string) => void;
|
||||
|
||||
// timeline global audio group
|
||||
audio: AudioGroup | null;
|
||||
@@ -52,6 +54,7 @@ export const TrackContext = createContext<{
|
||||
toggleAudioMute: () => void;
|
||||
addGlobalAudio: (clipId: string, animationClips: BaseClip[]) => void;
|
||||
selectAudioClip: (clipId: string) => void;
|
||||
deleteAudioClip: (clipId: string) => void;
|
||||
|
||||
// timeline objects group
|
||||
objects: ObjectGroup;
|
||||
@@ -61,6 +64,7 @@ export const TrackContext = createContext<{
|
||||
offset: number;
|
||||
}) => void;
|
||||
selectObjectClip: (clipId: string) => void;
|
||||
deleteObjectClip: (clipId: string) => void;
|
||||
|
||||
// current - only select one item - will be replaced
|
||||
selectedClip: string | null;
|
||||
@@ -103,20 +107,24 @@ export const TrackContext = createContext<{
|
||||
addCharacterAnimation: () => {},
|
||||
addCharacterAudio: () => {},
|
||||
selectCharacterClip: () => {},
|
||||
deleteCharacterClip: () => {},
|
||||
|
||||
camera: null,
|
||||
updateCamera: () => {},
|
||||
selectCameraClip: () => {},
|
||||
deleteCameraClip: () => {},
|
||||
|
||||
audio: null,
|
||||
updateAudio: () => {},
|
||||
toggleAudioMute: () => {},
|
||||
selectAudioClip: () => {},
|
||||
addGlobalAudio: () => {},
|
||||
deleteAudioClip: () => {},
|
||||
|
||||
objects: { id: "", objects: [] },
|
||||
updateObject: () => {},
|
||||
selectObjectClip: () => {},
|
||||
deleteObjectClip: () => {},
|
||||
|
||||
selectedClip: null,
|
||||
selectClip: () => {},
|
||||
|
||||
@@ -91,11 +91,21 @@ export default function useUpdateAudio() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const deleteAudioClip = useCallback((clipId: string) => {
|
||||
setAudio((oldAudio) => {
|
||||
return {
|
||||
...oldAudio,
|
||||
clips: [...oldAudio.clips.filter((clip) => clip.id !== clipId)],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
audio,
|
||||
updateAudio,
|
||||
toggleAudioMute,
|
||||
selectAudioClip,
|
||||
addGlobalAudio,
|
||||
deleteAudioClip,
|
||||
};
|
||||
}
|
||||
@@ -64,9 +64,19 @@ export default function useUpdateCamera() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const deleteCameraClip = useCallback((clipId: string) => {
|
||||
setCamera((oldCamera) => {
|
||||
return {
|
||||
...oldCamera,
|
||||
clips: [...oldCamera.clips.filter((clip) => clip.id !== clipId)],
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
camera,
|
||||
updateCamera,
|
||||
selectCameraClip,
|
||||
deleteCameraClip,
|
||||
};
|
||||
}
|
||||
@@ -203,6 +203,25 @@ export default function useUpdateCharacters() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const deleteCharacterClip = useCallback((clipId: string) => {
|
||||
setCharacters((oldCharacters) => {
|
||||
return [
|
||||
...oldCharacters.map((character) => ({
|
||||
...character,
|
||||
animationClips: character.animationClips.filter(
|
||||
(clip) => clip.id !== clipId,
|
||||
),
|
||||
positionClips: character.positionClips.filter(
|
||||
(clip) => clip.id !== clipId,
|
||||
),
|
||||
lipSyncClips: character.lipSyncClips.filter(
|
||||
(clip) => clip.id !== clipId,
|
||||
),
|
||||
})),
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
characters,
|
||||
updateCharacters,
|
||||
@@ -210,5 +229,6 @@ export default function useUpdateCharacters() {
|
||||
addCharacterAnimation,
|
||||
addCharacterAudio,
|
||||
selectCharacterClip,
|
||||
deleteCharacterClip,
|
||||
};
|
||||
}
|
||||
@@ -61,9 +61,22 @@ export default function useUpdateObject() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const deleteObjectClip = useCallback((clipId: string) => {
|
||||
setObjects((oldObjectGroup) => {
|
||||
return {
|
||||
...oldObjectGroup,
|
||||
objects: oldObjectGroup.objects.map((object) => ({
|
||||
...object,
|
||||
clips: object.clips.filter((clip) => clip.id !== clipId),
|
||||
})),
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
objects,
|
||||
updateObject,
|
||||
selectObjectClip,
|
||||
deleteObjectClip,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user