allow labeling segments #199

also fix color bug
pull/276/head
Mikael Finstad 2020-02-20 22:01:03 +08:00
rodzic bcbf2d5dff
commit 100011750c
2 zmienionych plików z 79 dodań i 27 usunięć

Wyświetl plik

@ -8,7 +8,7 @@ const { formatDuration } = require('./util');
const TimelineSeg = ({ const TimelineSeg = ({
duration, cutStart, cutEnd, isActive, segNum, duration, cutStart, cutEnd, isActive, segNum, name,
onSegClick, invertCutSegments, segBgColor, segActiveBgColor, segBorderColor, onSegClick, invertCutSegments, segBgColor, segActiveBgColor, segBorderColor,
}) => { }) => {
const cutSectionWidth = `${((cutEnd - cutStart) / duration) * 100}%`; const cutSectionWidth = `${((cutEnd - cutStart) / duration) * 100}%`;
@ -42,6 +42,9 @@ const TimelineSeg = ({
const onThisSegClick = () => onSegClick(segNum); const onThisSegClick = () => onSegClick(segNum);
const durationStr = cutEnd > cutStart ? `${formatDuration({ seconds: cutEnd - cutStart })} ` : '';
const title = `${durationStr}${name}`;
return ( return (
<motion.div <motion.div
style={wrapperStyle} style={wrapperStyle}
@ -51,7 +54,7 @@ const TimelineSeg = ({
exit={{ opacity: 0, scaleX: 0 }} exit={{ opacity: 0, scaleX: 0 }}
role="button" role="button"
onClick={onThisSegClick} onClick={onThisSegClick}
title={cutEnd > cutStart ? formatDuration({ seconds: cutEnd - cutStart }) : undefined} title={title}
> >
<div style={{ alignSelf: 'flex-start', flexShrink: 1, fontSize: 10, minWidth: 0, overflow: 'hidden' }}>{segNum + 1}</div> <div style={{ alignSelf: 'flex-start', flexShrink: 1, fontSize: 10, minWidth: 0, overflow: 'hidden' }}>{segNum + 1}</div>
@ -71,6 +74,10 @@ const TimelineSeg = ({
)} )}
</AnimatePresence> </AnimatePresence>
{name && <div style={{ flexBasis: 4, flexShrink: 1 }} />}
{name && <div style={{ flexShrink: 1, fontSize: 11, minWidth: 0, overflow: 'hidden', whiteSpace: 'nowrap' }}>{name}</div>}
<div style={{ flexGrow: 1 }} /> <div style={{ flexGrow: 1 }} />
</motion.div> </motion.div>
); );

Wyświetl plik

@ -1,6 +1,6 @@
import React, { memo, useEffect, useState, useCallback, useRef, Fragment } from 'react'; import React, { memo, useEffect, useState, useCallback, useRef, Fragment } from 'react';
import { IoIosHelpCircle, IoIosCamera } from 'react-icons/io'; import { IoIosHelpCircle, IoIosCamera } from 'react-icons/io';
import { FaPlus, FaMinus, FaHandPointRight, FaHandPointLeft, FaTrashAlt, FaVolumeMute, FaVolumeUp, FaYinYang, FaFileExport } from 'react-icons/fa'; import { FaPlus, FaMinus, FaHandPointRight, FaHandPointLeft, FaTrashAlt, FaVolumeMute, FaVolumeUp, FaYinYang, FaFileExport, FaTag } from 'react-icons/fa';
import { MdRotate90DegreesCcw, MdCallSplit, MdCallMerge } from 'react-icons/md'; import { MdRotate90DegreesCcw, MdCallSplit, MdCallMerge } from 'react-icons/md';
import { FiScissors } from 'react-icons/fi'; import { FiScissors } from 'react-icons/fi';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
@ -64,6 +64,7 @@ function createSegment({ start, end } = {}) {
return { return {
start, start,
end, end,
name: '',
color: generateColor(), color: generateColor(),
uuid: uuid.v4(), uuid: uuid.v4(),
}; };
@ -302,7 +303,6 @@ const App = memo(() => {
})(); })();
const setCutTime = useCallback((type, time) => { const setCutTime = useCallback((type, time) => {
const cloned = cloneDeep(cutSegments);
const currentSeg = currentCutSeg; const currentSeg = currentCutSeg;
if (type === 'start' && time >= getSegApparentEnd(currentSeg)) { if (type === 'start' && time >= getSegApparentEnd(currentSeg)) {
throw new Error('Start time must precede end time'); throw new Error('Start time must precede end time');
@ -310,12 +310,19 @@ const App = memo(() => {
if (type === 'end' && time <= getSegApparentStart(currentSeg)) { if (type === 'end' && time <= getSegApparentStart(currentSeg)) {
throw new Error('Start time must precede end time'); throw new Error('Start time must precede end time');
} }
const cloned = cloneDeep(cutSegments);
cloned[currentSegIndexSafe][type] = Math.min(Math.max(time, 0), duration); cloned[currentSegIndexSafe][type] = Math.min(Math.max(time, 0), duration);
setCutSegments(cloned); setCutSegments(cloned);
}, [ }, [
currentSegIndexSafe, getSegApparentEnd, cutSegments, currentCutSeg, setCutSegments, duration, currentSegIndexSafe, getSegApparentEnd, cutSegments, currentCutSeg, setCutSegments, duration,
]); ]);
const setCurrentSegmentName = (name) => {
const cloned = cloneDeep(cutSegments);
cloned[currentSegIndexSafe].name = name;
setCutSegments(cloned);
};
function formatTimecode(sec) { function formatTimecode(sec) {
return formatDuration({ seconds: sec, fps: timecodeShowFrames ? detectedFps : undefined }); return formatDuration({ seconds: sec, fps: timecodeShowFrames ? detectedFps : undefined });
} }
@ -1063,10 +1070,21 @@ const App = memo(() => {
const otherFormatsMap = fromPairs(Object.entries(allOutFormats) const otherFormatsMap = fromPairs(Object.entries(allOutFormats)
.filter(([f]) => ![...commonFormats, detectedFileFormat].includes(f))); .filter(([f]) => ![...commonFormats, detectedFileFormat].includes(f)));
const segColor = (currentCutSeg || {}).color; function getSegColors(seg) {
const segBgColor = segColor.alpha(0.5).string(); if (!seg) return {};
const segActiveBgColor = segColor.lighten(0.5).alpha(0.5).string(); const { color } = seg;
const segBorderColor = segColor.lighten(0.5).string(); return {
segBgColor: color.alpha(0.5).string(),
segActiveBgColor: color.lighten(0.5).alpha(0.5).string(),
segBorderColor: color.lighten(0.5).string(),
};
}
const {
segBgColor: currentSegBgColor,
segActiveBgColor: currentSegActiveBgColor,
segBorderColor: currentSegBorderColor,
} = getSegColors(currentCutSeg);
const jumpCutButtonStyle = { const jumpCutButtonStyle = {
position: 'absolute', color: 'black', bottom: 0, top: 0, padding: '2px 8px', position: 'absolute', color: 'black', bottom: 0, top: 0, padding: '2px 8px',
@ -1277,16 +1295,27 @@ const App = memo(() => {
return () => window.removeEventListener('keydown', keyScrollPreventer); return () => window.removeEventListener('keydown', keyScrollPreventer);
}, []); }, []);
async function onLabelSegmentPress() {
const { value } = await Swal.fire({
showCancelButton: true,
title: 'Label current segment',
inputValue: currentCutSeg.name,
input: 'text',
});
if (value != null) setCurrentSegmentName(value);
}
function renderSetCutpointButton(side) { function renderSetCutpointButton(side) {
const start = side === 'start'; const start = side === 'start';
const Icon = start ? FaHandPointLeft : FaHandPointRight; const Icon = start ? FaHandPointLeft : FaHandPointRight;
const border = `4px solid ${segBorderColor}`; const border = `4px solid ${currentSegBorderColor}`;
return ( return (
<Icon <Icon
size={13} size={13}
title="Set cut end to current position" title="Set cut end to current position"
role="button" role="button"
style={{ padding: start ? '4px 4px 4px 2px' : '4px 2px 4px 4px', borderLeft: start && border, borderRight: !start && border, background: segActiveBgColor, borderRadius: 6 }} style={{ padding: start ? '4px 4px 4px 2px' : '4px 2px 4px 4px', borderLeft: start && border, borderRight: !start && border, background: currentSegActiveBgColor, borderRadius: 6 }}
onClick={start ? setCutStart : setCutEnd} onClick={start ? setCutStart : setCutEnd}
/> />
); );
@ -1485,22 +1514,29 @@ const App = memo(() => {
{currentTimePos !== undefined && <motion.div transition={{ type: 'spring', damping: 70, stiffness: 800 }} animate={{ left: currentTimePos }} style={{ position: 'absolute', bottom: 0, top: 0, zIndex: 3, backgroundColor: 'black', width: currentTimeWidth, pointerEvents: 'none' }} />} {currentTimePos !== undefined && <motion.div transition={{ type: 'spring', damping: 70, stiffness: 800 }} animate={{ left: currentTimePos }} style={{ position: 'absolute', bottom: 0, top: 0, zIndex: 3, backgroundColor: 'black', width: currentTimeWidth, pointerEvents: 'none' }} />}
{commandedTimePos !== undefined && <div style={{ left: commandedTimePos, position: 'absolute', bottom: 0, top: 0, zIndex: 4, backgroundColor: 'white', width: currentTimeWidth, pointerEvents: 'none' }} />} {commandedTimePos !== undefined && <div style={{ left: commandedTimePos, position: 'absolute', bottom: 0, top: 0, zIndex: 4, backgroundColor: 'white', width: currentTimeWidth, pointerEvents: 'none' }} />}
{apparentCutSegments.map((seg, i) => ( {apparentCutSegments.map((seg, i) => {
<TimelineSeg const {
key={seg.uuid} segBgColor, segActiveBgColor, segBorderColor,
segNum={i} } = getSegColors(seg);
segBgColor={segBgColor}
segActiveBgColor={segActiveBgColor} return (
segBorderColor={segBorderColor} <TimelineSeg
onSegClick={currentSegIndexNew => setCurrentSegIndex(currentSegIndexNew)} key={seg.uuid}
isActive={i === currentSegIndexSafe} segNum={i}
duration={durationSafe} segBgColor={segBgColor}
cutStart={seg.start} segActiveBgColor={segActiveBgColor}
cutEnd={seg.end} segBorderColor={segBorderColor}
invertCutSegments={invertCutSegments} onSegClick={currentSegIndexNew => setCurrentSegIndex(currentSegIndexNew)}
zoomed={zoomed} isActive={i === currentSegIndexSafe}
/> duration={durationSafe}
))} name={seg.name}
cutStart={seg.start}
cutEnd={seg.end}
invertCutSegments={invertCutSegments}
zoomed={zoomed}
/>
);
})}
{inverseCutSegments && inverseCutSegments.map((seg, i) => ( {inverseCutSegments && inverseCutSegments.map((seg, i) => (
<InverseCutSegment <InverseCutSegment
@ -1599,7 +1635,7 @@ const App = memo(() => {
<FaMinus <FaMinus
size={30} size={30}
style={{ margin: '0 5px', background: cutSegments.length < 2 ? undefined : segBgColor, borderRadius: 3, color: 'white' }} style={{ margin: '0 5px', background: cutSegments.length < 2 ? undefined : currentSegBgColor, borderRadius: 3, color: 'white' }}
role="button" role="button"
title={`Delete current segment ${currentSegIndexSafe + 1}`} title={`Delete current segment ${currentSegIndexSafe + 1}`}
onClick={removeCutSegment} onClick={removeCutSegment}
@ -1615,6 +1651,15 @@ const App = memo(() => {
); );
})} })}
</select> </select>
<FaTag
size={14}
title="Label segment"
role="button"
style={{ padding: 4, border: `2px solid ${currentSegBorderColor}`, background: currentSegActiveBgColor, borderRadius: 6 }}
onClick={onLabelSegmentPress}
/>
</div> </div>
<div className="right-menu" style={{ position: 'absolute', right: 0, bottom: 0, padding: '.3em', display: 'flex', alignItems: 'center' }}> <div className="right-menu" style={{ position: 'absolute', right: 0, bottom: 0, padding: '.3em', display: 'flex', alignItems: 'center' }}>