Skip to content

Commit

Permalink
store fileinfoid for track items and project in redux and improve pre…
Browse files Browse the repository at this point in the history
…view frame rendering
  • Loading branch information
charlielee committed Dec 8, 2024
1 parent c7c48a4 commit 697b5c9
Show file tree
Hide file tree
Showing 16 changed files with 81 additions and 103 deletions.
3 changes: 2 additions & 1 deletion src/common/project/Project.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IsoDateTimeString } from "../Flavors";
import { FileInfoId, IsoDateTimeString } from "../Flavors";

export interface Project {
name: string;
directoryName: string;
projectFrameRate: number;
lastSaved: IsoDateTimeString;
fileInfoId: FileInfoId;
}
3 changes: 2 additions & 1 deletion src/common/project/TrackItem.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { FrameCount, TimelineIndex, TrackGroupId, TrackItemId } from "../Flavors";
import { FileInfoId, FrameCount, TimelineIndex, TrackGroupId, TrackItemId } from "../Flavors";

export interface TrackItem {
id: TrackItemId;
length: FrameCount;
fileName: string;
fileNumber: TimelineIndex;
trackGroupId: TrackGroupId;
fileInfoId: FileInfoId;
}
1 change: 1 addition & 0 deletions src/common/testConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const PROJECT: Project = {
directoryName: PROJECT_DIRECTORY_NAME,
projectFrameRate: DEFAULT_PROJECT_FRAME_RATE,
lastSaved: new Date("2024-01-01").toISOString(),
fileInfoId: "ca4d6500-013c-4a89-b0aa-a18387e6af30",
};

