mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 10:22:31 +01:00
improve renderibng performance #1881
This commit is contained in:
parent
d5fbac6f11
commit
18785e1f88
@ -31,6 +31,38 @@ const zoomOptions = Array(13).fill().map((unused, z) => 2 ** z);
|
|||||||
|
|
||||||
const leftRightWidth = 100;
|
const leftRightWidth = 100;
|
||||||
|
|
||||||
|
const InvertCutModeButton = memo(({ invertCutSegments, setInvertCutSegments }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const onYinYangClick = useCallback(() => {
|
||||||
|
setInvertCutSegments(v => {
|
||||||
|
const newVal = !v;
|
||||||
|
if (newVal) toast.fire({ title: t('When you export, selected segments on the timeline will be REMOVED - the surrounding areas will be KEPT') });
|
||||||
|
else toast.fire({ title: t('When you export, selected segments on the timeline will be KEPT - the surrounding areas will be REMOVED.') });
|
||||||
|
return newVal;
|
||||||
|
});
|
||||||
|
}, [setInvertCutSegments, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginLeft: 5 }}>
|
||||||
|
<motion.div
|
||||||
|
style={{ width: 24, height: 24 }}
|
||||||
|
animate={{ rotateX: invertCutSegments ? 0 : 180 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
<FaYinYang
|
||||||
|
size={24}
|
||||||
|
role="button"
|
||||||
|
title={invertCutSegments ? t('Discard selected segments') : t('Keep selected segments')}
|
||||||
|
style={{ color: invertCutSegments ? primaryTextColor : undefined }}
|
||||||
|
onClick={onYinYangClick}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, seekAbs, currentCutSeg, currentApparentCutSeg, isStart }) => {
|
const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, seekAbs, currentCutSeg, currentApparentCutSeg, isStart }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getSegColor } = useSegColors();
|
const { getSegColor } = useSegColors();
|
||||||
@ -179,15 +211,6 @@ const BottomBar = memo(({
|
|||||||
|
|
||||||
const { invertCutSegments, setInvertCutSegments, simpleMode, toggleSimpleMode, exportConfirmEnabled } = useUserSettings();
|
const { invertCutSegments, setInvertCutSegments, simpleMode, toggleSimpleMode, exportConfirmEnabled } = useUserSettings();
|
||||||
|
|
||||||
const onYinYangClick = useCallback(() => {
|
|
||||||
setInvertCutSegments(v => {
|
|
||||||
const newVal = !v;
|
|
||||||
if (newVal) toast.fire({ title: t('When you export, selected segments on the timeline will be REMOVED - the surrounding areas will be KEPT') });
|
|
||||||
else toast.fire({ title: t('When you export, selected segments on the timeline will be KEPT - the surrounding areas will be REMOVED.') });
|
|
||||||
return newVal;
|
|
||||||
});
|
|
||||||
}, [setInvertCutSegments, t]);
|
|
||||||
|
|
||||||
const rotationStr = `${rotation}°`;
|
const rotationStr = `${rotation}°`;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -365,21 +388,7 @@ const BottomBar = memo(({
|
|||||||
|
|
||||||
{!simpleMode && (
|
{!simpleMode && (
|
||||||
<>
|
<>
|
||||||
<div style={{ marginLeft: 5 }}>
|
<InvertCutModeButton invertCutSegments={invertCutSegments} setInvertCutSegments={setInvertCutSegments} />
|
||||||
<motion.div
|
|
||||||
style={{ width: 24, height: 24 }}
|
|
||||||
animate={{ rotateX: invertCutSegments ? 0 : 180 }}
|
|
||||||
transition={{ duration: 0.3 }}
|
|
||||||
>
|
|
||||||
<FaYinYang
|
|
||||||
size={24}
|
|
||||||
role="button"
|
|
||||||
title={invertCutSegments ? t('Discard selected segments') : t('Keep selected segments')}
|
|
||||||
style={{ color: invertCutSegments ? primaryTextColor : undefined }}
|
|
||||||
onClick={onYinYangClick}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div role="button" style={{ marginRight: 5, marginLeft: 10 }} title={t('Zoom')} onClick={timelineToggleComfortZoom}>{Math.floor(zoom)}x</div>
|
<div role="button" style={{ marginRight: 5, marginLeft: 10 }} title={t('Zoom')} onClick={timelineToggleComfortZoom}>{Math.floor(zoom)}x</div>
|
||||||
|
|
||||||
|
@ -24,8 +24,7 @@ const buttonBaseStyle = {
|
|||||||
|
|
||||||
const neutralButtonColor = 'var(--gray8)';
|
const neutralButtonColor = 'var(--gray8)';
|
||||||
|
|
||||||
|
const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, getFrameCount, updateSegOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onEditSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments, onDuplicateSegmentClick }) => {
|
||||||
const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onEditSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments, onDuplicateSegmentClick }) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getSegColor } = useSegColors();
|
const { getSegColor } = useSegColors();
|
||||||
|
|
||||||
@ -33,15 +32,18 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
|
|||||||
|
|
||||||
const contextMenuTemplate = useMemo(() => {
|
const contextMenuTemplate = useMemo(() => {
|
||||||
if (invertCutSegments) return [];
|
if (invertCutSegments) return [];
|
||||||
|
|
||||||
|
const updateOrder = (dir) => updateSegOrder(index, index + dir);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ label: t('Jump to start time'), click: jumpSegStart },
|
{ label: t('Jump to start time'), click: () => jumpSegStart(index) },
|
||||||
{ label: t('Jump to end time'), click: jumpSegEnd },
|
{ label: t('Jump to end time'), click: () => jumpSegEnd(index) },
|
||||||
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
|
||||||
{ label: t('Add segment'), click: addSegment },
|
{ label: t('Add segment'), click: addSegment },
|
||||||
{ label: t('Label segment'), click: onLabelPress },
|
{ label: t('Label segment'), click: () => onLabelPress(index) },
|
||||||
{ label: t('Remove segment'), click: onRemovePress },
|
{ label: t('Remove segment'), click: () => onRemovePress(index) },
|
||||||
{ label: t('Duplicate segment'), click: () => onDuplicateSegmentClick(seg) },
|
{ label: t('Duplicate segment'), click: () => onDuplicateSegmentClick(seg) },
|
||||||
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@ -60,7 +62,7 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
|
|||||||
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
|
||||||
{ label: t('Change segment order'), click: onReorderPress },
|
{ label: t('Change segment order'), click: () => onReorderPress(index) },
|
||||||
{ label: t('Increase segment order'), click: () => updateOrder(1) },
|
{ label: t('Increase segment order'), click: () => updateOrder(1) },
|
||||||
{ label: t('Decrease segment order'), click: () => updateOrder(-1) },
|
{ label: t('Decrease segment order'), click: () => updateOrder(-1) },
|
||||||
|
|
||||||
@ -69,7 +71,7 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
|
|||||||
{ label: t('Segment tags'), click: () => onEditSegmentTags(index) },
|
{ label: t('Segment tags'), click: () => onEditSegmentTags(index) },
|
||||||
{ label: t('Extract frames as image files'), click: () => onExtractSegmentFramesAsImages([seg.segId]) },
|
{ label: t('Extract frames as image files'), click: () => onExtractSegmentFramesAsImages([seg.segId]) },
|
||||||
];
|
];
|
||||||
}, [invertCutSegments, t, jumpSegStart, jumpSegEnd, addSegment, onLabelPress, onRemovePress, onLabelSelectedSegments, onRemoveSelected, onReorderPress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onInvertSelectedSegments, updateOrder, onEditSegmentTags, index, onExtractSegmentFramesAsImages]);
|
}, [invertCutSegments, t, addSegment, onLabelSelectedSegments, onRemoveSelected, updateSegOrder, index, jumpSegStart, jumpSegEnd, onLabelPress, onRemovePress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onInvertSelectedSegments, onReorderPress, onEditSegmentTags, onExtractSegmentFramesAsImages]);
|
||||||
|
|
||||||
useContextMenu(ref, contextMenuTemplate);
|
useContextMenu(ref, contextMenuTemplate);
|
||||||
|
|
||||||
@ -95,10 +97,10 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
|
|||||||
|
|
||||||
const timeStr = useMemo(() => `${formatTimecode({ seconds: seg.start })} - ${formatTimecode({ seconds: seg.end })}`, [seg.start, seg.end, formatTimecode]);
|
const timeStr = useMemo(() => `${formatTimecode({ seconds: seg.start })} - ${formatTimecode({ seconds: seg.end })}`, [seg.start, seg.end, formatTimecode]);
|
||||||
|
|
||||||
function onDoubleClick() {
|
const onDoubleClick = useCallback(() => {
|
||||||
if (invertCutSegments) return;
|
if (invertCutSegments) return;
|
||||||
jumpSegStart();
|
jumpSegStart(index);
|
||||||
}
|
}, [index, invertCutSegments, jumpSegStart]);
|
||||||
|
|
||||||
const durationMsFormatted = Math.floor(durationMs);
|
const durationMsFormatted = Math.floor(durationMs);
|
||||||
const frameCount = getFrameCount(duration);
|
const frameCount = getFrameCount(duration);
|
||||||
@ -114,14 +116,18 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
|
|||||||
|
|
||||||
const tags = useMemo(() => getSegmentTags(seg), [seg]);
|
const tags = useMemo(() => getSegmentTags(seg), [seg]);
|
||||||
|
|
||||||
|
const maybeOnClick = useCallback(() => !invertCutSegments && onClick(index), [index, invertCutSegments, onClick]);
|
||||||
|
|
||||||
|
const motionStyle = useMemo(() => ({ originY: 0, margin: '5px 0', background: 'var(--gray2)', border: isActive ? '1px solid var(--gray10)' : '1px solid transparent', padding: 5, borderRadius: 5, position: 'relative' }), [isActive]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
role="button"
|
role="button"
|
||||||
onClick={() => !invertCutSegments && onClick(index)}
|
onClick={maybeOnClick}
|
||||||
onDoubleClick={onDoubleClick}
|
onDoubleClick={onDoubleClick}
|
||||||
layout
|
layout
|
||||||
style={{ originY: 0, margin: '5px 0', background: 'var(--gray2)', border: isActive ? '1px solid var(--gray10)' : '1px solid transparent', padding: 5, borderRadius: 5, position: 'relative' }}
|
style={motionStyle}
|
||||||
initial={{ scaleY: 0 }}
|
initial={{ scaleY: 0 }}
|
||||||
animate={{ scaleY: 1, opacity: !selected && !invertCutSegments ? 0.5 : undefined }}
|
animate={{ scaleY: 1, opacity: !selected && !invertCutSegments ? 0.5 : undefined }}
|
||||||
exit={{ scaleY: 0 }}
|
exit={{ scaleY: 0 }}
|
||||||
@ -171,7 +177,7 @@ const SegmentList = memo(({
|
|||||||
|
|
||||||
const segments = invertCutSegments ? inverseCutSegments : apparentCutSegments;
|
const segments = invertCutSegments ? inverseCutSegments : apparentCutSegments;
|
||||||
|
|
||||||
const sortableList = segments.map((seg) => ({ id: seg.segId, seg }));
|
const sortableList = useMemo(() => segments.map((seg) => ({ id: seg.segId, seg })), [segments]);
|
||||||
|
|
||||||
const setSortableList = useCallback((newList) => {
|
const setSortableList = useCallback((newList) => {
|
||||||
if (isEqual(segments.map((s) => s.segId), newList.map((l) => l.id))) return; // No change
|
if (isEqual(segments.map((s) => s.segId), newList.map((l) => l.id))) return; // No change
|
||||||
@ -189,7 +195,7 @@ const SegmentList = memo(({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onReorderSegs(index) {
|
const onReorderSegs = useCallback(async (index) => {
|
||||||
if (apparentCutSegments.length < 2) return;
|
if (apparentCutSegments.length < 2) return;
|
||||||
const { value } = await Swal.fire({
|
const { value } = await Swal.fire({
|
||||||
title: `${t('Change order of segment')} ${index + 1}`,
|
title: `${t('Change order of segment')} ${index + 1}`,
|
||||||
@ -207,7 +213,7 @@ const SegmentList = memo(({
|
|||||||
const newOrder = parseInt(value, 10);
|
const newOrder = parseInt(value, 10);
|
||||||
updateSegOrder(index, newOrder - 1);
|
updateSegOrder(index, newOrder - 1);
|
||||||
}
|
}
|
||||||
}
|
}, [apparentCutSegments.length, t, updateSegOrder]);
|
||||||
|
|
||||||
function renderFooter() {
|
function renderFooter() {
|
||||||
const getButtonColor = (seg) => getSegColor(seg).desaturate(0.3).lightness(darkMode ? 45 : 55).string();
|
const getButtonColor = (seg) => getSegColor(seg).desaturate(0.3).lightness(darkMode ? 45 : 55).string();
|
||||||
@ -340,12 +346,12 @@ const SegmentList = memo(({
|
|||||||
onClick={onSegClick}
|
onClick={onSegClick}
|
||||||
addSegment={addSegment}
|
addSegment={addSegment}
|
||||||
onRemoveSelected={onRemoveSelected}
|
onRemoveSelected={onRemoveSelected}
|
||||||
onRemovePress={() => removeCutSegment(index)}
|
onRemovePress={removeCutSegment}
|
||||||
onReorderPress={() => onReorderSegs(index)}
|
onReorderPress={onReorderSegs}
|
||||||
onLabelPress={() => onLabelSegment(index)}
|
onLabelPress={onLabelSegment}
|
||||||
jumpSegStart={() => jumpSegStart(index)}
|
jumpSegStart={jumpSegStart}
|
||||||
jumpSegEnd={() => jumpSegEnd(index)}
|
jumpSegEnd={jumpSegEnd}
|
||||||
updateOrder={(dir) => updateSegOrder(index, index + dir)}
|
updateSegOrder={updateSegOrder}
|
||||||
getFrameCount={getFrameCount}
|
getFrameCount={getFrameCount}
|
||||||
formatTimecode={formatTimecode}
|
formatTimecode={formatTimecode}
|
||||||
currentSegIndex={currentSegIndex}
|
currentSegIndex={currentSegIndex}
|
||||||
|
@ -52,6 +52,11 @@ const CommandedTime = memo(({ commandedTimePercent }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const timelineHeight = 36;
|
||||||
|
|
||||||
|
const timeWrapperStyle = { position: 'absolute', height: timelineHeight, left: 0, right: 0, bottom: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' };
|
||||||
|
const timeStyle = { background: 'rgba(0,0,0,0.4)', borderRadius: 3, padding: '2px 4px', color: 'rgba(255, 255, 255, 0.8)' };
|
||||||
|
|
||||||
const Timeline = memo(({
|
const Timeline = memo(({
|
||||||
durationSafe, startTimeOffset, playerTime, commandedTime, relevantTime,
|
durationSafe, startTimeOffset, playerTime, commandedTime, relevantTime,
|
||||||
zoom, neighbouringKeyFrames, seekAbs, apparentCutSegments,
|
zoom, neighbouringKeyFrames, seekAbs, apparentCutSegments,
|
||||||
@ -62,8 +67,6 @@ const Timeline = memo(({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const timelineHeight = 36;
|
|
||||||
|
|
||||||
const { invertCutSegments } = useUserSettings();
|
const { invertCutSegments } = useUserSettings();
|
||||||
|
|
||||||
const timelineScrollerRef = useRef();
|
const timelineScrollerRef = useRef();
|
||||||
@ -328,8 +331,8 @@ const Timeline = memo(({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ position: 'absolute', height: timelineHeight, left: 0, right: 0, bottom: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }}>
|
<div style={timeWrapperStyle}>
|
||||||
<div style={{ background: 'rgba(0,0,0,0.4)', borderRadius: 3, padding: '2px 4px', color: 'rgba(255, 255, 255, 0.8)' }}>
|
<div style={timeStyle}>
|
||||||
{formatTimeAndFrames(displayTime)}{isZoomed ? ` ${displayTimePercent}` : ''}
|
{formatTimeAndFrames(displayTime)}{isZoomed ? ` ${displayTimePercent}` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React, { memo, useCallback } from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { IoIosSettings } from 'react-icons/io';
|
import { IoIosSettings } from 'react-icons/io';
|
||||||
import { FaLock, FaUnlock } from 'react-icons/fa';
|
import { FaLock, FaUnlock } from 'react-icons/fa';
|
||||||
import { IconButton, Button, CrossIcon, ListIcon, VolumeUpIcon, VolumeOffIcon } from 'evergreen-ui';
|
import { IconButton, CrossIcon, ListIcon, VolumeUpIcon, VolumeOffIcon } from 'evergreen-ui';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Button from './components/Button';
|
||||||
|
|
||||||
import ExportModeButton from './components/ExportModeButton';
|
import ExportModeButton from './components/ExportModeButton';
|
||||||
|
|
||||||
@ -11,6 +12,9 @@ import { primaryTextColor, controlsBackground, darkModeTransition } from './colo
|
|||||||
import useUserSettings from './hooks/useUserSettings';
|
import useUserSettings from './hooks/useUserSettings';
|
||||||
|
|
||||||
|
|
||||||
|
const outFmtStyle = { height: 20, maxWidth: 100 };
|
||||||
|
const exportModeStyle = { flexGrow: 0, flexBasis: 140 };
|
||||||
|
|
||||||
const TopMenu = memo(({
|
const TopMenu = memo(({
|
||||||
filePath, fileFormat, copyAnyAudioTrack, toggleStripAudio,
|
filePath, fileFormat, copyAnyAudioTrack, toggleStripAudio,
|
||||||
renderOutFmt, numStreamsToCopy, numStreamsTotal, setStreamsSelectorShown, toggleSettings,
|
renderOutFmt, numStreamsToCopy, numStreamsTotal, setStreamsSelectorShown, toggleSettings,
|
||||||
@ -35,17 +39,20 @@ const TopMenu = memo(({
|
|||||||
>
|
>
|
||||||
{filePath && (
|
{filePath && (
|
||||||
<>
|
<>
|
||||||
<Button height={20} iconBefore={ListIcon} onClick={withBlur(() => setStreamsSelectorShown(true))}>
|
<Button onClick={withBlur(() => setStreamsSelectorShown(true))}>
|
||||||
|
<ListIcon size="1em" verticalAlign="middle" marginRight=".3em" />
|
||||||
{t('Tracks')} ({numStreamsToCopy}/{numStreamsTotal})
|
{t('Tracks')} ({numStreamsToCopy}/{numStreamsTotal})
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
iconBefore={copyAnyAudioTrack ? VolumeUpIcon : VolumeOffIcon}
|
|
||||||
height={20}
|
|
||||||
title={copyAnyAudioTrack ? t('Keep audio tracks') : t('Discard audio tracks')}
|
title={copyAnyAudioTrack ? t('Keep audio tracks') : t('Discard audio tracks')}
|
||||||
onClick={withBlur(toggleStripAudio)}
|
onClick={withBlur(toggleStripAudio)}
|
||||||
>
|
>
|
||||||
{copyAnyAudioTrack ? t('Keep audio') : t('Discard audio')}
|
{copyAnyAudioTrack ? (
|
||||||
|
<><VolumeUpIcon size="1em" verticalAlign="middle" marginRight=".3em" />{t('Keep audio')}</>
|
||||||
|
) : (
|
||||||
|
<><VolumeOffIcon size="1em" verticalAlign="middle" marginRight=".3em" />{t('Discard audio')}</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -53,35 +60,34 @@ const TopMenu = memo(({
|
|||||||
<div style={{ flexGrow: 1 }} />
|
<div style={{ flexGrow: 1 }} />
|
||||||
|
|
||||||
{showClearWorkingDirButton && (
|
{showClearWorkingDirButton && (
|
||||||
<IconButton
|
<CrossIcon
|
||||||
intent="danger"
|
role="button"
|
||||||
icon={CrossIcon}
|
tabIndex={0}
|
||||||
height={20}
|
style={{ width: 20 }}
|
||||||
onClick={withBlur(clearOutDir)}
|
onClick={withBlur(clearOutDir)}
|
||||||
title={t('Clear working directory')}
|
title={t('Clear working directory')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
height={20}
|
|
||||||
onClick={withBlur(changeOutDir)}
|
onClick={withBlur(changeOutDir)}
|
||||||
title={customOutDir}
|
title={customOutDir}
|
||||||
paddingLeft={showClearWorkingDirButton ? 4 : undefined}
|
style={{ paddingLeft: showClearWorkingDirButton ? 4 : undefined }}
|
||||||
>
|
>
|
||||||
{customOutDir ? t('Working dir set') : t('Working dir unset')}
|
{customOutDir ? t('Working dir set') : t('Working dir unset')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{filePath && (
|
{filePath && (
|
||||||
<>
|
<>
|
||||||
{renderOutFmt({ height: 20, maxWidth: 100 })}
|
{renderOutFmt(outFmtStyle)}
|
||||||
|
|
||||||
{!simpleMode && (isCustomFormatSelected || outFormatLocked) && renderFormatLock()}
|
{!simpleMode && (isCustomFormatSelected || outFormatLocked) && renderFormatLock()}
|
||||||
|
|
||||||
<ExportModeButton selectedSegments={selectedSegments} style={{ flexGrow: 0, flexBasis: 140 }} />
|
<ExportModeButton selectedSegments={selectedSegments} style={exportModeStyle} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<IoIosSettings size={24} role="button" onClick={toggleSettings} style={{ verticalAlign: 'middle', marginLeft: 5 }} />
|
<IoIosSettings size={24} role="button" onClick={toggleSettings} style={{ marginLeft: 5 }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
10
src/components/Button.jsx
Normal file
10
src/components/Button.jsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
|
import styles from './Button.module.css';
|
||||||
|
|
||||||
|
const Button = memo(({ type = 'button', ...props }) => (
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading, react/button-has-type
|
||||||
|
<button className={styles.button} type={type} {...props} />
|
||||||
|
));
|
||||||
|
|
||||||
|
export default Button;
|
12
src/components/Button.module.css
Normal file
12
src/components/Button.module.css
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.button {
|
||||||
|
appearance: none;
|
||||||
|
font: inherit;
|
||||||
|
line-height: 140%;
|
||||||
|
font-size: .8em;
|
||||||
|
background-color: var(--gray3);
|
||||||
|
color: var(--gray12);
|
||||||
|
border-radius: .3em;
|
||||||
|
padding: 0 .5em 0 .3em;
|
||||||
|
outline: .05em solid var(--gray8);
|
||||||
|
border: .05em solid var(--gray7);
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import dataUriToBuffer from 'data-uri-to-buffer';
|
import dataUriToBuffer from 'data-uri-to-buffer';
|
||||||
import pMap from 'p-map';
|
import pMap from 'p-map';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { getSuffixedOutPath, getOutDir, transferTimestamps, getSuffixedFileName, getOutPath, escapeRegExp, fsOperationWithRetry } from '../util';
|
import { getSuffixedOutPath, getOutDir, transferTimestamps, getSuffixedFileName, getOutPath, escapeRegExp, fsOperationWithRetry } from '../util';
|
||||||
import { getNumDigits } from '../segments';
|
import { getNumDigits } from '../segments';
|
||||||
@ -23,7 +24,7 @@ function getFrameFromVideo(video, format, quality) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }) => {
|
export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }) => {
|
||||||
async function captureFramesRange({ customOutDir, filePath, fps, fromTime, toTime, estimatedMaxNumFiles, captureFormat, quality, filter, onProgress, outputTimestamps }) {
|
const captureFramesRange = useCallback(async ({ customOutDir, filePath, fps, fromTime, toTime, estimatedMaxNumFiles, captureFormat, quality, filter, onProgress, outputTimestamps }) => {
|
||||||
const getSuffix = (prefix) => `${prefix}.${captureFormat}`;
|
const getSuffix = (prefix) => `${prefix}.${captureFormat}`;
|
||||||
|
|
||||||
if (!outputTimestamps) {
|
if (!outputTimestamps) {
|
||||||
@ -67,9 +68,9 @@ export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }) => {
|
|||||||
}, { concurrency: 1 });
|
}, { concurrency: 1 });
|
||||||
|
|
||||||
return outPaths[0];
|
return outPaths[0];
|
||||||
}
|
}, [formatTimecode]);
|
||||||
|
|
||||||
async function captureFrameFromFfmpeg({ customOutDir, filePath, fromTime, captureFormat, quality }) {
|
const captureFrameFromFfmpeg = useCallback(async ({ customOutDir, filePath, fromTime, captureFormat, quality }) => {
|
||||||
const time = formatTimecode({ seconds: fromTime, fileNameFriendly: true });
|
const time = formatTimecode({ seconds: fromTime, fileNameFriendly: true });
|
||||||
const nameSuffix = `${time}.${captureFormat}`;
|
const nameSuffix = `${time}.${captureFormat}`;
|
||||||
const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix });
|
const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix });
|
||||||
@ -77,9 +78,9 @@ export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }) => {
|
|||||||
|
|
||||||
await transferTimestamps({ inPath: filePath, outPath, cutFrom: fromTime, treatOutputFileModifiedTimeAsStart });
|
await transferTimestamps({ inPath: filePath, outPath, cutFrom: fromTime, treatOutputFileModifiedTimeAsStart });
|
||||||
return outPath;
|
return outPath;
|
||||||
}
|
}, [formatTimecode, treatOutputFileModifiedTimeAsStart]);
|
||||||
|
|
||||||
async function captureFrameFromTag({ customOutDir, filePath, currentTime, captureFormat, video, quality }) {
|
const captureFrameFromTag = useCallback(async ({ customOutDir, filePath, currentTime, captureFormat, video, quality }) => {
|
||||||
const buf = getFrameFromVideo(video, captureFormat, quality);
|
const buf = getFrameFromVideo(video, captureFormat, quality);
|
||||||
|
|
||||||
const ext = mime.extension(buf.type);
|
const ext = mime.extension(buf.type);
|
||||||
@ -90,7 +91,7 @@ export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }) => {
|
|||||||
|
|
||||||
await transferTimestamps({ inPath: filePath, outPath, cutFrom: currentTime, treatOutputFileModifiedTimeAsStart });
|
await transferTimestamps({ inPath: filePath, outPath, cutFrom: currentTime, treatOutputFileModifiedTimeAsStart });
|
||||||
return outPath;
|
return outPath;
|
||||||
}
|
}, [formatTimecode, treatOutputFileModifiedTimeAsStart]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
captureFramesRange,
|
captureFramesRange,
|
||||||
|
29
src/hooks/useWhatChanged.js
Normal file
29
src/hooks/useWhatChanged.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/64997362/how-do-i-see-what-props-have-changed-in-react
|
||||||
|
export default function useWhatChanged(props) {
|
||||||
|
// cache the last set of props
|
||||||
|
const prev = React.useRef(props);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// check each prop to see if it has changed
|
||||||
|
const changed = Object.entries(props).reduce((a, [key, prop]) => {
|
||||||
|
if (prev.current[key] === prop) return a;
|
||||||
|
return {
|
||||||
|
...a,
|
||||||
|
[key]: {
|
||||||
|
prev: prev.current[key],
|
||||||
|
next: prop,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
if (Object.keys(changed).length > 0) {
|
||||||
|
console.group('Props That Changed');
|
||||||
|
console.log(changed);
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
prev.current = props;
|
||||||
|
}, [props]);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user