From 27df6c20e61c97fc525259f11728acd0fe759f34 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Fri, 15 Mar 2024 21:45:33 +0800 Subject: [PATCH] improve types --- .eslintignore | 1 + .eslintrc.cjs | 5 +- .gitignore | 1 + package.json | 6 +- public/compatPlayer.js | 5 +- public/configStore.js | 10 +- public/contextMenu.js | 6 +- public/electron.js | 16 +- public/ffmpeg.js | 5 + public/httpServer.js | 3 +- public/menu.js | 1 + public/update-checker.js | 1 + src/App.tsx | 59 +- src/NoFileLoaded.tsx | 4 +- src/{SegmentList.jsx => SegmentList.tsx} | 185 +++++- ...ardShortcuts.jsx => KeyboardShortcuts.tsx} | 54 +- src/components/SegmentCutpointButton.tsx | 4 +- src/components/Select.tsx | 2 +- src/components/SetCutpointButton.tsx | 4 +- src/components/Settings.tsx | 39 +- src/components/Sheet.tsx | 2 +- src/components/Switch.tsx | 4 +- src/contexts.js | 6 - src/contexts.ts | 32 ++ src/dialogs/index.tsx | 6 +- src/ffmpeg.ts | 4 +- src/global.d.ts | 1 - src/hooks/useContextMenu.ts | 5 +- src/hooks/useFfmpegOperations.ts | 13 +- src/hooks/useFrameCapture.ts | 10 +- src/hooks/{useKeyboard.js => useKeyboard.ts} | 18 +- src/hooks/useSegments.ts | 40 +- src/hooks/useUserSettings.js | 5 - src/hooks/useUserSettings.ts | 10 + src/hooks/useUserSettingsRoot.ts | 18 +- src/index.tsx | 2 +- src/segments.ts | 12 +- src/types.ts | 36 +- src/util.ts | 14 +- src/util/{colors.js => colors.ts} | 11 +- src/util/constants.js | 37 -- src/util/constants.ts | 9 + src/util/outputNameTemplate.ts | 4 +- tsconfig.json | 3 +- tsconfig.main.json | 21 + tsconfig.web.json | 4 +- types.ts | 102 ++++ yarn.lock | 526 +++--------------- 48 files changed, 691 insertions(+), 675 deletions(-) rename src/{SegmentList.jsx => SegmentList.tsx} (69%) rename src/components/{KeyboardShortcuts.jsx => KeyboardShortcuts.tsx} (91%) delete mode 100644 src/contexts.js create mode 100644 src/contexts.ts delete mode 100644 src/global.d.ts rename src/hooks/{useKeyboard.js => useKeyboard.ts} (59%) delete mode 100644 src/hooks/useUserSettings.js create mode 100644 src/hooks/useUserSettings.ts rename src/util/{colors.js => colors.ts} (69%) delete mode 100644 src/util/constants.js create mode 100644 src/util/constants.ts create mode 100644 tsconfig.main.json create mode 100644 types.ts diff --git a/.eslintignore b/.eslintignore index 22b1dba..65c7365 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ /dist /vite-dist +/ts-dist \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index aa03719..62ce0b3 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -18,10 +18,7 @@ module.exports = { browser: true, }, rules: { - 'import/no-extraneous-dependencies': ['error', { - devDependencies: true, - optionalDependencies: false, - }], + 'import/no-extraneous-dependencies': 0, }, }, { diff --git a/.gitignore b/.gitignore index b9d57b5..ccfeb8f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ node_modules /doc /ffmpeg /app.log +/ts-dist diff --git a/package.json b/package.json index 88e61e6..321280d 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,13 @@ "@radix-ui/react-switch": "^1.0.1", "@tsconfig/strictest": "^2.0.2", "@tsconfig/vite-react": "^3.0.0", + "@types/color": "^3.0.6", + "@types/css-modules": "^1.0.5", "@types/eslint": "^8", "@types/lodash": "^4.14.202", "@types/node": "18", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", "@types/sortablejs": "^1.15.0", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", @@ -100,8 +104,8 @@ "sortablejs": "^1.13.0", "sweetalert2": "^11.0.0", "sweetalert2-react-content": "^5.0.7", + "tiny-invariant": "^1.3.3", "typescript": "~5.2.0", - "typescript-plugin-css-modules": "^5.1.0", "use-debounce": "^5.1.0", "use-trace-update": "^1.3.0", "vite": "^4.5.2", diff --git a/public/compatPlayer.js b/public/compatPlayer.js index 362fc0f..016d928 100644 --- a/public/compatPlayer.js +++ b/public/compatPlayer.js @@ -63,7 +63,9 @@ function createMediaSourceStream({ path, videoStreamIndex, audioStreamIndex, see return; } - if (!err.killed) { + // @ts-expect-error todo + if (!(err.killed)) { + // @ts-expect-error todo console.warn(err.message); console.warn(stderr.toString('utf8')); } @@ -89,6 +91,7 @@ function readOneJpegFrameWrapper({ path, seekTo, videoStreamIndex }) { const { stdout } = await process; return stdout; } catch (err) { + // @ts-expect-error todo logger.error('renderOneJpegFrame', err.shortMessage); throw new Error('Failed to render JPEG frame'); } diff --git a/public/configStore.js b/public/configStore.js index 08dba4a..4c1dcf2 100644 --- a/public/configStore.js +++ b/public/configStore.js @@ -10,6 +10,7 @@ const logger = require('./logger'); const { app } = electron; +/** @type {import('../types').KeyBinding[]} */ const defaultKeyBindings = [ { keys: 'plus', action: 'addSegment' }, { keys: 'space', action: 'togglePlayResetSpeed' }, @@ -80,6 +81,7 @@ const defaultKeyBindings = [ { keys: 'alt+down', action: 'decreaseVolume' }, ]; +/** @type {import('../types').Config} */ const defaults = { captureFormat: 'jpeg', customOutDir: undefined, @@ -128,13 +130,14 @@ const defaults = { enableNativeHevc: true, enableUpdateCheck: true, cleanupChoices: { - trashTmpFiles: true, askForCleanup: true, closeFile: true, + trashTmpFiles: true, askForCleanup: true, closeFile: true, cleanupAfterExport: false, }, allowMultipleInstances: false, darkMode: true, preferStrongColors: false, outputFileNameMinZeroPadding: 1, cutFromAdjustmentFrames: 0, + invertTimelineScroll: undefined, }; // For portable app: https://github.com/mifi/lossless-cut/issues/645 @@ -146,7 +149,7 @@ async function getCustomStoragePath() { // https://github.com/mifi/lossless-cut/issues/645#issuecomment-1001363314 // https://stackoverflow.com/questions/46307797/how-to-get-the-original-path-of-a-portable-electron-app // https://github.com/electron-userland/electron-builder/blob/master/docs/configuration/nsis.md - const customStorageDir = process.env.PORTABLE_EXECUTABLE_DIR || dirname(app.getPath('exe')); + const customStorageDir = process.env['PORTABLE_EXECUTABLE_DIR'] || dirname(app.getPath('exe')); const customConfigPath = join(customStorageDir, 'config.json'); if (await pathExists(customConfigPath)) return customStorageDir; return undefined; @@ -158,15 +161,18 @@ async function getCustomStoragePath() { let store; +/** @type {import('../types').StoreGetConfig} */ function get(key) { return store.get(key); } +/** @type {import('../types').StoreSetConfig} */ function set(key, val) { if (val === undefined) store.delete(key); else store.set(key, val); } +/** @type {import('../types').StoreResetConfig} */ function reset(key) { set(key, defaults[key]); } diff --git a/public/contextMenu.js b/public/contextMenu.js index 16a30a1..6aadef5 100644 --- a/public/contextMenu.js +++ b/public/contextMenu.js @@ -6,7 +6,7 @@ module.exports = (window) => { const selectionMenu = Menu.buildFromTemplate([ { role: 'copy' }, { type: 'separator' }, - { role: 'selectall' }, + { role: 'selectAll' }, ]); const inputMenu = Menu.buildFromTemplate([ @@ -17,10 +17,10 @@ module.exports = (window) => { { role: 'copy' }, { role: 'paste' }, { type: 'separator' }, - { role: 'selectall' }, + { role: 'selectAll' }, ]); - window.webContents.on('context-menu', (e, props) => { + window.webContents.on('context-menu', (_e, props) => { const { selectionText, isEditable } = props; if (isEditable) { inputMenu.popup(window); diff --git a/public/electron.js b/public/electron.js index 425bf18..a6444b9 100644 --- a/public/electron.js +++ b/public/electron.js @@ -133,7 +133,6 @@ function createWindow() { ...getSizeOptions(), darkTheme: true, webPreferences: { - enableRemoteModule: true, contextIsolation: false, nodeIntegration: true, // https://github.com/electron/electron/issues/5107 @@ -226,15 +225,17 @@ function initApp() { // However when users start your app in command line, the system's single instance mechanism will be bypassed, and you have to use this method to ensure single instance. // This can be tested with one terminal: npx electron . // and another terminal: npx electron . path/to/file.mp4 - app.on('second-instance', (event, commandLine, workingDirectory, additionalData) => { + app.on('second-instance', (_event, _commandLine, _workingDirectory, additionalData) => { // Someone tried to run a second instance, we should focus our window. if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } + // @ts-expect-error todo if (!Array.isArray(additionalData?.argv)) return; + // @ts-expect-error todo const argv2 = parseCliArgs(additionalData.argv); logger.info('second-instance', argv2); @@ -268,26 +269,27 @@ function initApp() { event.preventDefault(); // recommended in docs https://www.electronjs.org/docs/latest/api/app#event-open-file-macos }); - ipcMain.on('setAskBeforeClose', (e, val) => { + ipcMain.on('setAskBeforeClose', (_e, val) => { askBeforeClose = val; }); - ipcMain.on('setLanguage', (e, language) => { + ipcMain.on('setLanguage', (_e, language) => { i18n.changeLanguage(language).then(() => updateMenu()).catch((err) => logger.error('Failed to set language', err)); }); - ipcMain.handle('tryTrashItem', async (e, path) => { + ipcMain.handle('tryTrashItem', async (_e, path) => { try { await stat(path); } catch (err) { + // @ts-expect-error todo if (err.code === 'ENOENT') return; } await shell.trashItem(path); }); - ipcMain.handle('showItemInFolder', (e, path) => shell.showItemInFolder(path)); + ipcMain.handle('showItemInFolder', (_e, path) => shell.showItemInFolder(path)); - ipcMain.on('apiKeyboardActionResponse', (e, { id }) => { + ipcMain.on('apiKeyboardActionResponse', (_e, { id }) => { apiKeyboardActionRequests.get(id)?.(); }); } diff --git a/public/ffmpeg.js b/public/ffmpeg.js index 2adf032..c35c677 100644 --- a/public/ffmpeg.js +++ b/public/ffmpeg.js @@ -61,6 +61,7 @@ function handleProgress(process, durationIn, onProgress, customMatcher = () => u // eslint-disable-next-line unicorn/better-regex if (!match) match = line.match(/(?:size|Lsize)=\s*[^\s]+\s+time=\s*([^\s]+)\s+/); if (!match) { + // @ts-expect-error todo customMatcher(line); return; } @@ -86,11 +87,13 @@ function handleProgress(process, durationIn, onProgress, customMatcher = () => u const progress = duration ? Math.min(progressTime / duration, 1) : 0; // sometimes progressTime will be greater than cutDuration onProgress(progress); } catch (err) { + // @ts-expect-error todo console.log('Failed to parse ffmpeg progress line:', err.message); } }); } +// @ts-expect-error todo function getExecaOptions({ env, ...customExecaOptions } = {}) { const execaOptions = { ...customExecaOptions, env: { ...env } }; // https://github.com/mifi/lossless-cut/issues/1143#issuecomment-1500883489 @@ -251,6 +254,7 @@ async function detectSceneChanges({ filePath, minChange, onProgress, from, to }) const match = line.match(/^frame:\d+\s+pts:\d+\s+pts_time:([\d.]+)/); if (!match) return; const time = parseFloat(match[1]); + // @ts-expect-error todo if (Number.isNaN(time) || time <= times.at(-1)) return; times.push(time); }); @@ -280,6 +284,7 @@ async function detectIntervals({ filePath, customArgs, onProgress, from, to, mat if (start == null || end == null || Number.isNaN(start) || Number.isNaN(end)) return; segments.push({ start, end }); } + // @ts-expect-error todo handleProgress(process, to - from, onProgress, customMatcher); await process; diff --git a/public/httpServer.js b/public/httpServer.js index 70fa883..dcb8bd9 100644 --- a/public/httpServer.js +++ b/public/httpServer.js @@ -20,7 +20,7 @@ module.exports = ({ port, onKeyboardAction }) => { const apiRouter = express.Router(); - app.get('/', (req, res) => res.send(`See ${homepage}`)); + app.get('/', (_req, res) => res.send(`See ${homepage}`)); app.use('/api', apiRouter); @@ -38,6 +38,7 @@ module.exports = ({ port, onKeyboardAction }) => { const host = '127.0.0.1'; server.listen(port, host, () => { logger.info('HTTP API listening on', `http://${host}:${port}/`); + // @ts-expect-error tod resolve(); }); diff --git a/public/menu.js b/public/menu.js index 36d05d5..2cd15ac 100644 --- a/public/menu.js +++ b/public/menu.js @@ -442,5 +442,6 @@ module.exports = ({ app, mainWindow, newVersion, isStoreBuild }) => { }); } + // @ts-expect-error todo Menu.setApplicationMenu(Menu.buildFromTemplate(menu)); }; diff --git a/public/update-checker.js b/public/update-checker.js index 5a90888..95fe75f 100644 --- a/public/update-checker.js +++ b/public/update-checker.js @@ -29,6 +29,7 @@ async function checkNewVersion() { if (semver.lt(currentVersion, newestVersion)) return newestVersion; return undefined; } catch (err) { + // @ts-expect-error todo logger.error('Failed to check github version', err.message); return undefined; } diff --git a/src/App.tsx b/src/App.tsx index 0daf23b..f2dd830 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,6 +15,7 @@ import sortBy from 'lodash/sortBy'; import flatMap from 'lodash/flatMap'; import isEqual from 'lodash/isEqual'; import sum from 'lodash/sum'; +import invariant from 'tiny-invariant'; import theme from './theme'; import useTimelineScroll from './hooks/useTimelineScroll'; @@ -85,7 +86,8 @@ import { rightBarWidth, leftBarWidth, ffmpegExtractWindow, zoomMax } from './uti import BigWaveform from './components/BigWaveform'; import isDev from './isDev'; -import { EdlFileType, FfmpegCommandLog, FfprobeChapter, FfprobeFormat, FfprobeStream, Html5ifyMode, PlaybackMode, SegmentToExport, StateSegment, Thumbnail, TunerType } from './types'; +import { EdlFileType, FfmpegCommandLog, FfprobeChapter, FfprobeFormat, FfprobeStream, FormatTimecode, Html5ifyMode, PlaybackMode, SegmentColorIndex, SegmentTags, SegmentToExport, StateSegment, Thumbnail, TunerType } from './types'; +import { CaptureFormat, KeyboardAction } from '../types'; const electron = window.require('electron'); const { exists } = window.require('fs-extra'); @@ -160,7 +162,7 @@ function App() { const [mifiLink, setMifiLink] = useState(); const [alwaysConcatMultipleFiles, setAlwaysConcatMultipleFiles] = useState(false); const [editingSegmentTagsSegmentIndex, setEditingSegmentTagsSegmentIndex] = useState(); - const [editingSegmentTags, setEditingSegmentTags] = useState>(); + const [editingSegmentTags, setEditingSegmentTags] = useState(); const [mediaSourceQuality, setMediaSourceQuality] = useState(0); const incrementMediaSourceQuality = useCallback(() => setMediaSourceQuality((v) => (v + 1) % mediaSourceQualities.length), []); @@ -404,8 +406,8 @@ function App() { userSeekAbs(nextFrame / fps); }, [detectedFps, userSeekAbs]); - const jumpSegStart = useCallback((index) => userSeekAbs(apparentCutSegments[index]!.start), [apparentCutSegments, userSeekAbs]); - const jumpSegEnd = useCallback((index) => userSeekAbs(apparentCutSegments[index]!.end), [apparentCutSegments, userSeekAbs]); + const jumpSegStart = useCallback((index: number) => userSeekAbs(apparentCutSegments[index]!.start), [apparentCutSegments, userSeekAbs]); + const jumpSegEnd = useCallback((index: number) => userSeekAbs(apparentCutSegments[index]!.end), [apparentCutSegments, userSeekAbs]); const jumpCutStart = useCallback(() => jumpSegStart(currentSegIndexSafe), [currentSegIndexSafe, jumpSegStart]); const jumpCutEnd = useCallback(() => jumpSegEnd(currentSegIndexSafe), [currentSegIndexSafe, jumpSegEnd]); const jumpTimelineStart = useCallback(() => userSeekAbs(0), [userSeekAbs]); @@ -414,7 +416,7 @@ function App() { const getFrameCount = useCallback((sec: number) => getFrameCountRaw(detectedFps, sec), [detectedFps]); - const formatTimecode = useCallback(({ seconds, shorten, fileNameFriendly }) => { + const formatTimecode = useCallback(({ seconds, shorten, fileNameFriendly }) => { if (timecodeFormat === 'frameCount') { const frameCount = getFrameCount(seconds); return frameCount != null ? String(frameCount) : ''; @@ -532,15 +534,17 @@ function App() { const { ensureWritableOutDir, ensureAccessToSourceDir } = useDirectoryAccess({ setCustomOutDir }); const toggleCaptureFormat = useCallback(() => setCaptureFormat((f) => { - const captureFormats = ['jpeg', 'png', 'webp']; + const captureFormats: CaptureFormat[] = ['jpeg', 'png', 'webp']; let index = captureFormats.indexOf(f); if (index === -1) index = 0; index += 1; if (index >= captureFormats.length) index = 0; - return captureFormats[index]; + const newCaptureFormat = captureFormats[index]; + if (newCaptureFormat == null) throw new Error(); + return newCaptureFormat; }), [setCaptureFormat]); - const toggleKeyframeCut = useCallback((showMessage) => setKeyframeCut((val) => { + const toggleKeyframeCut = useCallback((showMessage?: boolean) => setKeyframeCut((val) => { const newVal = !val; if (showMessage && !hideAllNotifications) { if (newVal) toast.fire({ title: i18n.t('Keyframe cut enabled'), text: i18n.t('Will now cut at the nearest keyframe before the desired start cutpoint. This is recommended for most files.') }); @@ -601,7 +605,7 @@ function App() { }), [allUserSettings, changeOutDir, effectiveExportMode, toggleCaptureFormat, toggleExportConfirmEnabled, toggleKeyframeCut, toggleMovFastStart, togglePreserveMetadataOnMerge, togglePreserveMovData, toggleSafeOutputFileName, toggleSegmentsToChapters, toggleSimpleMode]); const segColorsContext = useMemo(() => ({ - getSegColor: (seg) => { + getSegColor: (seg: SegmentColorIndex) => { const color = getSegColor(seg); return preferStrongColors ? color.desaturate(0.2) : color.desaturate(0.6); }, @@ -1051,7 +1055,9 @@ function App() { if (sendErrorReport) openSendConcatReportDialogWithState(err, reportState); }, [fileFormat, openSendConcatReportDialogWithState]); - const userConcatFiles = useCallback(async ({ paths, includeAllStreams, streams, fileFormat: outFormat, outFileName, clearBatchFilesAfterConcat }) => { + const userConcatFiles = useCallback(async ({ paths, includeAllStreams, streams, fileFormat: outFormat, outFileName, clearBatchFilesAfterConcat }: { + paths: string[], includeAllStreams: boolean, streams, fileFormat: string, outFileName: string, clearBatchFilesAfterConcat: boolean, + }) => { if (workingRef.current) return; try { setConcatDialogVisible(false); @@ -1076,6 +1082,7 @@ function App() { // console.log('merge', paths); const metadataFromPath = paths[0]; + invariant(metadataFromPath != null); const { haveExcludedStreams } = await concatFiles({ paths, outPath, outDir, outFormat, metadataFromPath, includeAllStreams, streams, ffmpegExperimental, onProgress: setCutProgress, preserveMovData, movFastStart, preserveMetadataOnMerge, chapters: chaptersFromSegments, appendFfmpegCommandLog }); const warnings: string[] = []; @@ -1258,7 +1265,6 @@ function App() { setCutProgress(0); setWorking({ text: i18n.t('Merging') }); - // @ts-expect-error name only exists for invertCutSegments = false const chapterNames = segmentsToChapters && !invertCutSegments ? segmentsToExport.map((s) => s.name) : undefined; await autoConcatCutSegments({ @@ -1367,7 +1373,7 @@ function App() { } }, [filePath, getRelevantTime, usingPreviewFile, captureFrameMethod, captureFrameFromFfmpeg, customOutDir, captureFormat, captureFrameQuality, captureFrameFromTag, hideAllNotifications]); - const extractSegmentFramesAsImages = useCallback(async (segIds) => { + const extractSegmentFramesAsImages = useCallback(async (segIds: string[]) => { if (!filePath || detectedFps == null || workingRef.current) return; const segments = apparentCutSegments.filter((seg) => segIds.includes(seg.segId)); const segmentsNumFrames = segments.reduce((acc, { start, end }) => acc + (getFrameCount(end - start) ?? 0), 0); @@ -1428,7 +1434,7 @@ function App() { loadCutSegments(await readEdlFile({ type, path }), append); }, [loadCutSegments]); - const loadMedia = useCallback(async ({ filePath: fp, projectPath }) => { + const loadMedia = useCallback(async ({ filePath: fp, projectPath }: { filePath: string, projectPath?: string }) => { async function tryOpenProjectPath(path, type) { if (!(await exists(path))) return false; await loadEdlFile({ path, type }); @@ -1596,7 +1602,7 @@ function App() { const seekAccelerationRef = useRef(1); - const userOpenSingleFile = useCallback(async ({ path: pathIn, isLlcProject }) => { + const userOpenSingleFile = useCallback(async ({ path: pathIn, isLlcProject }: { path: string, isLlcProject?: boolean }) => { let path = pathIn; let projectPath; @@ -1713,7 +1719,7 @@ function App() { }, [customOutDir, enableOverwriteOutput, filePath, hideAllNotifications, mainCopiedStreams, setWorking]); - const userHtml5ifyCurrentFile = useCallback(async ({ ignoreRememberedValue } = {}) => { + const userHtml5ifyCurrentFile = useCallback(async ({ ignoreRememberedValue }: { ignoreRememberedValue?: boolean } = {}) => { if (!filePath) return; let selectedOption = rememberConvertToSupportedFormat; @@ -1764,6 +1770,7 @@ function App() { try { setWorking({ text: i18n.t('Fixing file duration') }); setCutProgress(0); + invariant(fileFormat != null); const path = await fixInvalidDuration({ fileFormat, customOutDir, duration, onProgress: setCutProgress }); if (!hideAllNotifications) toast.fire({ icon: 'info', text: i18n.t('Duration has been fixed') }); @@ -2023,7 +2030,9 @@ function App() { onEditSegmentTags(currentSegIndexSafe); }, [currentSegIndexSafe, onEditSegmentTags]); - const mainActions: Record void> = useMemo(() => { + type MainKeyboardAction = Exclude; + + const mainActions = useMemo boolean) | ((a: { keyup?: boolean | undefined }) => void)>>(() => { async function exportYouTube() { if (!checkFileOpened()) return; @@ -2145,7 +2154,6 @@ function App() { showStreamsSelector: handleShowStreamsSelectorClick, html5ify: () => userHtml5ifyCurrentFile({ ignoreRememberedValue: true }), openFilesDialog, - toggleKeyboardShortcuts, toggleSettings, openSendReportDialog: () => { openSendReportDialogWithState(); }, detectBlackScenes: ({ keyup }) => { @@ -2164,15 +2172,16 @@ function App() { showIncludeExternalStreamsDialog, toggleFullscreenVideo, }; - }, [addSegment, alignSegmentTimesToKeyframes, apparentCutSegments, askStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, checkFileOpened, cleanupFilesDialog, clearSegments, closeBatch, closeFileWithConfirm, combineOverlappingSegments, combineSelectedSegments, concatBatch, convertFormatBatch, copySegmentsToClipboard, createFixedDurationSegments, createNumSegments, createRandomSegments, createSegmentsFromKeyframes, currentSegIndexSafe, cutSegments.length, cutSegmentsHistory, deselectAllSegments, detectBlackScenes, detectSceneChanges, detectSilentScenes, duplicateCurrentSegment, editCurrentSegmentTags, extractAllStreams, extractCurrentSegmentFramesAsImages, extractSelectedSegmentsFramesAsImages, fillSegmentsGaps, goToTimecode, handleShowStreamsSelectorClick, increaseRotation, invertAllSegments, invertSelectedSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, onExportPress, onLabelSegment, openFilesDialog, openSendReportDialogWithState, pause, play, removeCutSegment, removeSelectedSegments, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCurrentSegIndex, setCutEnd, setCutStart, setPlaybackVolume, shiftAllSegmentTimes, shortStep, showIncludeExternalStreamsDialog, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleFullscreenVideo, toggleKeyboardShortcuts, toggleKeyframeCut, toggleLastCommands, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleSettings, toggleShowKeyframes, toggleShowThumbnails, toggleStreamsSelector, toggleStripAudio, toggleStripThumbnail, toggleWaveformMode, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]); + }, [addSegment, alignSegmentTimesToKeyframes, apparentCutSegments, askStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, checkFileOpened, cleanupFilesDialog, clearSegments, closeBatch, closeFileWithConfirm, combineOverlappingSegments, combineSelectedSegments, concatBatch, convertFormatBatch, copySegmentsToClipboard, createFixedDurationSegments, createNumSegments, createRandomSegments, createSegmentsFromKeyframes, currentSegIndexSafe, cutSegments.length, cutSegmentsHistory, deselectAllSegments, detectBlackScenes, detectSceneChanges, detectSilentScenes, duplicateCurrentSegment, editCurrentSegmentTags, extractAllStreams, extractCurrentSegmentFramesAsImages, extractSelectedSegmentsFramesAsImages, fillSegmentsGaps, goToTimecode, handleShowStreamsSelectorClick, increaseRotation, invertAllSegments, invertSelectedSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, onExportPress, onLabelSegment, openFilesDialog, openSendReportDialogWithState, pause, play, removeCutSegment, removeSelectedSegments, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCurrentSegIndex, setCutEnd, setCutStart, setPlaybackVolume, shiftAllSegmentTimes, shortStep, showIncludeExternalStreamsDialog, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleFullscreenVideo, toggleKeyframeCut, toggleLastCommands, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleSettings, toggleShowKeyframes, toggleShowThumbnails, toggleStreamsSelector, toggleStripAudio, toggleStripThumbnail, toggleWaveformMode, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]); - const getKeyboardAction = useCallback((action: string) => mainActions[action], [mainActions]); + const getKeyboardAction = useCallback((action: MainKeyboardAction) => mainActions[action], [mainActions]); - const onKeyPress = useCallback(({ action, keyup }: { action: string, keyup: boolean }) => { - function tryMainActions() { - const fn = getKeyboardAction(action); + const onKeyPress = useCallback(({ action, keyup }: { action: KeyboardAction, keyup?: boolean | undefined }) => { + function tryMainActions(mainAction: MainKeyboardAction) { + const fn = getKeyboardAction(mainAction); if (!fn) return { match: false }; const bubble = fn({ keyup }); + if (bubble === undefined) return { match: true }; return { match: true, bubble }; } @@ -2205,7 +2214,7 @@ function App() { } // allow main actions - const { match, bubble } = tryMainActions(); + const { match, bubble } = tryMainActions(action); if (match) return bubble; return true; // bubble the event @@ -2547,7 +2556,6 @@ function App() { {showRightBar && isFileOpened && ( 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} /> - {/* @ts-expect-error todo */} - setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} mainActions={mainActions} /> + setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> diff --git a/src/NoFileLoaded.tsx b/src/NoFileLoaded.tsx index e4d9144..4412c86 100644 --- a/src/NoFileLoaded.tsx +++ b/src/NoFileLoaded.tsx @@ -39,13 +39,13 @@ const NoFileLoaded = memo(({ mifiLink, currentCutSeg, onClick, darkMode }: { )} - {mifiLink && typeof mifiLink === 'object' && 'loadUrl' in mifiLink && typeof mifiLink.loadUrl === 'string' && mifiLink.loadUrl && ( + {mifiLink && typeof mifiLink === 'object' && 'loadUrl' in mifiLink && typeof mifiLink.loadUrl === 'string' && mifiLink.loadUrl ? (