1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-24 19:32:29 +01:00

Implement merge function

Also upgrade to react 16, sweetalert2 8 and refactor a bit
This commit is contained in:
Mikael Finstad 2019-01-28 02:25:31 +01:00
parent 8817c2c80b
commit cfaa11028e
11 changed files with 318 additions and 195 deletions

View File

@ -60,17 +60,22 @@
"execa": "^0.5.0",
"file-type": "^4.1.0",
"github-api": "^3.0.0",
"hammerjs": "^2.0.8",
"jquery": "^3.1.1",
"lodash": "^4.16.4",
"mime-types": "^2.1.14",
"moment": "^2.18.1",
"mousetrap": "^1.6.1",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"react-hammerjs": "^0.5.0",
"prop-types": "^15.6.2",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-hammerjs": "^1.0.1",
"react-sortable-hoc": "^1.5.3",
"read-chunk": "^2.0.0",
"string-to-stream": "^1.1.1",
"strong-data-uri": "^1.0.4",
"sweetalert2": "^7.28.6",
"sweetalert2": "^8.0.1",
"sweetalert2-react-content": "^1.0.1",
"trash": "^4.3.0",
"which": "^1.2.11"
}

36
src/HelpSheet.jsx Normal file
View File

@ -0,0 +1,36 @@
const React = require('react');
const PropTypes = require('prop-types');
/* eslint-disable react/jsx-one-expression-per-line */
const HelpSheet = ({ visible }) => {
if (visible) {
return (
<div className="help-sheet">
<h1>Keyboard shortcuts</h1>
<ul>
<li><kbd>H</kbd> Show/hide help</li>
<li><kbd>SPACE</kbd>, <kbd>k</kbd> Play/pause</li>
<li><kbd>J</kbd> Slow down video</li>
<li><kbd>L</kbd> Speed up video</li>
<li><kbd></kbd> Seek backward 1 sec</li>
<li><kbd></kbd> Seek forward 1 sec</li>
<li><kbd>.</kbd> (period) Tiny seek forward (1/60 sec)</li>
<li><kbd>,</kbd> (comma) Tiny seek backward (1/60 sec)</li>
<li><kbd>I</kbd> Mark in / cut start point</li>
<li><kbd>O</kbd> Mark out / cut end point</li>
<li><kbd>E</kbd> Cut (export selection in the same directory)</li>
<li><kbd>C</kbd> Capture snapshot (in the same directory)</li>
</ul>
</div>
);
}
return null;
};
/* eslint-enable react/jsx-one-expression-per-line */
HelpSheet.propTypes = {
visible: PropTypes.bool.isRequired,
};
module.exports = HelpSheet;

View File

@ -3,7 +3,7 @@ const fs = require('fs');
const mime = require('mime-types');
const strongDataUri = require('strong-data-uri');
const util = require('./util');
const { formatDuration, getOutPath, transferTimestampsWithOffset } = require('./util');
bluebird.promisifyAll(fs);
@ -23,12 +23,12 @@ async function captureFrame(customOutDir, filePath, video, currentTime, captureF
const buf = getFrameFromVideo(video, captureFormat);
const ext = mime.extension(buf.mimetype);
const time = util.formatDuration(currentTime, true);
const time = formatDuration(currentTime, true);
const outPath = util.getOutPath(customOutDir, filePath, `${time}.${ext}`);
const outPath = getOutPath(customOutDir, filePath, `${time}.${ext}`);
await fs.writeFileAsync(outPath, buf);
const offset = -video.duration + currentTime;
return util.transferTimestampsWithOffset(filePath, outPath, offset);
return transferTimestampsWithOffset(filePath, outPath, offset);
}
module.exports = captureFrame;

View File

