import React, { memo, useCallback, useEffect } from 'react'; import { Select } from 'evergreen-ui'; import { motion } from 'framer-motion'; import { MdRotate90DegreesCcw } from 'react-icons/md'; import { useTranslation } from 'react-i18next'; import { IoIosCamera, IoMdKey } from 'react-icons/io'; import { FaYinYang, FaTrashAlt, FaStepBackward, FaStepForward, FaCaretLeft, FaCaretRight, FaPause, FaPlay, FaImages, FaKey } from 'react-icons/fa'; import { GiSoundWaves } from 'react-icons/gi'; // import useTraceUpdate from 'use-trace-update'; import { primaryTextColor, primaryColor } from './colors'; import SegmentCutpointButton from './components/SegmentCutpointButton'; import SetCutpointButton from './components/SetCutpointButton'; import ExportButton from './components/ExportButton'; import ToggleExportConfirm from './components/ToggleExportConfirm'; import CaptureFormatButton from './components/CaptureFormatButton'; import SimpleModeButton from './components/SimpleModeButton'; import { withBlur, toast, mirrorTransform } from './util'; import { getSegColor } from './util/colors'; import { formatDuration, parseDuration } from './util/duration'; import useUserSettings from './hooks/useUserSettings'; const isDev = window.require('electron-is-dev'); const start = new Date().getTime(); const zoomOptions = Array(13).fill().map((unused, z) => 2 ** z); const leftRightWidth = 100; const BottomBar = memo(({ zoom, setZoom, timelineToggleComfortZoom, isRotationSet, rotation, areWeCutting, increaseRotation, cleanupFilesDialog, captureSnapshot, onExportPress, segmentsToExport, hasVideo, seekAbs, currentSegIndexSafe, cutSegments, currentCutSeg, setCutStart, setCutEnd, setCurrentSegIndex, cutStartTimeManual, setCutStartTimeManual, cutEndTimeManual, setCutEndTimeManual, jumpTimelineStart, jumpTimelineEnd, jumpCutEnd, jumpCutStart, startTimeOffset, setCutTime, currentApparentCutSeg, playing, shortStep, togglePlay, setTimelineMode, hasAudio, timelineMode, keyframesEnabled, toggleKeyframesEnabled, seekClosestKeyframe, detectedFps, }) => { const { t } = useTranslation(); const { invertCutSegments, setInvertCutSegments, simpleMode, toggleSimpleMode } = 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}°`; // Clear manual overrides if upstream cut time has changed useEffect(() => { setCutStartTimeManual(); setCutEndTimeManual(); }, [setCutStartTimeManual, setCutEndTimeManual, currentApparentCutSeg.start, currentApparentCutSeg.end]); function renderJumpCutpointButton(direction) { const newIndex = currentSegIndexSafe + direction; const seg = cutSegments[newIndex]; const backgroundColor = seg && getSegColor(seg).alpha(0.5).string(); const opacity = seg ? undefined : 0.3; const text = seg ? `${newIndex + 1}` : '-'; const wide = text.length > 1; const segButtonStyle = { backgroundColor, opacity, padding: `6px ${wide ? 4 : 6}px`, borderRadius: 10, color: 'white', fontSize: wide ? 12 : 14, width: 20, boxSizing: 'border-box', letterSpacing: -1, lineHeight: '10px', fontWeight: 'bold', margin: '0 6px', }; return (
0 ? t('Select next segment') : t('Select previous segment')} (${newIndex + 1})`} onClick={() => seg && setCurrentSegIndex(newIndex)} > {text}
); } function renderCutTimeInput(type) { const isStart = type === 'start'; const cutTimeManual = isStart ? cutStartTimeManual : cutEndTimeManual; const cutTime = isStart ? currentApparentCutSeg.start : currentApparentCutSeg.end; const setCutTimeManual = isStart ? setCutStartTimeManual : setCutEndTimeManual; const isCutTimeManualSet = () => cutTimeManual !== undefined; const border = `1px solid ${getSegColor(currentCutSeg).alpha(0.8).string()}`; const cutTimeInputStyle = { background: 'white', border, borderRadius: 5, color: 'rgba(0, 0, 0, 0.7)', fontSize: 13, textAlign: 'center', padding: '1px 5px', marginTop: 0, marginBottom: 0, marginLeft: isStart ? 0 : 5, marginRight: isStart ? 5 : 0, boxSizing: 'border-box', fontFamily: 'inherit', width: 90, outline: 'none', }; function parseAndSetCutTime(text) { setCutTimeManual(text); // Don't proceed if not a valid time value const timeWithOffset = parseDuration(text); if (timeWithOffset === undefined) return; const timeWithoutOffset = Math.max(timeWithOffset - startTimeOffset, 0); try { setCutTime(type, timeWithoutOffset); seekAbs(timeWithoutOffset); } catch (err) { console.error('Cannot set cut time', err); // If we get an error from setCutTime, remain in the editing state (cutTimeManual) // https://github.com/mifi/lossless-cut/issues/988 } } function handleCutTimeInput(text) { // Allow the user to erase to reset if (text.length === 0) { setCutTimeManual(); return; } parseAndSetCutTime(text); } async function handleCutTimePaste(e) { e.preventDefault(); try { const clipboardData = e.clipboardData.getData('Text'); parseAndSetCutTime(clipboardData); } catch (err) { console.error(err); } } return ( handleCutTimeInput(e.target.value)} onPaste={handleCutTimePaste} value={isCutTimeManualSet() ? cutTimeManual : formatDuration({ seconds: cutTime + startTimeOffset })} /> ); } const PlayPause = playing ? FaPause : FaPlay; return ( <>
{!simpleMode && ( <> {hasAudio && ( setTimelineMode('waveform')} /> )} {hasVideo && ( <> setTimelineMode('thumbnails')} /> )} )}
{!simpleMode && ( <> {renderJumpCutpointButton(-1)} )} {!simpleMode && renderCutTimeInput('start')} seekClosestKeyframe(-1)} /> {!simpleMode && ( shortStep(-1)} /> )}
{!simpleMode && ( shortStep(1)} /> )} seekClosestKeyframe(1)} /> {!simpleMode && renderCutTimeInput('end')} {!simpleMode && ( <> {renderJumpCutpointButton(1)} )}
{simpleMode &&
{t('Toggle advanced view')}
} {!simpleMode && ( <>
{Math.floor(zoom)}x
{detectedFps != null &&
{detectedFps.toFixed(3)}
} )}
{!isDev && new Date().getTime() - start > 2 * 60 * 1000 && ['t', 'u', 'C', 's', 's', 'e', 'l', 's', 's', 'o', 'L'].reverse().join('')}
{hasVideo && ( <> {isRotationSet && rotationStr} )} {!simpleMode && ( )} {hasVideo && ( <> {!simpleMode && } )} {!simpleMode && }
); }); export default BottomBar;