diff --git a/package.json b/package.json index 15102aae..3f7e0819 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "license": "GPL-2.0-only", "devDependencies": { "@types/jest": "^26.0.20", - "array-move": "^3.0.1", "axios": "^0.21.2", "color": "^3.1.0", "concurrently": "^6.0.0", @@ -79,7 +78,6 @@ "react-icons": "^4.1.0", "react-lottie-player": "^1.3.3", "react-scripts": "5.0.0", - "react-sortable-hoc": "^2.0.0", "react-sortablejs": "^6.0.0", "react-syntax-highlighter": "^15.4.3", "react-use": "^17.3.2", diff --git a/src/App.jsx b/src/App.jsx index 2de8f932..a4b8ef29 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -93,7 +93,7 @@ const calcShouldShowKeyframes = (zoomedDuration) => (zoomedDuration != null && z const zoomMax = 2 ** 14; const rightBarWidth = 200; -const leftBarWidth = 200; +const leftBarWidth = 240; const videoStyle = { width: '100%', height: '100%', objectFit: 'contain' }; @@ -2224,6 +2224,7 @@ const App = memo(() => { filePath={filePath} width={leftBarWidth} batchFiles={batchFiles} + setBatchFiles={setBatchFiles} onBatchFileSelect={onBatchFileSelect} batchRemoveFile={batchRemoveFile} closeBatch={closeBatch} diff --git a/src/components/BatchFilesList.jsx b/src/components/BatchFilesList.jsx index 43c0401a..d242ac6a 100644 --- a/src/components/BatchFilesList.jsx +++ b/src/components/BatchFilesList.jsx @@ -1,8 +1,10 @@ -import React, { memo } from 'react'; +import React, { memo, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { motion } from 'framer-motion'; import { FaTimes, FaHatWizard } from 'react-icons/fa'; import { AiOutlineMergeCells } from 'react-icons/ai'; +import { ReactSortable } from 'react-sortablejs'; +import { SortAlphabeticalIcon, SortAlphabeticalDescIcon } from 'evergreen-ui'; import BatchFile from './BatchFile'; import { timelineBackground, controlsBackground } from '../colors'; @@ -16,9 +18,29 @@ const iconStyle = { padding: '3px 5px', }; -const BatchFilesList = memo(({ selectedBatchFiles, filePath, width, batchFiles, onBatchFileSelect, batchRemoveFile, closeBatch, onMergeFilesClick, onBatchConvertToSupportedFormatClick }) => { +const BatchFilesList = memo(({ selectedBatchFiles, filePath, width, batchFiles, setBatchFiles, onBatchFileSelect, batchRemoveFile, closeBatch, onMergeFilesClick, onBatchConvertToSupportedFormatClick }) => { const { t } = useTranslation(); + const [sortDesc, setSortDesc] = useState(); + + const sortableList = batchFiles.map((batchFile) => ({ id: batchFile.path, batchFile })); + + const setSortableList = useCallback((newList) => { + setBatchFiles(newList.map(({ batchFile }) => batchFile)); + }, [setBatchFiles]); + + const onSortClick = useCallback(() => { + const newSortDesc = sortDesc == null ? false : !sortDesc; + const sortedFiles = [...batchFiles]; + const order = newSortDesc ? -1 : 1; + // natural language sort (numeric) https://github.com/mifi/lossless-cut/issues/844 + sortedFiles.sort((a, b) => order * a.name.localeCompare(b.name, 'en-US', { numeric: true })); + setBatchFiles(sortedFiles); + setSortDesc(newSortDesc); + }, [batchFiles, setBatchFiles, sortDesc]); + + const SortIcon = sortDesc ? SortAlphabeticalDescIcon : SortAlphabeticalIcon; + return ( +
- {batchFiles.map(({ path, name }) => ( - - ))} + + {sortableList.map(({ batchFile: { path, name } }) => ( + + ))} +
); diff --git a/src/components/ConcatDialog.jsx b/src/components/ConcatDialog.jsx index 88965830..e828869c 100644 --- a/src/components/ConcatDialog.jsx +++ b/src/components/ConcatDialog.jsx @@ -1,8 +1,6 @@ -import React, { memo, useState, useCallback, useEffect } from 'react'; +import React, { memo, useState, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { Dialog, Pane, Checkbox, SortAlphabeticalIcon, SortAlphabeticalDescIcon, Button, Paragraph } from 'evergreen-ui'; -import { sortableContainer, sortableElement } from 'react-sortable-hoc'; -import arrayMove from 'array-move'; +import { Dialog, Checkbox, Button, Paragraph } from 'evergreen-ui'; import { AiOutlineMergeCells } from 'react-icons/ai'; import { readFileMeta, getSmarterOutFormat } from '../ffmpeg'; @@ -14,25 +12,9 @@ const { basename } = window.require('path'); const containerStyle = { color: 'black' }; const rowStyle = { - color: 'black', padding: '3px 10px', fontSize: 14, margin: '7px 0', overflowY: 'auto', whiteSpace: 'nowrap', cursor: 'grab', + color: 'black', fontSize: 14, margin: '4px 0px', overflowY: 'auto', whiteSpace: 'nowrap', }; -const SortableItem = sortableElement(({ value, sortIndex }) => ( - - {sortIndex + 1} - {'. '} - {basename(value)} - -)); - -const SortableContainer = sortableContainer(({ items }) => ( -
- {items.map((value, index) => ( - - ))} -
-)); - const ConcatDialog = memo(({ isShown, onHide, initialPaths, onConcat, segmentsToChapters, setSegmentsToChapters, @@ -44,19 +26,20 @@ const ConcatDialog = memo(({ const [paths, setPaths] = useState(initialPaths); const [includeAllStreams, setIncludeAllStreams] = useState(false); - const [sortDesc, setSortDesc] = useState(); const [fileMeta, setFileMeta] = useState(); const { fileFormat, setFileFormat, detectedFileFormat, setDetectedFileFormat, isCustomFormatSelected } = useFileFormatState(); - useEffect(() => { - setPaths(initialPaths); - + const firstPath = useMemo(() => { if (initialPaths.length === 0) return undefined; + return initialPaths[0]; + }, [initialPaths]); + + useEffect(() => { + if (!isShown) return undefined; let aborted = false; (async () => { - const firstPath = initialPaths[0]; setFileMeta(); setFileFormat(); setDetectedFileFormat(); @@ -70,22 +53,11 @@ const ConcatDialog = memo(({ return () => { aborted = true; }; - }, [initialPaths, setDetectedFileFormat, setFileFormat]); + }, [firstPath, isShown, setDetectedFileFormat, setFileFormat]); - const onSortEnd = useCallback(({ oldIndex, newIndex }) => { - const newPaths = arrayMove(paths, oldIndex, newIndex); - setPaths(newPaths); - }, [paths]); - - const onSortClick = useCallback(() => { - const newSortDesc = sortDesc == null ? false : !sortDesc; - const sortedPaths = [...paths]; - const order = newSortDesc ? -1 : 1; - // natural language sort (numeric) https://github.com/mifi/lossless-cut/issues/844 - sortedPaths.sort((a, b) => order * a.localeCompare(b, 'en-US', { numeric: true })); - setPaths(sortedPaths); - setSortDesc(newSortDesc); - }, [paths, sortDesc]); + useEffect(() => { + setPaths(initialPaths); + }, [initialPaths]); const onOutputFormatUserChange = useCallback((newFormat) => setFileFormat(newFormat), [setFileFormat]); @@ -101,7 +73,6 @@ const ConcatDialog = memo(({ footer={( <> {fileFormat && detectedFileFormat && } - @@ -109,16 +80,10 @@ const ConcatDialog = memo(({ >
- {t('This dialog can be used to concatenate files in series, e.g. one after the other:\n[file1][file2][file3]\nIt can NOT be used for merging tracks in parallell (like adding an audio track to a video).\nMake sure all files are of the exact same codecs & codec parameters (fps, resolution etc).\n\nDrag and drop to change the order of your files here:')} + {t('This dialog can be used to concatenate files in series, e.g. one after the other:\n[file1][file2][file3]\nIt can NOT be used for merging tracks in parallell (like adding an audio track to a video).\nMake sure all files are of the exact same codecs & codec parameters (fps, resolution etc).')}
- - -
+
setIncludeAllStreams(e.target.checked)} label={`${t('Include all tracks?')} ${t('If this is checked, all audio/video/subtitle/data tracks will be included. This may not always work for all file types. If not checked, only default streams will be included.')}`} /> setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} /> @@ -131,6 +96,16 @@ const ConcatDialog = memo(({ {t('Note that also other settings from the normal export dialog apply to this merge function. For more information about all options, see the export dialog.')}
+ +
+ {paths.map((path, index) => ( +
+ {index + 1} + {'. '} + {basename(path)} +
+ ))} +
); diff --git a/src/main.css b/src/main.css index aa95b6cf..d10be274 100644 --- a/src/main.css +++ b/src/main.css @@ -46,12 +46,6 @@ kbd { box-shadow: inset 0 -1px 0 #bbb; } -.dragging-helper-class { - color: rgba(0,0,0,0.5); - /* https://github.com/clauderic/react-sortable-hoc/issues/803 */ - z-index: 99999; -} - .hide-scrollbar::-webkit-scrollbar { display: none; } diff --git a/yarn.lock b/yarn.lock index 0db291c2..defc1a7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1020,7 +1020,7 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== @@ -2627,11 +2627,6 @@ array-includes@^3.1.3, array-includes@^3.1.4: get-intrinsic "^1.1.1" is-string "^1.0.7" -array-move@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/array-move/-/array-move-3.0.1.tgz#179645cc0987b65953a4fc06b6df9045e4ba9618" - integrity sha512-H3Of6NIn2nNU1gsVDqDnYKY/LCdWvCMMOWifNGhKcVQgiZ6nOek39aESOvro6zmueP07exSl93YLvkN4fZOkSg== - array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -6331,13 +6326,6 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - ip@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -9536,15 +9524,6 @@ react-scripts@5.0.0: optionalDependencies: fsevents "^2.3.2" -react-sortable-hoc@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz#f6780d8aa4b922a21f3e754af542f032677078b7" - integrity sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg== - dependencies: - "@babel/runtime" "^7.2.0" - invariant "^2.2.4" - prop-types "^15.5.7" - react-sortablejs@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/react-sortablejs/-/react-sortablejs-6.0.0.tgz#ba75ded6dce3fa1b5b3b52c70d1928fcdee2003d"