@ -7,8 +7,9 @@ const readChunk = require('read-chunk');
const _ = require('lodash');
const readline = require('readline');
const moment = require('moment');
const stringToStream = require('string-to-stream');
const util = require('./util');
const { formatDuration, getOutPath, transferTimestamps } = require('./util');
function getWithExt(name) {
return process.platform === 'win32' ? `${name}.exe` : name;
@ -51,9 +52,9 @@ async function cut({
includeAllStreams, onProgress, stripAudio, keyframeCut,
}) {
const ext = path.extname(filePath) || `.${format}`;
const cutSpecification = `${util.formatDuration(cutFrom, true)}-${util.formatDuration(cutToApparent, true)}`;
const cutSpecification = `${formatDuration(cutFrom, true)}-${formatDuration(cutToApparent, true)}`;
const outPath = util.getOutPath(customOutDir, filePath, `${cutSpecification}${ext}`);
const outPath = getOutPath(customOutDir, filePath, `${cutSpecification}${ext}`);
console.log('Cutting from', cutFrom, 'to', cutToApparent);
@ -101,7 +102,7 @@ async function cut({
const result = await process;
console.log(result.stdout);
await util.transferTimestamps(filePath, outPath);
await transferTimestamps(filePath, outPath);
}
async function html5ify(filePath, outPath, encodeVideo) {
@ -123,7 +124,37 @@ async function html5ify(filePath, outPath, encodeVideo) {
const result = await process;
console.log(result.stdout);
await util.transferTimestamps(filePath, outPath);
await transferTimestamps(filePath, outPath);
}
async function mergeFiles(paths) {
const firstPath = paths[0];
const ext = path.extname(firstPath);
const outPath = `${firstPath}-merged.${ext}`;
console.log('Merging files', { paths }, 'to', outPath);
// https://blog.yo1.dog/fix-for-ffmpeg-protocol-not-on-whitelist-error-for-urls/
const ffmpegArgs = [
'-f', 'concat', '-safe', '0', '-protocol_whitelist', 'file,pipe', '-i', '-',
'-c', 'copy',
'-map_metadata', '0',
'-y', outPath,
];
console.log('ffmpeg', ffmpegArgs.join(' '));
// https://superuser.com/questions/787064/filename-quoting-in-ffmpeg-concat
const concatTxt = paths.map(file => `file '${path.join(file).replace(/'/g, "'\\''")}'`).join('\n');
console.log(concatTxt);
const ffmpegPath = await getFfmpegPath();
const process = execa(ffmpegPath, ffmpegArgs);
stringToStream(concatTxt).pipe(process.stdin);
const result = await process;
console.log(result.stdout);
}
/**
@ -179,4 +210,5 @@ module.exports = {
cut,
getFormat,
html5ify,
mergeFiles,
};

View File

@ -151,3 +151,7 @@ input, button, textarea, :focus {
border-radius: 3px;
box-shadow: inset 0 -1px 0 #bbb;
}
.dragging-helper-class {
color: rgba(0,0,0,0.5);
}

View File

@ -54,6 +54,17 @@ module.exports = (app, mainWindow, newVersion) => {
const helpIndex = menu.findIndex(item => item.role === 'help');
if (helpIndex >= 0) {
menu.splice(helpIndex, 1, {
label: 'Tools',
submenu: [
{
label: 'Merge files',
click() {
mainWindow.webContents.send('show-merge-dialog', true);
},
},
],
},
{
role: 'help',
submenu: [
{

View File

@ -0,0 +1,71 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import {
sortableContainer,
sortableElement,
arrayMove,
} from 'react-sortable-hoc';
import { basename } from 'path';
const rowStyle = {
padding: 5, fontSize: 14, margin: '7px 0', boxShadow: '0 0 5px 0px rgba(0,0,0,0.3)', overflowY: 'auto', whiteSpace: 'nowrap',
};
const SortableItem = sortableElement(({ value, sortIndex }) => (
<div style={rowStyle} title={value}>
{sortIndex + 1}
{'. '}
{basename(value)}
</div>));
const SortableContainer = sortableContainer(({ items }) => (
<div>
{items.map((value, index) => (
<SortableItem key={value} index={index} sortIndex={index} value={value} />
))}
</div>
));
class SortableFiles extends PureComponent {
constructor(props) {
super(props);
this.state = {
items: props.items,
};
}
onSortEnd = ({ oldIndex, newIndex }) => {
const { items } = this.state;
const { onChange } = this.props;
const newItems = arrayMove(items, oldIndex, newIndex);
this.setState({ items: newItems });
onChange(newItems);
};
render() {
const { helperContainer } = this.props;
const { items } = this.state;
return (
<div>
<div><b>Sort your files for merge</b></div>
<SortableContainer
items={items}
onSortEnd={this.onSortEnd}
helperContainer={helperContainer}
getContainer={() => helperContainer().parentNode}
helperClass="dragging-helper-class"
/>
</div>
);
}
}
SortableFiles.propTypes = {
onChange: PropTypes.func.isRequired,
helperContainer: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
};
export default SortableFiles;

49
src/merge/merge.jsx Normal file
View File

@ -0,0 +1,49 @@
const React = require('react');
const swal = require('sweetalert2');
const withReactContent = require('sweetalert2-react-content');
const SortableFiles = require('./SortableFiles').default;
const { errorToast } = require('../util');
const MySwal = withReactContent(swal);
function showMergeDialog({ dialog, defaultPath, onMergeClick }) {
const title = 'Please select files to be merged';
const message = 'Please select files to be merged. The files need to be of the exact same format and codecs';
dialog.showOpenDialog({
title,
defaultPath,
properties: ['openFile', 'multiSelections'],
message,
}, async (paths) => {
if (!paths) return;
if (paths.length < 2) {
errorToast('More than one file must be selected');
return;
}
{
let swalElem;
let outPaths = paths;
const { dismiss } = await MySwal.fire({
width: '90%',
showCancelButton: true,
confirmButtonText: 'Merge!',
onBeforeOpen: (el) => { swalElem = el; },
html: (<SortableFiles
items={outPaths}
onChange={(val) => { outPaths = val; }}
helperContainer={() => swalElem}
/>),
});
if (!dismiss) {
onMergeClick(outPaths);
}
}
});
}
module.exports = { showMergeDialog };

View File

@ -4,29 +4,28 @@ const Mousetrap = require('mousetrap');
const round = require('lodash/round');
const clamp = require('lodash/clamp');
const throttle = require('lodash/throttle');
const Hammer = require('react-hammerjs');
const Hammer = require('react-hammerjs').default;
const path = require('path');
const trash = require('trash');
const swal = require('sweetalert2');
const React = require('react');
const ReactDOM = require('react-dom');
const classnames = require('classnames');
const HelpSheet = require('./HelpSheet');
const { showMergeDialog } = require('./merge/merge');
const captureFrame = require('./capture-frame');
const ffmpeg = require('./ffmpeg');
const {
getOutPath, parseDuration, formatDuration, toast, errorToast, showFfmpegFail,
getOutPath, parseDuration, formatDuration, toast, errorToast, showFfmpegFail, setFileNameTitle,
promptTimeOffset,
} = require('./util');
const { dialog } = electron.remote;
function setFileNameTitle(filePath) {
const appName = 'LosslessCut';
document.title = filePath ? `${appName} - ${path.basename(filePath)}` : 'appName';
}
function getVideo() {
return $('#player video')[0];
}
@ -54,35 +53,6 @@ function shortStep(dir) {
seekRel((1 / 60) * dir);
}
/* eslint-disable react/jsx-one-expression-per-line */
function renderHelpSheet(visible) {
if (visible) {
return (
<div className="help-sheet">
<h1>Keyboard shortcuts</h1>
<ul>
<li><kbd>H</kbd> Show/hide help</li>
<li><kbd>SPACE</kbd>, <kbd>k</kbd> Play/pause</li>
<li><kbd>J</kbd> Slow down video</li>
<li><kbd>L</kbd> Speed up video</li>
<li><kbd></kbd> Seek backward 1 sec</li>
<li><kbd></kbd> Seek forward 1 sec</li>
<li><kbd>.</kbd> (period) Tiny seek forward (1/60 sec)</li>
<li><kbd>,</kbd> (comma) Tiny seek backward (1/60 sec)</li>
<li><kbd>I</kbd> Mark in / cut start point</li>
<li><kbd>O</kbd> Mark out / cut end point</li>
<li><kbd>E</kbd> Cut (export selection in the same directory)</li>
<li><kbd>C</kbd> Capture snapshot (in the same directory)</li>
</ul>
</div>
);
}
return undefined;
}
/* eslint-enable react/jsx-one-expression-per-line */
function withBlur(cb) {
return (e) => {
e.target.blur();
@ -144,13 +114,13 @@ class App extends React.Component {
errorToast('Unsupported file');
return;
}
setFileNameTitle(filePath);
setFileNameTitle(filePath);
this.setState({ filePath, html5FriendlyPath, fileFormat });
} catch (err) {
if (err.code === 1 || err.code === 'ENOENT') {
if (err.code === 1 || err.code === 'ENOENT') {
errorToast('Unsupported file');
return;
}
return;
}
showFfmpegFail(err);
} finally {
this.setState({ working: false });
@ -179,26 +149,24 @@ class App extends React.Component {
}
});
async function promptTimeOffset(inputValue) {
const { value } = await swal({
title: 'Set custom start time offset',
text: 'Instead of video apparently starting at 0, you can offset by a specified value (useful for timecodes)',
input: 'text',
inputValue: inputValue || '',
showCancelButton: true,
inputPlaceholder: '00:00:00.000',
});
electron.ipcRenderer.on('show-merge-dialog', () => showMergeDialog({
dialog,
defaultPath: this.getOutputDir(),
onMergeClick: async (paths) => {
try {
this.setState({ working: true });
if (value === undefined) {
return undefined;
}
const duration = parseDuration(value);
// Invalid, try again
if (duration === undefined) return promptTimeOffset(value);
return duration;
}
// TODO customOutDir ?
// console.log('merge', paths);
await ffmpeg.mergeFiles(paths);
} catch (err) {
errorToast('Failed to merge files. Make sure they are all of the exact same format and codecs');
console.error('Failed to merge files', err);
} finally {
this.setState({ working: false });
}
},
}));
electron.ipcRenderer.on('set-start-offset', async () => {
const { startTimeOffset: startTimeOffsetOld } = this.state;
@ -303,7 +271,6 @@ class App extends React.Component {
return (this.state.currentTime || 0) + this.state.startTimeOffset;
}
increaseRotation = () => {
this.setState(({ rotation }) => ({ rotation: (rotation + 90) % 450 }));
}
@ -349,7 +316,7 @@ class App extends React.Component {
return video.play().catch((err) => {
console.log(err);
if (err.name === 'NotSupportedError') {
toast({ type: 'error', title: 'This format/codec is not supported. Try to convert it to a friendly format/codec in the player from the "File" menu. Note that this will only create a temporary, low quality encoded file used for previewing your cuts, and will not affect the final cut. The final cut will still be lossless. Audio is also removed to make it faster, but only in the preview.', timer: 10000 });
toast.fire({ type: 'error', title: 'This format/codec is not supported. Try to convert it to a friendly format/codec in the player from the "File" menu. Note that this will only create a temporary, low quality encoded file used for previewing your cuts, and will not affect the final cut. The final cut will still be lossless. Audio is also removed to make it faster, but only in the preview.', timer: 10000 });
}
});
}
@ -717,7 +684,7 @@ class App extends React.Component {
</button>
</div>
{renderHelpSheet(this.state.helpVisible)}
<HelpSheet visible={!!this.state.helpVisible} />
</div>
);
}

View File

@ -65,7 +65,7 @@ const toast = swal.mixin({
timer: 3000,
});
const errorToast = title => toast({
const errorToast = title => toast.fire({
type: 'error',
title,
});
@ -75,6 +75,31 @@ async function showFfmpegFail(err) {
return errorToast(`Failed to run ffmpeg: ${err.stack}`);
}
function setFileNameTitle(filePath) {
const appName = 'LosslessCut';
document.title = filePath ? `${appName} - ${path.basename(filePath)}` : 'appName';
}
async function promptTimeOffset(inputValue) {
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 timecodes)',
input: 'text',
inputValue: inputValue || '',
showCancelButton: true,
inputPlaceholder: '00:00:00.000',
});
if (value === undefined) {
return undefined;
}
const duration = parseDuration(value);
// Invalid, try again
if (duration === undefined) return promptTimeOffset(value);
return duration;
}
module.exports = {
formatDuration,
@ -85,4 +110,6 @@ module.exports = {
toast,
errorToast,
showFfmpegFail,
setFileNameTitle,
promptTimeOffset,
};

157
yarn.lock
View File

@ -292,11 +292,6 @@ arrify@^1.0.0, 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=
asar@^0.13.0:
version "0.13.1"
resolved "https://registry.yarnpkg.com/asar/-/asar-0.13.1.tgz#dfc73f574a7db256b09ba62d1f0e95cd4a6cb8d3"
@ -1357,11 +1352,6 @@ convert-source-map@^1.5.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
integrity sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=
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"
@ -1372,15 +1362,6 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
create-react-class@^15.6.0:
version "15.6.3"
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036"
integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==
dependencies:
fbjs "^0.8.9"
loose-envify "^1.3.1"
object-assign "^4.1.1"
cross-spawn-async@^2.1.1:
version "2.2.5"
resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc"
@ -1683,13 +1664,6 @@ emoji-regex@^6.5.1:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"
integrity sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==
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"
env-paths@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
@ -2042,19 +2016,6 @@ fast-levenshtein@~2.0.4:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fbjs@^0.8.16, fbjs@^0.8.9:
version "0.8.16"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
integrity sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=
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.9"
fd-slicer@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
@ -2655,11 +2616,6 @@ iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@~0.4.13:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
ignore@^3.3.5:
version "3.3.7"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
@ -2946,14 +2902,6 @@ isobject@^2.0.0:
dependencies:
isarray "1.0.0"
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"
@ -3383,14 +3331,6 @@ 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"
@ -3845,22 +3785,6 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
integrity sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=
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:
asap "~2.0.3"
prop-types@^15.5.10:
version "15.6.0"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
integrity sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=
dependencies:
fbjs "^0.8.16"
loose-envify "^1.3.1"
object-assign "^4.1.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"
@ -3932,42 +3856,41 @@ rcedit@^0.9.0:
resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-0.9.0.tgz#3910df57345399e2b0325f4a519007f89e55ef1c"
integrity sha1-ORDfVzRTmeKwMl9KUZAH+J5V7xw=
react-dom@^15.3.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730"
integrity sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=
react-dom@^16.7.0:
version "16.7.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0.tgz#a17b2a7ca89ee7390bc1ed5eb81783c7461748b8"
integrity sha512-D0Ufv1ExCAmF38P2Uh1lwpminZFRXEINJe53zRAbm4KPwSyd6DY/uDoS0Blj9jvPpn1+wivKpZYc8aAAN/nAkg==
dependencies:
fbjs "^0.8.9"
loose-envify "^1.1.0"
object-assign "^4.1.0"
prop-types "^15.5.10"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.12.0"
react-hammerjs@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/react-hammerjs/-/react-hammerjs-0.5.0.tgz#aa20e5c5f44d660f3e8e87ed11282f12173e77ae"
integrity sha1-qiDlxfRNZg8+joftESgvEhc+d64=
react-hammerjs@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/react-hammerjs/-/react-hammerjs-1.0.1.tgz#bc1ed9e9ef7da057163fb169ce12917b6d6ca7d8"
integrity sha1-vB7Z6e99oFcWP7FpzhKRe21sp9g=
dependencies:
hammerjs "^2.0.8"
react-sortable-hoc@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-1.4.0.tgz#b477ce700ba755754200a1dabd36e588e2f5608d"
integrity sha512-4++hdwMTrzpOHcqndi2M2gEsqgoGMGmmYzs3wp/xZdap/d8oT2yUR3m6STNi1d1trRyl9Ud0C54agbYH7XdQAQ==
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"
integrity sha512-suRfXqq3KRzhpHsUoc+srHBp5o7cwx7ZXSNH/PKtKOtKBw18JgXNQ7QbIMTCcH3mdog3espf+A9hybTixNfe3Q==
dependencies:
"@babel/runtime" "^7.2.0"
invariant "^2.2.4"
prop-types "^15.5.7"
react@^15.3.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"
integrity sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=
react@^16.7.0:
version "16.7.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.7.0.tgz#b674ec396b0a5715873b350446f7ea0802ab6381"
integrity sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==
dependencies:
create-react-class "^15.6.0"
fbjs "^0.8.9"
loose-envify "^1.1.0"
object-assign "^4.1.0"
prop-types "^15.5.10"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.12.0"
read-chunk@^2.0.0:
version "2.1.0"
@ -4377,6 +4300,14 @@ sanitize-filename@^1.6.0:
dependencies:
truncate-utf8-bytes "^1.0.0"
scheduler@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0.tgz#8ab17699939c0aedc5a196a657743c496538647b"
integrity sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
@ -4397,11 +4328,6 @@ 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"
@ -4667,10 +4593,15 @@ svg2png@4.1.1:
pn "^1.0.0"
yargs "^6.5.0"
sweetalert2@^7.28.6:
version "7.28.6"
resolved "https://registry.yarnpkg.com/sweetalert2/-/sweetalert2-7.28.6.tgz#6e20519eaf007dc3703c890c8db3797b6b08bc17"
integrity sha512-rkMfmOSkg7zWTCg/YOg1VuJDSf2fpniiYhYpBc0MNrOzJnx24bfDOCTj35RulgyDtKrwQIEVR2gVaG3eOnpCXQ==
sweetalert2-react-content@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sweetalert2-react-content/-/sweetalert2-react-content-1.0.1.tgz#0403145a1af819504e394df0dfc6194df67068a0"
integrity sha512-wZxDGbF24jzNXGsr3hjkSa5wQlhgq4wOPPZShe4RMujGdDuPtniodQGZOW2Tn38dzpgXSZ4/sb7++zxwueberw==
sweetalert2@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/sweetalert2/-/sweetalert2-8.0.1.tgz#d59fa124a56595c1e799dd4c635689afa0a5f811"
integrity sha512-N88+meCLt1t/5dQEAvBs31j6qcEVucE/bwMj8JiuPsMRvXhnWKMbvLAFPGNO7a/LECRVjT6o0/mccmIVZqQZSQ==
table@^4.0.3:
version "4.0.3"
@ -4867,11 +4798,6 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
ua-parser-js@^0.7.9:
version "0.7.17"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
integrity sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==
uid-number@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
@ -4953,11 +4879,6 @@ verror@1.3.6:
dependencies:
extsprintf "1.0.2"
whatwg-fetch@>=0.10.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
integrity sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=
which-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"