diff --git a/src/main/core/projects.js b/src/main/core/projects.js index 4408663..2ff7dc9 100644 --- a/src/main/core/projects.js +++ b/src/main/core/projects.js @@ -122,7 +122,6 @@ const createImageFile = async (projectPath, scene, ext, data) => { } await writeFile(filePath, data); return { - id, filename: `${id}.${ext}`, scene, path: filePath, @@ -134,7 +133,6 @@ export const savePicture = async (projectPath, track, ext, buffer) => { const trackId = Number(track); const file = await createImageFile(projectPath, trackId, ext, buffer); return { - id: file.id, filename: file.filename, deleted: false, length: 1, diff --git a/src/renderer/actions/index.js b/src/renderer/actions/index.js index 651e6a0..e06a200 100644 --- a/src/renderer/actions/index.js +++ b/src/renderer/actions/index.js @@ -31,7 +31,7 @@ const getDefaultPreview = async (data) => { for (let i = 0; i < (data?.project?.scenes?.length || 0); i++) { for (const picture of data?.project?.scenes?.[i]?.pictures || []) { if (!picture.deleted) { - return getFrameBlobUrl(picture.id); + return getFrameBlobUrl(picture.filename?.split('.')?.[0]); } } } @@ -48,7 +48,7 @@ const computeProject = async (data, bindPictureLink = true) => { pictures: await Promise.all( scene.pictures.map(async (picture) => ({ ...picture, - link: bindPictureLink ? await getFrameBlobUrl(picture.id) : null, + link: bindPictureLink ? await getFrameBlobUrl(picture.filename?.split('.')?.[0]) : null, })) ), }; @@ -150,7 +150,6 @@ export const Actions = { SAVE_PICTURE: async (evt, { buffer, extension = 'jpg' }) => { const frameId = await createFrame(buffer, extension); return { - id: `${frameId}`, filename: `${frameId}.${extension || 'dat'}`, deleted: false, length: 1, diff --git a/src/renderer/components/ShortcutsList/index.jsx b/src/renderer/components/ShortcutsList/index.jsx index 7e254a0..52936e7 100644 --- a/src/renderer/components/ShortcutsList/index.jsx +++ b/src/renderer/components/ShortcutsList/index.jsx @@ -56,6 +56,7 @@ const ShortcutsList = ({ t, shortcuts }) => { ONION_MORE: t('Increase onion skin'), ONION_LESS: t('Decrease onion skin'), MUTE: t('Mute / Unmute sounds'), + CLONE: t('Clone current frame'), DUPLICATE: t('Duplicate current frame'), DEDUPLICATE: t('Deduplicate current frame'), GRID: t('Show / Hide grid'), @@ -80,7 +81,7 @@ const ShortcutsList = ({ t, shortcuts }) => { 'ONION_MORE', 'GRID', ], - ACTIONS: ['DELETE_FRAME', 'DUPLICATE', 'DEDUPLICATE', 'HIDE_FRAME'], + ACTIONS: ['DELETE_FRAME', 'CLONE', 'DUPLICATE', 'DEDUPLICATE', 'HIDE_FRAME'], NAVIGATION: ['FRAME_LEFT', 'FRAME_RIGHT', 'FRAME_LIVE', 'FRAME_FIRST'], OTHER: ['HOME'], }; diff --git a/src/renderer/core/shortcuts.js b/src/renderer/core/shortcuts.js index 5d11c6f..dd9c959 100644 --- a/src/renderer/core/shortcuts.js +++ b/src/renderer/core/shortcuts.js @@ -26,6 +26,7 @@ const SHORTCUTS = { ONION_MORE: ['+'], ONION_LESS: ['-'], MUTE: ['m', '/', 'ctrl+m'], + CLONE: ['c'], DUPLICATE: ['pageup'], DEDUPLICATE: ['pagedown'], GRID: ['g'], diff --git a/src/renderer/hooks/useProject.js b/src/renderer/hooks/useProject.js index b393242..03e9b75 100644 --- a/src/renderer/hooks/useProject.js +++ b/src/renderer/hooks/useProject.js @@ -94,6 +94,25 @@ function useProject(options) { }); }); + // Action clone frame + const actionCloneFrame = useCallback(async (trackId, frameId) => { + const sceneId = Number(trackId); + setProjectData((oldData) => { + let d = structuredClone(oldData); + if (d.project.scenes[sceneId]) { + const newId = Math.max(0, ...d.project.scenes[sceneId].pictures.map((e) => e.id)) + 1; + d.project.scenes[sceneId].pictures = d.project.scenes[sceneId].pictures.reduce((acc, p) => { + if (`${p.id}` !== `${frameId}`) { + return [...acc, p]; + } else { + return [...acc, p, { ...p, id: newId }]; + } + }, []); + } + return d; + }); + }); + // Action delete frame const actionDeleteFrame = useCallback(async (trackId, frameId) => { const sceneId = Number(trackId); @@ -152,11 +171,12 @@ function useProject(options) { setProjectData((oldData) => { let d = structuredClone(oldData); if (d.project.scenes[sceneId]) { + const newId = Math.max(0, ...d.project.scenes[sceneId].pictures.map((e) => e.id)) + 1; const index = beforeFrameId === false ? -1 : d.project.scenes[sceneId].pictures.findIndex((f) => `${f.id}` === `${beforeFrameId}`); if (index >= 0) { - d.project.scenes[sceneId].pictures = [...d.project.scenes[sceneId].pictures.slice(0, index), addedPicture, ...d.project.scenes[sceneId].pictures.slice(index)]; + d.project.scenes[sceneId].pictures = [...d.project.scenes[sceneId].pictures.slice(0, index), { ...addedPicture, id: newId }, ...d.project.scenes[sceneId].pictures.slice(index)]; } else { - d.project.scenes[sceneId].pictures = [...d.project.scenes[sceneId].pictures, addedPicture]; + d.project.scenes[sceneId].pictures = [...d.project.scenes[sceneId].pictures, { ...addedPicture, id: newId }]; } } return d; @@ -211,7 +231,8 @@ function useProject(options) { changeFPS: actionChangeFPS, changeRatio: actionChangeRatio, applyHiddenFrameStatus: actionApplyHiddenFrameStatus, - actionApplyDuplicateFrameOffset: actionApplyDuplicateFrameOffset, + applyDuplicateFrameOffset: actionApplyDuplicateFrameOffset, + cloneFrame: actionCloneFrame, deleteFrame: actionDeleteFrame, rename: actionRename, moveFrame: actionMoveFrame, diff --git a/src/renderer/views/Animator.jsx b/src/renderer/views/Animator.jsx index fa6756b..7a87080 100644 --- a/src/renderer/views/Animator.jsx +++ b/src/renderer/views/Animator.jsx @@ -306,10 +306,13 @@ const Animator = ({ t }) => { projectActions.applyHiddenFrameStatus(track, currentFrameId, !currentFrame?.hidden); }, DUPLICATE: async () => { - projectActions.actionApplyDuplicateFrameOffset(track, currentFrameId, 1); + projectActions.applyDuplicateFrameOffset(track, currentFrameId, 1); + }, + CLONE: async () => { + projectActions.cloneFrame(track, currentFrameId); }, DEDUPLICATE: async () => { - projectActions.actionApplyDuplicateFrameOffset(track, currentFrameId, -1); + projectActions.applyDuplicateFrameOffset(track, currentFrameId, -1); }, MUTE: () => { setIsMuted(!isMuted); @@ -368,8 +371,8 @@ const Animator = ({ t }) => { showGrid={gridStatus} blendMode={differenceStatus} shortPlayStatus={shortPlayStatus} - loopStatus={loopStatus} shortPlayFrames={Number(settings.SHORT_PLAY) || 1} + loopStatus={loopStatus} cameraId={currentCameraId} cameraCapabilities={currentCameraCapabilities} fps={fps} @@ -411,7 +414,15 @@ const Animator = ({ t }) => { frameQuantity={pictures.length} isCurrentFrameHidden={!!currentFrame.hidden} /> - + {!showCameraSettings && !showProjectSettings && } setShowCameraSettings(false)}>