Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement delete frame #525

Merged
merged 14 commits into from
Oct 31, 2024
1 change: 1 addition & 0 deletions src/common/PageRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const enum PageRoute {
STARTUP_PREFERENCES_MODAL = "/startup/preferencesModal",
STARTUP_NEW_PROJECT_MODAL = "/startup/newProjectModal",
ANIMATOR = "/animator",
ANIMATOR_DELETE_FRAME = "/animator/deleteFrame",
ANIMATOR_EXPORT_VIDEO_MODAL = "/animator/exportVideoModal",
ANIMATOR_PREFERENCES_MODAL = "/animator/preferencesModal",
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useContext, useState } from "react";
import { useSelector } from "react-redux";
import { PageRoute } from "../../../../common/PageRoute";
import PlaybackContext, {
PlaybackFrameName,
} from "../../../context/PlaybackContext/PlaybackContext";
import { RootState } from "../../../redux/store";
import { getTrackLength } from "../../../services/project/projectCalculator";
import IconName from "../../common/Icon/IconName";
import IconButton from "../../common/IconButton/IconButton";
import InputRange from "../../common/Input/InputRange/InputRange";
Expand All @@ -18,20 +20,34 @@ export const AnimationToolbar = (): JSX.Element => {
);
const shortPlayFrameText = shortPlayLength === 1 ? "frame" : "frames";

const { startOrPausePlayback, stopPlayback, displayFrame, shortPlay, playing } =
useContext(PlaybackContext);
const {
startOrPausePlayback,
stopPlayback,
displayFrame,
shortPlay,
liveViewVisible,
timelineIndex,
playing,
} = useContext(PlaybackContext);
const frameTrack = useSelector((state: RootState) => state.project.take?.frameTrack);
if (frameTrack === undefined) {
throw "No frame track found in AnimationToolbar";
}

const [onionSkinAmount, setOnionSkinAmount] = useState(0);
const [loopPlayback, setLoopPlayback] = useState(false);

