mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 10:22:31 +01:00
Many UI improvements #189
- support arbitrary stream selection #214 - implement mute playbakc #125
This commit is contained in:
parent
996bd2d700
commit
e913982dc4
2
.babelrc
2
.babelrc
@ -1,7 +1,7 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"targets": { "electron": "1.8" }
|
||||
"targets": { "electron": "8.0" }
|
||||
}],
|
||||
"react"
|
||||
],
|
||||
|
@ -13,14 +13,16 @@
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "error",
|
||||
|
||||
"react/jsx-fragments": 0,
|
||||
"no-console": 0,
|
||||
"react/destructuring-assignment": 0,
|
||||
"react/forbid-prop-types": [1, { "forbid": ["any"] }],
|
||||
"jsx-a11y/click-events-have-key-events": 0,
|
||||
"jsx-a11y/interactive-supports-focus": 0,
|
||||
"react/jsx-one-expression-per-line": 0,
|
||||
"object-curly-newline": 0,
|
||||
"arrow-parens": 0,
|
||||
"jsx-a11y/control-has-associated-label": 0,
|
||||
"react/prop-types": 0
|
||||
"react/prop-types": 0,
|
||||
}
|
||||
}
|
||||
|
@ -55,10 +55,12 @@
|
||||
"color": "^3.1.0",
|
||||
"electron-default-menu": "^1.0.0",
|
||||
"electron-is-dev": "^0.1.2",
|
||||
"evergreen-ui": "^4.23.0",
|
||||
"execa": "^0.5.0",
|
||||
"ffmpeg-static": "3",
|
||||
"ffprobe-static": "^3.0.0",
|
||||
"file-type": "^12.4.0",
|
||||
"framer-motion": "^1.8.4",
|
||||
"fs-extra": "^8.1.0",
|
||||
"github-api": "^3.2.2",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -86,10 +88,10 @@
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
"electron 7.0"
|
||||
"electron 8.0"
|
||||
],
|
||||
"development": [
|
||||
"electron 7.0"
|
||||
"electron 8.0"
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
|
@ -1,10 +1,16 @@
|
||||
import React from 'react';
|
||||
import { IoIosCloseCircleOutline } from 'react-icons/io';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
const HelpSheet = ({ visible, onTogglePress, renderSettings }) => {
|
||||
if (visible) {
|
||||
return (
|
||||
<div className="help-sheet">
|
||||
const HelpSheet = ({ visible, onTogglePress, renderSettings }) => (
|
||||
<AnimatePresence>
|
||||
{visible && (
|
||||
<motion.div
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0, opacity: 0 }}
|
||||
className="help-sheet"
|
||||
>
|
||||
<IoIosCloseCircleOutline role="button" onClick={onTogglePress} size={30} style={{ position: 'fixed', right: 0, top: 0, padding: 20 }} />
|
||||
|
||||
<h1>Keyboard shortcuts</h1>
|
||||
@ -29,11 +35,9 @@ const HelpSheet = ({ visible, onTogglePress, renderSettings }) => {
|
||||
|
||||
<h1 style={{ marginTop: 40 }}>Settings</h1>
|
||||
{renderSettings()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
||||
export default HelpSheet;
|
||||
|
82
src/StreamsSelector.jsx
Normal file
82
src/StreamsSelector.jsx
Normal file
@ -0,0 +1,82 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { FaVideo, FaVideoSlash, FaFileExport, FaVolumeUp, FaVolumeMute, FaBan } from 'react-icons/fa';
|
||||
import { GoFileBinary } from 'react-icons/go';
|
||||
import { MdSubtitles } from 'react-icons/md';
|
||||
|
||||
const { formatDuration } = require('./util');
|
||||
const { getStreamFps } = require('./ffmpeg');
|
||||
|
||||
|
||||
const StreamsSelector = memo(({
|
||||
streams, copyStreamIds, toggleCopyStreamId, onExtractAllStreamsPress,
|
||||
}) => {
|
||||
if (!streams) return null;
|
||||
|
||||
return (
|
||||
<div style={{ color: 'black', padding: 10 }}>
|
||||
<p>Click to select which tracks to keep:</p>
|
||||
|
||||
<table>
|
||||
<thead style={{ background: 'rgba(0,0,0,0.1)' }}>
|
||||
<tr>
|
||||
<td />
|
||||
<td />
|
||||
<td>Type</td>
|
||||
<td>Tag</td>
|
||||
<td>Codec</td>
|
||||
<td>Duration</td>
|
||||
<td>Frames</td>
|
||||
<td>Bitrate</td>
|
||||
<td>Data</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{streams.map((stream) => {
|
||||
const bitrate = parseInt(stream.bit_rate, 10);
|
||||
const duration = parseInt(stream.duration, 10);
|
||||
|
||||
function onToggle() {
|
||||
toggleCopyStreamId(stream.index);
|
||||
}
|
||||
|
||||
const copyStream = copyStreamIds[stream.index];
|
||||
|
||||
let Icon;
|
||||
if (stream.codec_type === 'audio') {
|
||||
Icon = copyStream ? FaVolumeUp : FaVolumeMute;
|
||||
} else if (stream.codec_type === 'video') {
|
||||
Icon = copyStream ? FaVideo : FaVideoSlash;
|
||||
} else if (stream.codec_type === 'subtitle') {
|
||||
Icon = copyStream ? MdSubtitles : FaBan;
|
||||
} else {
|
||||
Icon = copyStream ? GoFileBinary : FaBan;
|
||||
}
|
||||
|
||||
const streamFps = getStreamFps(stream);
|
||||
|
||||
return (
|
||||
<tr key={stream.index} style={{ opacity: copyStream ? undefined : 0.4 }} onClick={onToggle}>
|
||||
<td><Icon size={20} style={{ padding: '0 5px', cursor: 'pointer' }} /></td>
|
||||
<td>{stream.index}</td>
|
||||
<td>{stream.codec_type}</td>
|
||||
<td>{stream.codec_tag !== '0x0000' && stream.codec_tag_string}</td>
|
||||
<td>{stream.codec_name}</td>
|
||||
<td>{!Number.isNaN(duration) && `${formatDuration({ seconds: duration })}`}</td>
|
||||
<td>{stream.nb_frames}</td>
|
||||
<td>{!Number.isNaN(bitrate) && `${(bitrate / 1e6).toFixed(1)}MBit/s`}</td>
|
||||
<td>{stream.width && stream.height && `${stream.width}x${stream.height}`} {stream.channels && `${stream.channels}c`} {stream.channel_layout} {streamFps && `${streamFps.toFixed(1)}fps`}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style={{ cursor: 'pointer', padding: 20 }} role="button" onClick={onExtractAllStreamsPress}>
|
||||
<FaFileExport size={30} style={{ verticalAlign: 'middle' }} /> Export each track as individual files
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default StreamsSelector;
|
@ -1,4 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const { formatDuration } = require('./util');
|
||||
|
||||
@ -44,12 +45,17 @@ const TimelineSeg = ({
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react/jsx-fragments
|
||||
<Fragment>
|
||||
<div>
|
||||
{cutStartTime !== undefined && (
|
||||
<div style={startMarkerStyle} className="cut-time-marker" role="button" tabIndex="0" onClick={onThisSegClick} />
|
||||
)}
|
||||
{isCutRangeValid && (cutStartTime !== undefined || cutEndTime !== undefined) && (
|
||||
<div
|
||||
<motion.div
|
||||
layoutTransition
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
|
||||
className="cut-section"
|
||||
style={cutSectionStyle}
|
||||
role="button"
|
||||
@ -61,7 +67,7 @@ const TimelineSeg = ({
|
||||
{cutEndTime !== undefined && (
|
||||
<div style={endMarkerStyle} className="cut-time-marker" role="button" tabIndex="0" onClick={onThisSegClick} />
|
||||
)}
|
||||
</Fragment>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -64,7 +64,7 @@ function handleProgress(process, cutDuration, onProgress) {
|
||||
|
||||
async function cut({
|
||||
filePath, format, cutFrom, cutTo, cutToApparent, videoDuration, rotation,
|
||||
includeAllStreams, onProgress, stripAudio, keyframeCut, outPath,
|
||||
onProgress, copyStreamIds, keyframeCut, outPath,
|
||||
}) {
|
||||
console.log('Cutting from', cutFrom, 'to', cutToApparent);
|
||||
|
||||
@ -90,13 +90,12 @@ async function cut({
|
||||
const ffmpegArgs = [
|
||||
...inputCutArgs,
|
||||
|
||||
...(stripAudio ? ['-an'] : ['-acodec', 'copy']),
|
||||
'-c', 'copy',
|
||||
|
||||
'-vcodec', 'copy',
|
||||
'-scodec', 'copy',
|
||||
|
||||
...(includeAllStreams ? ['-map', '0'] : []),
|
||||
...flatMap(Object.keys(copyStreamIds).filter(index => copyStreamIds[index]), index => ['-map', `0:${index}`]),
|
||||
'-map_metadata', '0',
|
||||
// https://video.stackexchange.com/questions/23741/how-to-prevent-ffmpeg-from-dropping-metadata
|
||||
'-movflags', 'use_metadata_tags',
|
||||
|
||||
// See https://github.com/mifi/lossless-cut/issues/170
|
||||
'-ignore_unknown',
|
||||
@ -121,7 +120,7 @@ async function cut({
|
||||
|
||||
async function cutMultiple({
|
||||
customOutDir, filePath, format, segments: segmentsUnsorted, videoDuration, rotation,
|
||||
includeAllStreams, onProgress, stripAudio, keyframeCut,
|
||||
onProgress, keyframeCut, copyStreamIds,
|
||||
}) {
|
||||
const segments = sortBy(segmentsUnsorted, 'cutFrom');
|
||||
const singleProgresses = {};
|
||||
@ -148,8 +147,7 @@ async function cutMultiple({
|
||||
format,
|
||||
videoDuration,
|
||||
rotation,
|
||||
includeAllStreams,
|
||||
stripAudio,
|
||||
copyStreamIds,
|
||||
keyframeCut,
|
||||
cutFrom,
|
||||
cutTo,
|
||||
@ -218,7 +216,7 @@ async function html5ifyDummy(filePath, outPath) {
|
||||
await transferTimestamps(filePath, outPath);
|
||||
}
|
||||
|
||||
async function mergeFiles({ paths, outPath, includeAllStreams }) {
|
||||
async function mergeFiles({ paths, outPath }) {
|
||||
console.log('Merging files', { paths }, 'to', outPath);
|
||||
|
||||
// https://blog.yo1.dog/fix-for-ffmpeg-protocol-not-on-whitelist-error-for-urls/
|
||||
@ -226,7 +224,7 @@ async function mergeFiles({ paths, outPath, includeAllStreams }) {
|
||||
'-f', 'concat', '-safe', '0', '-protocol_whitelist', 'file,pipe', '-i', '-',
|
||||
'-c', 'copy',
|
||||
|
||||
...(includeAllStreams ? ['-map', '0'] : []),
|
||||
'-map', '0',
|
||||
'-map_metadata', '0',
|
||||
|
||||
// See https://github.com/mifi/lossless-cut/issues/170
|
||||
@ -251,17 +249,17 @@ async function mergeFiles({ paths, outPath, includeAllStreams }) {
|
||||
console.log(result.stdout);
|
||||
}
|
||||
|
||||
async function mergeAnyFiles({ customOutDir, paths, includeAllStreams }) {
|
||||
async function mergeAnyFiles({ customOutDir, paths }) {
|
||||
const firstPath = paths[0];
|
||||
const ext = path.extname(firstPath);
|
||||
const outPath = getOutPath(customOutDir, firstPath, `merged${ext}`);
|
||||
return mergeFiles({ paths, outPath, includeAllStreams });
|
||||
return mergeFiles({ paths, outPath });
|
||||
}
|
||||
|
||||
async function autoMergeSegments({ customOutDir, sourceFile, segmentPaths, includeAllStreams }) {
|
||||
async function autoMergeSegments({ customOutDir, sourceFile, segmentPaths }) {
|
||||
const ext = path.extname(sourceFile);
|
||||
const outPath = getOutPath(customOutDir, sourceFile, `cut-merged-${new Date().getTime()}${ext}`);
|
||||
await mergeFiles({ paths: segmentPaths, outPath, includeAllStreams });
|
||||
await mergeFiles({ paths: segmentPaths, outPath });
|
||||
await pMap(segmentPaths, trash, { concurrency: 5 });
|
||||
}
|
||||
|
||||
@ -403,6 +401,21 @@ async function renderFrame(timestamp, filePath, rotation) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// https://www.ffmpeg.org/doxygen/3.2/libavutil_2utils_8c_source.html#l00079
|
||||
const defaultProcessedCodecTypes = [
|
||||
'video',
|
||||
'audio',
|
||||
'subtitle',
|
||||
];
|
||||
|
||||
function getStreamFps(stream) {
|
||||
const match = typeof stream.avg_frame_rate === 'string' && stream.avg_frame_rate.match(/^([0-9]+)\/([0-9]+)$/);
|
||||
if (stream.codec_type === 'video' && match) {
|
||||
return parseInt(match[1], 10) / parseInt(match[2], 10);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
cutMultiple,
|
||||
@ -414,4 +427,6 @@ module.exports = {
|
||||
extractAllStreams,
|
||||
renderFrame,
|
||||
getAllStreams,
|
||||
defaultProcessedCodecTypes,
|
||||
getStreamFps,
|
||||
};
|
||||
|
10
src/main.css
10
src/main.css
@ -52,19 +52,11 @@ input, button, textarea, :focus {
|
||||
padding: .3em;
|
||||
}
|
||||
|
||||
.left-menu {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
padding: .3em;
|
||||
}
|
||||
|
||||
.controls-wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 5.75rem;
|
||||
background: #6b6b6b;
|
||||
text-align: center;
|
||||
}
|
||||
@ -114,7 +106,7 @@ input, button, textarea, :focus {
|
||||
}
|
||||
|
||||
.help-sheet h1 {
|
||||
font-size: 1em;
|
||||
font-size: 1.2em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
446
src/renderer.jsx
446
src/renderer.jsx
@ -1,15 +1,24 @@
|
||||
import React, { memo, useEffect, useState, useCallback, useRef } from 'react';
|
||||
import { IoIosHelpCircle } from 'react-icons/io';
|
||||
import { IoIosHelpCircle, IoIosCamera } from 'react-icons/io';
|
||||
import { FaPlus, FaMinus, FaAngleLeft, FaAngleRight, FaTrashAlt, FaVolumeMute, FaVolumeUp } from 'react-icons/fa';
|
||||
import { MdRotate90DegreesCcw } from 'react-icons/md';
|
||||
import { FiScissors } from 'react-icons/fi';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
|
||||
import { Popover, Button } from 'evergreen-ui';
|
||||
import fromPairs from 'lodash/fromPairs';
|
||||
import clamp from 'lodash/clamp';
|
||||
import clone from 'lodash/clone';
|
||||
|
||||
import HelpSheet from './HelpSheet';
|
||||
import TimelineSeg from './TimelineSeg';
|
||||
import StreamsSelector from './StreamsSelector';
|
||||
import { loadMifiLink } from './mifi';
|
||||
|
||||
|
||||
const isDev = require('electron-is-dev');
|
||||
const electron = require('electron'); // eslint-disable-line
|
||||
const Mousetrap = require('mousetrap');
|
||||
const round = require('lodash/round');
|
||||
const clamp = require('lodash/clamp');
|
||||
const clone = require('lodash/clone');
|
||||
const Hammer = require('react-hammerjs').default;
|
||||
const path = require('path');
|
||||
const trash = require('trash');
|
||||
@ -25,6 +34,8 @@ const { showMergeDialog, showOpenAndMergeDialog } = require('./merge/merge');
|
||||
const captureFrame = require('./capture-frame');
|
||||
const ffmpeg = require('./ffmpeg');
|
||||
|
||||
const { defaultProcessedCodecTypes, getStreamFps } = ffmpeg;
|
||||
|
||||
|
||||
const {
|
||||
getOutPath, parseDuration, formatDuration, toast, errorToast, showFfmpegFail, setFileNameTitle,
|
||||
@ -80,13 +91,12 @@ const App = memo(() => {
|
||||
const [startTimeOffset, setStartTimeOffset] = useState(0);
|
||||
const [rotationPreviewRequested, setRotationPreviewRequested] = useState(false);
|
||||
const [filePath, setFilePath] = useState('');
|
||||
const [playbackRate, setPlaybackRate] = useState(1);
|
||||
const [detectedFps, setDetectedFps] = useState();
|
||||
const [streams, setStreams] = useState([]);
|
||||
const [copyStreamIds, setCopyStreamIds] = useState({});
|
||||
const [muted, setMuted] = useState(false);
|
||||
|
||||
// Global state
|
||||
const [stripAudio, setStripAudio] = useState(false);
|
||||
const [includeAllStreams, setIncludeAllStreams] = useState(true);
|
||||
const [captureFormat, setCaptureFormat] = useState('jpeg');
|
||||
const [customOutDir, setCustomOutDir] = useState();
|
||||
const [keyframeCut, setKeyframeCut] = useState(true);
|
||||
@ -98,6 +108,11 @@ const App = memo(() => {
|
||||
const videoRef = useRef();
|
||||
const timelineWrapperRef = useRef();
|
||||
|
||||
|
||||
function toggleCopyStreamId(index) {
|
||||
setCopyStreamIds(v => ({ ...v, [index]: !v[index] }));
|
||||
}
|
||||
|
||||
function seekAbs(val) {
|
||||
const video = videoRef.current;
|
||||
if (val == null || Number.isNaN(val)) return;
|
||||
@ -140,8 +155,10 @@ const App = memo(() => {
|
||||
setStartTimeOffset(0);
|
||||
setRotationPreviewRequested(false);
|
||||
setFilePath(''); // Setting video src="" prevents memory leak in chromium
|
||||
setPlaybackRate(1);
|
||||
setDetectedFps();
|
||||
setStreams([]);
|
||||
setCopyStreamIds({});
|
||||
setMuted(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => () => {
|
||||
@ -290,7 +307,7 @@ const App = memo(() => {
|
||||
|
||||
// console.log('merge', paths);
|
||||
await ffmpeg.mergeAnyFiles({
|
||||
customOutDir, paths, includeAllStreams,
|
||||
customOutDir, paths,
|
||||
});
|
||||
} catch (err) {
|
||||
errorToast('Failed to merge files. Make sure they are all of the exact same format and codecs');
|
||||
@ -298,14 +315,23 @@ const App = memo(() => {
|
||||
} finally {
|
||||
setWorking(false);
|
||||
}
|
||||
}, [customOutDir, includeAllStreams]);
|
||||
}, [customOutDir]);
|
||||
|
||||
const toggleCaptureFormat = () => setCaptureFormat(f => (f === 'png' ? 'jpeg' : 'png'));
|
||||
const toggleIncludeAllStreams = () => setIncludeAllStreams(v => !v);
|
||||
const toggleStripAudio = () => setStripAudio(sa => !sa);
|
||||
const toggleKeyframeCut = () => setKeyframeCut(val => !val);
|
||||
const toggleAutoMerge = () => setAutoMerge(val => !val);
|
||||
|
||||
const copyAnyAudioTrack = streams.some(stream => copyStreamIds[stream.index] && stream.codec_type === 'audio');
|
||||
function toggleStripAudio() {
|
||||
setCopyStreamIds((old) => {
|
||||
const newCopyStreamIds = { ...old };
|
||||
streams.forEach((stream) => {
|
||||
if (stream.codec_type === 'audio') newCopyStreamIds[stream.index] = !copyAnyAudioTrack;
|
||||
});
|
||||
return newCopyStreamIds;
|
||||
});
|
||||
}
|
||||
|
||||
const removeCutSegment = useCallback(() => {
|
||||
if (cutSegments.length < 2) return;
|
||||
|
||||
@ -327,8 +353,6 @@ const App = memo(() => {
|
||||
seekAbs((relX / target.offsetWidth) * (duration || 0));
|
||||
}
|
||||
|
||||
const onPlaybackRateChange = () => setPlaybackRate(videoRef.current.playbackRate);
|
||||
|
||||
const playCommand = useCallback(() => {
|
||||
const video = videoRef.current;
|
||||
if (playing) return video.pause();
|
||||
@ -389,8 +413,7 @@ const App = memo(() => {
|
||||
format: fileFormat,
|
||||
videoDuration: duration,
|
||||
rotation: effectiveRotation,
|
||||
includeAllStreams,
|
||||
stripAudio,
|
||||
copyStreamIds,
|
||||
keyframeCut,
|
||||
segments,
|
||||
onProgress: setCutProgress,
|
||||
@ -403,9 +426,10 @@ const App = memo(() => {
|
||||
customOutDir,
|
||||
sourceFile: filePath,
|
||||
segmentPaths: outFiles,
|
||||
includeAllStreams,
|
||||
});
|
||||
}
|
||||
|
||||
toast.fire({ timer: 10000, type: 'success', title: `Cut completed! Output file(s) can be found at: ${getOutDir(customOutDir, filePath)}. You can change the output directory in settings` });
|
||||
} catch (err) {
|
||||
console.error('stdout:', err.stdout);
|
||||
console.error('stderr:', err.stderr);
|
||||
@ -417,15 +441,18 @@ const App = memo(() => {
|
||||
|
||||
showFfmpegFail(err);
|
||||
} finally {
|
||||
toast.fire({ timer: 10000, type: 'success', title: `Cut completed! Output file(s) can be found at: ${getOutDir(customOutDir, filePath)}. You can change the output directory in settings` });
|
||||
setWorking(false);
|
||||
}
|
||||
}, [
|
||||
effectiveRotation, getApparentCutStartTime, getApparentCutEndTime, getCutEndTime,
|
||||
getCutStartTime, isCutRangeValid, working, cutSegments, duration, filePath, keyframeCut,
|
||||
autoMerge, customOutDir, fileFormat, includeAllStreams, stripAudio,
|
||||
autoMerge, customOutDir, fileFormat, copyStreamIds,
|
||||
]);
|
||||
|
||||
function showUnsupportedFileMessage() {
|
||||
toast.fire({ timer: 10000, type: 'warning', title: 'This video is not natively supported', text: 'This means that there is no audio in the preview and it has low quality. The final cut operation will however be lossless and contains audio!' });
|
||||
}
|
||||
|
||||
// TODO use ffmpeg to capture frame
|
||||
const capture = useCallback(async () => {
|
||||
if (!filePath) return;
|
||||
@ -459,12 +486,16 @@ const App = memo(() => {
|
||||
await ffmpeg.html5ifyDummy(fp, html5ifiedDummyPathDummy);
|
||||
setDummyVideoPath(html5ifiedDummyPathDummy);
|
||||
setHtml5FriendlyPath();
|
||||
showUnsupportedFileMessage();
|
||||
}, [customOutDir]);
|
||||
|
||||
const checkExistingHtml5FriendlyFile = useCallback(async (fp, speed) => {
|
||||
const existing = getHtml5ifiedPath(fp, speed);
|
||||
const ret = existing && await exists(existing);
|
||||
if (ret) setHtml5FriendlyPath(existing);
|
||||
if (ret) {
|
||||
setHtml5FriendlyPath(existing);
|
||||
showUnsupportedFileMessage();
|
||||
}
|
||||
return ret;
|
||||
}, [getHtml5ifiedPath]);
|
||||
|
||||
@ -487,14 +518,17 @@ const App = memo(() => {
|
||||
}
|
||||
|
||||
const { streams: streamsNew } = await ffmpeg.getAllStreams(fp);
|
||||
// console.log('streams', streams);
|
||||
console.log('streams', streamsNew);
|
||||
setStreams(streamsNew);
|
||||
setCopyStreamIds(fromPairs(streamsNew.map((stream) => [
|
||||
stream.index, defaultProcessedCodecTypes.includes(stream.codec_type),
|
||||
])));
|
||||
|
||||
|
||||
streamsNew.find((stream) => {
|
||||
const match = typeof stream.avg_frame_rate === 'string' && stream.avg_frame_rate.match(/^([0-9]+)\/([0-9]+)$/);
|
||||
if (stream.codec_type === 'video' && match) {
|
||||
const fps = parseInt(match[1], 10) / parseInt(match[2], 10);
|
||||
setDetectedFps(fps);
|
||||
const streamFps = getStreamFps(stream);
|
||||
if (streamFps != null) {
|
||||
setDetectedFps(streamFps);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -507,6 +541,7 @@ const App = memo(() => {
|
||||
|
||||
if (html5FriendlyPathRequested) {
|
||||
setHtml5FriendlyPath(html5FriendlyPathRequested);
|
||||
showUnsupportedFileMessage();
|
||||
} else if (
|
||||
!(await checkExistingHtml5FriendlyFile(fp, 'slow-audio') || await checkExistingHtml5FriendlyFile(fp, 'slow') || await checkExistingHtml5FriendlyFile(fp, 'fast'))
|
||||
&& !doesPlayerSupportFile(streamsNew)
|
||||
@ -572,6 +607,25 @@ const App = memo(() => {
|
||||
electron.ipcRenderer.send('renderer-ready');
|
||||
}, []);
|
||||
|
||||
const extractAllStreams = useCallback(async () => {
|
||||
if (!filePath) return;
|
||||
|
||||
try {
|
||||
setWorking(true);
|
||||
await ffmpeg.extractAllStreams({ customOutDir, filePath });
|
||||
toast.fire({ type: 'success', title: `All streams can be found as separate files at: ${getOutDir(customOutDir, filePath)}` });
|
||||
} catch (err) {
|
||||
errorToast('Failed to extract all streams');
|
||||
console.error('Failed to extract all streams', err);
|
||||
} finally {
|
||||
setWorking(false);
|
||||
}
|
||||
}, [customOutDir, filePath]);
|
||||
|
||||
function onExtractAllStreamsPress() {
|
||||
extractAllStreams();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function fileOpened(event, filePaths) {
|
||||
if (!filePaths || filePaths.length !== 1) return;
|
||||
@ -622,20 +676,6 @@ const App = memo(() => {
|
||||
setStartTimeOffset(newStartTimeOffset);
|
||||
}
|
||||
|
||||
async function extractAllStreams() {
|
||||
if (!filePath) return;
|
||||
|
||||
try {
|
||||
setWorking(true);
|
||||
await ffmpeg.extractAllStreams({ customOutDir, filePath });
|
||||
} catch (err) {
|
||||
errorToast('Failed to extract all streams');
|
||||
console.error('Failed to extract all streams', err);
|
||||
} finally {
|
||||
setWorking(false);
|
||||
}
|
||||
}
|
||||
|
||||
electron.ipcRenderer.on('file-opened', fileOpened);
|
||||
electron.ipcRenderer.on('close-file', closeFile);
|
||||
electron.ipcRenderer.on('html5ify', html5ify);
|
||||
@ -653,7 +693,7 @@ const App = memo(() => {
|
||||
};
|
||||
}, [
|
||||
load, mergeFiles, outputDir, filePath, customOutDir, startTimeOffset, getHtml5ifiedPath,
|
||||
createDummyVideo, resetState,
|
||||
createDummyVideo, resetState, extractAllStreams,
|
||||
]);
|
||||
|
||||
const onDrop = useCallback((ev) => {
|
||||
@ -722,9 +762,6 @@ const App = memo(() => {
|
||||
const jumpCutButtonStyle = {
|
||||
position: 'absolute', color: 'black', bottom: 0, top: 0, padding: '2px 8px',
|
||||
};
|
||||
const infoSpanStyle = {
|
||||
background: 'rgba(255, 255, 255, 0.4)', padding: '.1em .4em', margin: '0 3px', fontSize: 13, borderRadius: '.3em',
|
||||
};
|
||||
|
||||
function renderOutFmt({ width } = {}) {
|
||||
return (
|
||||
@ -778,7 +815,7 @@ const App = memo(() => {
|
||||
type="button"
|
||||
onClick={toggleAutoMerge}
|
||||
>
|
||||
{autoMerge ? 'Auto merge segments to one file (am)' : 'Export separate segments (nm)'}
|
||||
{autoMerge ? 'Auto merge segments to one file' : 'Export separate segments'}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@ -790,27 +827,11 @@ const App = memo(() => {
|
||||
type="button"
|
||||
onClick={toggleKeyframeCut}
|
||||
>
|
||||
{keyframeCut ? 'Nearest keyframe cut (kc) - will cut at the nearest keyframe' : 'Normal cut (nc) - cut accurate position but could leave an empty portion'}
|
||||
{keyframeCut ? 'Nearest keyframe cut - will cut at the nearest keyframe' : 'Normal cut - cut accurate position but could leave an empty portion'}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Include treams</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleIncludeAllStreams}
|
||||
>
|
||||
{includeAllStreams ? 'include all streams (audio, video, subtitle, data) (all)' : 'include only primary streams (1 audio and 1 video stream only) (ps)'}
|
||||
</button>
|
||||
|
||||
<div>
|
||||
Note that some streams like subtitles and data are not possible to cut and will therefore be transferred as is.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
Delete audio?
|
||||
@ -820,7 +841,7 @@ const App = memo(() => {
|
||||
type="button"
|
||||
onClick={toggleStripAudio}
|
||||
>
|
||||
{stripAudio ? 'Delete all audio tracks' : 'Keep all audio tracks'}
|
||||
{!copyAnyAudioTrack ? 'Delete all audio tracks' : 'Keep audio tracks'}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@ -832,9 +853,9 @@ const App = memo(() => {
|
||||
type="button"
|
||||
onClick={setOutputDir}
|
||||
>
|
||||
{outputDir ? 'Custom output directory (cd)' : 'Output files to same directory as input (id)'}
|
||||
{customOutDir ? 'Custom output directory' : 'Output files to same directory as current file'}
|
||||
</button>
|
||||
<div>{outputDir}</div>
|
||||
<div>{customOutDir}</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -855,12 +876,68 @@ const App = memo(() => {
|
||||
loadMifiLink().then(setMifiLink);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Testing:
|
||||
if (isDev) load('/Users/mifi/Downloads/inp.MOV');
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const topBarHeight = '2rem';
|
||||
const bottomBarHeight = '6rem';
|
||||
|
||||
const VolumeIcon = muted ? FaVolumeMute : FaVolumeUp;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ background: '#6b6b6b', height: topBarHeight, display: 'flex', alignItems: 'center', padding: '0 5px', justifyContent: 'space-between' }}>
|
||||
<Popover
|
||||
content={(
|
||||
<StreamsSelector
|
||||
streams={streams}
|
||||
copyStreamIds={copyStreamIds}
|
||||
toggleCopyStreamId={toggleCopyStreamId}
|
||||
onExtractAllStreamsPress={onExtractAllStreamsPress}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<Button height={20} iconBefore="list">Tracks</Button>
|
||||
</Popover>
|
||||
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
|
||||
{renderOutFmt({ width: 60 })}
|
||||
|
||||
<button
|
||||
style={{ opacity: cutSegments.length < 2 ? 0.4 : undefined }}
|
||||
type="button"
|
||||
title={`Auto merge segments to one file after export? ${autoMerge ? 'Auto merge enabled' : 'No merging'}`}
|
||||
onClick={withBlur(toggleAutoMerge)}
|
||||
>
|
||||
{autoMerge ? 'Merge cuts' : 'Separate cuts'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Cut mode ${keyframeCut ? 'nearest keyframe cut' : 'normal cut'}`}
|
||||
onClick={withBlur(toggleKeyframeCut)}
|
||||
>
|
||||
{keyframeCut ? 'Keyframe cut' : 'Normal cut'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Delete audio? Current: ${copyAnyAudioTrack ? 'keep audio tracks' : 'delete audio tracks'}`}
|
||||
onClick={withBlur(toggleStripAudio)}
|
||||
>
|
||||
{copyAnyAudioTrack ? 'Keep audio' : 'Delete audio'}
|
||||
</button>
|
||||
|
||||
<IoIosHelpCircle size={24} role="button" onClick={toggleHelp} style={{ verticalAlign: 'middle', marginLeft: 5 }} />
|
||||
</div>
|
||||
|
||||
{!filePath && (
|
||||
<div style={{ position: 'fixed', left: 0, right: 0, top: 0, bottom: '6rem', border: '2vmin dashed #252525', color: '#505050', margin: '5vmin', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', whiteSpace: 'nowrap' }}>
|
||||
<div style={{ position: 'fixed', left: 0, right: 0, top: topBarHeight, bottom: bottomBarHeight, border: '2vmin dashed #252525', color: '#505050', margin: '5vmin', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', whiteSpace: 'nowrap' }}>
|
||||
<div style={{ fontSize: '9vmin' }}>DROP VIDEO</div>
|
||||
<div style={{ fontSize: '3vmin' }}>PRESS H FOR HELP</div>
|
||||
|
||||
{mifiLink && mifiLink.loadUrl && (
|
||||
<div style={{ position: 'relative', margin: '3vmin', width: '60vmin', height: '20vmin' }}>
|
||||
@ -874,7 +951,7 @@ const App = memo(() => {
|
||||
|
||||
{working && (
|
||||
<div style={{
|
||||
color: 'white', background: 'rgba(0, 0, 0, 0.3)', borderRadius: '.5em', margin: '1em', padding: '.2em .5em', position: 'absolute', zIndex: 1, top: 0, left: 0,
|
||||
color: 'white', background: 'rgba(0, 0, 0, 0.3)', borderRadius: '.5em', margin: '1em', padding: '.2em .5em', position: 'absolute', zIndex: 1, top: topBarHeight, left: 0,
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-cog fa-spin fa-3x fa-fw" style={{ verticalAlign: 'middle', width: '1em', height: '1em' }} />
|
||||
@ -886,22 +963,13 @@ const App = memo(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{rotationPreviewRequested && (
|
||||
<div style={{
|
||||
position: 'absolute', zIndex: 1, top: '1em', right: '1em', color: 'white',
|
||||
}}
|
||||
>
|
||||
Lossless rotation preview
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* eslint-disable jsx-a11y/media-has-caption */}
|
||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: '6rem', pointerEvents: 'none' }}>
|
||||
<div style={{ position: 'absolute', top: topBarHeight, left: 0, right: 0, bottom: bottomBarHeight }}>
|
||||
<video
|
||||
muted={muted}
|
||||
ref={videoRef}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
src={fileUri}
|
||||
onRateChange={onPlaybackRateChange}
|
||||
onPlay={() => onPlayingChange(true)}
|
||||
onPause={() => onPlayingChange(false)}
|
||||
onDurationChange={e => setDuration(e.target.duration)}
|
||||
@ -920,13 +988,30 @@ const App = memo(() => {
|
||||
</div>
|
||||
{/* eslint-enable jsx-a11y/media-has-caption */}
|
||||
|
||||
{(html5FriendlyPath || dummyVideoPath) && (
|
||||
<div style={{ position: 'absolute', bottom: 100, right: 0, maxWidth: 300, background: 'rgba(0,0,0,0.2)', color: 'rgba(255,255,255,0.8)', boxShadow: 'rgba(0,0,0,0.2) 0 0 15px 15px' }}>
|
||||
This video is not natively supported, so there is no audio in the preview and it is of low quality. <b>The final cut operation will however be lossless and contain audio!</b>
|
||||
{rotationPreviewRequested && (
|
||||
<div style={{
|
||||
position: 'absolute', top: topBarHeight, marginTop: '1em', marginRight: '1em', right: 0, color: 'white',
|
||||
}}
|
||||
>
|
||||
Lossless rotation preview
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="controls-wrapper">
|
||||
{filePath && (
|
||||
<div style={{
|
||||
position: 'absolute', margin: '1em', right: 0, bottom: bottomBarHeight, color: 'rgba(255,255,255,0.7)',
|
||||
}}
|
||||
>
|
||||
<VolumeIcon
|
||||
title="Mute preview? (will not affect output)"
|
||||
size={30}
|
||||
role="button"
|
||||
onClick={() => setMuted(v => !v)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="controls-wrapper" style={{ height: bottomBarHeight }}>
|
||||
<Hammer
|
||||
onTap={handleTap}
|
||||
onPan={handleTap}
|
||||
@ -934,25 +1019,31 @@ const App = memo(() => {
|
||||
>
|
||||
<div>
|
||||
<div className="timeline-wrapper" ref={timelineWrapperRef}>
|
||||
{currentTimePos !== undefined && <div className="current-time" style={{ left: currentTimePos }} />}
|
||||
{currentTimePos !== undefined && <div className="current-time" style={{ left: currentTimePos, pointerEvents: 'none' }} />}
|
||||
|
||||
{cutSegments.map((seg, i) => (
|
||||
<TimelineSeg
|
||||
key={seg.uuid}
|
||||
segNum={i}
|
||||
color={seg.color}
|
||||
onSegClick={currentSegNew => setCurrentSeg(currentSegNew)}
|
||||
isActive={i === currentSeg}
|
||||
isCutRangeValid={isCutRangeValid(i)}
|
||||
duration={durationSafe}
|
||||
cutStartTime={getCutStartTime(i)}
|
||||
cutEndTime={getCutEndTime(i)}
|
||||
apparentCutStart={getApparentCutStartTime(i)}
|
||||
apparentCutEnd={getApparentCutEndTime(i)}
|
||||
/>
|
||||
))}
|
||||
<AnimatePresence>
|
||||
{cutSegments.map((seg, i) => (
|
||||
<TimelineSeg
|
||||
key={seg.uuid}
|
||||
segNum={i}
|
||||
color={seg.color}
|
||||
onSegClick={currentSegNew => setCurrentSeg(currentSegNew)}
|
||||
isActive={i === currentSeg}
|
||||
isCutRangeValid={isCutRangeValid(i)}
|
||||
duration={durationSafe}
|
||||
cutStartTime={getCutStartTime(i)}
|
||||
cutEndTime={getCutEndTime(i)}
|
||||
apparentCutStart={getApparentCutStartTime(i)}
|
||||
apparentCutEnd={getApparentCutEndTime(i)}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
|
||||
<div id="current-time-display">{formatTimecode(offsetCurrentTime)}</div>
|
||||
<div id="current-time-display">
|
||||
<span role="button" onClick={() => setTimecodeShowFrames(v => !v)}>
|
||||
{formatTimecode(offsetCurrentTime)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Hammer>
|
||||
@ -1019,135 +1110,78 @@ const App = memo(() => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<i
|
||||
style={{ background: segBgColor }}
|
||||
<FaAngleLeft
|
||||
title="Set cut start to current position"
|
||||
className="button fa fa-angle-left"
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
style={{ background: segBgColor, borderRadius: 10, padding: 5 }}
|
||||
size={16}
|
||||
onClick={setCutStart}
|
||||
/>
|
||||
<i
|
||||
title={cutSegments.length > 1 ? 'Export all segments' : 'Export selection'}
|
||||
className="button fa fa-scissors"
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
onClick={cutClick}
|
||||
/>
|
||||
<i
|
||||
<FaTrashAlt
|
||||
title="Delete source file"
|
||||
className="button fa fa-trash"
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
style={{ padding: 5 }}
|
||||
size={16}
|
||||
onClick={deleteSourceClick}
|
||||
/>
|
||||
<i
|
||||
style={{ background: segBgColor }}
|
||||
title="Set cut end to current position"
|
||||
className="button fa fa-angle-right"
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
/>
|
||||
<FaAngleRight
|
||||
title="Set cut end to current position"
|
||||
style={{ background: segBgColor, borderRadius: 10, padding: 5 }}
|
||||
size={16}
|
||||
onClick={setCutEnd}
|
||||
role="button"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="left-menu">
|
||||
{renderOutFmt({ width: 30 })}
|
||||
|
||||
<span style={infoSpanStyle} title="Playback rate">
|
||||
{round(playbackRate, 1) || 1}
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Average FPS (${timecodeShowFrames ? 'FPS fraction' : 'millisecond fraction'})`}
|
||||
onClick={withBlur(() => setTimecodeShowFrames(v => !v))}
|
||||
>
|
||||
{detectedFps ? round(detectedFps, 1) || 1 : '-'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
style={{ ...infoSpanStyle, background: segBgColor, color: 'white' }}
|
||||
disabled={cutSegments.length < 2}
|
||||
type="button"
|
||||
title={`Delete selected segment ${currentSeg + 1}`}
|
||||
onClick={withBlur(() => removeCutSegment())}
|
||||
>
|
||||
d
|
||||
{currentSeg + 1}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
<div className="left-menu" style={{ position: 'absolute', left: 0, bottom: 0, padding: '.3em', display: 'flex', alignItems: 'center' }}>
|
||||
<FaPlus
|
||||
size={30}
|
||||
style={{ margin: '0 5px', color: 'white' }}
|
||||
role="button"
|
||||
title="Add cut segment"
|
||||
onClick={withBlur(() => addCutSegment())}
|
||||
>
|
||||
c+
|
||||
</button>
|
||||
onClick={addCutSegment}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Auto merge segments to one file after export (and trash segments)? ${autoMerge ? 'Auto merge enabled' : 'No merging'}`}
|
||||
onClick={withBlur(toggleAutoMerge)}
|
||||
>
|
||||
{autoMerge ? 'am' : 'nm'}
|
||||
</button>
|
||||
|
||||
<IoIosHelpCircle size={22} role="button" onClick={toggleHelp} style={{ verticalAlign: 'middle' }} />
|
||||
<FaMinus
|
||||
size={30}
|
||||
style={{ margin: '0 5px', background: cutSegments.length < 2 ? undefined : segBgColor, borderRadius: 3, color: 'white' }}
|
||||
role="button"
|
||||
title={`Delete current segment ${currentSeg + 1}`}
|
||||
onClick={removeCutSegment}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="right-menu">
|
||||
<button
|
||||
type="button"
|
||||
title={`Cut mode ${keyframeCut ? 'nearest keyframe cut' : 'normal cut'}`}
|
||||
onClick={withBlur(toggleKeyframeCut)}
|
||||
>
|
||||
{keyframeCut ? 'kc' : 'nc'}
|
||||
</button>
|
||||
<div className="right-menu" style={{ position: 'absolute', right: 0, bottom: 0, padding: '.3em', display: 'flex', alignItems: 'center' }}>
|
||||
<div>
|
||||
<span style={{ width: 40, textAlign: 'right', display: 'inline-block' }}>{isRotationSet && rotationStr}</span>
|
||||
<MdRotate90DegreesCcw
|
||||
size={26}
|
||||
style={{ margin: '0 5px', verticalAlign: 'middle' }}
|
||||
title={`Set output rotation. Current: ${isRotationSet ? rotationStr : 'Don\'t modify'}`}
|
||||
onClick={increaseRotation}
|
||||
role="button"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Set output streams. Current: ${includeAllStreams ? 'include (and cut) all streams' : 'include only primary streams'}`}
|
||||
onClick={withBlur(toggleIncludeAllStreams)}
|
||||
>
|
||||
{includeAllStreams ? 'all' : 'ps'}
|
||||
</button>
|
||||
{renderCaptureFormatButton()}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Delete audio? Current: ${stripAudio ? 'delete audio tracks' : 'keep audio tracks'}`}
|
||||
onClick={withBlur(toggleStripAudio)}
|
||||
>
|
||||
{stripAudio ? 'da' : 'ka'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Set output rotation. Current: ${isRotationSet ? rotationStr : 'Don\'t modify'}`}
|
||||
onClick={withBlur(increaseRotation)}
|
||||
>
|
||||
{isRotationSet ? rotationStr : '-°'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Custom output dir (cancel to restore default). Current: ${outputDir || 'Not set (use input dir)'}`}
|
||||
onClick={withBlur(setOutputDir)}
|
||||
>
|
||||
{outputDir ? 'cd' : 'id'}
|
||||
</button>
|
||||
|
||||
<i
|
||||
<IoIosCamera
|
||||
style={{ paddingLeft: 5, paddingRight: 15 }}
|
||||
size={25}
|
||||
title="Capture frame"
|
||||
style={{ margin: '-.4em -.2em' }}
|
||||
className="button fa fa-camera"
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
onClick={capture}
|
||||
/>
|
||||
|
||||
{renderCaptureFormatButton()}
|
||||
<span style={{ background: 'hsl(194, 78%, 47%)', borderRadius: 5, padding: '3px 7px', fontSize: 14 }}>
|
||||
<FiScissors
|
||||
style={{ verticalAlign: 'middle', marginRight: 3 }}
|
||||
size={16}
|
||||
onClick={cutClick}
|
||||
title={cutSegments.length > 1 ? 'Export all segments' : 'Export selection'}
|
||||
/>
|
||||
Export
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<HelpSheet
|
||||
|
16
src/util.js
16
src/util.js
@ -1,7 +1,7 @@
|
||||
const _ = require('lodash');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const swal = require('sweetalert2');
|
||||
const fs = require('fs-extra');
|
||||
const Swal = require('sweetalert2');
|
||||
|
||||
const randomColor = require('./random-color');
|
||||
|
||||
@ -49,8 +49,8 @@ function getOutPath(customOutDir, filePath, nameSuffix) {
|
||||
|
||||
async function transferTimestamps(inPath, outPath) {
|
||||
try {
|
||||
const stat = await fs.statAsync(inPath);
|
||||
await fs.utimesAsync(outPath, stat.atime.getTime() / 1000, stat.mtime.getTime() / 1000);
|
||||
const stat = await fs.stat(inPath);
|
||||
await fs.utimes(outPath, stat.atime.getTime() / 1000, stat.mtime.getTime() / 1000);
|
||||
} catch (err) {
|
||||
console.error('Failed to set output file modified time', err);
|
||||
}
|
||||
@ -58,15 +58,15 @@ async function transferTimestamps(inPath, outPath) {
|
||||
|
||||
async function transferTimestampsWithOffset(inPath, outPath, offset) {
|
||||
try {
|
||||
const stat = await fs.statAsync(inPath);
|
||||
const stat = await fs.stat(inPath);
|
||||
const time = (stat.mtime.getTime() / 1000) + offset;
|
||||
await fs.utimesAsync(outPath, time, time);
|
||||
await fs.utimes(outPath, time, time);
|
||||
} catch (err) {
|
||||
console.error('Failed to set output file modified time', err);
|
||||
}
|
||||
}
|
||||
|
||||
const toast = swal.mixin({
|
||||
const toast = Swal.mixin({
|
||||
toast: true,
|
||||
position: 'top',
|
||||
showConfirmButton: false,
|
||||
@ -89,7 +89,7 @@ function setFileNameTitle(filePath) {
|
||||
}
|
||||
|
||||
async function promptTimeOffset(inputValue) {
|
||||
const { value } = await swal.fire({
|
||||
const { value } = await Swal.fire({
|
||||
title: 'Set custom start time offset',
|
||||
text: 'Instead of video apparently starting at 0, you can offset by a specified value (useful for viewing/cutting videos according to timecodes)',
|
||||
input: 'text',
|
||||
|
383
yarn.lock
383
yarn.lock
@ -62,6 +62,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409"
|
||||
integrity sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==
|
||||
|
||||
"@babel/runtime@7.0.0-beta.42":
|
||||
version "7.0.0-beta.42"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.42.tgz#352e40c92e0460d3e82f49bd7e79f6cda76f919f"
|
||||
integrity sha512-iOGRzUoONLOtmCvjUsZv3mZzgCT6ljHQY5fr1qG1QIiJQwtM7zbPWGGpa3QWETq+UqwWyJnoi5XZDZRwZDFciQ==
|
||||
dependencies:
|
||||
core-js "^2.5.3"
|
||||
regenerator-runtime "^0.11.1"
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.5":
|
||||
version "7.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308"
|
||||
@ -109,6 +117,14 @@
|
||||
lodash "^4.17.10"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@blueprintjs/icons@^3.2.0":
|
||||
version "3.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@blueprintjs/icons/-/icons-3.13.0.tgz#bff93a9ea5ced03afd2b3a65ddf4bb90bda485e4"
|
||||
integrity sha512-fvXGsAJ66SSjeHv3OeXjLEdKdPJ3wVztjhJQCAd51uebhj3FJ16EDDvO7BqBw5FyVkLkU11KAxSoCFZt7TC9GA==
|
||||
dependencies:
|
||||
classnames "^2.2"
|
||||
tslib "~1.9.0"
|
||||
|
||||
"@develar/schema-utils@~2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.1.0.tgz#eceb1695bfbed6f6bb84666d5d3abe5e1fd54e17"
|
||||
@ -132,6 +148,39 @@
|
||||
global-agent "^2.0.2"
|
||||
global-tunnel-ng "^2.7.1"
|
||||
|
||||
"@emotion/hash@^0.7.1":
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.7.4.tgz#f14932887422c9056b15a8d222a9074a7dfa2831"
|
||||
integrity sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A==
|
||||
|
||||
"@emotion/is-prop-valid@^0.8.2":
|
||||
version "0.8.6"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.6.tgz#4757646f0a58e9dec614c47c838e7147d88c263c"
|
||||
integrity sha512-mnZMho3Sq8BfzkYYRVc8ilQTnc8U02Ytp6J1AwM6taQStZ3AhsEJBX2LzhA/LJirNCwM2VtHL3VFIZ+sNJUgUQ==
|
||||
dependencies:
|
||||
"@emotion/memoize" "0.7.4"
|
||||
|
||||
"@emotion/memoize@0.7.4":
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
|
||||
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
|
||||
|
||||
"@popmotion/easing@^1.0.1", "@popmotion/easing@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@popmotion/easing/-/easing-1.0.2.tgz#17d925c45b4bf44189e5a38038d149df42d8c0b4"
|
||||
integrity sha512-IkdW0TNmRnWTeWI7aGQIVDbKXPWHVEYdGgd5ZR4SH/Ty/61p63jCjrPxX1XrR7IGkl08bjhJROStD7j+RKgoIw==
|
||||
|
||||
"@popmotion/popcorn@^0.4.2", "@popmotion/popcorn@^0.4.4":
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@popmotion/popcorn/-/popcorn-0.4.4.tgz#a5f906fccdff84526e3fcb892712d7d8a98d6adc"
|
||||
integrity sha512-jYO/8319fKoNLMlY4ZJPiPu8Ea8occYwRZhxpaNn/kZsK4QG2E7XFlXZMJBsTWDw7I1i0uaqyC4zn1nwEezLzg==
|
||||
dependencies:
|
||||
"@popmotion/easing" "^1.0.1"
|
||||
framesync "^4.0.1"
|
||||
hey-listen "^1.0.8"
|
||||
style-value-types "^3.1.7"
|
||||
tslib "^1.10.0"
|
||||
|
||||
"@sindresorhus/df@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/df/-/df-1.0.1.tgz#c69b66f52f6fcdd287c807df210305dbaf78500d"
|
||||
@ -198,6 +247,19 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
||||
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
||||
|
||||
"@types/react@^16.9.5":
|
||||
version "16.9.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.19.tgz#c842aa83ea490007d29938146ff2e4d9e4360c40"
|
||||
integrity sha512-LJV97//H+zqKWMms0kvxaKYJDG05U2TtQB3chRLF8MPNs+MQh/H1aGlyDUxjaHvu08EAGerdX2z4LTBc7ns77A==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
csstype "^2.2.0"
|
||||
|
||||
"@xobotyi/scrollbar-width@1.8.2":
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.8.2.tgz#056946ac41ade4885c576619c8d70c46c77e9683"
|
||||
@ -464,6 +526,11 @@ arrify@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
|
||||
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
|
||||
|
||||
asap@~2.0.3:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
@ -1466,6 +1533,11 @@ ci-info@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
|
||||
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
|
||||
|
||||
classnames@^2.2, classnames@^2.2.6:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||
|
||||
clean-stack@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
||||
@ -1582,6 +1654,11 @@ commander@^2.11.0:
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
|
||||
integrity sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==
|
||||
|
||||
compute-scroll-into-view@^1.0.9:
|
||||
version "1.0.13"
|
||||
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.13.tgz#be1b1663b0e3f56cd5f7713082549f562a3477e2"
|
||||
integrity sha512-o+w9w7A98aAFi/GjK8cxSV+CdASuPa2rR5UWs3+yHkJzWqaKoBEufFNWYaXInCSmUfDCVhesG+v9MTWqOjsxFg==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
@ -1653,11 +1730,21 @@ copy-to-clipboard@^3.2.0:
|
||||
dependencies:
|
||||
toggle-selection "^1.0.6"
|
||||
|
||||
core-js@^1.0.0:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
|
||||
|
||||
core-js@^2.4.0, core-js@^2.5.0:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
|
||||
integrity sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=
|
||||
|
||||
core-js@^2.5.3:
|
||||
version "2.6.11"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
|
||||
integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
|
||||
|
||||
core-js@^3.3.3:
|
||||
version "3.3.6"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.3.6.tgz#6ad1650323c441f45379e176ed175c0d021eac92"
|
||||
@ -1734,6 +1821,11 @@ css-tree@^1.0.0-alpha.28:
|
||||
mdn-data "2.0.6"
|
||||
source-map "^0.6.1"
|
||||
|
||||
csstype@^2.2.0:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
|
||||
integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
|
||||
|
||||
csstype@^2.5.5:
|
||||
version "2.6.8"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431"
|
||||
@ -1903,6 +1995,13 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dom-helpers@^3.2.1, dom-helpers@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
|
||||
integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
|
||||
dot-prop@^5.1.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb"
|
||||
@ -1920,6 +2019,16 @@ dotenv@^8.2.0:
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
|
||||
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
|
||||
|
||||
downshift@^3.3.1:
|
||||
version "3.4.8"
|
||||
resolved "https://registry.yarnpkg.com/downshift/-/downshift-3.4.8.tgz#06b7ad9e9c423a58e8a9049b2a00a5d19c7ef954"
|
||||
integrity sha512-dZL3iNL/LbpHNzUQAaVq/eTD1ocnGKKjbAl/848Q0KEp6t81LJbS37w3f93oD6gqqAnjdgM7Use36qZSipHXBw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.5"
|
||||
compute-scroll-into-view "^1.0.9"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^16.9.0"
|
||||
|
||||
duplexer3@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||
@ -2039,6 +2148,13 @@ encodeurl@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||
|
||||
encoding@^0.1.11:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
|
||||
integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
|
||||
dependencies:
|
||||
iconv-lite "~0.4.13"
|
||||
|
||||
end-of-stream@^1.1.0:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
@ -2332,6 +2448,29 @@ eventemitter3@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb"
|
||||
integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==
|
||||
|
||||
evergreen-ui@^4.23.0:
|
||||
version "4.23.0"
|
||||
resolved "https://registry.yarnpkg.com/evergreen-ui/-/evergreen-ui-4.23.0.tgz#764d977bc4dc10530c56935f06a460c895b54bdd"
|
||||
integrity sha512-9nX7LxFEqdlaqvd+68u5nybfqTefdFLjm6eJPyX2Kv10prB2+HA8S4ldYifOp1VpTDRCpHogzasuUjKd7BJaRA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
"@blueprintjs/icons" "^3.2.0"
|
||||
"@types/react" "^16.9.5"
|
||||
arrify "^1.0.1"
|
||||
classnames "^2.2.6"
|
||||
dom-helpers "^3.2.1"
|
||||
downshift "^3.3.1"
|
||||
fuzzaldrin-plus "^0.6.0"
|
||||
glamor "^2.20.40"
|
||||
lodash.debounce "^4.0.8"
|
||||
lodash.mapvalues "^4.6.0"
|
||||
prop-types "^15.6.2"
|
||||
react-scrollbar-size "^2.0.2"
|
||||
react-tiny-virtual-list "^2.1.4"
|
||||
react-transition-group "^2.5.0"
|
||||
tinycolor2 "^1.4.1"
|
||||
ui-box "^2.1.2"
|
||||
|
||||
execa@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-0.2.2.tgz#e2ead472c2c31aad6f73f1ac956eef45e12320cb"
|
||||
@ -2446,6 +2585,19 @@ fastest-stable-stringify@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz#9122d406d4c9d98bea644a6b6853d5874b87b028"
|
||||
integrity sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg=
|
||||
|
||||
fbjs@^0.8.12, fbjs@^0.8.16:
|
||||
version "0.8.17"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
|
||||
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
|
||||
dependencies:
|
||||
core-js "^1.0.0"
|
||||
isomorphic-fetch "^2.1.1"
|
||||
loose-envify "^1.0.0"
|
||||
object-assign "^4.1.0"
|
||||
promise "^7.1.1"
|
||||
setimmediate "^1.0.5"
|
||||
ua-parser-js "^0.7.18"
|
||||
|
||||
fd-slicer@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
|
||||
@ -2587,6 +2739,30 @@ form-data@~2.3.2:
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
framer-motion@^1.8.4:
|
||||
version "1.8.4"
|
||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-1.8.4.tgz#5772ff9cbd3b9d154e2c242461893c04a5f0cb26"
|
||||
integrity sha512-MMtJ3m0UpSb+val+aL8snz57p47LJS8lBSzvybhXjJgQHpufTZEzbp62eXNVdBlCHnC02J5+NmHRz4AAN6J9Qg==
|
||||
dependencies:
|
||||
"@popmotion/easing" "^1.0.2"
|
||||
"@popmotion/popcorn" "^0.4.2"
|
||||
framesync "^4.0.4"
|
||||
hey-listen "^1.0.8"
|
||||
popmotion "9.0.0-beta-8"
|
||||
style-value-types "^3.1.6"
|
||||
stylefire "^7.0.2"
|
||||
tslib "^1.10.0"
|
||||
optionalDependencies:
|
||||
"@emotion/is-prop-valid" "^0.8.2"
|
||||
|
||||
framesync@^4.0.0, framesync@^4.0.1, framesync@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/framesync/-/framesync-4.0.4.tgz#79c42c0118f26821c078570db0ff81fb863516a2"
|
||||
integrity sha512-mdP0WvVHe0/qA62KG2LFUAOiWLng5GLpscRlwzBxu2VXOp6B8hNs5C5XlFigsMgrfDrr2YbqTsgdWZTc4RXRMQ==
|
||||
dependencies:
|
||||
hey-listen "^1.0.8"
|
||||
tslib "^1.10.0"
|
||||
|
||||
fs-extra@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950"
|
||||
@ -2652,6 +2828,11 @@ functional-red-black-tree@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
||||
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
||||
|
||||
fuzzaldrin-plus@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/fuzzaldrin-plus/-/fuzzaldrin-plus-0.6.0.tgz#832f6489fbe876769459599c914a670ec22947ee"
|
||||
integrity sha1-gy9kifvodnaUWVmckUpnDsIpR+4=
|
||||
|
||||
gauge@~2.7.3:
|
||||
version "2.7.4"
|
||||
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
|
||||
@ -2715,6 +2896,17 @@ github-api@^3.2.2:
|
||||
js-base64 "^2.1.9"
|
||||
utf8 "^2.1.1"
|
||||
|
||||
glamor@^2.20.40:
|
||||
version "2.20.40"
|
||||
resolved "https://registry.yarnpkg.com/glamor/-/glamor-2.20.40.tgz#f606660357b7cf18dface731ad1a2cfa93817f05"
|
||||
integrity sha512-DNXCd+c14N9QF8aAKrfl4xakPk5FdcFwmH7sD0qnC0Pr7xoZ5W9yovhUrY/dJc3psfGGXC58vqQyRtuskyUJxA==
|
||||
dependencies:
|
||||
fbjs "^0.8.12"
|
||||
inline-style-prefixer "^3.0.6"
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.5.10"
|
||||
through "^2.3.8"
|
||||
|
||||
glob-base@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
|
||||
@ -2973,6 +3165,11 @@ hawk@3.1.3, hawk@~3.1.3:
|
||||
hoek "2.x.x"
|
||||
sntp "1.x.x"
|
||||
|
||||
hey-listen@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
|
||||
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
|
||||
|
||||
hoek@2.x.x:
|
||||
version "2.16.3"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
||||
@ -3026,7 +3223,7 @@ hyphenate-style-name@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48"
|
||||
integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==
|
||||
|
||||
iconv-lite@^0.4.24:
|
||||
iconv-lite@^0.4.24, iconv-lite@~0.4.13:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
@ -3096,6 +3293,14 @@ ini@~1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
|
||||
integrity sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=
|
||||
|
||||
inline-style-prefixer@^3.0.6:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz#8551b8e5b4d573244e66a34b04f7d32076a2b534"
|
||||
integrity sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=
|
||||
dependencies:
|
||||
bowser "^1.7.3"
|
||||
css-in-js-utils "^2.0.0"
|
||||
|
||||
inline-style-prefixer@^4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz#d390957d26f281255fe101da863158ac6eb60911"
|
||||
@ -3104,6 +3309,13 @@ inline-style-prefixer@^4.0.0:
|
||||
bowser "^1.7.3"
|
||||
css-in-js-utils "^2.0.0"
|
||||
|
||||
inline-style-prefixer@^5.0.4:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-5.1.2.tgz#e5a5a3515e25600e016b71e39138971228486c33"
|
||||
integrity sha512-PYUF+94gDfhy+LsQxM0g3d6Hge4l1pAqOSOiZuHWzMvQEGsbRQ/ck2WioLqrY2ZkHyPgVUXxn+hrkF7D6QUGbA==
|
||||
dependencies:
|
||||
css-in-js-utils "^2.0.0"
|
||||
|
||||
inquirer@^7.0.0:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703"
|
||||
@ -3402,6 +3614,14 @@ isobject@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
||||
|
||||
isomorphic-fetch@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
|
||||
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
|
||||
dependencies:
|
||||
node-fetch "^1.0.1"
|
||||
whatwg-fetch ">=0.10.0"
|
||||
|
||||
isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
@ -3642,6 +3862,16 @@ locate-path@^5.0.0:
|
||||
dependencies:
|
||||
p-locate "^4.1.0"
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||
|
||||
lodash.mapvalues@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
|
||||
integrity sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=
|
||||
|
||||
lodash@^4.17.10, lodash@^4.17.13, lodash@^4.17.4:
|
||||
version "4.17.13"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93"
|
||||
@ -3896,6 +4126,14 @@ nice-try@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
|
||||
dependencies:
|
||||
encoding "^0.1.11"
|
||||
is-stream "^1.0.1"
|
||||
|
||||
node-pre-gyp@^0.6.39:
|
||||
version "0.6.39"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649"
|
||||
@ -4371,6 +4609,18 @@ pn@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
|
||||
integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
|
||||
|
||||
popmotion@9.0.0-beta-8:
|
||||
version "9.0.0-beta-8"
|
||||
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.0-beta-8.tgz#f5a709f11737734e84f2a6b73f9bcf25ee30c388"
|
||||
integrity sha512-6eQzqursPvnP7ePvdfPeY4wFHmS3OLzNP8rJRvmfFfEIfpFqrQgLsM50Gd9AOvGKJtYJOFknNG+dsnzCpgIdAA==
|
||||
dependencies:
|
||||
"@popmotion/easing" "^1.0.1"
|
||||
"@popmotion/popcorn" "^0.4.2"
|
||||
framesync "^4.0.4"
|
||||
hey-listen "^1.0.8"
|
||||
style-value-types "^3.1.6"
|
||||
tslib "^1.10.0"
|
||||
|
||||
prelude-ls@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
@ -4411,15 +4661,14 @@ progress@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
|
||||
integrity sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=
|
||||
|
||||
prop-types@^15.5.7, prop-types@^15.6.2:
|
||||
version "15.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
|
||||
promise@^7.1.1:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|
||||
integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
|
||||
dependencies:
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
asap "~2.0.3"
|
||||
|
||||
prop-types@^15.7.2:
|
||||
prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
@ -4428,6 +4677,14 @@ prop-types@^15.7.2:
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.8.1"
|
||||
|
||||
prop-types@^15.5.7, prop-types@^15.6.2:
|
||||
version "15.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
|
||||
dependencies:
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
proto-list@~1.2.1:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||
@ -4509,6 +4766,16 @@ react-dom@^16.12.0:
|
||||
prop-types "^15.6.2"
|
||||
scheduler "^0.18.0"
|
||||
|
||||
react-event-listener@^0.5.1:
|
||||
version "0.5.10"
|
||||
resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.5.10.tgz#378403c555fe616f312891507a742ecbbe2c90de"
|
||||
integrity sha512-YZklRszh9hq3WP3bdNLjFwJcTCVe7qyTf5+LWNaHfZQaZrptsefDK2B5HHpOsEEaMHvjllUPr0+qIFVTSsurow==
|
||||
dependencies:
|
||||
"@babel/runtime" "7.0.0-beta.42"
|
||||
fbjs "^0.8.16"
|
||||
prop-types "^15.6.0"
|
||||
warning "^3.0.0"
|
||||
|
||||
react-fast-compare@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||
@ -4528,11 +4795,26 @@ react-icons@^3.9.0:
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
|
||||
react-is@^16.8.1:
|
||||
react-is@^16.8.1, react-is@^16.9.0:
|
||||
version "16.12.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
|
||||
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
|
||||
|
||||
react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||
|
||||
react-scrollbar-size@^2.0.2:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-scrollbar-size/-/react-scrollbar-size-2.1.0.tgz#105e797135cab92b1f9e16f00071db7f29f80754"
|
||||
integrity sha512-9dDUJvk7S48r0TRKjlKJ9e/LkLLYgc9LdQR6W21I8ZqtSrEsedPOoMji4nU3DHy7fx2l8YMScJS/N7qiloYzXQ==
|
||||
dependencies:
|
||||
babel-runtime "^6.26.0"
|
||||
prop-types "^15.6.0"
|
||||
react-event-listener "^0.5.1"
|
||||
stifle "^1.0.2"
|
||||
|
||||
react-sortable-hoc@^1.5.3:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-1.5.3.tgz#99482ee6435e898cae3cd4632958bb9c7cc5a948"
|
||||
@ -4542,6 +4824,23 @@ react-sortable-hoc@^1.5.3:
|
||||
invariant "^2.2.4"
|
||||
prop-types "^15.5.7"
|
||||
|
||||
react-tiny-virtual-list@^2.1.4:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-tiny-virtual-list/-/react-tiny-virtual-list-2.2.0.tgz#eafb6fcf764e4ed41150ff9752cdaad8b35edf4a"
|
||||
integrity sha512-MDiy2xyqfvkWrRiQNdHFdm36lfxmcLLKuYnUqcf9xIubML85cmYCgzBJrDsLNZ3uJQ5LEHH9BnxGKKSm8+C0Bw==
|
||||
dependencies:
|
||||
prop-types "^15.5.7"
|
||||
|
||||
react-transition-group@^2.5.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
|
||||
integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==
|
||||
dependencies:
|
||||
dom-helpers "^3.4.0"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
|
||||
react-use@^13.24.0:
|
||||
version "13.24.0"
|
||||
resolved "https://registry.yarnpkg.com/react-use/-/react-use-13.24.0.tgz#f4574e26cfaaad65e3f04c0d5ff80c1836546236"
|
||||
@ -4702,7 +5001,7 @@ regenerator-runtime@^0.10.5:
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
|
||||
integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=
|
||||
|
||||
regenerator-runtime@^0.11.0:
|
||||
regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
||||
@ -5070,6 +5369,11 @@ set-immediate-shim@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
|
||||
integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=
|
||||
|
||||
setimmediate@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||
@ -5234,6 +5538,11 @@ stat-mode@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-1.0.0.tgz#68b55cb61ea639ff57136f36b216a291800d1465"
|
||||
integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==
|
||||
|
||||
stifle@^1.0.2:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/stifle/-/stifle-1.1.1.tgz#4e4c565f19dcf9a6efa3a7379a70c42179edb8d6"
|
||||
integrity sha512-INvON4DXLAWxpor+f0ZHnYQYXBqDXQRW1znLpf5/C/AWzJ0eQQAThfdqHQ5BDkiyywD67rQGvbE4LC+Aig6K/Q==
|
||||
|
||||
string-to-stream@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-1.1.1.tgz#aba78f73e70661b130ee3e1c0192be4fef6cb599"
|
||||
@ -5378,6 +5687,25 @@ strong-data-uri@^1.0.5:
|
||||
dependencies:
|
||||
truncate "^2.0.1"
|
||||
|
||||
style-value-types@^3.1.6, style-value-types@^3.1.7:
|
||||
version "3.1.7"
|
||||
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.1.7.tgz#3d7d3cf9cb9f9ee86c00e19ba65d6a181a0db33a"
|
||||
integrity sha512-jPaG5HcAPs3vetSwOJozrBXxuHo9tjZVnbRyBjxqb00c2saIoeuBJc1/2MtvB8eRZy41u/BBDH0CpfzWixftKg==
|
||||
dependencies:
|
||||
hey-listen "^1.0.8"
|
||||
tslib "^1.10.0"
|
||||
|
||||
stylefire@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/stylefire/-/stylefire-7.0.2.tgz#874a82dd2bcada39c13e75e0c67b70009e06f556"
|
||||
integrity sha512-LFIBP6fIA+EMqLSvM4V6zLa+O/iAcHoNJVuXkkZ5G8+T+Pd3KaQLqgxrpkeo1bwWQHqzgab8U3V3gudO231fZA==
|
||||
dependencies:
|
||||
"@popmotion/popcorn" "^0.4.4"
|
||||
framesync "^4.0.0"
|
||||
hey-listen "^1.0.8"
|
||||
style-value-types "^3.1.7"
|
||||
tslib "^1.10.0"
|
||||
|
||||
stylis@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1"
|
||||
@ -5490,11 +5818,16 @@ throttleit@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c"
|
||||
integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=
|
||||
|
||||
through@^2.3.6:
|
||||
through@^2.3.6, through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
|
||||
tinycolor2@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
|
||||
integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
@ -5579,7 +5912,7 @@ tslib@^1.10.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||
|
||||
tslib@^1.9.0:
|
||||
tslib@^1.9.0, tslib@~1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
||||
@ -5630,6 +5963,20 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
ua-parser-js@^0.7.18:
|
||||
version "0.7.21"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
|
||||
integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
|
||||
|
||||
ui-box@^2.1.2:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ui-box/-/ui-box-2.1.3.tgz#f2ef9c549d8c60dfdd7fbdea36d7956b96a814fa"
|
||||
integrity sha512-taaEYH+tKdTXkrv0hVPl6NCGf5XAo+a940+/czsnWR0JYtnS4THMXo7nmzQzD/4MzD9PKG721hlPgTwHQbrBMQ==
|
||||
dependencies:
|
||||
"@emotion/hash" "^0.7.1"
|
||||
inline-style-prefixer "^5.0.4"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
uid-number@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
|
||||
@ -5743,6 +6090,18 @@ verror@1.3.6:
|
||||
dependencies:
|
||||
extsprintf "1.0.2"
|
||||
|
||||
warning@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
|
||||
integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
whatwg-fetch@>=0.10.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
|
||||
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
|
||||
|
||||
which-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
|
||||
|
Loading…
Reference in New Issue
Block a user