1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-25 11:43:17 +01:00

Many UI improvements #189

- support arbitrary stream selection #214
- implement mute playbakc #125
This commit is contained in:
Mikael Finstad 2020-02-16 12:33:38 +08:00
parent 996bd2d700
commit e913982dc4
11 changed files with 764 additions and 268 deletions

View File

@ -1,7 +1,7 @@
{
"presets": [
["env", {
"targets": { "electron": "1.8" }
"targets": { "electron": "8.0" }
}],
"react"
],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View File

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