move sorting from merge to batch files

closes #1043
pull/901/head
Mikael Finstad 2022-02-24 22:50:59 +08:00
rodzic bdb050492f
commit eab8a2c9b5
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 25AB36E3E81CBC26
6 zmienionych plików z 58 dodań i 86 usunięć

Wyświetl plik

@ -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",

Wyświetl plik

@ -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}

Wyświetl plik

@ -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 (
<motion.div
className="no-user-select"
@ -32,13 +54,16 @@ const BatchFilesList = memo(({ selectedBatchFiles, filePath, width, batchFiles,
<div style={{ flexGrow: 1 }} />
<FaHatWizard size={17} role="button" title={`${t('Convert to supported format')}...`} style={iconStyle} onClick={onBatchConvertToSupportedFormatClick} />
<AiOutlineMergeCells size={20} role="button" title={`${t('Merge/concatenate files')}...`} style={iconStyle} onClick={onMergeFilesClick} />
<SortIcon size={25} role="button" title={t('Sort items')} style={iconStyle} onClick={onSortClick} />
<FaTimes size={20} role="button" title={t('Close batch')} style={iconStyle} onClick={closeBatch} />
</div>
<div style={{ overflowX: 'hidden', overflowY: 'auto' }}>
{batchFiles.map(({ path, name }) => (
<BatchFile key={path} path={path} name={name} isSelected={selectedBatchFiles.includes(path)} isOpen={filePath === path} onSelect={onBatchFileSelect} onDelete={batchRemoveFile} />
))}
<ReactSortable list={sortableList} setList={setSortableList}>
{sortableList.map(({ batchFile: { path, name } }) => (
<BatchFile key={path} path={path} name={name} isSelected={selectedBatchFiles.includes(path)} isOpen={filePath === path} onSelect={onBatchFileSelect} onDelete={batchRemoveFile} />
))}
</ReactSortable>
</div>
</motion.div>
);

Wyświetl plik

@ -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 }) => (
<Pane elevation={1} style={rowStyle} title={value}>
{sortIndex + 1}
{'. '}
{basename(value)}
</Pane>
));
const SortableContainer = sortableContainer(({ items }) => (
<div style={{ padding: '0 3px' }}>
{items.map((value, index) => (
<SortableItem key={value} index={index} sortIndex={index} value={value} />
))}
</div>
));
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 && <OutputFormatSelect style={{ maxWidth: 150 }} detectedFileFormat={detectedFileFormat} fileFormat={fileFormat} onOutputFormatUserChange={onOutputFormatUserChange} />}
<Button iconBefore={sortDesc ? SortAlphabeticalDescIcon : SortAlphabeticalIcon} onClick={onSortClick}>{t('Sort items')}</Button>
<Button onClick={onHide} style={{ marginLeft: 10 }}>Cancel</Button>
<Button iconBefore={<AiOutlineMergeCells />} isLoading={detectedFileFormat == null} appearance="primary" onClick={onConcatClick}>{t('Merge!')}</Button>
</>
@ -109,16 +80,10 @@ const ConcatDialog = memo(({
>
<div style={containerStyle}>
<div style={{ whiteSpace: 'pre-wrap', fontSize: 14, marginBottom: 10 }}>
{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).')}
</div>
<SortableContainer
items={paths}
onSortEnd={onSortEnd}
helperClass="dragging-helper-class"
/>
<div style={{ marginTop: 10 }}>
<div style={{ marginTop: 10, marginBottom: 10 }}>
<Checkbox checked={includeAllStreams} onChange={(e) => 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.')}`} />
<Checkbox checked={preserveMetadataOnMerge} onChange={(e) => setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} />
@ -131,6 +96,16 @@ const ConcatDialog = memo(({
<Paragraph>{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.')}</Paragraph>
</div>
<div>
{paths.map((path, index) => (
<div style={rowStyle} title={path}>
{index + 1}
{'. '}
<span style={{ color: 'rgba(0,0,0,0.7)' }}>{basename(path)}</span>
</div>
))}
</div>
</div>
</Dialog>
);

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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"