diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..2fbd88746 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,34 @@ +{ + "plugins": [ + "flowtype" + ], + "extends": [ + "airbnb", + "plugin:import/electron", + "plugin:flowtype/recommended", + "plugin:prettier/recommended" + ], + "settings": { + "import/resolver": { + "webpack": { + "config": "webpack.renderer.additions.js" + } + } + }, + "parser": "babel-eslint", + "env": { + "browser": true, + "node": true + }, + "globals": { + "__static": true, + "staticResourcesPath": true, + "__": true, + "__n": true, + "app": true + }, + "rules": { + "import/no-commonjs": 1, + "import/no-amd": 1 + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index e20de7b15..023767f02 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /lbry-app /lbry-venv /static/daemon/lbrynet* +/static/locales /daemon/build /daemon/venv /daemon/requirements.txt diff --git a/.lintstagedrc b/.lintstagedrc new file mode 100644 index 000000000..bd9f228ce --- /dev/null +++ b/.lintstagedrc @@ -0,0 +1,12 @@ +{ + "linters": { + "src/**/*.{js,jsx,scss,json}": [ + "prettier --write", + "git add" + ], + "src/**/*.{js,jsx}": [ + "eslint --fix", + "git add" + ] + } +} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..1d3fce725 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "trailingComma": "es5", + "printWidth": 100, + "singleQuote": true +} \ No newline at end of file diff --git a/README.md b/README.md index aed0caca0..9cb673480 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ Run `yarn dev` This will set up a server that will automatically compile any changes made inside `src\` folder and automatically reload the app without losing the state. -### Packaging +### Build -Run `yarn dist` +Run `yarn build` We use [electron-builder](https://github.com/electron-userland/electron-builder) to create distributable packages. @@ -60,16 +60,16 @@ exit python -m pip install -r build\requirements.txt npm install -g yarn yarn install -yarn dist +yarn build ``` -3. Download the lbry daemon and cli [binaries](https://github.com/lbryio/lbry/releases) and place them in `dist\` - -### Building lbry-app -Run `yarn dist` +3. Download the lbry daemon and cli [binaries](https://github.com/lbryio/lbry/releases) and place them in `static\daemon` ### Ongoing Development Run `yarn dev` +### Build +Run `yarn build` + This will set up a server that will automatically compile any changes made inside `src\` folder and automatically reload the app without losing the state. ## Internationalization diff --git a/build/build.ps1 b/build/build.ps1 index 3fb9d90b8..730fd80af 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -26,7 +26,7 @@ dir static\daemon\ # verify that daemon binary is there rm daemon.zip # build electron app -yarn dist +yarn build dir dist # verify that binary was built/named correctly python build\upload_assets.py diff --git a/build/build.sh b/build/build.sh index 37a26b0a1..2273eff00 100755 --- a/build/build.sh +++ b/build/build.sh @@ -77,7 +77,7 @@ if [ "$FULL_BUILD" == "true" ]; then security unlock-keychain -p ${KEYCHAIN_PASSWORD} osx-build.keychain fi - yarn dist + yarn build # electron-build has a publish feature, but I had a hard time getting # it to reliably work and it also seemed difficult to configure. Not proud of diff --git a/src/renderer/extractLocals.js b/build/extractLocals.js similarity index 51% rename from src/renderer/extractLocals.js rename to build/extractLocals.js index b4437dbf5..8f48b1492 100644 --- a/src/renderer/extractLocals.js +++ b/build/extractLocals.js @@ -1,20 +1,21 @@ -var extract = require("i18n-extract"); +const extract = require("i18n-extract"); const fs = require("fs"); +const path = require("path"); -var dir = __dirname + "/../../dist/locales"; -var path = dir + "/en.json"; +const outputDir = `${__dirname}/../static/locales`; +const outputPath = `${outputDir}/en.json`; -if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir); } -fs.writeFile(path, "{}", "utf8", function(err) { +fs.writeFile(outputPath, "{}", "utf8", err => { if (err) { return console.log(err); } - var enLocale = require(path); + const enLocale = require(outputPath); - const keys = extract.extractFromFiles(["js/**/*.{js,jsx}"], { + const keys = extract.extractFromFiles("src/**/*.{js,jsx}", { marker: "__", }); @@ -22,21 +23,21 @@ fs.writeFile(path, "{}", "utf8", function(err) { reports = reports.concat(extract.findMissing(enLocale, keys)); if (reports.length > 0) { - fs.readFile(path, "utf8", function readFileCallback(err, data) { + fs.readFile(outputPath, "utf8", (err, data) => { if (err) { console.log(err); } else { localeObj = JSON.parse(data); - for (var i = 0; i < reports.length; i++) { + for (let i = 0; i < reports.length; i++) { // no need to care for other types than MISSING because starting file will always be empty if (reports[i].type === "MISSING") { localeObj[reports[i].key] = reports[i].key; } } - var json = JSON.stringify(localeObj, null, "\t"); //convert it back to json-string - fs.writeFile(path, json, "utf8", function callback(err) { + const json = JSON.stringify(localeObj, null, "\t"); // convert it back to json-string + fs.writeFile(outputPath, json, "utf8", err => { if (err) { throw err; } diff --git a/package.json b/package.json index f15fb3714..55b1a0905 100644 --- a/package.json +++ b/package.json @@ -14,21 +14,23 @@ "name": "LBRY Inc.", "email": "hello@lbry.io" }, + "main": "src/main/index.js", "scripts": { - "extract-langs": "node src/renderer/extractLocals.js", + "extract-langs": "node build/extractLocals.js", "dev": "electron-webpack dev", "compile": "electron-webpack && yarn extract-langs", - "dist": "yarn compile && electron-builder", - "dist:dir": "yarn dist -- --dir -c.compression=store -c.mac.identity=null", + "build": "yarn compile && electron-builder build", "postinstall": "electron-builder install-app-deps", - "precommit": "lint-staged" + "precommit": "lint-staged", + "lint": "eslint 'src/**/*.{js,jsx}' --fix", + "pretty-print": "prettier 'src/**/*.{js,jsx,scss,json}' --write" }, - "main": "src/main/index.js", "keywords": [ "lbry" ], "dependencies": { "amplitude-js": "^4.0.0", + "bluebird": "^3.5.1", "classnames": "^2.2.5", "electron-dl": "^1.6.0", "formik": "^0.10.4", @@ -64,18 +66,27 @@ "y18n": "^4.0.0" }, "devDependencies": { + "babel-eslint": "^8.0.3", "babel-plugin-module-resolver": "^3.0.0", "babel-plugin-react-require": "^3.0.0", "babel-polyfill": "^6.20.0", "babel-preset-env": "^1.6.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.18.0", - "bluebird": "^3.5.1", "devtron": "^1.4.0", "electron": "^1.7.9", - "electron-builder": "^19.48.2", + "electron-builder": "^19.49.0", "electron-devtools-installer": "^2.2.1", "electron-webpack": "^1.11.0", + "eslint": "^4.13.1", + "eslint-config-airbnb": "^16.1.0", + "eslint-config-prettier": "^2.9.0", + "eslint-import-resolver-webpack": "^0.8.3", + "eslint-plugin-flowtype": "^2.40.1", + "eslint-plugin-import": "^2.8.0", + "eslint-plugin-jsx-a11y": "^6.0.3", + "eslint-plugin-prettier": "^2.4.0", + "eslint-plugin-react": "^7.5.1", "flow-babel-webpack-plugin": "^1.1.0", "flow-bin": "^0.61.0", "flow-typed": "^2.2.3", @@ -98,12 +109,6 @@ "yarn": "^1.3" }, "license": "MIT", - "lint-staged": { - "src/**/*.{js,jsx}": [ - "prettier --trailing-comma es5 --write", - "git add" - ] - }, "lbrySettings": { "lbrynetDaemonVersion": "0.18.0", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-daemon-vDAEMONVER-OSNAME.zip" diff --git a/src/main/index.js b/src/main/index.js index 0c8e114d6..383be184d 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1,67 +1,39 @@ +/* eslint-disable no-console */ // Module imports -const { - app, - BrowserWindow, - ipcMain, - Menu, - Tray, - globalShortcut, -} = require("electron"); -const path = require("path"); -const url = require("url"); -const jayson = require("jayson"); -const semver = require("semver"); -const https = require("https"); -const keytar = require("keytar"); -// tree-kill has better cross-platform handling of -// killing a process. child-process.kill was unreliable -const kill = require("tree-kill"); -const child_process = require("child_process"); -const assert = require("assert"); +import Path from 'path'; +import Url from 'url'; +import Jayson from 'jayson'; +import Semver from 'semver'; +import Https from 'https'; +import Keytar from 'keytar'; +import ChildProcess from 'child_process'; +import Assert from 'assert'; +import { app, BrowserWindow, globalShortcut, ipcMain, Menu, Tray } from 'electron'; +import mainMenu from './menu/mainMenu'; + const localVersion = app.getVersion(); -const setMenu = require("./menu/main-menu.js"); -export const contextMenu = require("./menu/context-menu"); +export { contextMenu as Default } from './menu/contextMenu'; // Debug configs -const isDevelopment = process.env.NODE_ENV === "development"; -if (isDevelopment) { - try { - const { - default: installExtension, - REACT_DEVELOPER_TOOLS, - REDUX_DEVTOOLS, - } = require("electron-devtools-installer"); - app.on("ready", () => { - [REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS].forEach(extension => { - installExtension(extension) - .then(name => console.log(`Added Extension: ${name}`)) - .catch(err => console.log("An error occurred: ", err)); - }); - }); - } catch (err) { - console.error(err); - } -} +const isDevelopment = process.env.NODE_ENV === 'development'; // Misc constants -const LATEST_RELEASE_API_URL = - "https://api.github.com/repos/lbryio/lbry-app/releases/latest"; -const DAEMON_PATH = - process.env.LBRY_DAEMON || path.join(__static, "daemon/lbrynet-daemon"); +const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest'; +const DAEMON_PATH = process.env.LBRY_DAEMON || Path.join(__static, 'daemon/lbrynet-daemon'); const rendererUrl = isDevelopment ? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}` : `file://${__dirname}/index.html`; -let client = jayson.client.http({ - host: "localhost", +const client = Jayson.client.http({ + host: 'localhost', port: 5279, - path: "/", + path: '/', timeout: 1000, }); // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. -let win; +let rendererWindow; // Also keep the daemon subprocess alive let daemonSubprocess; @@ -94,11 +66,10 @@ function processRequestedUri(uri) { // lbry://channel/#claimid. We remove the slash here as well. // On Linux and Mac, we just return the URI as given. - if (process.platform === "win32") { - return uri.replace(/\/$/, "").replace("/#", "#"); - } else { - return uri; + if (process.platform === 'win32') { + return uri.replace(/\/$/, '').replace('/#', '#'); } + return uri; } /* @@ -109,226 +80,21 @@ function processRequestedUri(uri) { function openItem(fullPath) { const subprocOptions = { detached: true, - stdio: "ignore", + stdio: 'ignore', }; let child; - if (process.platform === "darwin") { - child = child_process.spawn("open", [fullPath], subprocOptions); - } else if (process.platform === "linux") { - child = child_process.spawn("xdg-open", [fullPath], subprocOptions); - } else if (process.platform === "win32") { - child = child_process.spawn( - fullPath, - Object.assign({}, subprocOptions, { shell: true }) - ); + if (process.platform === 'darwin') { + child = ChildProcess.spawn('open', [fullPath], subprocOptions); + } else if (process.platform === 'linux') { + child = ChildProcess.spawn('xdg-open', [fullPath], subprocOptions); + } else if (process.platform === 'win32') { + child = ChildProcess.spawn(fullPath, Object.assign({}, subprocOptions, { shell: true })); } // Causes child process reference to be garbage collected, allowing main process to exit child.unref(); } - -function getPidsForProcessName(name) { - if (process.platform === "win32") { - const tasklistOut = child_process.execSync( - `tasklist /fi "Imagename eq ${name}.exe" /nh`, - { encoding: "utf8" } - ); - if (tasklistOut.startsWith("INFO")) { - return []; - } else { - return tasklistOut.match(/[^\r\n]+/g).map(line => line.split(/\s+/)[1]); // Second column of every non-empty line - } - } else { - const pgrepOut = child_process.spawnSync("pgrep", ["-x", name], { - encoding: "utf8", - }).stdout; - return pgrepOut.match(/\d+/g); - } -} - -function createWindow() { - // Disable renderer process's webSecurity on development to enable CORS. - win = isDevelopment - ? new BrowserWindow({ - backgroundColor: "#155B4A", - minWidth: 800, - minHeight: 600, - webPreferences: { webSecurity: false }, - }) - : new BrowserWindow({ - backgroundColor: "#155B4A", - minWidth: 800, - minHeight: 600, - }); - - win.webContents.session.setUserAgent(`LBRY/${localVersion}`); - - win.maximize(); - if (isDevelopment) { - win.webContents.openDevTools(); - } - win.loadURL(rendererUrl); - if (openUri) { - // We stored and received a URI that an external app requested before we had a window object - win.webContents.on("did-finish-load", () => { - win.webContents.send("open-uri-requested", openUri, true); - }); - } - - win.removeAllListeners(); - - win.on("close", function(event) { - if (minimize) { - event.preventDefault(); - win.hide(); - } - }); - - win.on("closed", () => { - win = null; - }); - - win.on("hide", () => { - // Checks what to show in the tray icon menu - if (minimize) updateTray(); - }); - - win.on("show", () => { - // Checks what to show in the tray icon menu - if (minimize) updateTray(); - }); - - win.on("blur", () => { - // Checks what to show in the tray icon menu - if (minimize) updateTray(); - - // Unregisters Alt+F4 shortcut - globalShortcut.unregister("Alt+F4"); - }); - - win.on("focus", () => { - // Checks what to show in the tray icon menu - if (minimize) updateTray(); - - // Registers shortcut for closing(quitting) the app - globalShortcut.register("Alt+F4", () => safeQuit()); - - win.webContents.send("window-is-focused", null); - }); - - // Menu bar - win.setAutoHideMenuBar(true); - win.setMenuBarVisibility(isDevelopment); - setMenu(); -} - -function createTray() { - // Minimize to tray logic follows: - // Set the tray icon - let iconPath; - if (process.platform === "darwin") { - // Using @2x for mac retina screens so the icon isn't blurry - // file name needs to include "Template" at the end for dark menu bar - iconPath = path.join(__static, "/img/fav/macTemplate@2x.png"); - } else { - iconPath = path.join(__static, "/img/fav/32x32.png"); - } - - tray = new Tray(iconPath); - tray.setToolTip("LBRY App"); - tray.setTitle("LBRY"); - tray.on("double-click", () => { - win.show(); - }); -} - -// This needs to be done as for linux the context menu doesn't update automatically(docs) -function updateTray() { - let contextMenu = Menu.buildFromTemplate(getMenuTemplate()); - if (tray) { - tray.setContextMenu(contextMenu); - } else { - console.log("How did update tray get called without a tray?"); - } -} - -function getMenuTemplate() { - return [ - getToggleItem(), - { - label: "Quit", - click: () => safeQuit(), - }, - ]; - - function getToggleItem() { - if (win.isVisible() && win.isFocused()) { - return { - label: "Hide LBRY App", - click: () => win.hide(), - }; - } else { - return { - label: "Show LBRY App", - click: () => win.show(), - }; - } - } -} - -function handleOpenUriRequested(uri) { - if (!win) { - // Window not created yet, so store up requested URI for when it is - openUri = processRequestedUri(uri); - } else { - if (win.isMinimized()) { - win.restore(); - } else if (!win.isVisible()) { - win.show(); - } - - win.focus(); - win.webContents.send("open-uri-requested", processRequestedUri(uri)); - } -} - -function handleDaemonSubprocessExited() { - console.log("The daemon has exited."); - daemonSubprocess = null; - if (!daemonStopRequested) { - // We didn't request to stop the daemon, so display a - // warning and schedule a quit. - // - // TODO: maybe it would be better to restart the daemon? - if (win) { - console.log("Did not request daemon stop, so quitting in 5 seconds."); - win.loadURL(`file://${__static}/warning.html`); - setTimeout(quitNow, 5000); - } else { - console.log("Did not request daemon stop, so quitting."); - quitNow(); - } - } -} - -function launchDaemon() { - assert(!daemonSubprocess, "Tried to launch daemon twice"); - - console.log("Launching daemon:", DAEMON_PATH); - daemonSubprocess = child_process.spawn(DAEMON_PATH); - // Need to handle the data event instead of attaching to - // process.stdout because the latter doesn't work. I believe on - // windows it buffers stdout and we don't get any meaningful output - daemonSubprocess.stdout.on("data", buf => { - console.log(String(buf).trim()); - }); - daemonSubprocess.stderr.on("data", buf => { - console.log(String(buf).trim()); - }); - daemonSubprocess.on("exit", handleDaemonSubprocessExited); -} - /* * Quits by first killing the daemon, the calling quitting for real. */ @@ -337,6 +103,155 @@ export function safeQuit() { app.quit(); } +function getMenuTemplate() { + function getToggleItem() { + if (rendererWindow.isVisible() && rendererWindow.isFocused()) { + return { + label: 'Hide LBRY App', + click: () => rendererWindow.hide(), + }; + } + return { + label: 'Show LBRY App', + click: () => rendererWindow.show(), + }; + } + + return [ + getToggleItem(), + { + label: 'Quit', + click: () => safeQuit(), + }, + ]; +} + +// This needs to be done as for linux the context menu doesn't update automatically(docs) +function updateTray() { + const trayContextMenu = Menu.buildFromTemplate(getMenuTemplate()); + if (tray) { + tray.setContextMenu(trayContextMenu); + } else { + console.log('How did update tray get called without a tray?'); + } +} + +function createWindow() { + // Disable renderer process's webSecurity on development to enable CORS. + let windowConfiguration = { + backgroundColor: '#155B4A', + minWidth: 800, + minHeight: 600, + autoHideMenuBar: true, + }; + + windowConfiguration = isDevelopment + ? { + ...windowConfiguration, + webPreferences: { + webSecurity: false, + }, + } + : windowConfiguration; + + let window = new BrowserWindow(windowConfiguration); + + window.webContents.session.setUserAgent(`LBRY/${localVersion}`); + + window.maximize(); + if (isDevelopment) { + window.webContents.openDevTools(); + } + window.loadURL(rendererUrl); + if (openUri) { + // We stored and received a URI that an external app requested before we had a window object + window.webContents.on('did-finish-load', () => { + window.webContents.send('open-uri-requested', openUri, true); + }); + } + + window.removeAllListeners(); + + window.on('close', event => { + if (minimize) { + event.preventDefault(); + window.hide(); + } + }); + + window.on('closed', () => { + window = null; + }); + + window.on('hide', () => { + // Checks what to show in the tray icon menu + if (minimize) updateTray(); + }); + + window.on('show', () => { + // Checks what to show in the tray icon menu + if (minimize) updateTray(); + }); + + window.on('blur', () => { + // Checks what to show in the tray icon menu + if (minimize) updateTray(); + + // Unregisters Alt+F4 shortcut + globalShortcut.unregister('Alt+F4'); + }); + + window.on('focus', () => { + // Checks what to show in the tray icon menu + if (minimize) updateTray(); + + // Registers shortcut for closing(quitting) the app + globalShortcut.register('Alt+F4', () => safeQuit()); + + window.webContents.send('window-is-focused', null); + }); + + mainMenu(); + + return window; +} + +function createTray() { + // Minimize to tray logic follows: + // Set the tray icon + let iconPath; + if (process.platform === 'darwin') { + // Using @2x for mac retina screens so the icon isn't blurry + // file name needs to include "Template" at the end for dark menu bar + iconPath = Path.join(__static, '/img/fav/macTemplate@2x.png'); + } else { + iconPath = Path.join(__static, '/img/fav/32x32.png'); + } + + tray = new Tray(iconPath); + tray.setToolTip('LBRY App'); + tray.setTitle('LBRY'); + tray.on('double-click', () => { + rendererWindow.show(); + }); +} + +function handleOpenUriRequested(uri) { + if (!rendererWindow) { + // Window not created yet, so store up requested URI for when it is + openUri = processRequestedUri(uri); + } else { + if (rendererWindow.isMinimized()) { + rendererWindow.restore(); + } else if (!rendererWindow.isVisible()) { + rendererWindow.show(); + } + + rendererWindow.focus(); + rendererWindow.webContents.send('open-uri-requested', processRequestedUri(uri)); + } +} + /* * Quits without any preparation. When a quit is requested (either through the * interface or through app.quit()), we abort the quit, try to shut down the daemon, @@ -347,16 +262,52 @@ function quitNow() { safeQuit(); } +function handleDaemonSubprocessExited() { + console.log('The daemon has exited.'); + daemonSubprocess = null; + if (!daemonStopRequested) { + // We didn't request to stop the daemon, so display a + // warning and schedule a quit. + // + // TODO: maybe it would be better to restart the daemon? + if (rendererWindow) { + console.log('Did not request daemon stop, so quitting in 5 seconds.'); + rendererWindow.loadURL(`file://${__static}/warning.html`); + setTimeout(quitNow, 5000); + } else { + console.log('Did not request daemon stop, so quitting.'); + quitNow(); + } + } +} + +function launchDaemon() { + Assert(!daemonSubprocess, 'Tried to launch daemon twice'); + + console.log('Launching daemon:', DAEMON_PATH); + daemonSubprocess = ChildProcess.spawn(DAEMON_PATH); + // Need to handle the data event instead of attaching to + // process.stdout because the latter doesn't work. I believe on + // windows it buffers stdout and we don't get any meaningful output + daemonSubprocess.stdout.on('data', buf => { + console.log(String(buf).trim()); + }); + daemonSubprocess.stderr.on('data', buf => { + console.log(String(buf).trim()); + }); + daemonSubprocess.on('exit', handleDaemonSubprocessExited); +} + const isSecondaryInstance = app.makeSingleInstance(argv => { if (argv.length >= 2) { handleOpenUriRequested(argv[1]); // This will handle restoring and focusing the window - } else if (win) { - if (win.isMinimized()) { - win.restore(); - } else if (!win.isVisible()) { - win.show(); + } else if (rendererWindow) { + if (rendererWindow.isMinimized()) { + rendererWindow.restore(); + } else if (!rendererWindow.isVisible()) { + rendererWindow.show(); } - win.focus(); + rendererWindow.focus(); } }); @@ -368,126 +319,44 @@ if (isSecondaryInstance) { function launchDaemonIfNotRunning() { // Check if the daemon is already running. If we get // an error its because its not running - console.log("Checking for lbrynet daemon"); - client.request("status", [], function(err, res) { + console.log('Checking for lbrynet daemon'); + client.request('status', [], err => { if (err) { - console.log("lbrynet daemon needs to be launched"); + console.log('lbrynet daemon needs to be launched'); launchDaemon(); } else { - console.log("lbrynet daemon is already running"); + console.log('lbrynet daemon is already running'); } }); } -/* - * Last resort for killing unresponsive daemon instances. - * Looks for any processes called "lbrynet-daemon" and - * tries to force kill them. - */ -function forceKillAllDaemonsAndQuit() { - console.log( - "Attempting to force kill any running lbrynet-daemon instances..." - ); - - const daemonPids = getPidsForProcessName("lbrynet-daemon"); - if (!daemonPids) { - console.log("No lbrynet-daemon found running."); - quitNow(); - } else { - console.log( - `Found ${ - daemonPids.length - } running daemon instances. Attempting to force kill...` - ); - - for (const pid of daemonPids) { - let daemonKillAttemptsComplete = 0; - kill(pid, "SIGKILL", err => { - daemonKillAttemptsComplete++; - if (err) { - console.log( - `Failed to force kill daemon task with pid ${pid}. Error message: ${ - err.message - }` - ); - } else { - console.log(`Force killed daemon task with pid ${pid}.`); - } - if (daemonKillAttemptsComplete >= daemonPids.length - 1) { - quitNow(); - } - }); +// Taken from webtorrent-desktop +function checkLinuxTraySupport(cb) { + // Check that we're on Ubuntu (or another debian system) and that we have + // libappindicator1. + ChildProcess.exec('dpkg --get-selections libappindicator1', (err, stdout) => { + if (err) return cb(err); + // Unfortunately there's no cleaner way, as far as I can tell, to check + // whether a debian package is installed: + if (stdout.endsWith('\tinstall\n')) { + return cb(null); } - } -} - -app.setAsDefaultProtocolClient("lbry"); - -app.on("ready", function() { - launchDaemonIfNotRunning(); - if (process.platform === "linux") { - checkLinuxTraySupport(err => { - if (!err) createTray(); - else minimize = false; - }); - } else { - createTray(); - } - createWindow(); -}); - -// Quit when all windows are closed. -app.on("window-all-closed", () => { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== "darwin") { - app.quit(); - } -}); - -app.on("before-quit", event => { - if (!readyToQuit) { - // We need to shutdown the daemon before we're ready to actually quit. This - // event will be triggered re-entrantly once preparation is done. - event.preventDefault(); - shutdownDaemonAndQuit(); - } else { - console.log("Quitting."); - } -}); - -app.on("activate", () => { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (win === null) { - createWindow(); - } -}); - -if (process.platform === "darwin") { - app.on("open-url", (event, uri) => { - handleOpenUriRequested(uri); + return cb(new Error('debian package not installed')); }); -} else if (process.argv.length >= 2) { - handleOpenUriRequested(process.argv[1]); } // When a quit is attempted, this is called. It attempts to shutdown the daemon, // then calls quitNow() to quit for real. function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) { function doShutdown() { - console.log("Shutting down daemon"); + console.log('Shutting down daemon'); daemonStopRequested = true; - client.request("daemon_stop", [], (err, res) => { + client.request('daemon_stop', [], err => { if (err) { - console.log( - `received error when stopping lbrynet-daemon. Error message: ${ - err.message - }\n` - ); - console.log("You will need to manually kill the daemon."); + console.log(`received error when stopping lbrynet-daemon. Error message: ${err.message}\n`); + console.log('You will need to manually kill the daemon.'); } else { - console.log("Successfully stopped daemon via RPC call."); + console.log('Successfully stopped daemon via RPC call.'); quitNow(); } }); @@ -496,7 +365,7 @@ function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) { if (daemonSubprocess) { doShutdown(); } else if (!evenIfNotStartedByApp) { - console.log("Not killing lbrynet-daemon because app did not start it"); + console.log('Not killing lbrynet-daemon because app did not start it'); quitNow(); } else { doShutdown(); @@ -506,111 +375,154 @@ function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) { // If not, we should wait until the daemon is closed before we start the install. } -// Taken from webtorrent-desktop -function checkLinuxTraySupport(cb) { - // Check that we're on Ubuntu (or another debian system) and that we have - // libappindicator1. - child_process.exec("dpkg --get-selections libappindicator1", function( - err, - stdout - ) { - if (err) return cb(err); - // Unfortunately there's no cleaner way, as far as I can tell, to check - // whether a debian package is installed: - if (stdout.endsWith("\tinstall\n")) { - cb(null); - } else { - cb(new Error("debian package not installed")); - } - }); +if (isDevelopment) { + import('devtron') + .then(({ install }) => { + install(); + console.log('Added Extension: Devtron'); + }) + .catch(error => { + console.error(error); + }); + import('electron-devtools-installer') + .then(({ default: installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS }) => { + app.on('ready', () => { + [REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS].forEach(extension => { + installExtension(extension) + .then(name => console.log(`Added Extension: ${name}`)) + .catch(err => console.log('An error occurred: ', err)); + }); + }); + }) + .catch(error => { + console.error(error); + }); } -ipcMain.on("upgrade", (event, installerPath) => { - app.on("quit", () => { - console.log("Launching upgrade installer at", installerPath); +app.setAsDefaultProtocolClient('lbry'); + +app.on('ready', () => { + launchDaemonIfNotRunning(); + if (process.platform === 'linux') { + checkLinuxTraySupport(err => { + if (!err) createTray(); + else minimize = false; + }); + } else { + createTray(); + } + rendererWindow = createWindow(); +}); + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('before-quit', event => { + if (!readyToQuit) { + // We need to shutdown the daemon before we're ready to actually quit. This + // event will be triggered re-entrantly once preparation is done. + event.preventDefault(); + shutdownDaemonAndQuit(); + } else { + console.log('Quitting.'); + } +}); + +app.on('activate', () => { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (rendererWindow === null) { + createWindow(); + } +}); + +if (process.platform === 'darwin') { + app.on('open-url', (event, uri) => { + handleOpenUriRequested(uri); + }); +} else if (process.argv.length >= 2) { + handleOpenUriRequested(process.argv[1]); +} + +ipcMain.on('upgrade', (event, installerPath) => { + app.on('quit', () => { + console.log('Launching upgrade installer at', installerPath); // This gets triggered called after *all* other quit-related events, so // we'll only get here if we're fully prepared and quitting for real. openItem(installerPath); }); - if (win) { - win.loadURL(`file://${__static}/upgrade.html`); + if (rendererWindow) { + rendererWindow.loadURL(`file://${__static}/upgrade.html`); } shutdownDaemonAndQuit(true); // wait for daemon to shut down before upgrading // what to do if no shutdown in a long time? - console.log("Update downloaded to", installerPath); + console.log('Update downloaded to', installerPath); console.log( - "The app will close, and you will be prompted to install the latest version of LBRY." + 'The app will close, and you will be prompted to install the latest version of LBRY.' ); - console.log("After the install is complete, please reopen the app."); + console.log('After the install is complete, please reopen the app.'); }); -ipcMain.on("version-info-requested", () => { +ipcMain.on('version-info-requested', () => { function formatRc(ver) { // Adds dash if needed to make RC suffix semver friendly - return ver.replace(/([^-])rc/, "$1-rc"); + return ver.replace(/([^-])rc/, '$1-rc'); } - let result = ""; + let result = ''; const opts = { headers: { - "User-Agent": `LBRY/${localVersion}`, + 'User-Agent': `LBRY/${localVersion}`, }, }; - const req = https.get( - Object.assign(opts, url.parse(LATEST_RELEASE_API_URL)), - res => { - res.on("data", data => { - result += data; - }); - res.on("end", () => { - const tagName = JSON.parse(result).tag_name; - const [_, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/); - if (!remoteVersion) { - if (win) { - win.webContents.send("version-info-received", null); - } - } else { - const upgradeAvailable = semver.gt( - formatRc(remoteVersion), - formatRc(localVersion) - ); - if (win) { - win.webContents.send("version-info-received", { - remoteVersion, - localVersion, - upgradeAvailable, - }); - } + const req = Https.get(Object.assign(opts, Url.parse(LATEST_RELEASE_API_URL)), res => { + res.on('data', data => { + result += data; + }); + res.on('end', () => { + const tagName = JSON.parse(result).tag_name; + const [, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/); + if (!remoteVersion) { + if (rendererWindow) { + rendererWindow.webContents.send('version-info-received', null); } - }); - } - ); + } else { + const upgradeAvailable = Semver.gt(formatRc(remoteVersion), formatRc(localVersion)); + if (rendererWindow) { + rendererWindow.webContents.send('version-info-received', { + remoteVersion, + localVersion, + upgradeAvailable, + }); + } + } + }); + }); - req.on("error", err => { - console.log("Failed to get current version from GitHub. Error:", err); - if (win) { - win.webContents.send("version-info-received", null); + req.on('error', err => { + console.log('Failed to get current version from GitHub. Error:', err); + if (rendererWindow) { + rendererWindow.webContents.send('version-info-received', null); } }); }); -ipcMain.on("get-auth-token", event => { - keytar.getPassword("LBRY", "auth_token").then(token => { - event.sender.send( - "auth-token-response", - token ? token.toString().trim() : null - ); +ipcMain.on('get-auth-token', event => { + Keytar.getPassword('LBRY', 'auth_token').then(token => { + event.sender.send('auth-token-response', token ? token.toString().trim() : null); }); }); -ipcMain.on("set-auth-token", (event, token) => { - keytar.setPassword( - "LBRY", - "auth_token", - token ? token.toString().trim() : null - ); +ipcMain.on('set-auth-token', (event, token) => { + Keytar.setPassword('LBRY', 'auth_token', token ? token.toString().trim() : null); }); diff --git a/src/main/menu/context-menu.js b/src/main/menu/context-menu.js deleted file mode 100644 index f38de5d1c..000000000 --- a/src/main/menu/context-menu.js +++ /dev/null @@ -1,36 +0,0 @@ -const {Menu} = require('electron'); -const electron = require('electron'); -const app = electron.app; - -const contextMenuTemplate = [ - { - role: 'cut', - }, - { - role: 'copy', - }, - { - role: 'paste', - }, -]; - -module.exports = { - showContextMenu(win, posX, posY, showDevItems) { - let template = contextMenuTemplate.slice(); - if (showDevItems) { - template.push({ - type: 'separator', - }); - template.push( - { - label: 'Inspect Element', - click() { - win.inspectElement(posX, posY); - } - } - ); - } - - Menu.buildFromTemplate(template).popup(win); - }, -}; diff --git a/src/main/menu/contextMenu.js b/src/main/menu/contextMenu.js new file mode 100644 index 000000000..ad33c6bc2 --- /dev/null +++ b/src/main/menu/contextMenu.js @@ -0,0 +1,30 @@ +import { Menu } from 'electron'; + +const contextMenuTemplate = [ + { + role: 'cut', + }, + { + role: 'copy', + }, + { + role: 'paste', + }, +]; + +export default function contextMenu(win, posX, posY, showDevItems) { + const template = contextMenuTemplate.slice(); + if (showDevItems) { + template.push({ + type: 'separator', + }); + template.push({ + label: 'Inspect Element', + click() { + win.inspectElement(posX, posY); + }, + }); + } + + Menu.buildFromTemplate(template).popup(win); +} diff --git a/src/main/menu/main-menu.js b/src/main/menu/mainMenu.js similarity index 63% rename from src/main/menu/main-menu.js rename to src/main/menu/mainMenu.js index 7b5854d75..879ce8206 100644 --- a/src/main/menu/main-menu.js +++ b/src/main/menu/mainMenu.js @@ -1,5 +1,5 @@ -const { app, shell, Menu } = require('electron'); -const { safeQuit } = require('../index.js'); +import { app, Menu, shell } from 'electron'; +import { safeQuit } from '../index'; const baseTemplate = [ { @@ -7,10 +7,10 @@ const baseTemplate = [ submenu: [ { label: 'Quit', - accelerator: "CommandOrControl+Q", + accelerator: 'CommandOrControl+Q', click: () => safeQuit(), }, - ] + ], }, { label: 'Edit', @@ -36,32 +36,32 @@ const baseTemplate = [ { role: 'selectall', }, - ] + ], }, { label: 'View', submenu: [ { - role: 'reload' + role: 'reload', }, { label: 'Developer', submenu: [ { - role: 'forcereload' + role: 'forcereload', }, { - role: 'toggledevtools' + role: 'toggledevtools', }, - ] + ], }, { - type: 'separator' + type: 'separator', }, { - role: 'togglefullscreen' - } - ] + role: 'togglefullscreen', + }, + ], }, { role: 'help', @@ -72,34 +72,34 @@ const baseTemplate = [ if (focusedWindow) { focusedWindow.webContents.send('open-menu', '/help'); } - } + }, }, { label: 'Frequently Asked Questions', - click(item, focusedWindow){ - shell.openExternal('https://lbry.io/faq') - } + click() { + shell.openExternal('https://lbry.io/faq'); + }, }, { - type: 'separator' + type: 'separator', }, { label: 'Report Issue', - click(item, focusedWindow){ + click() { shell.openExternal('https://lbry.io/faq/contributing#report-a-bug'); - } + }, }, { - type: 'separator' + type: 'separator', }, { label: 'Developer API Guide', - click(item, focusedWindow){ - shell.openExternal('https://lbry.io/quickstart') - } + click() { + shell.openExternal('https://lbry.io/quickstart'); + }, }, - ] - } + ], + }, ]; const macOSAppMenuTemplate = { @@ -126,11 +126,13 @@ const macOSAppMenuTemplate = { { role: 'quit', }, - ] + ], }; -module.exports = () => { - let template = baseTemplate.slice(); - (process.platform === 'darwin') && template.unshift(macOSAppMenuTemplate); - Menu.setApplicationMenu(Menu.buildFromTemplate(template)); +export default () => { + const template = baseTemplate.slice(); + if (process.platform === 'darwin') { + template.unshift(macOSAppMenuTemplate); + } + Menu.setApplicationMenu(Menu.buildFromTemplate(template)); }; diff --git a/src/renderer/app.js b/src/renderer/app.js index e249810f9..75752fac7 100644 --- a/src/renderer/app.js +++ b/src/renderer/app.js @@ -1,29 +1,40 @@ -import store from "store.js"; -import { remote } from "electron"; +import store from 'store'; +import { remote } from 'electron'; +import Path from 'path'; +import y18n from 'y18n'; -const env = process.env.NODE_ENV || "production"; -const config = { - ...require(`./config/${env}`), -}; -const i18n = require("y18n")({ - directory: remote.app.getAppPath() + "/locales", +const env = process.env.NODE_ENV || 'production'; +const i18n = y18n({ + directory: Path.join(remote.app.getAppPath(), '/../static/locales').replace(/\\/g, '\\\\'), updateFiles: false, - locale: "en", + locale: 'en', }); + const logs = []; const app = { - env: env, - config: config, - store: store, - i18n: i18n, - logs: logs, - log: function(message) { + env, + store, + i18n, + logs, + log(message) { logs.push(message); }, }; -window.__ = i18n.__; -window.__n = i18n.__n; +// Workaround for https://github.com/electron-userland/electron-webpack/issues/52 +if (env !== 'development') { + window.staticResourcesPath = Path.join(remote.app.getAppPath(), '../static').replace( + /\\/g, + '\\\\' + ); +} else { + window.staticResourcesPath = ''; +} +// eslint-disable-next-line no-underscore-dangle +global.__ = i18n.__; +// eslint-disable-next-line no-underscore-dangle +global.__n = i18n.__n; global.app = app; -module.exports = app; + +export default app; diff --git a/src/renderer/component/address/index.js b/src/renderer/component/address/index.js index 00bec962e..b861b75c5 100644 --- a/src/renderer/component/address/index.js +++ b/src/renderer/component/address/index.js @@ -1,6 +1,6 @@ -import { connect } from "react-redux"; -import { doShowSnackBar } from "redux/actions/app"; -import Address from "./view"; +import { connect } from 'react-redux'; +import { doShowSnackBar } from 'redux/actions/app'; +import Address from './view'; export default connect(null, { doShowSnackBar, diff --git a/src/renderer/component/address/view.jsx b/src/renderer/component/address/view.jsx index 96049eb35..65ea563c0 100644 --- a/src/renderer/component/address/view.jsx +++ b/src/renderer/component/address/view.jsx @@ -1,8 +1,8 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { clipboard } from "electron"; -import Link from "component/link"; -import classnames from "classnames"; +import React from 'react'; +import PropTypes from 'prop-types'; +import { clipboard } from 'electron'; +import Link from 'component/link'; +import classnames from 'classnames'; export default class Address extends React.PureComponent { static propTypes = { @@ -21,8 +21,8 @@ export default class Address extends React.PureComponent { return (
{ @@ -32,7 +32,7 @@ export default class Address extends React.PureComponent { this._inputElem.select(); }} readOnly="readonly" - value={address || ""} + value={address || ''} /> {showCopyButton && ( @@ -41,7 +41,7 @@ export default class Address extends React.PureComponent { icon="clipboard" onClick={() => { clipboard.writeText(address); - doShowSnackBar({ message: __("Address copied") }); + doShowSnackBar({ message: __('Address copied') }); }} /> diff --git a/src/renderer/component/app/index.js b/src/renderer/component/app/index.js index 66b50bb71..c7173f606 100644 --- a/src/renderer/component/app/index.js +++ b/src/renderer/component/app/index.js @@ -1,14 +1,14 @@ -import React from "react"; -import { connect } from "react-redux"; +import React from 'react'; +import { connect } from 'react-redux'; import { selectPageTitle, selectHistoryIndex, selectActiveHistoryEntry, -} from "redux/selectors/navigation"; -import { selectUser } from "redux/selectors/user"; -import { doAlertError } from "redux/actions/app"; -import { doRecordScroll } from "redux/actions/navigation"; -import App from "./view"; +} from 'redux/selectors/navigation'; +import { selectUser } from 'redux/selectors/user'; +import { doAlertError } from 'redux/actions/app'; +import { doRecordScroll } from 'redux/actions/navigation'; +import App from './view'; const select = (state, props) => ({ pageTitle: selectPageTitle(state), diff --git a/src/renderer/component/app/view.jsx b/src/renderer/component/app/view.jsx index 11e66e252..fb43b6574 100644 --- a/src/renderer/component/app/view.jsx +++ b/src/renderer/component/app/view.jsx @@ -1,10 +1,10 @@ -import React from "react"; -import Router from "component/router/index"; -import Header from "component/header"; -import Theme from "component/theme"; -import ModalRouter from "modal/modalRouter"; -import ReactModal from "react-modal"; -import throttle from "util/throttle"; +import React from 'react'; +import Router from 'component/router/index'; +import Header from 'component/header'; +import Theme from 'component/theme'; +import ModalRouter from 'modal/modalRouter'; +import ReactModal from 'react-modal'; +import throttle from 'util/throttle'; class App extends React.PureComponent { constructor() { @@ -15,25 +15,25 @@ class App extends React.PureComponent { componentWillMount() { const { alertError } = this.props; - document.addEventListener("unhandledError", event => { + document.addEventListener('unhandledError', event => { alertError(event.detail); }); } componentDidMount() { const { recordScroll } = this.props; - const mainContent = document.getElementById("main-content"); + const mainContent = document.getElementById('main-content'); this.mainContent = mainContent; const scrollListener = () => recordScroll(this.mainContent.scrollTop); - this.mainContent.addEventListener("scroll", throttle(scrollListener, 750)); + this.mainContent.addEventListener('scroll', throttle(scrollListener, 750)); - ReactModal.setAppElement("#window"); //fuck this + ReactModal.setAppElement('#window'); // fuck this } componentWillUnmount() { - this.mainContent.removeEventListener("scroll", this.scrollListener); + this.mainContent.removeEventListener('scroll', this.scrollListener); } componentWillReceiveProps(props) { @@ -50,7 +50,7 @@ class App extends React.PureComponent { } setTitleFromProps(props) { - window.document.title = props.pageTitle || "LBRY"; + window.document.title = props.pageTitle || 'LBRY'; } render() { diff --git a/src/renderer/component/cardMedia/index.js b/src/renderer/component/cardMedia/index.js index 3616b0331..1f7988712 100644 --- a/src/renderer/component/cardMedia/index.js +++ b/src/renderer/component/cardMedia/index.js @@ -1,6 +1,6 @@ -import React from "react"; -import { connect } from "react-redux"; -import CardMedia from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import CardMedia from './view'; const select = state => ({}); const perform = dispatch => ({}); diff --git a/src/renderer/component/cardMedia/view.jsx b/src/renderer/component/cardMedia/view.jsx index d0f45f4ac..06f80f1d7 100644 --- a/src/renderer/component/cardMedia/view.jsx +++ b/src/renderer/component/cardMedia/view.jsx @@ -1,18 +1,18 @@ -import React from "react"; +import React from 'react'; class CardMedia extends React.PureComponent { static AUTO_THUMB_CLASSES = [ - "purple", - "red", - "pink", - "indigo", - "blue", - "light-blue", - "cyan", - "teal", - "green", - "yellow", - "orange", + 'purple', + 'red', + 'pink', + 'indigo', + 'blue', + 'light-blue', + 'cyan', + 'teal', + 'green', + 'yellow', + 'orange', ]; componentWillMount() { @@ -29,12 +29,7 @@ class CardMedia extends React.PureComponent { const atClass = this.state.autoThumbClass; if (thumbnail) { - return ( -
- ); + return
; } return ( @@ -42,8 +37,8 @@ class CardMedia extends React.PureComponent {
{title && title - .replace(/\s+/g, "") - .substring(0, Math.min(title.replace(" ", "").length, 5)) + .replace(/\s+/g, '') + .substring(0, Math.min(title.replace(' ', '').length, 5)) .toUpperCase()}
diff --git a/src/renderer/component/cardVerify/index.js b/src/renderer/component/cardVerify/index.js index 53bdf05b9..390622f3d 100644 --- a/src/renderer/component/cardVerify/index.js +++ b/src/renderer/component/cardVerify/index.js @@ -1,7 +1,7 @@ -import React from "react"; -import { connect } from "react-redux"; -import { selectUserEmail } from "redux/selectors/user"; -import CardVerify from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { selectUserEmail } from 'redux/selectors/user'; +import CardVerify from './view'; const select = state => ({ email: selectUserEmail(state), diff --git a/src/renderer/component/cardVerify/view.jsx b/src/renderer/component/cardVerify/view.jsx index 4494cbf6c..4690b61d2 100644 --- a/src/renderer/component/cardVerify/view.jsx +++ b/src/renderer/component/cardVerify/view.jsx @@ -1,6 +1,6 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Link from "component/link"; +import React from 'react'; +import PropTypes from 'prop-types'; +import Link from 'component/link'; let scriptLoading = false; let scriptLoaded = false; @@ -48,8 +48,8 @@ class CardVerify extends React.Component { scriptLoading = true; - const script = document.createElement("script"); - script.src = "https://checkout.stripe.com/checkout.js"; + const script = document.createElement('script'); + script.src = 'https://checkout.stripe.com/checkout.js'; script.async = 1; this.loadPromise = (() => { @@ -69,12 +69,8 @@ class CardVerify extends React.Component { }; }); const wrappedPromise = new Promise((accept, cancel) => { - promise.then( - () => (canceled ? cancel({ isCanceled: true }) : accept()) - ); - promise.catch( - error => (canceled ? cancel({ isCanceled: true }) : cancel(error)) - ); + promise.then(() => (canceled ? cancel({ isCanceled: true }) : accept())); + promise.catch(error => (canceled ? cancel({ isCanceled: true }) : cancel(error))); }); return { @@ -85,9 +81,7 @@ class CardVerify extends React.Component { }; })(); - this.loadPromise.promise - .then(this.onScriptLoaded) - .catch(this.onScriptError); + this.loadPromise.promise.then(this.onScriptLoaded).catch(this.onScriptError); document.body.appendChild(script); } @@ -119,7 +113,7 @@ class CardVerify extends React.Component { }; onScriptError = (...args) => { - throw new Error("Unable to load credit validation script."); + throw new Error('Unable to load credit validation script.'); }; onClosed = () => { @@ -139,10 +133,10 @@ class CardVerify extends React.Component { CardVerify.stripeHandler.open({ allowRememberMe: false, closed: this.onClosed, - description: __("Confirm Identity"), + description: __('Confirm Identity'), email: this.props.email, - locale: "auto", - panelLabel: "Verify", + locale: 'auto', + panelLabel: 'Verify', token: this.props.token, zipCode: true, }); @@ -151,9 +145,7 @@ class CardVerify extends React.Component { onClick = () => { if (scriptDidError) { try { - throw new Error( - "Tried to call onClick, but StripeCheckout failed to load" - ); + throw new Error('Tried to call onClick, but StripeCheckout failed to load'); } catch (x) {} } else if (CardVerify.stripeHandler) { this.showStripeDialog(); @@ -168,9 +160,7 @@ class CardVerify extends React.Component { button="alt" label={this.props.label} icon="icon-lock" - disabled={ - this.props.disabled || this.state.open || this.hasPendingClick - } + disabled={this.props.disabled || this.state.open || this.hasPendingClick} onClick={this.onClick.bind(this)} /> ); diff --git a/src/renderer/component/channelTile/index.js b/src/renderer/component/channelTile/index.js index 4c86e8b14..51a4835f6 100644 --- a/src/renderer/component/channelTile/index.js +++ b/src/renderer/component/channelTile/index.js @@ -1,11 +1,11 @@ -import React from "react"; -import { connect } from "react-redux"; -import { makeSelectClaimForUri } from "redux/selectors/claims"; -import { doNavigate } from "redux/actions/navigation"; -import { doResolveUri } from "redux/actions/content"; -import { makeSelectTotalItemsForChannel } from "redux/selectors/content"; -import { makeSelectIsUriResolving } from "redux/selectors/content"; -import ChannelTile from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; +import { doNavigate } from 'redux/actions/navigation'; +import { doResolveUri } from 'redux/actions/content'; +import { makeSelectTotalItemsForChannel } from 'redux/selectors/content'; +import { makeSelectIsUriResolving } from 'redux/selectors/content'; +import ChannelTile from './view'; const select = (state, props) => ({ claim: makeSelectClaimForUri(props.uri)(state), diff --git a/src/renderer/component/channelTile/view.jsx b/src/renderer/component/channelTile/view.jsx index e4a4ed8be..73efed63a 100644 --- a/src/renderer/component/channelTile/view.jsx +++ b/src/renderer/component/channelTile/view.jsx @@ -1,6 +1,6 @@ -import React from "react"; -import CardMedia from "component/cardMedia"; -import { TruncatedText, BusyMessage } from "component/common.js"; +import React from 'react'; +import CardMedia from 'component/cardMedia'; +import { TruncatedText, BusyMessage } from 'component/common.js'; class ChannelTile extends React.PureComponent { componentDidMount() { @@ -26,12 +26,12 @@ class ChannelTile extends React.PureComponent { channelId = claim.claim_id; } - let onClick = () => navigate("/show", { uri }); + const onClick = () => navigate('/show', { uri }); return (
-
+
{channelName && }
@@ -40,19 +40,15 @@ class ChannelTile extends React.PureComponent {
- {isResolvingUri && ( - - )} + {isResolvingUri && } {totalItems > 0 && ( - This is a channel with {totalItems}{" "} - {totalItems === 1 ? " item" : " items"} inside of it. + This is a channel with {totalItems} {totalItems === 1 ? ' item' : ' items'}{' '} + inside of it. )} {!isResolvingUri && - !totalItems && ( - This is an empty channel. - )} + !totalItems && This is an empty channel.}
diff --git a/src/renderer/component/common.js b/src/renderer/component/common.js index 2be86b00b..bf4a4d8ce 100644 --- a/src/renderer/component/common.js +++ b/src/renderer/component/common.js @@ -1,9 +1,9 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { formatCredits, formatFullPrice } from "util/formatCredits"; -import lbry from "../lbry.js"; +import React from 'react'; +import PropTypes from 'prop-types'; +import { formatCredits, formatFullPrice } from 'util/formatCredits'; +import lbry from '../lbry.js'; -//component/icon.js +// component/icon.js export class Icon extends React.PureComponent { static propTypes = { icon: PropTypes.string.isRequired, @@ -13,12 +13,9 @@ export class Icon extends React.PureComponent { render() { const { fixed, className } = this.props; - const spanClassName = - "icon " + - ("fixed" in this.props ? "icon-fixed-width " : "") + - this.props.icon + - " " + - (this.props.className || ""); + const spanClassName = `icon ${'fixed' in this.props ? 'icon-fixed-width ' : ''}${ + this.props.icon + } ${this.props.className || ''}`; return ; } } @@ -34,10 +31,7 @@ export class TruncatedText extends React.PureComponent { render() { return ( - + {this.props.children} ); @@ -73,14 +67,14 @@ export class CreditAmount extends React.PureComponent { showFree: PropTypes.bool, showFullPrice: PropTypes.bool, showPlus: PropTypes.bool, - look: PropTypes.oneOf(["indicator", "plain", "fee"]), + look: PropTypes.oneOf(['indicator', 'plain', 'fee']), }; static defaultProps = { precision: 2, label: true, showFree: false, - look: "indicator", + look: 'indicator', showFullPrice: false, showPlus: false, }; @@ -90,46 +84,43 @@ export class CreditAmount extends React.PureComponent { const { amount, precision, showFullPrice } = this.props; let formattedAmount; - let fullPrice = formatFullPrice(amount, 2); + const fullPrice = formatFullPrice(amount, 2); if (showFullPrice) { formattedAmount = fullPrice; } else { formattedAmount = amount > 0 && amount < minimumRenderableAmount - ? "<" + minimumRenderableAmount + ? `<${minimumRenderableAmount}` : formatCredits(amount, precision); } let amountText; if (this.props.showFree && parseFloat(this.props.amount) === 0) { - amountText = __("free"); + amountText = __('free'); } else { if (this.props.label) { const label = - typeof this.props.label === "string" + typeof this.props.label === 'string' ? this.props.label - : parseFloat(amount) == 1 ? __("credit") : __("credits"); + : parseFloat(amount) == 1 ? __('credit') : __('credits'); - amountText = formattedAmount + " " + label; + amountText = `${formattedAmount} ${label}`; } else { amountText = formattedAmount; } if (this.props.showPlus && amount > 0) { - amountText = "+" + amountText; + amountText = `+${amountText}`; } } return ( - + {amountText} {this.props.isEstimate ? ( * @@ -155,7 +146,7 @@ export class Thumbnail extends React.PureComponent { constructor(props) { super(props); - this._defaultImageUri = lbry.imagePath("default-thumb.svg"); + this._defaultImageUri = lbry.imagePath('default-thumb.svg'); this._maxLoadTime = 10000; this._isMounted = false; @@ -180,7 +171,7 @@ export class Thumbnail extends React.PureComponent { } render() { - const className = this.props.className ? this.props.className : "", + const className = this.props.className ? this.props.className : '', otherProps = Object.assign({}, this.props); delete otherProps.className; return ( diff --git a/src/renderer/component/common/spinner.jsx b/src/renderer/component/common/spinner.jsx index 8863459c8..14938f9b9 100644 --- a/src/renderer/component/common/spinner.jsx +++ b/src/renderer/component/common/spinner.jsx @@ -1,16 +1,14 @@ -import React from "react"; -import classnames from "classnames"; +import React from 'react'; +import classnames from 'classnames'; -export default ({ dark, className }) => { - return ( -
- ); -}; +export default ({ dark, className }) => ( +
+); diff --git a/src/renderer/component/dateTime/index.js b/src/renderer/component/dateTime/index.js index dbef1e030..06009a4ca 100644 --- a/src/renderer/component/dateTime/index.js +++ b/src/renderer/component/dateTime/index.js @@ -1,14 +1,11 @@ -import React from "react"; -import { connect } from "react-redux"; -import { makeSelectBlockDate } from "redux/selectors/wallet"; -import { doFetchBlock } from "redux/actions/wallet"; -import DateTime from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { makeSelectBlockDate } from 'redux/selectors/wallet'; +import { doFetchBlock } from 'redux/actions/wallet'; +import DateTime from './view'; const select = (state, props) => ({ - date: - !props.date && props.block - ? makeSelectBlockDate(props.block)(state) - : props.date, + date: !props.date && props.block ? makeSelectBlockDate(props.block)(state) : props.date, }); const perform = dispatch => ({ diff --git a/src/renderer/component/dateTime/view.jsx b/src/renderer/component/dateTime/view.jsx index b5dff1239..83f7fa755 100644 --- a/src/renderer/component/dateTime/view.jsx +++ b/src/renderer/component/dateTime/view.jsx @@ -1,15 +1,15 @@ -import React from "react"; +import React from 'react'; class DateTime extends React.PureComponent { - static SHOW_DATE = "date"; - static SHOW_TIME = "time"; - static SHOW_BOTH = "both"; + static SHOW_DATE = 'date'; + static SHOW_TIME = 'time'; + static SHOW_BOTH = 'both'; static defaultProps = { formatOptions: { - month: "long", - day: "numeric", - year: "numeric", + month: 'long', + day: 'numeric', + year: 'numeric', }, }; @@ -37,12 +37,12 @@ class DateTime extends React.PureComponent { {date && (show == DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) && - date.toLocaleDateString([locale, "en-US"], formatOptions)} - {show == DateTime.SHOW_BOTH && " "} + date.toLocaleDateString([locale, 'en-US'], formatOptions)} + {show == DateTime.SHOW_BOTH && ' '} {date && (show == DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) && date.toLocaleTimeString()} - {!date && "..."} + {!date && '...'} ); } diff --git a/src/renderer/component/file-selector.js b/src/renderer/component/file-selector.js index adc481782..18284268b 100644 --- a/src/renderer/component/file-selector.js +++ b/src/renderer/component/file-selector.js @@ -1,16 +1,17 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React from 'react'; +import PropTypes from 'prop-types'; + +const { remote } = require('electron'); -const { remote } = require("electron"); class FileSelector extends React.PureComponent { static propTypes = { - type: PropTypes.oneOf(["file", "directory"]), + type: PropTypes.oneOf(['file', 'directory']), initPath: PropTypes.string, onFileChosen: PropTypes.func, }; static defaultProps = { - type: "file", + type: 'file', }; constructor(props) { @@ -27,10 +28,7 @@ class FileSelector extends React.PureComponent { handleButtonClick() { remote.dialog.showOpenDialog( { - properties: - this.props.type == "file" - ? ["openFile"] - : ["openDirectory", "createDirectory"], + properties: this.props.type == 'file' ? ['openFile'] : ['openDirectory', 'createDirectory'], }, paths => { if (!paths) { @@ -40,7 +38,7 @@ class FileSelector extends React.PureComponent { const path = paths[0]; this.setState({ - path: path, + path, }); if (this.props.onFileChosen) { this.props.onFileChosen(path); @@ -59,12 +57,10 @@ class FileSelector extends React.PureComponent { > - {this.props.type == "file" - ? __("Choose File") - : __("Choose Directory")} + {this.props.type == 'file' ? __('Choose File') : __('Choose Directory')} - {" "} + {' '}
diff --git a/src/renderer/component/fileActions/index.js b/src/renderer/component/fileActions/index.js index 53030756f..b848342c0 100644 --- a/src/renderer/component/fileActions/index.js +++ b/src/renderer/component/fileActions/index.js @@ -1,14 +1,14 @@ -import React from "react"; -import { connect } from "react-redux"; -import { makeSelectFileInfoForUri } from "redux/selectors/file_info"; -import { makeSelectCostInfoForUri } from "redux/selectors/cost_info"; -import { doOpenModal } from "redux/actions/app"; -import { makeSelectClaimIsMine } from "redux/selectors/claims"; -import FileActions from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { makeSelectFileInfoForUri } from 'redux/selectors/file_info'; +import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info'; +import { doOpenModal } from 'redux/actions/app'; +import { makeSelectClaimIsMine } from 'redux/selectors/claims'; +import FileActions from './view'; const select = (state, props) => ({ fileInfo: makeSelectFileInfoForUri(props.uri)(state), - /*availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix*/ + /* availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix */ costInfo: makeSelectCostInfoForUri(props.uri)(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state), }); diff --git a/src/renderer/component/fileActions/view.jsx b/src/renderer/component/fileActions/view.jsx index a382191ee..b2d83f3b9 100644 --- a/src/renderer/component/fileActions/view.jsx +++ b/src/renderer/component/fileActions/view.jsx @@ -1,7 +1,7 @@ -import React from "react"; -import Link from "component/link"; -import FileDownloadLink from "component/fileDownloadLink"; -import * as modals from "constants/modal_types"; +import React from 'react'; +import Link from 'component/link'; +import FileDownloadLink from 'component/fileDownloadLink'; +import * as modals from 'constants/modal_types'; class FileActions extends React.PureComponent { render() { @@ -17,7 +17,7 @@ class FileActions extends React.PureComponent { openModal(modals.CONFIRM_FILE_REMOVE, { uri })} /> @@ -28,22 +28,22 @@ class FileActions extends React.PureComponent { icon="icon-flag" href={`https://lbry.io/dmca?claim_id=${claimId}`} className="no-underline" - label={__("report")} + label={__('report')} /> )} {claimIsMine && ( ({ claim: makeSelectClaimForUri(props.uri)(state), diff --git a/src/renderer/component/fileCard/view.jsx b/src/renderer/component/fileCard/view.jsx index a7ec6a5fa..9a74a49fb 100644 --- a/src/renderer/component/fileCard/view.jsx +++ b/src/renderer/component/fileCard/view.jsx @@ -1,14 +1,14 @@ -import React from "react"; -import lbryuri from "lbryuri.js"; -import CardMedia from "component/cardMedia"; -import Link from "component/link"; -import { TruncatedText } from "component/common"; -import Icon from "component/icon"; -import FilePrice from "component/filePrice"; -import UriIndicator from "component/uriIndicator"; -import NsfwOverlay from "component/nsfwOverlay"; -import TruncatedMarkdown from "component/truncatedMarkdown"; -import * as icons from "constants/icons"; +import React from 'react'; +import lbryuri from 'lbryuri.js'; +import CardMedia from 'component/cardMedia'; +import Link from 'component/link'; +import { TruncatedText } from 'component/common'; +import Icon from 'component/icon'; +import FilePrice from 'component/filePrice'; +import UriIndicator from 'component/uriIndicator'; +import NsfwOverlay from 'component/nsfwOverlay'; +import TruncatedMarkdown from 'component/truncatedMarkdown'; +import * as icons from 'constants/icons'; class FileCard extends React.PureComponent { constructor(props) { @@ -59,35 +59,27 @@ class FileCard extends React.PureComponent { const uri = lbryuri.normalize(this.props.uri); const title = metadata && metadata.title ? metadata.title : uri; - const thumbnail = - metadata && metadata.thumbnail ? metadata.thumbnail : null; + const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - const isRewardContent = - claim && rewardedContentClaimIds.includes(claim.claim_id); + const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); - let description = ""; + let description = ''; if (isResolvingUri && !claim) { - description = __("Loading..."); + description = __('Loading...'); } else if (metadata && metadata.description) { description = metadata.description; } else if (claim === null) { - description = __("This address contains no content."); + description = __('This address contains no content.'); } return (
- navigate("/show", { uri })} - className="card__link" - > + navigate('/show', { uri })} className="card__link">
@@ -95,12 +87,11 @@ class FileCard extends React.PureComponent {
- {" "} - {isRewardContent && }{" "} + {isRewardContent && }{' '} {fileInfo && } - +
diff --git a/src/renderer/component/fileDetails/index.js b/src/renderer/component/fileDetails/index.js index fb29fe042..9b877dec9 100644 --- a/src/renderer/component/fileDetails/index.js +++ b/src/renderer/component/fileDetails/index.js @@ -1,13 +1,13 @@ -import React from "react"; -import { connect } from "react-redux"; +import React from 'react'; +import { connect } from 'react-redux'; import { makeSelectClaimForUri, makeSelectContentTypeForUri, makeSelectMetadataForUri, -} from "redux/selectors/claims"; -import FileDetails from "./view"; -import { doOpenFileInFolder } from "redux/actions/file_info"; -import { makeSelectFileInfoForUri } from "redux/selectors/file_info"; +} from 'redux/selectors/claims'; +import FileDetails from './view'; +import { doOpenFileInFolder } from 'redux/actions/file_info'; +import { makeSelectFileInfoForUri } from 'redux/selectors/file_info'; const select = (state, props) => ({ claim: makeSelectClaimForUri(props.uri)(state), diff --git a/src/renderer/component/fileDetails/view.jsx b/src/renderer/component/fileDetails/view.jsx index 55afed97d..9b6753484 100644 --- a/src/renderer/component/fileDetails/view.jsx +++ b/src/renderer/component/fileDetails/view.jsx @@ -1,27 +1,20 @@ -import React from "react"; -import ReactMarkdown from "react-markdown"; -import lbry from "lbry.js"; -import FileActions from "component/fileActions"; -import Link from "component/link"; -import DateTime from "component/dateTime"; +import React from 'react'; +import ReactMarkdown from 'react-markdown'; +import lbry from 'lbry.js'; +import FileActions from 'component/fileActions'; +import Link from 'component/link'; +import DateTime from 'component/dateTime'; -const path = require("path"); +const path = require('path'); class FileDetails extends React.PureComponent { render() { - const { - claim, - contentType, - fileInfo, - metadata, - openFolder, - uri, - } = this.props; + const { claim, contentType, fileInfo, metadata, openFolder, uri } = this.props; if (!claim || !metadata) { return (
- {__("Empty claim or metadata info.")} + {__('Empty claim or metadata info.')}
); } @@ -29,9 +22,7 @@ class FileDetails extends React.PureComponent { const { description, language, license } = metadata; const mediaType = lbry.getMediaType(contentType); - const downloadPath = fileInfo - ? path.normalize(fileInfo.download_path) - : null; + const downloadPath = fileInfo ? path.normalize(fileInfo.download_path) : null; return (
@@ -40,33 +31,31 @@ class FileDetails extends React.PureComponent {
- + - + - + {downloadPath && ( - + )} diff --git a/src/renderer/component/fileDownloadLink/index.js b/src/renderer/component/fileDownloadLink/index.js index e35dc5b94..4b5ad978c 100644 --- a/src/renderer/component/fileDownloadLink/index.js +++ b/src/renderer/component/fileDownloadLink/index.js @@ -1,20 +1,20 @@ -import React from "react"; -import { connect } from "react-redux"; +import React from 'react'; +import { connect } from 'react-redux'; import { makeSelectFileInfoForUri, makeSelectDownloadingForUri, makeSelectLoadingForUri, -} from "redux/selectors/file_info"; -import { makeSelectCostInfoForUri } from "redux/selectors/cost_info"; -import { doFetchAvailability } from "redux/actions/availability"; -import { doOpenFileInShell } from "redux/actions/file_info"; -import { doPurchaseUri, doStartDownload } from "redux/actions/content"; -import { setVideoPause } from "redux/actions/video"; -import FileDownloadLink from "./view"; +} from 'redux/selectors/file_info'; +import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info'; +import { doFetchAvailability } from 'redux/actions/availability'; +import { doOpenFileInShell } from 'redux/actions/file_info'; +import { doPurchaseUri, doStartDownload } from 'redux/actions/content'; +import { setVideoPause } from 'redux/actions/video'; +import FileDownloadLink from './view'; const select = (state, props) => ({ fileInfo: makeSelectFileInfoForUri(props.uri)(state), - /*availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix*/ + /* availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix */ downloading: makeSelectDownloadingForUri(props.uri)(state), costInfo: makeSelectCostInfoForUri(props.uri)(state), loading: makeSelectLoadingForUri(props.uri)(state), diff --git a/src/renderer/component/fileDownloadLink/view.jsx b/src/renderer/component/fileDownloadLink/view.jsx index 0619460dd..2c4aba0dc 100644 --- a/src/renderer/component/fileDownloadLink/view.jsx +++ b/src/renderer/component/fileDownloadLink/view.jsx @@ -1,6 +1,6 @@ -import React from "react"; -import { Icon, BusyMessage } from "component/common"; -import Link from "component/link"; +import React from 'react'; +import { Icon, BusyMessage } from 'component/common'; +import Link from 'component/link'; class FileDownloadLink extends React.PureComponent { componentWillMount() { @@ -55,9 +55,7 @@ class FileDownloadLink extends React.PureComponent { fileInfo && fileInfo.written_bytes ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0, - label = fileInfo - ? progress.toFixed(0) + __("% complete") - : __("Connecting..."), + label = fileInfo ? progress.toFixed(0) + __('% complete') : __('Connecting...'), labelWithIcon = ( @@ -69,7 +67,7 @@ class FileDownloadLink extends React.PureComponent {
{labelWithIcon}
@@ -78,24 +76,23 @@ class FileDownloadLink extends React.PureComponent { ); } else if (fileInfo === null && !downloading) { if (!costInfo) { - return ; - } else { - return ( - { - purchaseUri(uri); - }} - /> - ); + return ; } + return ( + { + purchaseUri(uri); + }} + /> + ); } else if (fileInfo && fileInfo.download_path) { return ( ({}); diff --git a/src/renderer/component/fileList/view.jsx b/src/renderer/component/fileList/view.jsx index 10dd2d2d7..ba1ba296c 100644 --- a/src/renderer/component/fileList/view.jsx +++ b/src/renderer/component/fileList/view.jsx @@ -1,23 +1,23 @@ -import React from "react"; -import lbryuri from "lbryuri.js"; -import FormField from "component/formField"; -import FileTile from "component/fileTile"; -import { BusyMessage } from "component/common.js"; +import React from 'react'; +import lbryuri from 'lbryuri.js'; +import FormField from 'component/formField'; +import FileTile from 'component/fileTile'; +import { BusyMessage } from 'component/common.js'; class FileList extends React.PureComponent { constructor(props) { super(props); this.state = { - sortBy: "date", + sortBy: 'date', }; this._sortFunctions = { - date: function(fileInfos) { + date(fileInfos) { return fileInfos.slice().reverse(); }, - title: function(fileInfos) { - return fileInfos.slice().sort(function(fileInfo1, fileInfo2) { + title(fileInfos) { + return fileInfos.slice().sort((fileInfo1, fileInfo2) => { const title1 = fileInfo1.value ? fileInfo1.value.stream.metadata.title.toLowerCase() : fileInfo1.name; @@ -28,25 +28,21 @@ class FileList extends React.PureComponent { return -1; } else if (title1 > title2) { return 1; - } else { - return 0; } + return 0; }); }, - filename: function(fileInfos) { - return fileInfos - .slice() - .sort(function({ file_name: fileName1 }, { file_name: fileName2 }) { - const fileName1Lower = fileName1.toLowerCase(); - const fileName2Lower = fileName2.toLowerCase(); - if (fileName1Lower < fileName2Lower) { - return -1; - } else if (fileName2Lower > fileName1Lower) { - return 1; - } else { - return 0; - } - }); + filename(fileInfos) { + return fileInfos.slice().sort(({ file_name: fileName1 }, { file_name: fileName2 }) => { + const fileName1Lower = fileName1.toLowerCase(); + const fileName2Lower = fileName2.toLowerCase(); + if (fileName1Lower < fileName2Lower) { + return -1; + } else if (fileName2Lower > fileName1Lower) { + return 1; + } + return 0; + }); }, }; } @@ -54,9 +50,8 @@ class FileList extends React.PureComponent { getChannelSignature(fileInfo) { if (fileInfo.value) { return fileInfo.value.publisherSignature.certificateId; - } else { - return fileInfo.metadata.publisherSignature.certificateId; } + return fileInfo.metadata.publisherSignature.certificateId; } handleSortChanged(event) { @@ -71,7 +66,7 @@ class FileList extends React.PureComponent { const content = []; this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => { - let uriParams = {}; + const uriParams = {}; if (fileInfo.channel_name) { uriParams.channelName = fileInfo.channel_name; @@ -89,7 +84,7 @@ class FileList extends React.PureComponent { uri={uri} showPrice={false} showLocal={false} - showActions={true} + showActions showEmpty={this.props.fileTileShowEmpty} /> ); @@ -98,10 +93,10 @@ class FileList extends React.PureComponent {
{fetching && } - {__("Sort by")}{" "} + {__('Sort by')}{' '} - - + + {content} diff --git a/src/renderer/component/fileListSearch/index.js b/src/renderer/component/fileListSearch/index.js index cd6e087f6..ec06231af 100644 --- a/src/renderer/component/fileListSearch/index.js +++ b/src/renderer/component/fileListSearch/index.js @@ -1,11 +1,8 @@ -import React from "react"; -import { connect } from "react-redux"; -import { doSearch } from "redux/actions/search"; -import { - selectIsSearching, - makeSelectSearchUris, -} from "redux/selectors/search"; -import FileListSearch from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { doSearch } from 'redux/actions/search'; +import { selectIsSearching, makeSelectSearchUris } from 'redux/selectors/search'; +import FileListSearch from './view'; const select = (state, props) => ({ isSearching: selectIsSearching(state), diff --git a/src/renderer/component/fileListSearch/view.jsx b/src/renderer/component/fileListSearch/view.jsx index 00a759d5e..afe5d4f36 100644 --- a/src/renderer/component/fileListSearch/view.jsx +++ b/src/renderer/component/fileListSearch/view.jsx @@ -1,9 +1,9 @@ -import React from "react"; -import FileTile from "component/fileTile"; -import ChannelTile from "component/channelTile"; -import Link from "component/link"; -import { BusyMessage } from "component/common.js"; -import lbryuri from "lbryuri"; +import React from 'react'; +import FileTile from 'component/fileTile'; +import ChannelTile from 'component/channelTile'; +import Link from 'component/link'; +import { BusyMessage } from 'component/common.js'; +import lbryuri from 'lbryuri'; const SearchNoResults = props => { const { query } = props; @@ -11,8 +11,8 @@ const SearchNoResults = props => { return (
- {(__("No one has checked anything in for %s yet."), query)}{" "} - + {(__('No one has checked anything in for %s yet.'), query)}{' '} +
); @@ -38,18 +38,14 @@ class FileListSearch extends React.PureComponent { return (
- {isSearching && - !uris && ( - - )} + {isSearching && !uris && } - {isSearching && - uris && } + {isSearching && uris && } {uris && uris.length ? uris.map( uri => - lbryuri.parse(uri).name[0] === "@" ? ( + lbryuri.parse(uri).name[0] === '@' ? ( ) : ( diff --git a/src/renderer/component/filePrice/index.js b/src/renderer/component/filePrice/index.js index 7460b0a4e..93eaee79d 100644 --- a/src/renderer/component/filePrice/index.js +++ b/src/renderer/component/filePrice/index.js @@ -1,12 +1,12 @@ -import React from "react"; -import { connect } from "react-redux"; -import { doFetchCostInfoForUri } from "redux/actions/cost_info"; +import React from 'react'; +import { connect } from 'react-redux'; +import { doFetchCostInfoForUri } from 'redux/actions/cost_info'; import { makeSelectCostInfoForUri, makeSelectFetchingCostInfoForUri, -} from "redux/selectors/cost_info"; -import { makeSelectClaimForUri } from "redux/selectors/claims"; -import FilePrice from "./view"; +} from 'redux/selectors/cost_info'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; +import FilePrice from './view'; const select = (state, props) => ({ costInfo: makeSelectCostInfoForUri(props.uri)(state), diff --git a/src/renderer/component/filePrice/view.jsx b/src/renderer/component/filePrice/view.jsx index a2b5067df..5a5947f14 100644 --- a/src/renderer/component/filePrice/view.jsx +++ b/src/renderer/component/filePrice/view.jsx @@ -1,5 +1,5 @@ -import React from "react"; -import { CreditAmount } from "component/common"; +import React from 'react'; +import { CreditAmount } from 'component/common'; class FilePrice extends React.PureComponent { componentWillMount() { @@ -19,14 +19,12 @@ class FilePrice extends React.PureComponent { } render() { - const { costInfo, look = "indicator", showFullPrice = false } = this.props; + const { costInfo, look = 'indicator', showFullPrice = false } = this.props; const isEstimate = costInfo ? !costInfo.includesData : null; if (!costInfo) { - return ( - ??? - ); + return ???; } return ( @@ -34,7 +32,7 @@ class FilePrice extends React.PureComponent { label={false} amount={costInfo.cost} isEstimate={isEstimate} - showFree={true} + showFree showFullPrice={showFullPrice} /> ); diff --git a/src/renderer/component/fileTile/index.js b/src/renderer/component/fileTile/index.js index f352b1633..601a94a7e 100644 --- a/src/renderer/component/fileTile/index.js +++ b/src/renderer/component/fileTile/index.js @@ -1,18 +1,12 @@ -import React from "react"; -import { connect } from "react-redux"; -import { doNavigate } from "redux/actions/navigation"; -import { doResolveUri } from "redux/actions/content"; -import { - makeSelectClaimForUri, - makeSelectMetadataForUri, -} from "redux/selectors/claims"; -import { makeSelectFileInfoForUri } from "redux/selectors/file_info"; -import { selectShowNsfw } from "redux/selectors/settings"; -import { - makeSelectIsUriResolving, - selectRewardContentClaimIds, -} from "redux/selectors/content"; -import FileTile from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { doNavigate } from 'redux/actions/navigation'; +import { doResolveUri } from 'redux/actions/content'; +import { makeSelectClaimForUri, makeSelectMetadataForUri } from 'redux/selectors/claims'; +import { makeSelectFileInfoForUri } from 'redux/selectors/file_info'; +import { selectShowNsfw } from 'redux/selectors/settings'; +import { makeSelectIsUriResolving, selectRewardContentClaimIds } from 'redux/selectors/content'; +import FileTile from './view'; const select = (state, props) => ({ claim: makeSelectClaimForUri(props.uri)(state), diff --git a/src/renderer/component/fileTile/view.jsx b/src/renderer/component/fileTile/view.jsx index fbf3c9110..231cfc293 100644 --- a/src/renderer/component/fileTile/view.jsx +++ b/src/renderer/component/fileTile/view.jsx @@ -1,15 +1,15 @@ -import React from "react"; -import * as icons from "constants/icons"; -import lbryuri from "lbryuri.js"; -import CardMedia from "component/cardMedia"; -import { TruncatedText } from "component/common.js"; -import FilePrice from "component/filePrice"; -import NsfwOverlay from "component/nsfwOverlay"; -import Icon from "component/icon"; +import React from 'react'; +import * as icons from 'constants/icons'; +import lbryuri from 'lbryuri.js'; +import CardMedia from 'component/cardMedia'; +import { TruncatedText } from 'component/common.js'; +import FilePrice from 'component/filePrice'; +import NsfwOverlay from 'component/nsfwOverlay'; +import Icon from 'component/icon'; class FileTile extends React.PureComponent { - static SHOW_EMPTY_PUBLISH = "publish"; - static SHOW_EMPTY_PENDING = "pending"; + static SHOW_EMPTY_PUBLISH = 'publish'; + static SHOW_EMPTY_PENDING = 'pending'; static defaultProps = { showPrice: true, @@ -36,11 +36,7 @@ class FileTile extends React.PureComponent { } handleMouseOver() { - if ( - this.props.obscureNsfw && - this.props.metadata && - this.props.metadata.nsfw - ) { + if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { this.setState({ showNsfwHelp: true, }); @@ -73,59 +69,49 @@ class FileTile extends React.PureComponent { const isClaimed = !!claim; const isClaimable = lbryuri.isClaimable(uri); const title = - isClaimed && metadata && metadata.title - ? metadata.title - : lbryuri.parse(uri).contentName; - const thumbnail = - metadata && metadata.thumbnail ? metadata.thumbnail : null; + isClaimed && metadata && metadata.title ? metadata.title : lbryuri.parse(uri).contentName; + const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - const isRewardContent = - claim && rewardedContentClaimIds.includes(claim.claim_id); + const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); - let onClick = () => navigate("/show", { uri }); + let onClick = () => navigate('/show', { uri }); - let name = ""; + let name = ''; if (claim) { name = claim.name; } - let description = ""; + let description = ''; if (isClaimed) { description = metadata && metadata.description; } else if (isResolvingUri) { - description = __("Loading..."); + description = __('Loading...'); } else if (showEmpty === FileTile.SHOW_EMPTY_PUBLISH) { - onClick = () => navigate("/publish", {}); + onClick = () => navigate('/publish', {}); description = ( - {__("This location is unused.")}{" "} - {isClaimable && ( - {__("Put something here!")} - )} + {__('This location is unused.')}{' '} + {isClaimable && {__('Put something here!')}} ); } else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) { - description = ( - - {__("This file is pending confirmation.")} - - ); + description = {__('This file is pending confirmation.')}; } return (
-
+
- {showPrice && }{" "} - {isRewardContent && }{" "} + {showPrice && }{' '} + {isRewardContent && }{' '} {showLocal && fileInfo && }

@@ -134,9 +120,7 @@ class FileTile extends React.PureComponent {

{description && (
- - {description} - + {description}
)}
diff --git a/src/renderer/component/form.js b/src/renderer/component/form.js index 820ae251c..135df952d 100644 --- a/src/renderer/component/form.js +++ b/src/renderer/component/form.js @@ -1,14 +1,14 @@ -import React from "react"; -import PropTypes from "prop-types"; -import FormField from "component/formField"; -import { Icon } from "component/common.js"; +import React from 'react'; +import PropTypes from 'prop-types'; +import FormField from 'component/formField'; +import { Icon } from 'component/common.js'; let formFieldCounter = 0; -export const formFieldNestedLabelTypes = ["radio", "checkbox"]; +export const formFieldNestedLabelTypes = ['radio', 'checkbox']; export function formFieldId() { - return "form-field-" + ++formFieldCounter; + return `form-field-${++formFieldCounter}`; } export class Form extends React.PureComponent { @@ -26,11 +26,7 @@ export class Form extends React.PureComponent { } render() { - return ( -
this.handleSubmit(event)}> - {this.props.children} - - ); + return
this.handleSubmit(event)}>{this.props.children}; } } @@ -50,7 +46,7 @@ export class FormRow extends React.PureComponent { this._field = null; - this._fieldRequiredText = __("This field is required"); + this._fieldRequiredText = __('This field is required'); this.state = this.getStateFromProps(props); } @@ -63,11 +59,9 @@ export class FormRow extends React.PureComponent { return { isError: !!props.errorMessage, errorMessage: - typeof props.errorMessage === "string" + typeof props.errorMessage === 'string' ? props.errorMessage - : props.errorMessage instanceof Error - ? props.errorMessage.toString() - : "", + : props.errorMessage instanceof Error ? props.errorMessage.toString() : '', }; } @@ -85,7 +79,7 @@ export class FormRow extends React.PureComponent { clearError(text) { this.setState({ isError: false, - errorMessage: "", + errorMessage: '', }); } @@ -116,9 +110,7 @@ export class FormRow extends React.PureComponent { render() { const fieldProps = Object.assign({}, this.props), elementId = formFieldId(), - renderLabelInFormField = formFieldNestedLabelTypes.includes( - this.props.type - ); + renderLabelInFormField = formFieldNestedLabelTypes.includes(this.props.type); if (!renderLabelInFormField) { delete fieldProps.label; @@ -128,28 +120,24 @@ export class FormRow extends React.PureComponent { delete fieldProps.isFocus; return ( -
+
{this.props.label && !renderLabelInFormField ? (
) : ( - "" + '' )} { @@ -163,12 +151,12 @@ export class FormRow extends React.PureComponent { {!this.state.isError && this.props.helper ? (
{this.props.helper}
) : ( - "" + '' )} {this.state.isError ? (
{this.state.errorMessage}
) : ( - "" + '' )}
); @@ -178,16 +166,14 @@ export class FormRow extends React.PureComponent { export const Submit = props => { const { title, label, icon, disabled } = props; - const className = - "button-block" + - " button-primary" + - " button-set-item" + - " button--submit" + - (disabled ? " disabled" : ""); + const className = `${'button-block' + + ' button-primary' + + ' button-set-item' + + ' button--submit'}${disabled ? ' disabled' : ''}`; const content = ( - {"icon" in props ? : null} + {'icon' in props ? : null} {label ? {label} : null} ); diff --git a/src/renderer/component/formField/index.js b/src/renderer/component/formField/index.js index ca01b3e99..5977b6b2c 100644 --- a/src/renderer/component/formField/index.js +++ b/src/renderer/component/formField/index.js @@ -1,5 +1,5 @@ -import React from "react"; -import { connect } from "react-redux"; -import FormField from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import FormField from './view'; export default connect(null, null, null, { withRef: true })(FormField); diff --git a/src/renderer/component/formField/view.jsx b/src/renderer/component/formField/view.jsx index bb8cc5024..960b08c17 100644 --- a/src/renderer/component/formField/view.jsx +++ b/src/renderer/component/formField/view.jsx @@ -1,11 +1,11 @@ -import React from "react"; -import PropTypes from "prop-types"; -import FileSelector from "component/file-selector.js"; -import SimpleMDE from "react-simplemde-editor"; -import { formFieldNestedLabelTypes, formFieldId } from "../form"; -import style from "react-simplemde-editor/dist/simplemde.min.css"; +import React from 'react'; +import PropTypes from 'prop-types'; +import FileSelector from 'component/file-selector.js'; +import SimpleMDE from 'react-simplemde-editor'; +import { formFieldNestedLabelTypes, formFieldId } from '../form'; +import style from 'react-simplemde-editor/dist/simplemde.min.css'; -const formFieldFileSelectorTypes = ["file", "directory"]; +const formFieldFileSelectorTypes = ['file', 'directory']; class FormField extends React.PureComponent { static propTypes = { @@ -14,10 +14,7 @@ class FormField extends React.PureComponent { postfix: PropTypes.string, hasError: PropTypes.bool, trim: PropTypes.bool, - regexp: PropTypes.oneOfType([ - PropTypes.instanceOf(RegExp), - PropTypes.string, - ]), + regexp: PropTypes.oneOfType([PropTypes.instanceOf(RegExp), PropTypes.string]), }; static defaultProps = { @@ -27,7 +24,7 @@ class FormField extends React.PureComponent { constructor(props) { super(props); - this._fieldRequiredText = __("This field is required"); + this._fieldRequiredText = __('This field is required'); this._type = null; this._element = null; this._extraElementProps = {}; @@ -39,22 +36,22 @@ class FormField extends React.PureComponent { } componentWillMount() { - if (["text", "number", "radio", "checkbox"].includes(this.props.type)) { - this._element = "input"; + if (['text', 'number', 'radio', 'checkbox'].includes(this.props.type)) { + this._element = 'input'; this._type = this.props.type; - } else if (this.props.type == "text-number") { - this._element = "input"; - this._type = "text"; - } else if (this.props.type == "SimpleMDE") { + } else if (this.props.type == 'text-number') { + this._element = 'input'; + this._type = 'text'; + } else if (this.props.type == 'SimpleMDE') { this._element = SimpleMDE; - this._type = "textarea"; + this._type = 'textarea'; this._extraElementProps.options = { placeholder: this.props.placeholder, - hideIcons: ["heading", "image", "fullscreen", "side-by-side"], + hideIcons: ['heading', 'image', 'fullscreen', 'side-by-side'], }; } else if (formFieldFileSelectorTypes.includes(this.props.type)) { - this._element = "input"; - this._type = "hidden"; + this._element = 'input'; + this._type = 'hidden'; } else { // Non field, e.g.
{__("Content-Type")}{__('Content-Type')} {mediaType}
{__("Language")}{__('Language')} {language}
{__("License")}{__('License')} {license}
{__("Downloaded to")}{__('Downloaded to')} - openFolder(downloadPath)}> - {downloadPath} - + openFolder(downloadPath)}>{downloadPath}