return (
<Toolbar className="animation-toolbar">
<ToolbarItem stretch align={ToolbarItemAlign.LEFT}>
<IconButton title="Undo Last Frame" icon={IconName.UNDO} onClick={() => undefined} />
<IconButton
title={`Short Play (${shortPlayLength} ${shortPlayFrameText})`}
icon={IconName.PLAY_SHORT}
onClick={shortPlay}
title={
timelineIndex === undefined ? "Undo Last Frame" : `Delete Frame ${timelineIndex + 1}`
}
icon={liveViewVisible ? IconName.UNDO : IconName.DELETE}
onClick={
getTrackLength(frameTrack) === 0 ? () => undefined : PageRoute.ANIMATOR_DELETE_FRAME
}
/>
<InputRange
id="animation-toolbar__onion-skin-range"
Expand Down Expand Up @@ -75,6 +91,11 @@ export const AnimationToolbar = (): JSX.Element => {

<ToolbarItem stretch align={ToolbarItemAlign.RIGHT}>
<PlaybackSpeedSelect />
<IconButton
title={`Short Play (${shortPlayLength} ${shortPlayFrameText})`}
icon={IconName.PLAY_SHORT}
onClick={shortPlay}
/>
<IconButton
title={`${loopPlayback ? "Disable" : "Enable"} Loop Playback`}
icon={IconName.PLAY_LOOP}
Expand Down
25 changes: 1 addition & 24 deletions src/renderer/components/animator/Animator/Animator.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import { useSelector } from "react-redux";
import CaptureContextProvider from "../../../context/CaptureContext/CaptureContextProvider";
import PlaybackContextProvider from "../../../context/PlaybackContext/PlaybackContextProvider";
import useProjectAndTake from "../../../hooks/useProjectAndTake";
import { RootState } from "../../../redux/store";
import Content from "../../common/Content/Content";
import Page from "../../common/Page/Page";
import PageBody from "../../common/PageBody/PageBody";
Expand All @@ -15,7 +10,7 @@ import StatusToolbar from "../StatusToolbar/StatusToolbar";
import { Timeline } from "../Timeline/Timeline";
import TitleToolbar from "../TitleToolbar/TitleToolbar";

const Animator = (): JSX.Element => {
export const Animator = (): JSX.Element => {
return (
<Page>
<TitleToolbar />
Expand All @@ -35,21 +30,3 @@ const Animator = (): JSX.Element => {
</Page>
);
};

const AnimatorWithProvider = (): JSX.Element => {
const { take } = useProjectAndTake();
const selectors = useSelector((state: RootState) => ({
shortPlayLength: state.app.userPreferences.shortPlayLength,
playbackSpeed: state.project.playbackSpeed,
}));

return (
<CaptureContextProvider>
<PlaybackContextProvider take={take} {...selectors}>
<Animator />
</PlaybackContextProvider>
</CaptureContextProvider>
);
};

export default AnimatorWithProvider;
56 changes: 10 additions & 46 deletions src/renderer/components/common/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
import { Navigate, Outlet, Route, Routes } from "react-router-dom";
import { Navigate, Route, Routes } from "react-router-dom";
import { PageRoute } from "../../../../common/PageRoute";
import Animator from "../../animator/Animator/Animator";
import ExportVideoModal from "../../modals/ExportVideoModal/ExportVideoModal";
import PreferencesModal from "../../modals/PreferencesModal/PreferencesModal";
import { NewProjectModal } from "../../modals/NewProjectModal/NewProjectModal";
import { StartupPage } from "../../startup/StartupPage/StartupPage";
import AppListener from "../AppListener/AppListener";
import AppLoad from "../AppLoad/AppLoad";
import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store";
import { useEffect } from "react";
import { FileManagerContextProvider } from "../../../context/FileManagerContext/FileManagerContextProvider";
import { ProjectFilesContextProvider } from "../../../context/ProjectFilesContext.tsx/ProjectFilesContextProvider";
import { PersistedDirectoriesContextProvider } from "../../../context/PersistedDirectoriesContext/PersistedDirectoriesContextProvider";
import { ProjectFilesContextProvider } from "../../../context/ProjectFilesContext.tsx/ProjectFilesContextProvider";
import { Theme } from "../../ui/Theme/Theme";
import AppListener from "../AppListener/AppListener";
import AppLoad from "../AppLoad/AppLoad";
import { useAnimatorRoutesAndProviders } from "./useAnimatorRoutesAndProviders";
import { useStartupRoutes } from "./useStartupRoutes";

const App = (): JSX.Element => {
const { project, take } = useSelector((state: RootState) => state.project);

useEffect(() => {
document.title = project ? `${project.name} - Boats Animator` : "Boats Animator";
}, [project]);
const startupRoutes = useStartupRoutes();
const animatorRoutes = useAnimatorRoutesAndProviders();

return (
<Theme>
Expand All @@ -32,35 +23,8 @@ const App = (): JSX.Element => {
<ProjectFilesContextProvider>
<Routes>
<Route index element={<Navigate to={PageRoute.STARTUP} />} />

<Route
path={PageRoute.STARTUP}
element={
<>
<Outlet></Outlet>
<StartupPage />
</>
}
>
<Route path={PageRoute.STARTUP_NEW_PROJECT_MODAL} element={<NewProjectModal />} />
<Route path={PageRoute.STARTUP_PREFERENCES_MODAL} element={<PreferencesModal />} />
</Route>

<Route
path={PageRoute.ANIMATOR}
element={
<>
<Outlet />
{project && take && <Animator />}
</>
}
>
<Route
path={PageRoute.ANIMATOR_EXPORT_VIDEO_MODAL}
element={<ExportVideoModal />}
/>
<Route path={PageRoute.ANIMATOR_PREFERENCES_MODAL} element={<PreferencesModal />} />
</Route>
{startupRoutes}
{animatorRoutes}
</Routes>
</ProjectFilesContextProvider>
</PersistedDirectoriesContextProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useSelector } from "react-redux";
import { Outlet, Route } from "react-router-dom";
import { PageRoute } from "../../../../common/PageRoute";
import CaptureContextProvider from "../../../context/CaptureContext/CaptureContextProvider";
import PlaybackContextProvider from "../../../context/PlaybackContext/PlaybackContextProvider";
import { RootState } from "../../../redux/store";
import { Animator } from "../../animator/Animator/Animator";
import { DeleteFrameModal } from "../../modals/DeleteFrameModal/DeleteFrameModal";
import ExportVideoModal from "../../modals/ExportVideoModal/ExportVideoModal";
import PreferencesModal from "../../modals/PreferencesModal/PreferencesModal";
import { useEffect } from "react";

export const useAnimatorRoutesAndProviders = () => {
const project = useSelector((state: RootState) => state.project.project);
const take = useSelector((state: RootState) => state.project.take);
const shortPlayLength = useSelector(
(state: RootState) => state.app.userPreferences.shortPlayLength
);
const playbackSpeed = useSelector((state: RootState) => state.project.playbackSpeed);

useEffect(() => {
document.title = project ? `${project.name} - Boats Animator` : "Boats Animator";
}, [project]);

if (project === undefined || take === undefined) {
return <></>;
}

return (
<Route
path={PageRoute.ANIMATOR}
element={
<CaptureContextProvider>
<PlaybackContextProvider
take={take}
shortPlayLength={shortPlayLength}
playbackSpeed={playbackSpeed}
>
<Outlet />
<Animator />
</PlaybackContextProvider>
</CaptureContextProvider>
}
>
<Route path={PageRoute.ANIMATOR_DELETE_FRAME} element={<DeleteFrameModal />} />
<Route path={PageRoute.ANIMATOR_EXPORT_VIDEO_MODAL} element={<ExportVideoModal />} />
<Route path={PageRoute.ANIMATOR_PREFERENCES_MODAL} element={<PreferencesModal />} />
</Route>
);
};
20 changes: 20 additions & 0 deletions src/renderer/components/common/App/useStartupRoutes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Outlet, Route } from "react-router-dom";
import { PageRoute } from "../../../../common/PageRoute";
import { NewProjectModal } from "../../modals/NewProjectModal/NewProjectModal";
import PreferencesModal from "../../modals/PreferencesModal/PreferencesModal";
import { StartupPage } from "../../startup/StartupPage/StartupPage";

export const useStartupRoutes = () => (
<Route
path={PageRoute.STARTUP}
element={
<>
<Outlet></Outlet>
<StartupPage />
</>
}
>
<Route path={PageRoute.STARTUP_NEW_PROJECT_MODAL} element={<NewProjectModal />} />
<Route path={PageRoute.STARTUP_PREFERENCES_MODAL} element={<PreferencesModal />} />
</Route>
);
5 changes: 3 additions & 2 deletions src/renderer/components/common/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
IoSyncOutline,
IoTimer,
IoToggle,
IoTrashOutline,
IoVideocam,
} from "react-icons/io5";
import "./Icon.css";
Expand Down Expand Up @@ -62,6 +63,8 @@ const getIconByName = (name: IconName, active: boolean, props: IconBaseProps) =>
return <IoCloseOutline {...props} />;
case IconName.CONNECT:
return <IoShareOutline {...props} />;
case IconName.DELETE:
return <IoTrashOutline {...props} />;
case IconName.DISCORD:
return <IoLogoDiscord {...props} />;
case IconName.DOCUMENT:
Expand Down Expand Up @@ -129,8 +132,6 @@ const getIconByName = (name: IconName, active: boolean, props: IconBaseProps) =>
return <IoFilmOutline {...props} />;
case IconName.WEBSITE:
return <IoGlobeOutline {...props} />;
// default:
// Error("No icon has been defined for this IconName in `Icon.tsx`!");
}
};

Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/common/Icon/IconName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const enum IconName {
CIRCLE = "CIRCLE",
CIRCLE_UP = "CIRCLE_UP",
CLOSE = "CLOSE",
DELETE = "DELETE",
DISCORD = "DISCORD",
DOCUMENT = "DOCUMENT",
ERROR = "ERROR",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useContext } from "react";
import { useNavigate } from "react-router-dom";
import { PageRoute } from "../../../../common/PageRoute";
import PlaybackContext from "../../../context/PlaybackContext/PlaybackContext";
import { SemanticColor } from "../../ui/Theme/SemanticColor";
import { UiButton } from "../../ui/UiButton/UiButton";
import { UiModal } from "../../ui/UiModal/UiModal";
import { UiModalFooter } from "../../ui/UiModalFooter/UiModalFooter";

export const DeleteFrameModal = () => {
const navigate = useNavigate();
const { deleteFrameAtCurrentTimelineIndex, timelineIndex } = useContext(PlaybackContext);

return (
<UiModal title="Delete frame?" onClose={PageRoute.ANIMATOR}>
<p>
This will permanently delete{" "}
{timelineIndex === undefined ? "the last frame captured" : `frame ${timelineIndex + 1}`}.
</p>
<UiModalFooter>
<UiButton onClick={PageRoute.ANIMATOR}>Cancel</UiButton>
<UiButton
onClick={async () => {
await deleteFrameAtCurrentTimelineIndex?.();
navigate(PageRoute.ANIMATOR);
}}
semanticColor={SemanticColor.DANGER}
>
Delete frame
</UiButton>
</UiModalFooter>
</UiModal>
);
};
3 changes: 3 additions & 0 deletions src/renderer/components/ui/Theme/Theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { createTheme, DEFAULT_THEME, MantineProvider } from "@mantine/core";
import { ReactNode } from "react";

import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
import "./Theme.css";
import { SemanticColor } from "./SemanticColor";
import { Notifications } from "@mantine/notifications";

interface ThemeProps {
children: ReactNode;
Expand All @@ -24,6 +26,7 @@ export const Theme = ({ children }: ThemeProps) => {

return (
<MantineProvider forceColorScheme="dark" theme={theme}>
<Notifications />
{children}
</MantineProvider>
);
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/ui/UiModalFooter/UiModalFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ interface UiModalFooterProps {
export const UiModalFooter = ({ children }: UiModalFooterProps) => (
<Paper>
<Divider my="lg" />
<Group>{children}</Group>
<Group justify="flex-end">{children}</Group>
</Paper>
);
2 changes: 2 additions & 0 deletions src/renderer/context/PlaybackContext/PlaybackContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface PlaybackContextProps {
stopPlayback: (i?: TimelineIndex | undefined, pause?: boolean) => void;
displayFrame: (name: PlaybackFrameName) => void;
shortPlay: () => void;
deleteFrameAtCurrentTimelineIndex?: () => Promise<void>;
timelineIndex: TimelineIndex | undefined;
liveViewVisible: boolean;
playing: boolean;
Expand All @@ -23,6 +24,7 @@ const defaultValue: PlaybackContextProps = {
stopPlayback: () => undefined,
displayFrame: () => undefined,
shortPlay: () => undefined,
deleteFrameAtCurrentTimelineIndex: undefined,
timelineIndex: undefined,
liveViewVisible: true,
playing: false,
Expand Down
Loading