export const TAKE: Take = {
Expand Down
10 changes: 4 additions & 6 deletions src/renderer/components/animator/Preview/Preview.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useContext } from "react";
import { TrackItem } from "../../../../common/project/TrackItem";
import { ImagingDeviceContext } from "../../../context/ImagingDeviceContext/ImagingDeviceContext";
import PlaybackContext from "../../../context/PlaybackContext/PlaybackContext";
import { ProjectFilesContext } from "../../../context/ProjectFilesContext.tsx/ProjectFilesContext";
Expand All @@ -15,16 +14,15 @@ export const Preview = (): JSX.Element => {
const { take } = useProjectAndTake();
const { deviceIdentifier, deviceStatus, deviceLoading } = useContext(ImagingDeviceContext);

const { getTrackItemFileInfo } = useContext(ProjectFilesContext);
const { hasCameraAccess } = useContext(ImagingDeviceContext);
const { liveViewVisible, timelineIndex } = useContext(PlaybackContext);
const { getTrackItemObjectURL } = useContext(ProjectFilesContext);

const highlightedTrackItem = getHighlightedTrackItem(take.frameTrack, timelineIndex);

const getTrackItemObjectURL = (trackItem: TrackItem) =>
getTrackItemFileInfo!(trackItem.id)?.objectURL;

const previewSrc = highlightedTrackItem ? getTrackItemObjectURL(highlightedTrackItem) : undefined;
const previewSrc = highlightedTrackItem
? getTrackItemObjectURL?.(highlightedTrackItem)
: undefined;

if (!hasCameraAccess) {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useEffect, useRef } from "react";
import "./PreviewFrame.css";

interface PreviewFrameProps {
Expand All @@ -9,15 +9,14 @@ interface PreviewFrameProps {
const PreviewFrame = ({ src, opacity }: PreviewFrameProps): JSX.Element => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const imageRef = useRef<HTMLImageElement>(new Image());
const [imageWidth, setImageWidth] = useState(0);
const [imageHeight, setImageHeight] = useState(0);

const drawImage = () => {
const context = canvasRef.current?.getContext("2d");
if (!context) {
return;
if (canvasRef.current) {
canvasRef.current.width = imageRef.current.naturalWidth;
canvasRef.current.height = imageRef.current.naturalHeight;
}

const context = canvasRef.current?.getContext("2d");
context?.drawImage(imageRef.current, 0, 0);
};

Expand All @@ -28,25 +27,11 @@ const PreviewFrame = ({ src, opacity }: PreviewFrameProps): JSX.Element => {
const image = imageRef.current;
image.src = src;
image.addEventListener("load", drawImage);
setImageWidth(image.naturalWidth);
setImageHeight(image.naturalHeight);

return () => image.removeEventListener("load", drawImage);
}, [src]);

useEffect(() => {
drawImage();
}, [imageWidth, imageHeight]);

return (
<canvas
className="preview-frame"
ref={canvasRef}
width={imageWidth}
height={imageHeight}
style={{ opacity }}
/>
);
return <canvas className="preview-frame" ref={canvasRef} style={{ opacity }} />;
};

export default PreviewFrame;
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useContext } from "react";
import { useSelector } from "react-redux";
import { TrackItem } from "../../../../../common/project/TrackItem";
import PlaybackContext from "../../../../context/PlaybackContext/PlaybackContext";
import { ProjectFilesContext } from "../../../../context/ProjectFilesContext.tsx/ProjectFilesContext";
import useProjectAndTake from "../../../../hooks/useProjectAndTake";
Expand All @@ -10,10 +9,7 @@ import PreviewFrame from "../PreviewFrame/PreviewFrame";
export const PreviewFrameOnionSkin = () => {
const { take } = useProjectAndTake();
const { liveViewVisible } = useContext(PlaybackContext);
const { getTrackItemFileInfo } = useContext(ProjectFilesContext);

const getTrackItemObjectURL = (trackItem: TrackItem) =>
getTrackItemFileInfo!(trackItem.id)?.objectURL;
const { getTrackItemObjectURL } = useContext(ProjectFilesContext);

const enableOnionSkin = useSelector((state: RootState) => state.project.enableOnionSkin);
const onionSkinOpacity = useSelector((state: RootState) => state.project.onionSkinOpacity);
Expand All @@ -25,13 +21,11 @@ export const PreviewFrameOnionSkin = () => {

const showOnionSkinFrames = liveViewVisible && enableOnionSkin;

// todo handle not showing onion skin when rapid capture (probably due to getTrackItemFileInfo being undefined)
console.log(trackItems);

// todo why does this render so many times on load
if (showOnionSkinFrames) {
return trackItems.map((trackItem) => (
<PreviewFrame
src={getTrackItemObjectURL(trackItem)}
src={getTrackItemObjectURL?.(trackItem)}
opacity={onionSkinOpacity / 100}
key={trackItem.id}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { FileInfoType } from "../../../../services/fileManager/FileInfo";
import { useContext } from "react";
import { TimelineIndex } from "../../../../../common/Flavors";
import { Track } from "../../../../../common/project/Track";
import { ProjectFilesContext } from "../../../../context/ProjectFilesContext.tsx/ProjectFilesContext";
import { FileInfoType } from "../../../../services/fileManager/FileInfo";
import {
getHighlightedTrackItem,
getTrackItemTitle,
Expand All @@ -9,9 +11,6 @@ import TimelineLiveViewButton from "../TimelineLiveView/TimelineLiveView";
import TimelineTrackItem from "../TimelineTrackItem/TimelineTrackItem";
import TimelineTrackNoItems from "../TimelineTrackNoItems/TimelineTrackNoItems";
import "./TimelineTrack.css";
import { useContext } from "react";
import { ProjectFilesContext } from "../../../../context/ProjectFilesContext.tsx/ProjectFilesContext";
import { TrackItem } from "../../../../../common/project/TrackItem";

interface TimelineTrackProps {
track: Track;
Expand All @@ -27,10 +26,7 @@ const TimelineTrack = ({
onClickLiveView,
}: TimelineTrackProps): JSX.Element => {
const highlightedTrackItem = getHighlightedTrackItem(track, timelineIndex);
const { getTrackItemFileInfo } = useContext(ProjectFilesContext);

const getTrackItemObjectURL = (trackItem: TrackItem) =>
getTrackItemFileInfo!(trackItem.id)?.objectURL;
const { getTrackItemObjectURL } = useContext(ProjectFilesContext);

return (
<div className="timeline-track">
Expand All @@ -40,7 +36,7 @@ const TimelineTrack = ({
return (
<TimelineTrackItem
title={getTrackItemTitle(track, i)}
dataUrl={getTrackItemObjectURL(trackItem)}
dataUrl={getTrackItemObjectURL?.(trackItem)}
highlighted={highlightedTrackItem?.id === trackItem.id}
key={trackItem.id}
onClick={() => onClickItem(i)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { notifications } from "@mantine/notifications";
import { ReactNode, useContext } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useSelector } from "react-redux";
import cameraSound from "../../audio/camera.wav";
import useProjectAndTake from "../../hooks/useProjectAndTake";
import { addFrameTrackItem } from "../../redux/slices/projectSlice";
import { RootState } from "../../redux/store";
import { makeFrameTrackItem } from "../../services/project/projectBuilder";
import { getNextFileNumber } from "../../services/project/projectCalculator";
Expand All @@ -17,7 +16,6 @@ interface CaptureContextProviderProps {
}

const CaptureContextProvider = ({ children }: CaptureContextProviderProps) => {
const dispatch = useDispatch();
const { take } = useProjectAndTake();
const playCaptureSound = useSelector(
(state: RootState) => state.app.userPreferences.playCaptureSound
Expand Down Expand Up @@ -46,8 +44,7 @@ const CaptureContextProvider = ({ children }: CaptureContextProviderProps) => {

const fileNumber = getNextFileNumber(take.frameTrack);
const trackItem = makeFrameTrackItem(take, fileNumber);
await saveTrackItemToDisk!(take, trackItem, imageData);
dispatch(addFrameTrackItem(trackItem));
await saveTrackItemToDisk?.(take, trackItem, imageData);
} catch (e) {
rLogger.warn(
"captureImageError",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const PlaybackContextProvider = ({ take, children }: PlaybackContextProviderProp
"playback.deleteFrameAtCurrentTimelineIndex.deleted",
`deleted track item ${trackItem.fileName}`
);
await deleteTrackItem?.(trackItem.id);
await deleteTrackItem?.(trackItem);
notifications.show({ message: "Deleted frame" });
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { createContext } from "react";
import { TrackItemId } from "../../../common/Flavors";
import { Take } from "../../../common/project/Take";
import { TrackItem } from "../../../common/project/TrackItem";
import { FileInfo } from "../../services/fileManager/FileInfo";

interface ProjectFilesContextProps {
saveTrackItemToDisk: (take: Take, trackItem: TrackItem, blob: Blob) => Promise<void>;
getTrackItemFileInfo: (trackItemId: TrackItemId) => FileInfo | undefined;
deleteTrackItem: (trackItemId: TrackItemId) => Promise<void>;
deleteTrackItem: (trackItem: TrackItem) => Promise<void>;
getTrackItemObjectURL: (trackItem: TrackItem) => string;
}

export const ProjectFilesContext = createContext<Partial<ProjectFilesContextProps>>({});
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { ReactNode, useContext, useEffect, useState } from "react";
import { ProjectFilesContext } from "./ProjectFilesContext";
import { FileInfo } from "../../services/fileManager/FileInfo";
import { FileManagerContext } from "../FileManagerContext/FileManagerContext";
import useProjectDirectory from "../../hooks/useProjectDirectory";
import { FileInfoId, TrackItemId } from "../../../common/Flavors";
import { ReactNode, useContext, useEffect } from "react";
import { Take } from "../../../common/project/Take";
import { TrackItem } from "../../../common/project/TrackItem";
import { PROJECT_INFO_FILE_NAME } from "../../../common/utils";
import useProjectDirectory from "../../hooks/useProjectDirectory";
import { FileInfoType } from "../../services/fileManager/FileInfo";
import {
makeTakeDirectoryName,
makeProjectInfoFileJson,
makeTakeDirectoryName,
} from "../../services/project/projectBuilder";
import { PROJECT_INFO_FILE_NAME } from "../../../common/utils";
import { FileInfoType } from "../../services/fileManager/FileInfo";
import { FileManagerContext } from "../FileManagerContext/FileManagerContext";
import { ProjectFilesContext } from "./ProjectFilesContext";

import { Project } from "../../../common/project/Project";
import { useDispatch, useSelector } from "react-redux";
import { Project } from "../../../common/project/Project";
import { addFrameTrackItem, removeFrameTrackItem } from "../../redux/slices/projectSlice";
import { RootState } from "../../redux/store";
import * as rLogger from "../../services/rLogger/rLogger";
import { removeFrameTrackItem } from "../../redux/slices/projectSlice";

interface ProjectFilesContextProviderProps {
children: ReactNode;
Expand All @@ -31,9 +29,6 @@ export const ProjectFilesContextProvider = ({ children }: ProjectFilesContextPro
const appVersion = useSelector((state: RootState) => state.app.appVersion);
const dispatch = useDispatch();

const [projectInfoFileId, setProjectInfoFileId] = useState<FileInfoId | undefined>(undefined);
const [trackItemFiles, setTrackItemFiles] = useState<Record<TrackItemId, FileInfoId>>({});

const saveTrackItemToDisk = async (
take: Take,
trackItem: TrackItem,
Expand All @@ -42,36 +37,37 @@ export const ProjectFilesContextProvider = ({ children }: ProjectFilesContextPro
if (projectDirectory === undefined) {
throw "Missing projectDirectory";
}
if (fileManager === undefined) {
throw "Missing FileManager";
}

const takeDirectoryName = makeTakeDirectoryName(take);
const takeDirectoryHandle = await fileManager?.createDirectory(
const takeDirectoryHandle = await fileManager.createDirectory(
takeDirectoryName,
projectDirectory.handle
);
if (takeDirectoryHandle === undefined) {
throw "Unable to create take directory handle";
}

const fileInfoId = await fileManager?.createFile!(
await fileManager.createFile(
trackItem.fileInfoId,
trackItem.fileName,
takeDirectoryHandle,
FileInfoType.FRAME,
data
);
if (fileInfoId === undefined) {
throw "Unable to create track item fileInfo";
}

setTrackItemFiles((p) => ({ ...p, [trackItem.id]: fileInfoId }));
dispatch(addFrameTrackItem(trackItem));
};

const getTrackItemFileInfo = (trackItemId: TrackItemId): FileInfo | undefined =>
fileManager?.findFile!(trackItemFiles[trackItemId]);
const deleteTrackItem = async (trackItem: TrackItem) => {
await fileManager?.deleteFile(trackItem.fileInfoId);
dispatch(removeFrameTrackItem(trackItem.id));
};

const deleteTrackItem = async (trackItemId: TrackItemId): Promise<void> => {
await fileManager?.deleteFile(trackItemFiles[trackItemId]);
setTrackItemFiles(({ [trackItemId]: _, ...otherTrackItemFiles }) => otherTrackItemFiles);
dispatch(removeFrameTrackItem(trackItemId));
const getTrackItemObjectURL = (trackItem: TrackItem) => {
const objectURL = fileManager?.findFile(trackItem.fileInfoId)?.objectURL;
if (objectURL === undefined) {
throw `Unable to find objectURL for trackItem ${trackItem.id}`;
}
return objectURL;
};

const updateProjectAndTakeLastSaved = (project: Project, take: Take): [Project, Take[]] => {
Expand All @@ -86,23 +82,29 @@ export const ProjectFilesContextProvider = ({ children }: ProjectFilesContextPro
if (projectDirectory === undefined) {
throw "Unable to save project file info as missing projectDirectory";
}
if (fileManager === undefined) {
throw "Unable to save project file info as missing FileManager";
}

const projectFileInfo = fileManager.findFile(project.fileInfoId);

const projectFileJson = await makeProjectInfoFileJson(appVersion, project, takes);
const profileFileString = JSON.stringify(projectFileJson);
const data = new Blob([profileFileString], { type: "application/json" });

if (projectInfoFileId) {
if (projectFileInfo) {
rLogger.info(
"projectFilesContext.saveProject.update",
`Updating project info file ${projectInfoFileId}`
`Updating project info file ${projectFileInfo.fileInfoId}`
);
await fileManager?.updateFile(projectInfoFileId, data);
await fileManager.updateFile(projectFileInfo.fileInfoId, data);
} else {
rLogger.info(
"projectFilesContext.saveProject.create",
`Creating new project info file in ${projectDirectory.handle.name}`
);
const fileInfoId = await fileManager?.createFile(
const fileInfoId = await fileManager.createFile(
project.fileInfoId,
PROJECT_INFO_FILE_NAME,
projectDirectory.handle,
FileInfoType.PROJECT_INFO,
Expand All @@ -111,7 +113,6 @@ export const ProjectFilesContextProvider = ({ children }: ProjectFilesContextPro
if (fileInfoId === undefined) {
throw "Unable to create project info file";
}
setProjectInfoFileId(fileInfoId);
}
};

Expand All @@ -125,7 +126,7 @@ export const ProjectFilesContextProvider = ({ children }: ProjectFilesContextPro

return (
<ProjectFilesContext.Provider
value={{ saveTrackItemToDisk, getTrackItemFileInfo, deleteTrackItem }}
value={{ saveTrackItemToDisk, deleteTrackItem, getTrackItemObjectURL }}
>
{children}
</ProjectFilesContext.Provider>
Expand Down
Loading

0 comments on commit 697b5c9

Please sign in to comment.