diff --git a/.eslintrc b/.eslintrc index 581685e8..bdcaded3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,12 +1,14 @@ { "extends": "airbnb", + "parser": "babel-eslint", "env": { "node": true, "browser": true, }, "rules": { "no-alert": 0, - "no-console": 0 + "no-console": 0, + "react/destructuring-assignment": 0, }, "plugins": [ "react" diff --git a/package.json b/package.json index 2f4389fc..e90b2fa1 100644 --- a/package.json +++ b/package.json @@ -37,16 +37,17 @@ "license": "MIT", "devDependencies": { "babel-cli": "^6.18.0", + "babel-eslint": "^10.0.1", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-env": "^1.6.1", "babel-preset-react": "^6.16.0", "electron-packager": "^8.1.0", - "eslint": "^3.8.0", - "eslint-config-airbnb": "^12.0.0", - "eslint-plugin-import": "^1.16.0", - "eslint-plugin-jsx-a11y": "^2.2.3", - "eslint-plugin-react": "^6.4.1", + "eslint": "^5.6.1", + "eslint-config-airbnb": "^17.1.0", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-jsx-a11y": "^6.1.1", + "eslint-plugin-react": "^7.11.1", "gh-release": "^2.2.1", "icon-gen": "^1.2.0" }, diff --git a/src/index.js b/src/index.js index 88ff5f4d..4d584d6d 100644 --- a/src/index.js +++ b/src/index.js @@ -5,8 +5,8 @@ const menu = require('./menu'); const { checkNewVersion } = require('./update-checker'); -const app = electron.app; -const BrowserWindow = electron.BrowserWindow; +const { app } = electron; +const { BrowserWindow } = electron; app.setName('LosslessCut'); diff --git a/src/menu.js b/src/menu.js index 0cd2d648..2fb73a1d 100644 --- a/src/menu.js +++ b/src/menu.js @@ -1,8 +1,8 @@ const electron = require('electron'); // eslint-disable-line const defaultMenu = require('electron-default-menu'); -const Menu = electron.Menu; -const dialog = electron.dialog; +const { Menu } = electron; +const { dialog } = electron; const homepage = 'https://github.com/mifi/lossless-cut'; const releasesPage = 'https://github.com/mifi/lossless-cut/releases'; diff --git a/src/renderer.jsx b/src/renderer.jsx index 93a5d4fd..495330b7 100644 --- a/src/renderer.jsx +++ b/src/renderer.jsx @@ -14,7 +14,7 @@ const captureFrame = require('./capture-frame'); const ffmpeg = require('./ffmpeg'); const util = require('./util'); -const dialog = electron.remote.dialog; +const { dialog } = electron.remote; function setFileNameTitle(filePath) { const appName = 'LosslessCut'; @@ -27,7 +27,7 @@ function getVideo() { function seekAbs(val) { const video = getVideo(); - if (val == null || isNaN(val)) return; + if (val == null || Number.isNaN(val)) return; let outVal = val; if (outVal < 0) outVal = 0; @@ -48,29 +48,34 @@ function shortStep(dir) { seekRel((1 / 60) * dir); } +/* eslint-disable react/jsx-one-expression-per-line */ function renderHelpSheet(visible) { if (visible) { - return (
-

Keyboard shortcuts

- -
); + return ( +
+

Keyboard shortcuts

+ +
+ ); } return undefined; } +/* eslint-enable react/jsx-one-expression-per-line */ + function withBlur(cb) { return (e) => { @@ -114,8 +119,10 @@ class App extends React.Component { }; const load = (filePath, html5FriendlyPath) => { + const { working } = this.state; + console.log('Load', { filePath, html5FriendlyPath }); - if (this.state.working) return alert('I\'m busy'); + if (working) return alert('I\'m busy'); this.resetState(); @@ -159,7 +166,8 @@ class App extends React.Component { } }); - document.ondragover = document.ondragend = ev => ev.preventDefault(); + document.ondragover = ev => ev.preventDefault(); + document.ondragend = document.ondragover; document.body.ondrop = (ev) => { ev.preventDefault(); @@ -196,31 +204,33 @@ class App extends React.Component { this.setState({ duration }); } - onCutProgress(cutProgress) { + onCutProgress = (cutProgress) => { this.setState({ cutProgress }); } - setCutStart() { - this.setState({ cutStartTime: this.state.currentTime }); + setCutStart = () => { + this.setState(({ currentTime }) => ({ cutStartTime: currentTime })); } - setCutEnd() { - this.setState({ cutEndTime: this.state.currentTime }); + setCutEnd = () => { + this.setState(({ currentTime }) => ({ cutEndTime: currentTime })); } - setOutputDir() { + setOutputDir = () => { dialog.showOpenDialog({ properties: ['openDirectory'] }, (paths) => { this.setState({ customOutDir: (paths && paths.length === 1) ? paths[0] : undefined }); }); } getFileUri() { - return (this.state.html5FriendlyPath || this.state.filePath || '').replace(/#/g, '%23'); + const { html5FriendlyPath, filePath } = this.state; + return (html5FriendlyPath || filePath || '').replace(/#/g, '%23'); } getOutputDir() { - if (this.state.customOutDir) return this.state.customOutDir; - if (this.state.filePath) return path.dirname(this.state.filePath); + const { customOutDir, filePath } = this.state; + if (customOutDir) return customOutDir; + if (filePath) return path.dirname(filePath); return undefined; } @@ -238,72 +248,48 @@ class App extends React.Component { return 0; // Haven't gotten duration yet } - isRotationSet() { - // 360 means we don't modify rotation - return this.state.rotation !== 360; + + increaseRotation = () => { + this.setState(({ rotation }) => ({ rotation: (rotation + 90) % 450 })); } - isCutRangeValid() { - return this.state.cutStartTime < this.getApparentCutEndTime(); - } - - increaseRotation() { - const rotation = (this.state.rotation + 90) % 450; - this.setState({ rotation }); - } - - resetState() { - const video = getVideo(); - video.currentTime = 0; - video.playbackRate = 1; - this.setState(localState); - setFileNameTitle(); - } - - toggleCaptureFormat() { + toggleCaptureFormat = () => { const isPng = this.state.captureFormat === 'png'; this.setState({ captureFormat: isPng ? 'jpeg' : 'png' }); } - toggleIncludeAllStreams() { - this.setState({ includeAllStreams: !this.state.includeAllStreams }); + toggleIncludeAllStreams = () => { + this.setState(({ includeAllStreams }) => ({ includeAllStreams: !includeAllStreams })); } - jumpCutStart() { + toggleStripAudio = () => this.setState(({ stripAudio }) => ({ stripAudio: !stripAudio })); + + toggleKeyframeCut = () => this.setState(({ keyframeCut }) => ({ keyframeCut: !keyframeCut })); + + jumpCutStart = () => { seekAbs(this.state.cutStartTime); } - jumpCutEnd() { + jumpCutEnd = () => { seekAbs(this.getApparentCutEndTime()); } - handlePan(e) { + handlePan = (e) => { _.throttle(e2 => this.handleTap(e2), 200)(e); } - handleTap(e) { + handleTap = (e) => { const $target = $('.timeline-wrapper'); const parentOffset = $target.offset(); const relX = e.srcEvent.pageX - parentOffset.left; setCursor((relX / $target[0].offsetWidth) * this.state.duration); } - changePlaybackRate(dir) { - const video = getVideo(); - if (!this.state.playing) { - video.playbackRate = 0.5; // dir * 0.5; - video.play(); - } else { - const newRate = video.playbackRate + (dir * 0.15); - video.playbackRate = _.clamp(newRate, 0.05, 16); - } - } - - playbackRateChange() { + playbackRateChange = () => { this.state.playbackRate = getVideo().playbackRate; } - playCommand() { + playCommand = () => { const video = getVideo(); if (this.state.playing) return video.pause(); @@ -315,8 +301,8 @@ class App extends React.Component { }); } - async deleteSourceClick() { - if (this.state.working || !confirm('Are you sure you want to move the source file to trash?')) return; + deleteSourceClick = async () => { + if (this.state.working || !window.confirm('Are you sure you want to move the source file to trash?')) return; const { filePath } = this.state; this.setState({ working: true }); @@ -324,7 +310,7 @@ class App extends React.Component { this.resetState(); } - async cutClick() { + cutClick = async () => { if (this.state.working) return alert('I\'m busy'); const { @@ -352,7 +338,7 @@ class App extends React.Component { includeAllStreams, stripAudio, keyframeCut, - onProgress: progress => this.onCutProgress(progress), + onProgress: this.onCutProgress, }); } catch (err) { console.error('stdout:', err.stdout); @@ -367,18 +353,45 @@ class App extends React.Component { } } - capture() { - const filePath = this.state.filePath; - const outputDir = this.state.customOutDir; - const currentTime = this.state.currentTime; - const captureFormat = this.state.captureFormat; + capture = () => { + const { + filePath, customOutDir: outputDir, currentTime, captureFormat, + } = this.state; if (!filePath) return; captureFrame(outputDir, filePath, getVideo(), currentTime, captureFormat) .catch(err => alert(err)); } + changePlaybackRate(dir) { + const video = getVideo(); + if (!this.state.playing) { + video.playbackRate = 0.5; // dir * 0.5; + video.play(); + } else { + const newRate = video.playbackRate + (dir * 0.15); + video.playbackRate = _.clamp(newRate, 0.05, 16); + } + } + + resetState() { + const video = getVideo(); + video.currentTime = 0; + video.playbackRate = 1; + this.setState(localState); + setFileNameTitle(); + } + + isRotationSet() { + // 360 means we don't modify rotation + return this.state.rotation !== 360; + } + + isCutRangeValid() { + return this.state.cutStartTime < this.getApparentCutEndTime(); + } + toggleHelp() { - this.setState({ helpVisible: !this.state.helpVisible }); + this.setState(({ helpVisible }) => ({ helpVisible: !helpVisible })); } renderCutTimeInput(type) { @@ -404,58 +417,67 @@ class App extends React.Component { }; - return ( handleCutTimeInput(e.target.value)} - value={isCutTimeManualSet() - ? this.state[cutTimeManualKey] - : util.formatDuration(type === 'start' ? this.state.cutStartTime : this.getApparentCutEndTime()) + return ( + handleCutTimeInput(e.target.value)} + value={isCutTimeManualSet() + ? this.state[cutTimeManualKey] + : util.formatDuration(type === 'start' ? this.state.cutStartTime : this.getApparentCutEndTime()) } - />); + /> + ); } render() { - 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' }; + 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', + }; - return (
- {!this.state.filePath &&
DROP VIDEO
} - {this.state.working && ( -
- - {this.state.cutProgress != null && - - {Math.floor(this.state.cutProgress * 100)} % - - } -
- )} - -
-
- -
- this.handleTap(e)} - onPan={e => this.handlePan(e)} - options={{ - recognizers: { - }, - }} + return ( +
+ {!this.state.filePath &&
DROP VIDEO
} + {this.state.working && ( +
-
-
+ + {this.state.cutProgress != null && ( + + {`${Math.floor(this.state.cutProgress * 100)} %`} + + )} +
+ )} - {this.isCutRangeValid() && + {/* eslint-disable jsx-a11y/media-has-caption */} +
+
+ {/* eslint-enable jsx-a11y/media-has-caption */} + +
+ +
+
+ + {this.isCutRangeValid() && (
+ ) } -
{util.formatDuration(this.state.currentTime)}
-
- +
{util.formatDuration(this.state.currentTime)}
+
+ -
-