Setup ESLint and Prettier and apply changes to sources #891

Merged
IGassmann merged 22 commits from issue/763 into master 2017-12-27 21:22:51 +01:00
298 changed files with 6769 additions and 7403 deletions

34
.eslintrc.json Normal file
View file

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

1
.gitignore vendored
View file

@ -12,6 +12,7 @@
/lbry-app /lbry-app
/lbry-venv /lbry-venv
/static/daemon/lbrynet* /static/daemon/lbrynet*
/static/locales
/daemon/build /daemon/build
/daemon/venv /daemon/venv
/daemon/requirements.txt /daemon/requirements.txt

12
.lintstagedrc Normal file
View file

@ -0,0 +1,12 @@
{
"linters": {
"src/**/*.{js,jsx,scss,json}": [
"prettier --write",
"git add"
],
"src/**/*.{js,jsx}": [
"eslint --fix",
"git add"
]
}
}

5
.prettierrc.json Normal file
View file

@ -0,0 +1,5 @@
{
"trailingComma": "es5",
"printWidth": 100,
"singleQuote": true
}

View file

@ -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. 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) We use [electron-builder](https://github.com/electron-userland/electron-builder)
to create distributable packages. to create distributable packages.
@ -60,16 +60,16 @@ exit
python -m pip install -r build\requirements.txt python -m pip install -r build\requirements.txt
npm install -g yarn npm install -g yarn
yarn install 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\` 3. Download the lbry daemon and cli [binaries](https://github.com/lbryio/lbry/releases) and place them in `static\daemon`
### Building lbry-app
Run `yarn dist`
### Ongoing Development ### Ongoing Development
Run `yarn dev` 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. 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 ## Internationalization

View file

@ -26,7 +26,7 @@ dir static\daemon\ # verify that daemon binary is there
rm daemon.zip rm daemon.zip
# build electron app # build electron app
yarn dist yarn build
dir dist # verify that binary was built/named correctly dir dist # verify that binary was built/named correctly
python build\upload_assets.py python build\upload_assets.py

View file

@ -77,7 +77,7 @@ if [ "$FULL_BUILD" == "true" ]; then
security unlock-keychain -p ${KEYCHAIN_PASSWORD} osx-build.keychain security unlock-keychain -p ${KEYCHAIN_PASSWORD} osx-build.keychain
fi fi
yarn dist yarn build
# electron-build has a publish feature, but I had a hard time getting # 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 # it to reliably work and it also seemed difficult to configure. Not proud of

View file

@ -1,20 +1,21 @@
var extract = require("i18n-extract"); const extract = require("i18n-extract");
const fs = require("fs"); const fs = require("fs");
const path = require("path");
var dir = __dirname + "/../../dist/locales"; const outputDir = `${__dirname}/../static/locales`;
var path = dir + "/en.json"; const outputPath = `${outputDir}/en.json`;
if (!fs.existsSync(dir)) { if (!fs.existsSync(outputDir)) {
fs.mkdirSync(dir); fs.mkdirSync(outputDir);
} }
fs.writeFile(path, "{}", "utf8", function(err) { fs.writeFile(outputPath, "{}", "utf8", err => {
if (err) { if (err) {
return console.log(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: "__", marker: "__",
}); });
@ -22,21 +23,21 @@ fs.writeFile(path, "{}", "utf8", function(err) {
reports = reports.concat(extract.findMissing(enLocale, keys)); reports = reports.concat(extract.findMissing(enLocale, keys));
if (reports.length > 0) { if (reports.length > 0) {
fs.readFile(path, "utf8", function readFileCallback(err, data) { fs.readFile(outputPath, "utf8", (err, data) => {
if (err) { if (err) {
console.log(err); console.log(err);
} else { } else {
localeObj = JSON.parse(data); 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 // no need to care for other types than MISSING because starting file will always be empty
if (reports[i].type === "MISSING") { if (reports[i].type === "MISSING") {
localeObj[reports[i].key] = reports[i].key; localeObj[reports[i].key] = reports[i].key;
} }
} }
var json = JSON.stringify(localeObj, null, "\t"); //convert it back to json-string const json = JSON.stringify(localeObj, null, "\t"); // convert it back to json-string
fs.writeFile(path, json, "utf8", function callback(err) { fs.writeFile(outputPath, json, "utf8", err => {
if (err) { if (err) {
throw err; throw err;
} }

View file

@ -14,21 +14,23 @@
"name": "LBRY Inc.", "name": "LBRY Inc.",
"email": "hello@lbry.io" "email": "hello@lbry.io"
}, },
"main": "src/main/index.js",
"scripts": { "scripts": {
"extract-langs": "node src/renderer/extractLocals.js", "extract-langs": "node build/extractLocals.js",
"dev": "electron-webpack dev", "dev": "electron-webpack dev",
"compile": "electron-webpack && yarn extract-langs", "compile": "electron-webpack && yarn extract-langs",
"dist": "yarn compile && electron-builder", "build": "yarn compile && electron-builder build",
"dist:dir": "yarn dist -- --dir -c.compression=store -c.mac.identity=null",
"postinstall": "electron-builder install-app-deps", "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": [ "keywords": [
"lbry" "lbry"
], ],
"dependencies": { "dependencies": {
"amplitude-js": "^4.0.0", "amplitude-js": "^4.0.0",
"bluebird": "^3.5.1",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"electron-dl": "^1.6.0", "electron-dl": "^1.6.0",
"formik": "^0.10.4", "formik": "^0.10.4",
@ -64,18 +66,27 @@
"y18n": "^4.0.0" "y18n": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"babel-eslint": "^8.0.3",
"babel-plugin-module-resolver": "^3.0.0", "babel-plugin-module-resolver": "^3.0.0",
"babel-plugin-react-require": "^3.0.0", "babel-plugin-react-require": "^3.0.0",
"babel-polyfill": "^6.20.0", "babel-polyfill": "^6.20.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.18.0", "babel-preset-stage-2": "^6.18.0",
"bluebird": "^3.5.1",
"devtron": "^1.4.0", "devtron": "^1.4.0",
"electron": "^1.7.9", "electron": "^1.7.9",
"electron-builder": "^19.48.2", "electron-builder": "^19.49.0",
"electron-devtools-installer": "^2.2.1", "electron-devtools-installer": "^2.2.1",
"electron-webpack": "^1.11.0", "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-babel-webpack-plugin": "^1.1.0",
"flow-bin": "^0.61.0", "flow-bin": "^0.61.0",
"flow-typed": "^2.2.3", "flow-typed": "^2.2.3",
@ -98,12 +109,6 @@
"yarn": "^1.3" "yarn": "^1.3"
}, },
"license": "MIT", "license": "MIT",
"lint-staged": {
"src/**/*.{js,jsx}": [
"prettier --trailing-comma es5 --write",
"git add"
]
},
"lbrySettings": { "lbrySettings": {
"lbrynetDaemonVersion": "0.18.0", "lbrynetDaemonVersion": "0.18.0",
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-daemon-vDAEMONVER-OSNAME.zip" "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-daemon-vDAEMONVER-OSNAME.zip"

View file

@ -1,67 +1,39 @@
/* eslint-disable no-console */
// Module imports // Module imports
const { import Path from 'path';
app, import Url from 'url';
BrowserWindow, import Jayson from 'jayson';
ipcMain, import Semver from 'semver';
Menu, import Https from 'https';
Tray, import Keytar from 'keytar';
globalShortcut, import ChildProcess from 'child_process';
} = require("electron"); import Assert from 'assert';
const path = require("path"); import { app, BrowserWindow, globalShortcut, ipcMain, Menu, Tray } from 'electron';
const url = require("url"); import mainMenu from './menu/mainMenu';
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");
const localVersion = app.getVersion(); const localVersion = app.getVersion();
const setMenu = require("./menu/main-menu.js"); export { contextMenu as Default } from './menu/contextMenu';
export const contextMenu = require("./menu/context-menu");
// Debug configs // Debug configs
const isDevelopment = process.env.NODE_ENV === "development"; 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);
}
}
// Misc constants // Misc constants
const LATEST_RELEASE_API_URL = const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest';
"https://api.github.com/repos/lbryio/lbry-app/releases/latest"; const DAEMON_PATH = process.env.LBRY_DAEMON || Path.join(__static, 'daemon/lbrynet-daemon');
const DAEMON_PATH =
process.env.LBRY_DAEMON || path.join(__static, "daemon/lbrynet-daemon");
const rendererUrl = isDevelopment const rendererUrl = isDevelopment
? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}` ? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`
: `file://${__dirname}/index.html`; : `file://${__dirname}/index.html`;
let client = jayson.client.http({ const client = Jayson.client.http({
host: "localhost", host: 'localhost',
port: 5279, port: 5279,
path: "/", path: '/',
timeout: 1000, timeout: 1000,
}); });
// Keep a global reference of the window object, if you don't, the window will // 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. // be closed automatically when the JavaScript object is garbage collected.
let win; let rendererWindow;
// Also keep the daemon subprocess alive // Also keep the daemon subprocess alive
let daemonSubprocess; let daemonSubprocess;
@ -94,11 +66,10 @@ function processRequestedUri(uri) {
// lbry://channel/#claimid. We remove the slash here as well. // lbry://channel/#claimid. We remove the slash here as well.
// On Linux and Mac, we just return the URI as given. // On Linux and Mac, we just return the URI as given.
if (process.platform === "win32") { if (process.platform === 'win32') {
return uri.replace(/\/$/, "").replace("/#", "#"); return uri.replace(/\/$/, '').replace('/#', '#');
} else {
return uri;
} }
return uri;
} }
/* /*
@ -109,226 +80,21 @@ function processRequestedUri(uri) {
function openItem(fullPath) { function openItem(fullPath) {
const subprocOptions = { const subprocOptions = {
detached: true, detached: true,
stdio: "ignore", stdio: 'ignore',
}; };
let child; let child;
if (process.platform === "darwin") { if (process.platform === 'darwin') {
child = child_process.spawn("open", [fullPath], subprocOptions); child = ChildProcess.spawn('open', [fullPath], subprocOptions);
} else if (process.platform === "linux") { } else if (process.platform === 'linux') {
child = child_process.spawn("xdg-open", [fullPath], subprocOptions); child = ChildProcess.spawn('xdg-open', [fullPath], subprocOptions);
} else if (process.platform === "win32") { } else if (process.platform === 'win32') {
child = child_process.spawn( child = ChildProcess.spawn(fullPath, Object.assign({}, subprocOptions, { shell: true }));
fullPath,
Object.assign({}, subprocOptions, { shell: true })
);
} }
// Causes child process reference to be garbage collected, allowing main process to exit // Causes child process reference to be garbage collected, allowing main process to exit
child.unref(); 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. * Quits by first killing the daemon, the calling quitting for real.
*/ */
@ -337,6 +103,155 @@ export function safeQuit() {
app.quit(); 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 * 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, * interface or through app.quit()), we abort the quit, try to shut down the daemon,
@ -347,16 +262,52 @@ function quitNow() {
safeQuit(); 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 => { const isSecondaryInstance = app.makeSingleInstance(argv => {
if (argv.length >= 2) { if (argv.length >= 2) {
handleOpenUriRequested(argv[1]); // This will handle restoring and focusing the window handleOpenUriRequested(argv[1]); // This will handle restoring and focusing the window
} else if (win) { } else if (rendererWindow) {
if (win.isMinimized()) { if (rendererWindow.isMinimized()) {
win.restore(); rendererWindow.restore();
} else if (!win.isVisible()) { } else if (!rendererWindow.isVisible()) {
win.show(); rendererWindow.show();
} }
win.focus(); rendererWindow.focus();
} }
}); });
@ -368,126 +319,44 @@ if (isSecondaryInstance) {
function launchDaemonIfNotRunning() { function launchDaemonIfNotRunning() {
// Check if the daemon is already running. If we get // Check if the daemon is already running. If we get
// an error its because its not running // an error its because its not running
console.log("Checking for lbrynet daemon"); console.log('Checking for lbrynet daemon');
client.request("status", [], function(err, res) { client.request('status', [], err => {
if (err) { if (err) {
console.log("lbrynet daemon needs to be launched"); console.log('lbrynet daemon needs to be launched');
launchDaemon(); launchDaemon();
} else { } else {
console.log("lbrynet daemon is already running"); console.log('lbrynet daemon is already running');
} }
}); });
} }
/* // Taken from webtorrent-desktop
* Last resort for killing unresponsive daemon instances. function checkLinuxTraySupport(cb) {
* Looks for any processes called "lbrynet-daemon" and // Check that we're on Ubuntu (or another debian system) and that we have
* tries to force kill them. // libappindicator1.
*/ ChildProcess.exec('dpkg --get-selections libappindicator1', (err, stdout) => {
function forceKillAllDaemonsAndQuit() { if (err) return cb(err);
console.log( // Unfortunately there's no cleaner way, as far as I can tell, to check
"Attempting to force kill any running lbrynet-daemon instances..." // whether a debian package is installed:
); if (stdout.endsWith('\tinstall\n')) {
return cb(null);
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();
} }
return cb(new Error('debian package not installed'));
}); });
}
}
}
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);
});
} else if (process.argv.length >= 2) {
handleOpenUriRequested(process.argv[1]);
} }
// When a quit is attempted, this is called. It attempts to shutdown the daemon, // When a quit is attempted, this is called. It attempts to shutdown the daemon,
// then calls quitNow() to quit for real. // then calls quitNow() to quit for real.
function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) { function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) {
function doShutdown() { function doShutdown() {
console.log("Shutting down daemon"); console.log('Shutting down daemon');
daemonStopRequested = true; daemonStopRequested = true;
client.request("daemon_stop", [], (err, res) => { client.request('daemon_stop', [], err => {
if (err) { if (err) {
console.log( console.log(`received error when stopping lbrynet-daemon. Error message: ${err.message}\n`);
`received error when stopping lbrynet-daemon. Error message: ${ console.log('You will need to manually kill the daemon.');
err.message
}\n`
);
console.log("You will need to manually kill the daemon.");
} else { } else {
console.log("Successfully stopped daemon via RPC call."); console.log('Successfully stopped daemon via RPC call.');
quitNow(); quitNow();
} }
}); });
@ -496,7 +365,7 @@ function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) {
if (daemonSubprocess) { if (daemonSubprocess) {
doShutdown(); doShutdown();
} else if (!evenIfNotStartedByApp) { } 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(); quitNow();
} else { } else {
doShutdown(); doShutdown();
@ -506,80 +375,131 @@ function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) {
// If not, we should wait until the daemon is closed before we start the install. // If not, we should wait until the daemon is closed before we start the install.
} }
// Taken from webtorrent-desktop if (isDevelopment) {
function checkLinuxTraySupport(cb) { import('devtron')
// Check that we're on Ubuntu (or another debian system) and that we have .then(({ install }) => {
// libappindicator1. install();
child_process.exec("dpkg --get-selections libappindicator1", function( console.log('Added Extension: Devtron');
err, })
stdout .catch(error => {
) { console.error(error);
if (err) return cb(err); });
// Unfortunately there's no cleaner way, as far as I can tell, to check import('electron-devtools-installer')
// whether a debian package is installed: .then(({ default: installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS }) => {
if (stdout.endsWith("\tinstall\n")) { app.on('ready', () => {
cb(null); [REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS].forEach(extension => {
} else { installExtension(extension)
cb(new Error("debian package not installed")); .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.setAsDefaultProtocolClient('lbry');
app.on("quit", () => {
console.log("Launching upgrade installer at", installerPath); 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 // 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. // we'll only get here if we're fully prepared and quitting for real.
openItem(installerPath); openItem(installerPath);
}); });
if (win) { if (rendererWindow) {
win.loadURL(`file://${__static}/upgrade.html`); rendererWindow.loadURL(`file://${__static}/upgrade.html`);
} }
shutdownDaemonAndQuit(true); shutdownDaemonAndQuit(true);
// wait for daemon to shut down before upgrading // wait for daemon to shut down before upgrading
// what to do if no shutdown in a long time? // what to do if no shutdown in a long time?
console.log("Update downloaded to", installerPath); console.log('Update downloaded to', installerPath);
console.log( 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) { function formatRc(ver) {
// Adds dash if needed to make RC suffix semver friendly // 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 = { const opts = {
headers: { headers: {
"User-Agent": `LBRY/${localVersion}`, 'User-Agent': `LBRY/${localVersion}`,
}, },
}; };
const req = https.get( const req = Https.get(Object.assign(opts, Url.parse(LATEST_RELEASE_API_URL)), res => {
Object.assign(opts, url.parse(LATEST_RELEASE_API_URL)), res.on('data', data => {
res => {
res.on("data", data => {
result += data; result += data;
}); });
res.on("end", () => { res.on('end', () => {
const tagName = JSON.parse(result).tag_name; const tagName = JSON.parse(result).tag_name;
const [_, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/); const [, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/);
if (!remoteVersion) { if (!remoteVersion) {
if (win) { if (rendererWindow) {
win.webContents.send("version-info-received", null); rendererWindow.webContents.send('version-info-received', null);
} }
} else { } else {
const upgradeAvailable = semver.gt( const upgradeAvailable = Semver.gt(formatRc(remoteVersion), formatRc(localVersion));
formatRc(remoteVersion), if (rendererWindow) {
formatRc(localVersion) rendererWindow.webContents.send('version-info-received', {
);
if (win) {
win.webContents.send("version-info-received", {
remoteVersion, remoteVersion,
localVersion, localVersion,
upgradeAvailable, upgradeAvailable,
@ -587,30 +507,22 @@ ipcMain.on("version-info-requested", () => {
} }
} }
}); });
} });
);
req.on("error", err => { req.on('error', err => {
console.log("Failed to get current version from GitHub. Error:", err); console.log('Failed to get current version from GitHub. Error:', err);
if (win) { if (rendererWindow) {
win.webContents.send("version-info-received", null); rendererWindow.webContents.send('version-info-received', null);
} }
}); });
}); });
ipcMain.on("get-auth-token", event => { ipcMain.on('get-auth-token', event => {
keytar.getPassword("LBRY", "auth_token").then(token => { Keytar.getPassword('LBRY', 'auth_token').then(token => {
event.sender.send( event.sender.send('auth-token-response', token ? token.toString().trim() : null);
"auth-token-response",
token ? token.toString().trim() : null
);
}); });
}); });
ipcMain.on("set-auth-token", (event, token) => { ipcMain.on('set-auth-token', (event, token) => {
keytar.setPassword( Keytar.setPassword('LBRY', 'auth_token', token ? token.toString().trim() : null);
"LBRY",
"auth_token",
token ? token.toString().trim() : null
);
}); });

View file

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

View file

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

View file

@ -1,5 +1,5 @@
const { app, shell, Menu } = require('electron'); import { app, Menu, shell } from 'electron';
const { safeQuit } = require('../index.js'); import { safeQuit } from '../index';
const baseTemplate = [ const baseTemplate = [
{ {
@ -7,10 +7,10 @@ const baseTemplate = [
submenu: [ submenu: [
{ {
label: 'Quit', label: 'Quit',
accelerator: "CommandOrControl+Q", accelerator: 'CommandOrControl+Q',
click: () => safeQuit(), click: () => safeQuit(),
}, },
] ],
}, },
{ {
label: 'Edit', label: 'Edit',
@ -36,32 +36,32 @@ const baseTemplate = [
{ {
role: 'selectall', role: 'selectall',
}, },
] ],
}, },
{ {
label: 'View', label: 'View',
submenu: [ submenu: [
{ {
role: 'reload' role: 'reload',
}, },
{ {
label: 'Developer', label: 'Developer',
submenu: [ submenu: [
{ {
role: 'forcereload' role: 'forcereload',
}, },
{ {
role: 'toggledevtools' role: 'toggledevtools',
}, },
] ],
}, },
{ {
type: 'separator' type: 'separator',
}, },
{ {
role: 'togglefullscreen' role: 'togglefullscreen',
} },
] ],
}, },
{ {
role: 'help', role: 'help',
@ -72,34 +72,34 @@ const baseTemplate = [
if (focusedWindow) { if (focusedWindow) {
focusedWindow.webContents.send('open-menu', '/help'); focusedWindow.webContents.send('open-menu', '/help');
} }
} },
}, },
{ {
label: 'Frequently Asked Questions', label: 'Frequently Asked Questions',
click(item, focusedWindow){ click() {
shell.openExternal('https://lbry.io/faq') shell.openExternal('https://lbry.io/faq');
} },
}, },
{ {
type: 'separator' type: 'separator',
}, },
{ {
label: 'Report Issue', label: 'Report Issue',
click(item, focusedWindow){ click() {
shell.openExternal('https://lbry.io/faq/contributing#report-a-bug'); shell.openExternal('https://lbry.io/faq/contributing#report-a-bug');
} },
}, },
{ {
type: 'separator' type: 'separator',
}, },
{ {
label: 'Developer API Guide', label: 'Developer API Guide',
click(item, focusedWindow){ click() {
shell.openExternal('https://lbry.io/quickstart') shell.openExternal('https://lbry.io/quickstart');
} },
},
],
}, },
]
}
]; ];
const macOSAppMenuTemplate = { const macOSAppMenuTemplate = {
@ -126,11 +126,13 @@ const macOSAppMenuTemplate = {
{ {
role: 'quit', role: 'quit',
}, },
] ],
}; };
module.exports = () => { export default () => {
let template = baseTemplate.slice(); const template = baseTemplate.slice();
(process.platform === 'darwin') && template.unshift(macOSAppMenuTemplate); if (process.platform === 'darwin') {
template.unshift(macOSAppMenuTemplate);
}
Menu.setApplicationMenu(Menu.buildFromTemplate(template)); Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}; };

View file

@ -1,29 +1,40 @@
import store from "store.js"; import store from 'store';
import { remote } from "electron"; import { remote } from 'electron';
import Path from 'path';
import y18n from 'y18n';
const env = process.env.NODE_ENV || "production"; const env = process.env.NODE_ENV || 'production';
const config = { const i18n = y18n({
...require(`./config/${env}`), directory: Path.join(remote.app.getAppPath(), '/../static/locales').replace(/\\/g, '\\\\'),
};
const i18n = require("y18n")({
directory: remote.app.getAppPath() + "/locales",
updateFiles: false, updateFiles: false,
locale: "en", locale: 'en',
}); });
const logs = []; const logs = [];
const app = { const app = {
env: env, env,
config: config, store,
store: store, i18n,
i18n: i18n, logs,
logs: logs, log(message) {
log: function(message) {
logs.push(message); logs.push(message);
}, },
}; };
window.__ = i18n.__; // Workaround for https://github.com/electron-userland/electron-webpack/issues/52
window.__n = i18n.__n; 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; global.app = app;
module.exports = app;
export default app;

View file

@ -1,6 +1,6 @@
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { doShowSnackBar } from "redux/actions/app"; import { doShowSnackBar } from 'redux/actions/app';
import Address from "./view"; import Address from './view';
export default connect(null, { export default connect(null, {
doShowSnackBar, doShowSnackBar,

View file

@ -1,8 +1,8 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { clipboard } from "electron"; import { clipboard } from 'electron';
import Link from "component/link"; import Link from 'component/link';
import classnames from "classnames"; import classnames from 'classnames';
export default class Address extends React.PureComponent { export default class Address extends React.PureComponent {
static propTypes = { static propTypes = {
@ -21,8 +21,8 @@ export default class Address extends React.PureComponent {
return ( return (
<div className="form-field form-field--address"> <div className="form-field form-field--address">
<input <input
className={classnames("input-copyable", { className={classnames('input-copyable', {
"input-copyable--with-copy-btn": showCopyButton, 'input-copyable--with-copy-btn': showCopyButton,
})} })}
type="text" type="text"
ref={input => { ref={input => {
@ -32,7 +32,7 @@ export default class Address extends React.PureComponent {
this._inputElem.select(); this._inputElem.select();
}} }}
readOnly="readonly" readOnly="readonly"
value={address || ""} value={address || ''}
/> />
{showCopyButton && ( {showCopyButton && (
<span className="header__item"> <span className="header__item">
@ -41,7 +41,7 @@ export default class Address extends React.PureComponent {
icon="clipboard" icon="clipboard"
onClick={() => { onClick={() => {
clipboard.writeText(address); clipboard.writeText(address);
doShowSnackBar({ message: __("Address copied") }); doShowSnackBar({ message: __('Address copied') });
}} }}
/> />
</span> </span>

View file

@ -1,14 +1,14 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { import {
selectPageTitle, selectPageTitle,
selectHistoryIndex, selectHistoryIndex,
selectActiveHistoryEntry, selectActiveHistoryEntry,
} from "redux/selectors/navigation"; } from 'redux/selectors/navigation';
import { selectUser } from "redux/selectors/user"; import { selectUser } from 'redux/selectors/user';
import { doAlertError } from "redux/actions/app"; import { doAlertError } from 'redux/actions/app';
import { doRecordScroll } from "redux/actions/navigation"; import { doRecordScroll } from 'redux/actions/navigation';
import App from "./view"; import App from './view';
const select = (state, props) => ({ const select = (state, props) => ({
pageTitle: selectPageTitle(state), pageTitle: selectPageTitle(state),

View file

@ -1,10 +1,10 @@
import React from "react"; import React from 'react';
import Router from "component/router/index"; import Router from 'component/router/index';
import Header from "component/header"; import Header from 'component/header';
import Theme from "component/theme"; import Theme from 'component/theme';
import ModalRouter from "modal/modalRouter"; import ModalRouter from 'modal/modalRouter';
import ReactModal from "react-modal"; import ReactModal from 'react-modal';
import throttle from "util/throttle"; import throttle from 'util/throttle';
class App extends React.PureComponent { class App extends React.PureComponent {
constructor() { constructor() {
@ -15,25 +15,25 @@ class App extends React.PureComponent {
componentWillMount() { componentWillMount() {
const { alertError } = this.props; const { alertError } = this.props;
document.addEventListener("unhandledError", event => { document.addEventListener('unhandledError', event => {
alertError(event.detail); alertError(event.detail);
}); });
} }
componentDidMount() { componentDidMount() {
const { recordScroll } = this.props; const { recordScroll } = this.props;
const mainContent = document.getElementById("main-content"); const mainContent = document.getElementById('main-content');
this.mainContent = mainContent; this.mainContent = mainContent;
const scrollListener = () => recordScroll(this.mainContent.scrollTop); 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() { componentWillUnmount() {
this.mainContent.removeEventListener("scroll", this.scrollListener); this.mainContent.removeEventListener('scroll', this.scrollListener);
} }
componentWillReceiveProps(props) { componentWillReceiveProps(props) {
@ -50,7 +50,7 @@ class App extends React.PureComponent {
} }
setTitleFromProps(props) { setTitleFromProps(props) {
window.document.title = props.pageTitle || "LBRY"; window.document.title = props.pageTitle || 'LBRY';
} }
render() { render() {

View file

@ -1,6 +1,6 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import CardMedia from "./view"; import CardMedia from './view';
const select = state => ({}); const select = state => ({});
const perform = dispatch => ({}); const perform = dispatch => ({});

View file

@ -1,18 +1,18 @@
import React from "react"; import React from 'react';
class CardMedia extends React.PureComponent { class CardMedia extends React.PureComponent {
static AUTO_THUMB_CLASSES = [ static AUTO_THUMB_CLASSES = [
"purple", 'purple',
"red", 'red',
"pink", 'pink',
"indigo", 'indigo',
"blue", 'blue',
"light-blue", 'light-blue',
"cyan", 'cyan',
"teal", 'teal',
"green", 'green',
"yellow", 'yellow',
"orange", 'orange',
]; ];
componentWillMount() { componentWillMount() {
@ -29,12 +29,7 @@ class CardMedia extends React.PureComponent {
const atClass = this.state.autoThumbClass; const atClass = this.state.autoThumbClass;
if (thumbnail) { if (thumbnail) {
return ( return <div className="card__media" style={{ backgroundImage: `url('${thumbnail}')` }} />;
<div
className="card__media"
style={{ backgroundImage: "url('" + thumbnail + "')" }}
/>
);
} }
return ( return (
@ -42,8 +37,8 @@ class CardMedia extends React.PureComponent {
<div className="card__autothumb__text"> <div className="card__autothumb__text">
{title && {title &&
title title
.replace(/\s+/g, "") .replace(/\s+/g, '')
.substring(0, Math.min(title.replace(" ", "").length, 5)) .substring(0, Math.min(title.replace(' ', '').length, 5))
.toUpperCase()} .toUpperCase()}
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { selectUserEmail } from "redux/selectors/user"; import { selectUserEmail } from 'redux/selectors/user';
import CardVerify from "./view"; import CardVerify from './view';
const select = state => ({ const select = state => ({
email: selectUserEmail(state), email: selectUserEmail(state),

View file

@ -1,6 +1,6 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import Link from "component/link"; import Link from 'component/link';
let scriptLoading = false; let scriptLoading = false;
let scriptLoaded = false; let scriptLoaded = false;
@ -48,8 +48,8 @@ class CardVerify extends React.Component {
scriptLoading = true; scriptLoading = true;
const script = document.createElement("script"); const script = document.createElement('script');
script.src = "https://checkout.stripe.com/checkout.js"; script.src = 'https://checkout.stripe.com/checkout.js';
script.async = 1; script.async = 1;
this.loadPromise = (() => { this.loadPromise = (() => {
@ -69,12 +69,8 @@ class CardVerify extends React.Component {
}; };
}); });
const wrappedPromise = new Promise((accept, cancel) => { const wrappedPromise = new Promise((accept, cancel) => {
promise.then( promise.then(() => (canceled ? cancel({ isCanceled: true }) : accept()));
() => (canceled ? cancel({ isCanceled: true }) : accept()) promise.catch(error => (canceled ? cancel({ isCanceled: true }) : cancel(error)));
);
promise.catch(
error => (canceled ? cancel({ isCanceled: true }) : cancel(error))
);
}); });
return { return {
@ -85,9 +81,7 @@ class CardVerify extends React.Component {
}; };
})(); })();
this.loadPromise.promise this.loadPromise.promise.then(this.onScriptLoaded).catch(this.onScriptError);
.then(this.onScriptLoaded)
.catch(this.onScriptError);
document.body.appendChild(script); document.body.appendChild(script);
} }
@ -119,7 +113,7 @@ class CardVerify extends React.Component {
}; };
onScriptError = (...args) => { onScriptError = (...args) => {
throw new Error("Unable to load credit validation script."); throw new Error('Unable to load credit validation script.');
}; };
onClosed = () => { onClosed = () => {
@ -139,10 +133,10 @@ class CardVerify extends React.Component {
CardVerify.stripeHandler.open({ CardVerify.stripeHandler.open({
allowRememberMe: false, allowRememberMe: false,
closed: this.onClosed, closed: this.onClosed,
description: __("Confirm Identity"), description: __('Confirm Identity'),
email: this.props.email, email: this.props.email,
locale: "auto", locale: 'auto',
panelLabel: "Verify", panelLabel: 'Verify',
token: this.props.token, token: this.props.token,
zipCode: true, zipCode: true,
}); });
@ -151,9 +145,7 @@ class CardVerify extends React.Component {
onClick = () => { onClick = () => {
if (scriptDidError) { if (scriptDidError) {
try { try {
throw new Error( throw new Error('Tried to call onClick, but StripeCheckout failed to load');
"Tried to call onClick, but StripeCheckout failed to load"
);
} catch (x) {} } catch (x) {}
} else if (CardVerify.stripeHandler) { } else if (CardVerify.stripeHandler) {
this.showStripeDialog(); this.showStripeDialog();
@ -168,9 +160,7 @@ class CardVerify extends React.Component {
button="alt" button="alt"
label={this.props.label} label={this.props.label}
icon="icon-lock" icon="icon-lock"
disabled={ disabled={this.props.disabled || this.state.open || this.hasPendingClick}
this.props.disabled || this.state.open || this.hasPendingClick
}
onClick={this.onClick.bind(this)} onClick={this.onClick.bind(this)}
/> />
); );

View file

@ -1,11 +1,11 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { makeSelectClaimForUri } from "redux/selectors/claims"; import { makeSelectClaimForUri } from 'redux/selectors/claims';
import { doNavigate } from "redux/actions/navigation"; import { doNavigate } from 'redux/actions/navigation';
import { doResolveUri } from "redux/actions/content"; import { doResolveUri } from 'redux/actions/content';
import { makeSelectTotalItemsForChannel } from "redux/selectors/content"; import { makeSelectTotalItemsForChannel } from 'redux/selectors/content';
import { makeSelectIsUriResolving } from "redux/selectors/content"; import { makeSelectIsUriResolving } from 'redux/selectors/content';
import ChannelTile from "./view"; import ChannelTile from './view';
const select = (state, props) => ({ const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),

View file

@ -1,6 +1,6 @@
import React from "react"; import React from 'react';
import CardMedia from "component/cardMedia"; import CardMedia from 'component/cardMedia';
import { TruncatedText, BusyMessage } from "component/common.js"; import { TruncatedText, BusyMessage } from 'component/common.js';
class ChannelTile extends React.PureComponent { class ChannelTile extends React.PureComponent {
componentDidMount() { componentDidMount() {
@ -26,12 +26,12 @@ class ChannelTile extends React.PureComponent {
channelId = claim.claim_id; channelId = claim.claim_id;
} }
let onClick = () => navigate("/show", { uri }); const onClick = () => navigate('/show', { uri });
return ( return (
<section className="file-tile card"> <section className="file-tile card">
<div onClick={onClick} className="card__link"> <div onClick={onClick} className="card__link">
<div className={"card__inner file-tile__row"}> <div className="card__inner file-tile__row">
{channelName && <CardMedia title={channelName} thumbnail={null} />} {channelName && <CardMedia title={channelName} thumbnail={null} />}
<div className="file-tile__content"> <div className="file-tile__content">
<div className="card__title-primary"> <div className="card__title-primary">
@ -40,19 +40,15 @@ class ChannelTile extends React.PureComponent {
</h3> </h3>
</div> </div>
<div className="card__content card__subtext"> <div className="card__content card__subtext">
{isResolvingUri && ( {isResolvingUri && <BusyMessage message={__('Resolving channel')} />}
<BusyMessage message={__("Resolving channel")} />
)}
{totalItems > 0 && ( {totalItems > 0 && (
<span> <span>
This is a channel with {totalItems}{" "} This is a channel with {totalItems} {totalItems === 1 ? ' item' : ' items'}{' '}
{totalItems === 1 ? " item" : " items"} inside of it. inside of it.
</span> </span>
)} )}
{!isResolvingUri && {!isResolvingUri &&
!totalItems && ( !totalItems && <span className="empty">This is an empty channel.</span>}
<span className="empty">This is an empty channel.</span>
)}
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,9 +1,9 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { formatCredits, formatFullPrice } from "util/formatCredits"; import { formatCredits, formatFullPrice } from 'util/formatCredits';
import lbry from "../lbry.js"; import lbry from '../lbry.js';
//component/icon.js // component/icon.js
export class Icon extends React.PureComponent { export class Icon extends React.PureComponent {
static propTypes = { static propTypes = {
icon: PropTypes.string.isRequired, icon: PropTypes.string.isRequired,
@ -13,12 +13,9 @@ export class Icon extends React.PureComponent {
render() { render() {
const { fixed, className } = this.props; const { fixed, className } = this.props;
const spanClassName = const spanClassName = `icon ${'fixed' in this.props ? 'icon-fixed-width ' : ''}${
"icon " + this.props.icon
("fixed" in this.props ? "icon-fixed-width " : "") + } ${this.props.className || ''}`;
this.props.icon +
" " +
(this.props.className || "");
return <span className={spanClassName} />; return <span className={spanClassName} />;
} }
} }
@ -34,10 +31,7 @@ export class TruncatedText extends React.PureComponent {
render() { render() {
return ( return (
<span <span className="truncated-text" style={{ WebkitLineClamp: this.props.lines }}>
className="truncated-text"
style={{ WebkitLineClamp: this.props.lines }}
>
{this.props.children} {this.props.children}
</span> </span>
); );
@ -73,14 +67,14 @@ export class CreditAmount extends React.PureComponent {
showFree: PropTypes.bool, showFree: PropTypes.bool,
showFullPrice: PropTypes.bool, showFullPrice: PropTypes.bool,
showPlus: PropTypes.bool, showPlus: PropTypes.bool,
look: PropTypes.oneOf(["indicator", "plain", "fee"]), look: PropTypes.oneOf(['indicator', 'plain', 'fee']),
}; };
static defaultProps = { static defaultProps = {
precision: 2, precision: 2,
label: true, label: true,
showFree: false, showFree: false,
look: "indicator", look: 'indicator',
showFullPrice: false, showFullPrice: false,
showPlus: false, showPlus: false,
}; };
@ -90,46 +84,43 @@ export class CreditAmount extends React.PureComponent {
const { amount, precision, showFullPrice } = this.props; const { amount, precision, showFullPrice } = this.props;
let formattedAmount; let formattedAmount;
let fullPrice = formatFullPrice(amount, 2); const fullPrice = formatFullPrice(amount, 2);
if (showFullPrice) { if (showFullPrice) {
formattedAmount = fullPrice; formattedAmount = fullPrice;
} else { } else {
formattedAmount = formattedAmount =
amount > 0 && amount < minimumRenderableAmount amount > 0 && amount < minimumRenderableAmount
? "<" + minimumRenderableAmount ? `<${minimumRenderableAmount}`
: formatCredits(amount, precision); : formatCredits(amount, precision);
} }
let amountText; let amountText;
if (this.props.showFree && parseFloat(this.props.amount) === 0) { if (this.props.showFree && parseFloat(this.props.amount) === 0) {
amountText = __("free"); amountText = __('free');
} else { } else {
if (this.props.label) { if (this.props.label) {
const label = const label =
typeof this.props.label === "string" typeof this.props.label === 'string'
? this.props.label ? this.props.label
: parseFloat(amount) == 1 ? __("credit") : __("credits"); : parseFloat(amount) == 1 ? __('credit') : __('credits');
amountText = formattedAmount + " " + label; amountText = `${formattedAmount} ${label}`;
} else { } else {
amountText = formattedAmount; amountText = formattedAmount;
} }
if (this.props.showPlus && amount > 0) { if (this.props.showPlus && amount > 0) {
amountText = "+" + amountText; amountText = `+${amountText}`;
} }
} }
return ( return (
<span <span className={`credit-amount credit-amount--${this.props.look}`} title={fullPrice}>
className={`credit-amount credit-amount--${this.props.look}`}
title={fullPrice}
>
<span>{amountText}</span> <span>{amountText}</span>
{this.props.isEstimate ? ( {this.props.isEstimate ? (
<span <span
className="credit-amount__estimate" className="credit-amount__estimate"
title={__("This is an estimate and does not include data fees")} title={__('This is an estimate and does not include data fees')}
> >
* *
</span> </span>
@ -155,7 +146,7 @@ export class Thumbnail extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this._defaultImageUri = lbry.imagePath("default-thumb.svg"); this._defaultImageUri = lbry.imagePath('default-thumb.svg');
this._maxLoadTime = 10000; this._maxLoadTime = 10000;
this._isMounted = false; this._isMounted = false;
@ -180,7 +171,7 @@ export class Thumbnail extends React.PureComponent {
} }
render() { render() {
const className = this.props.className ? this.props.className : "", const className = this.props.className ? this.props.className : '',
otherProps = Object.assign({}, this.props); otherProps = Object.assign({}, this.props);
delete otherProps.className; delete otherProps.className;
return ( return (

View file

@ -1,16 +1,14 @@
import React from "react"; import React from 'react';
import classnames from "classnames"; import classnames from 'classnames';
export default ({ dark, className }) => { export default ({ dark, className }) => (
return (
<div <div
className={classnames( className={classnames(
"spinner", 'spinner',
{ {
"spinner--dark": dark, 'spinner--dark': dark,
}, },
className className
)} )}
/> />
); );
};

View file

@ -1,14 +1,11 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { makeSelectBlockDate } from "redux/selectors/wallet"; import { makeSelectBlockDate } from 'redux/selectors/wallet';
import { doFetchBlock } from "redux/actions/wallet"; import { doFetchBlock } from 'redux/actions/wallet';
import DateTime from "./view"; import DateTime from './view';
const select = (state, props) => ({ const select = (state, props) => ({
date: date: !props.date && props.block ? makeSelectBlockDate(props.block)(state) : props.date,
!props.date && props.block
? makeSelectBlockDate(props.block)(state)
: props.date,
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -1,15 +1,15 @@
import React from "react"; import React from 'react';
class DateTime extends React.PureComponent { class DateTime extends React.PureComponent {
static SHOW_DATE = "date"; static SHOW_DATE = 'date';
static SHOW_TIME = "time"; static SHOW_TIME = 'time';
static SHOW_BOTH = "both"; static SHOW_BOTH = 'both';
static defaultProps = { static defaultProps = {
formatOptions: { formatOptions: {
month: "long", month: 'long',
day: "numeric", day: 'numeric',
year: "numeric", year: 'numeric',
}, },
}; };
@ -37,12 +37,12 @@ class DateTime extends React.PureComponent {
<span> <span>
{date && {date &&
(show == DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) && (show == DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) &&
date.toLocaleDateString([locale, "en-US"], formatOptions)} date.toLocaleDateString([locale, 'en-US'], formatOptions)}
{show == DateTime.SHOW_BOTH && " "} {show == DateTime.SHOW_BOTH && ' '}
{date && {date &&
(show == DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) && (show == DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) &&
date.toLocaleTimeString()} date.toLocaleTimeString()}
{!date && "..."} {!date && '...'}
</span> </span>
); );
} }

View file

@ -1,16 +1,17 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
const { remote } = require('electron');
const { remote } = require("electron");
class FileSelector extends React.PureComponent { class FileSelector extends React.PureComponent {
static propTypes = { static propTypes = {
type: PropTypes.oneOf(["file", "directory"]), type: PropTypes.oneOf(['file', 'directory']),
initPath: PropTypes.string, initPath: PropTypes.string,
onFileChosen: PropTypes.func, onFileChosen: PropTypes.func,
}; };
static defaultProps = { static defaultProps = {
type: "file", type: 'file',
}; };
constructor(props) { constructor(props) {
@ -27,10 +28,7 @@ class FileSelector extends React.PureComponent {
handleButtonClick() { handleButtonClick() {
remote.dialog.showOpenDialog( remote.dialog.showOpenDialog(
{ {
properties: properties: this.props.type == 'file' ? ['openFile'] : ['openDirectory', 'createDirectory'],
this.props.type == "file"
? ["openFile"]
: ["openDirectory", "createDirectory"],
}, },
paths => { paths => {
if (!paths) { if (!paths) {
@ -40,7 +38,7 @@ class FileSelector extends React.PureComponent {
const path = paths[0]; const path = paths[0];
this.setState({ this.setState({
path: path, path,
}); });
if (this.props.onFileChosen) { if (this.props.onFileChosen) {
this.props.onFileChosen(path); this.props.onFileChosen(path);
@ -59,12 +57,10 @@ class FileSelector extends React.PureComponent {
> >
<span className="button__content"> <span className="button__content">
<span className="button-label"> <span className="button-label">
{this.props.type == "file" {this.props.type == 'file' ? __('Choose File') : __('Choose Directory')}
? __("Choose File")
: __("Choose Directory")}
</span> </span>
</span> </span>
</button>{" "} </button>{' '}
<span className="file-selector__path"> <span className="file-selector__path">
<input <input
className="input-copyable" className="input-copyable"
@ -76,7 +72,7 @@ class FileSelector extends React.PureComponent {
this._inputElem.select(); this._inputElem.select();
}} }}
readOnly="readonly" readOnly="readonly"
value={this.state.path || __("No File Chosen")} value={this.state.path || __('No File Chosen')}
/> />
</span> </span>
</div> </div>

View file

@ -1,14 +1,14 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { makeSelectFileInfoForUri } from "redux/selectors/file_info"; import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
import { makeSelectCostInfoForUri } from "redux/selectors/cost_info"; import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
import { doOpenModal } from "redux/actions/app"; import { doOpenModal } from 'redux/actions/app';
import { makeSelectClaimIsMine } from "redux/selectors/claims"; import { makeSelectClaimIsMine } from 'redux/selectors/claims';
import FileActions from "./view"; import FileActions from './view';
const select = (state, props) => ({ const select = (state, props) => ({
fileInfo: makeSelectFileInfoForUri(props.uri)(state), 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), costInfo: makeSelectCostInfoForUri(props.uri)(state),
claimIsMine: makeSelectClaimIsMine(props.uri)(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state),
}); });

View file

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import Link from "component/link"; import Link from 'component/link';
import FileDownloadLink from "component/fileDownloadLink"; import FileDownloadLink from 'component/fileDownloadLink';
import * as modals from "constants/modal_types"; import * as modals from 'constants/modal_types';
class FileActions extends React.PureComponent { class FileActions extends React.PureComponent {
render() { render() {
@ -17,7 +17,7 @@ class FileActions extends React.PureComponent {
<Link <Link
button="text" button="text"
icon="icon-trash" icon="icon-trash"
label={__("Remove")} label={__('Remove')}
className="no-underline" className="no-underline"
onClick={() => openModal(modals.CONFIRM_FILE_REMOVE, { uri })} onClick={() => openModal(modals.CONFIRM_FILE_REMOVE, { uri })}
/> />
@ -28,22 +28,22 @@ class FileActions extends React.PureComponent {
icon="icon-flag" icon="icon-flag"
href={`https://lbry.io/dmca?claim_id=${claimId}`} href={`https://lbry.io/dmca?claim_id=${claimId}`}
className="no-underline" className="no-underline"
label={__("report")} label={__('report')}
/> />
)} )}
<Link <Link
button="primary" button="primary"
icon="icon-gift" icon="icon-gift"
label={__("Support")} label={__('Support')}
navigate="/show" navigate="/show"
className="card__action--right" className="card__action--right"
navigateParams={{ uri, tab: "tip" }} navigateParams={{ uri, tab: 'tip' }}
/> />
{claimIsMine && ( {claimIsMine && (
<Link <Link
button="alt" button="alt"
icon="icon-edit" icon="icon-edit"
label={__("Edit")} label={__('Edit')}
navigate="/publish" navigate="/publish"
className="card__action--right" className="card__action--right"
navigateParams={{ id: claimId }} navigateParams={{ id: claimId }}

View file

@ -1,18 +1,12 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { doNavigate } from "redux/actions/navigation"; import { doNavigate } from 'redux/actions/navigation';
import { doResolveUri } from "redux/actions/content"; import { doResolveUri } from 'redux/actions/content';
import { selectShowNsfw } from "redux/selectors/settings"; import { selectShowNsfw } from 'redux/selectors/settings';
import { import { makeSelectClaimForUri, makeSelectMetadataForUri } from 'redux/selectors/claims';
makeSelectClaimForUri, import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
makeSelectMetadataForUri, import { makeSelectIsUriResolving, selectRewardContentClaimIds } from 'redux/selectors/content';
} from "redux/selectors/claims"; import FileCard from './view';
import { makeSelectFileInfoForUri } from "redux/selectors/file_info";
import {
makeSelectIsUriResolving,
selectRewardContentClaimIds,
} from "redux/selectors/content";
import FileCard from "./view";
const select = (state, props) => ({ const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),

View file

@ -1,14 +1,14 @@
import React from "react"; import React from 'react';
import lbryuri from "lbryuri.js"; import lbryuri from 'lbryuri.js';
import CardMedia from "component/cardMedia"; import CardMedia from 'component/cardMedia';
import Link from "component/link"; import Link from 'component/link';
import { TruncatedText } from "component/common"; import { TruncatedText } from 'component/common';
import Icon from "component/icon"; import Icon from 'component/icon';
import FilePrice from "component/filePrice"; import FilePrice from 'component/filePrice';
import UriIndicator from "component/uriIndicator"; import UriIndicator from 'component/uriIndicator';
import NsfwOverlay from "component/nsfwOverlay"; import NsfwOverlay from 'component/nsfwOverlay';
import TruncatedMarkdown from "component/truncatedMarkdown"; import TruncatedMarkdown from 'component/truncatedMarkdown';
import * as icons from "constants/icons"; import * as icons from 'constants/icons';
class FileCard extends React.PureComponent { class FileCard extends React.PureComponent {
constructor(props) { constructor(props) {
@ -59,35 +59,27 @@ class FileCard extends React.PureComponent {
const uri = lbryuri.normalize(this.props.uri); const uri = lbryuri.normalize(this.props.uri);
const title = metadata && metadata.title ? metadata.title : uri; const title = metadata && metadata.title ? metadata.title : uri;
const thumbnail = const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null;
metadata && metadata.thumbnail ? metadata.thumbnail : null;
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
const isRewardContent = const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
claim && rewardedContentClaimIds.includes(claim.claim_id);
let description = ""; let description = '';
if (isResolvingUri && !claim) { if (isResolvingUri && !claim) {
description = __("Loading..."); description = __('Loading...');
} else if (metadata && metadata.description) { } else if (metadata && metadata.description) {
description = metadata.description; description = metadata.description;
} else if (claim === null) { } else if (claim === null) {
description = __("This address contains no content."); description = __('This address contains no content.');
} }
return ( return (
<section <section
className={ className={`card card--small card--link ${obscureNsfw ? 'card--obscured ' : ''}`}
"card card--small card--link " +
(obscureNsfw ? "card--obscured " : "")
}
onMouseEnter={this.handleMouseOver.bind(this)} onMouseEnter={this.handleMouseOver.bind(this)}
onMouseLeave={this.handleMouseOut.bind(this)} onMouseLeave={this.handleMouseOut.bind(this)}
> >
<div className="card__inner"> <div className="card__inner">
<Link <Link onClick={() => navigate('/show', { uri })} className="card__link">
onClick={() => navigate("/show", { uri })}
className="card__link"
>
<CardMedia title={title} thumbnail={thumbnail} /> <CardMedia title={title} thumbnail={thumbnail} />
<div className="card__title-identity"> <div className="card__title-identity">
<div className="card__title" title={title}> <div className="card__title" title={title}>
@ -95,12 +87,11 @@ class FileCard extends React.PureComponent {
</div> </div>
<div className="card__subtitle"> <div className="card__subtitle">
<span className="card__indicators card--file-subtitle"> <span className="card__indicators card--file-subtitle">
<FilePrice uri={uri} />{" "} <FilePrice uri={uri} /> {isRewardContent && <Icon icon={icons.FEATURED} />}{' '}
{isRewardContent && <Icon icon={icons.FEATURED} />}{" "}
{fileInfo && <Icon icon={icons.LOCAL} />} {fileInfo && <Icon icon={icons.LOCAL} />}
</span> </span>
<span className="card--file-subtitle"> <span className="card--file-subtitle">
<UriIndicator uri={uri} link={true} span={true} smallCard /> <UriIndicator uri={uri} link span smallCard />
</span> </span>
</div> </div>
</div> </div>

View file

@ -1,13 +1,13 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { import {
makeSelectClaimForUri, makeSelectClaimForUri,
makeSelectContentTypeForUri, makeSelectContentTypeForUri,
makeSelectMetadataForUri, makeSelectMetadataForUri,
} from "redux/selectors/claims"; } from 'redux/selectors/claims';
import FileDetails from "./view"; import FileDetails from './view';
import { doOpenFileInFolder } from "redux/actions/file_info"; import { doOpenFileInFolder } from 'redux/actions/file_info';
import { makeSelectFileInfoForUri } from "redux/selectors/file_info"; import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
const select = (state, props) => ({ const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),

View file

@ -1,27 +1,20 @@
import React from "react"; import React from 'react';
import ReactMarkdown from "react-markdown"; import ReactMarkdown from 'react-markdown';
import lbry from "lbry.js"; import lbry from 'lbry.js';
import FileActions from "component/fileActions"; import FileActions from 'component/fileActions';
import Link from "component/link"; import Link from 'component/link';
import DateTime from "component/dateTime"; import DateTime from 'component/dateTime';
const path = require("path"); const path = require('path');
class FileDetails extends React.PureComponent { class FileDetails extends React.PureComponent {
render() { render() {
const { const { claim, contentType, fileInfo, metadata, openFolder, uri } = this.props;
claim,
contentType,
fileInfo,
metadata,
openFolder,
uri,
} = this.props;
if (!claim || !metadata) { if (!claim || !metadata) {
return ( return (
<div className="card__content"> <div className="card__content">
<span className="empty">{__("Empty claim or metadata info.")}</span> <span className="empty">{__('Empty claim or metadata info.')}</span>
</div> </div>
); );
} }
@ -29,9 +22,7 @@ class FileDetails extends React.PureComponent {
const { description, language, license } = metadata; const { description, language, license } = metadata;
const mediaType = lbry.getMediaType(contentType); const mediaType = lbry.getMediaType(contentType);
const downloadPath = fileInfo const downloadPath = fileInfo ? path.normalize(fileInfo.download_path) : null;
? path.normalize(fileInfo.download_path)
: null;
return ( return (
<div> <div>
@ -40,33 +31,31 @@ class FileDetails extends React.PureComponent {
<div className="divider__horizontal" /> <div className="divider__horizontal" />
<div className="card__content card__subtext card__subtext--allow-newlines"> <div className="card__content card__subtext card__subtext--allow-newlines">
<ReactMarkdown <ReactMarkdown
source={description || ""} source={description || ''}
escapeHtml={true} escapeHtml
disallowedTypes={["Heading", "HtmlInline", "HtmlBlock"]} disallowedTypes={['Heading', 'HtmlInline', 'HtmlBlock']}
/> />
</div> </div>
<div className="card__content"> <div className="card__content">
<table className="table-standard table-stretch"> <table className="table-standard table-stretch">
<tbody> <tbody>
<tr> <tr>
<td>{__("Content-Type")}</td> <td>{__('Content-Type')}</td>
<td>{mediaType}</td> <td>{mediaType}</td>
</tr> </tr>
<tr> <tr>
<td>{__("Language")}</td> <td>{__('Language')}</td>
<td>{language}</td> <td>{language}</td>
</tr> </tr>
<tr> <tr>
<td>{__("License")}</td> <td>{__('License')}</td>
<td>{license}</td> <td>{license}</td>
</tr> </tr>
{downloadPath && ( {downloadPath && (
<tr> <tr>
<td>{__("Downloaded to")}</td> <td>{__('Downloaded to')}</td>
<td> <td>
<Link onClick={() => openFolder(downloadPath)}> <Link onClick={() => openFolder(downloadPath)}>{downloadPath}</Link>
{downloadPath}
</Link>
</td> </td>
</tr> </tr>
)} )}

View file

@ -1,20 +1,20 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { import {
makeSelectFileInfoForUri, makeSelectFileInfoForUri,
makeSelectDownloadingForUri, makeSelectDownloadingForUri,
makeSelectLoadingForUri, makeSelectLoadingForUri,
} from "redux/selectors/file_info"; } from 'redux/selectors/file_info';
import { makeSelectCostInfoForUri } from "redux/selectors/cost_info"; import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
import { doFetchAvailability } from "redux/actions/availability"; import { doFetchAvailability } from 'redux/actions/availability';
import { doOpenFileInShell } from "redux/actions/file_info"; import { doOpenFileInShell } from 'redux/actions/file_info';
import { doPurchaseUri, doStartDownload } from "redux/actions/content"; import { doPurchaseUri, doStartDownload } from 'redux/actions/content';
import { setVideoPause } from "redux/actions/video"; import { setVideoPause } from 'redux/actions/video';
import FileDownloadLink from "./view"; import FileDownloadLink from './view';
const select = (state, props) => ({ const select = (state, props) => ({
fileInfo: makeSelectFileInfoForUri(props.uri)(state), 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), downloading: makeSelectDownloadingForUri(props.uri)(state),
costInfo: makeSelectCostInfoForUri(props.uri)(state), costInfo: makeSelectCostInfoForUri(props.uri)(state),
loading: makeSelectLoadingForUri(props.uri)(state), loading: makeSelectLoadingForUri(props.uri)(state),

View file

@ -1,6 +1,6 @@
import React from "react"; import React from 'react';
import { Icon, BusyMessage } from "component/common"; import { Icon, BusyMessage } from 'component/common';
import Link from "component/link"; import Link from 'component/link';
class FileDownloadLink extends React.PureComponent { class FileDownloadLink extends React.PureComponent {
componentWillMount() { componentWillMount() {
@ -55,9 +55,7 @@ class FileDownloadLink extends React.PureComponent {
fileInfo && fileInfo.written_bytes fileInfo && fileInfo.written_bytes
? fileInfo.written_bytes / fileInfo.total_bytes * 100 ? fileInfo.written_bytes / fileInfo.total_bytes * 100
: 0, : 0,
label = fileInfo label = fileInfo ? progress.toFixed(0) + __('% complete') : __('Connecting...'),
? progress.toFixed(0) + __("% complete")
: __("Connecting..."),
labelWithIcon = ( labelWithIcon = (
<span className="button__content"> <span className="button__content">
<Icon icon="icon-download" /> <Icon icon="icon-download" />
@ -69,7 +67,7 @@ class FileDownloadLink extends React.PureComponent {
<div className="faux-button-block file-download button-set-item"> <div className="faux-button-block file-download button-set-item">
<div <div
className="faux-button-block file-download__overlay" className="faux-button-block file-download__overlay"
style={{ width: progress + "%" }} style={{ width: `${progress}%` }}
> >
{labelWithIcon} {labelWithIcon}
</div> </div>
@ -78,12 +76,12 @@ class FileDownloadLink extends React.PureComponent {
); );
} else if (fileInfo === null && !downloading) { } else if (fileInfo === null && !downloading) {
if (!costInfo) { if (!costInfo) {
return <BusyMessage message={__("Fetching cost info")} />; return <BusyMessage message={__('Fetching cost info')} />;
} else { }
return ( return (
<Link <Link
button="text" button="text"
label={__("Download")} label={__('Download')}
icon="icon-download" icon="icon-download"
className="no-underline" className="no-underline"
onClick={() => { onClick={() => {
@ -91,11 +89,10 @@ class FileDownloadLink extends React.PureComponent {
}} }}
/> />
); );
}
} else if (fileInfo && fileInfo.download_path) { } else if (fileInfo && fileInfo.download_path) {
return ( return (
<Link <Link
label={__("Open")} label={__('Open')}
button="text" button="text"
icon="icon-external-link-square" icon="icon-external-link-square"
className="no-underline" className="no-underline"

View file

@ -1,6 +1,6 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import FileList from "./view"; import FileList from './view';
const select = state => ({}); const select = state => ({});

View file

@ -1,23 +1,23 @@
import React from "react"; import React from 'react';
import lbryuri from "lbryuri.js"; import lbryuri from 'lbryuri.js';
import FormField from "component/formField"; import FormField from 'component/formField';
import FileTile from "component/fileTile"; import FileTile from 'component/fileTile';
import { BusyMessage } from "component/common.js"; import { BusyMessage } from 'component/common.js';
class FileList extends React.PureComponent { class FileList extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
sortBy: "date", sortBy: 'date',
}; };
this._sortFunctions = { this._sortFunctions = {
date: function(fileInfos) { date(fileInfos) {
return fileInfos.slice().reverse(); return fileInfos.slice().reverse();
}, },
title: function(fileInfos) { title(fileInfos) {
return fileInfos.slice().sort(function(fileInfo1, fileInfo2) { return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
const title1 = fileInfo1.value const title1 = fileInfo1.value
? fileInfo1.value.stream.metadata.title.toLowerCase() ? fileInfo1.value.stream.metadata.title.toLowerCase()
: fileInfo1.name; : fileInfo1.name;
@ -28,24 +28,20 @@ class FileList extends React.PureComponent {
return -1; return -1;
} else if (title1 > title2) { } else if (title1 > title2) {
return 1; return 1;
} else {
return 0;
} }
return 0;
}); });
}, },
filename: function(fileInfos) { filename(fileInfos) {
return fileInfos return fileInfos.slice().sort(({ file_name: fileName1 }, { file_name: fileName2 }) => {
.slice()
.sort(function({ file_name: fileName1 }, { file_name: fileName2 }) {
const fileName1Lower = fileName1.toLowerCase(); const fileName1Lower = fileName1.toLowerCase();
const fileName2Lower = fileName2.toLowerCase(); const fileName2Lower = fileName2.toLowerCase();
if (fileName1Lower < fileName2Lower) { if (fileName1Lower < fileName2Lower) {
return -1; return -1;
} else if (fileName2Lower > fileName1Lower) { } else if (fileName2Lower > fileName1Lower) {
return 1; return 1;
} else {
return 0;
} }
return 0;
}); });
}, },
}; };
@ -54,9 +50,8 @@ class FileList extends React.PureComponent {
getChannelSignature(fileInfo) { getChannelSignature(fileInfo) {
if (fileInfo.value) { if (fileInfo.value) {
return fileInfo.value.publisherSignature.certificateId; return fileInfo.value.publisherSignature.certificateId;
} else {
return fileInfo.metadata.publisherSignature.certificateId;
} }
return fileInfo.metadata.publisherSignature.certificateId;
} }
handleSortChanged(event) { handleSortChanged(event) {
@ -71,7 +66,7 @@ class FileList extends React.PureComponent {
const content = []; const content = [];
this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => { this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
let uriParams = {}; const uriParams = {};
if (fileInfo.channel_name) { if (fileInfo.channel_name) {
uriParams.channelName = fileInfo.channel_name; uriParams.channelName = fileInfo.channel_name;
@ -89,7 +84,7 @@ class FileList extends React.PureComponent {
uri={uri} uri={uri}
showPrice={false} showPrice={false}
showLocal={false} showLocal={false}
showActions={true} showActions
showEmpty={this.props.fileTileShowEmpty} showEmpty={this.props.fileTileShowEmpty}
/> />
); );
@ -98,10 +93,10 @@ class FileList extends React.PureComponent {
<section className="file-list__header"> <section className="file-list__header">
{fetching && <BusyMessage />} {fetching && <BusyMessage />}
<span className="sort-section"> <span className="sort-section">
{__("Sort by")}{" "} {__('Sort by')}{' '}
<FormField type="select" onChange={this.handleSortChanged.bind(this)}> <FormField type="select" onChange={this.handleSortChanged.bind(this)}>
<option value="date">{__("Date")}</option> <option value="date">{__('Date')}</option>
<option value="title">{__("Title")}</option> <option value="title">{__('Title')}</option>
</FormField> </FormField>
</span> </span>
{content} {content}

View file

@ -1,11 +1,8 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { doSearch } from "redux/actions/search"; import { doSearch } from 'redux/actions/search';
import { import { selectIsSearching, makeSelectSearchUris } from 'redux/selectors/search';
selectIsSearching, import FileListSearch from './view';
makeSelectSearchUris,
} from "redux/selectors/search";
import FileListSearch from "./view";
const select = (state, props) => ({ const select = (state, props) => ({
isSearching: selectIsSearching(state), isSearching: selectIsSearching(state),

View file

@ -1,9 +1,9 @@
import React from "react"; import React from 'react';
import FileTile from "component/fileTile"; import FileTile from 'component/fileTile';
import ChannelTile from "component/channelTile"; import ChannelTile from 'component/channelTile';
import Link from "component/link"; import Link from 'component/link';
import { BusyMessage } from "component/common.js"; import { BusyMessage } from 'component/common.js';
import lbryuri from "lbryuri"; import lbryuri from 'lbryuri';
const SearchNoResults = props => { const SearchNoResults = props => {
const { query } = props; const { query } = props;
@ -11,8 +11,8 @@ const SearchNoResults = props => {
return ( return (
<section> <section>
<span className="empty"> <span className="empty">
{(__("No one has checked anything in for %s yet."), query)}{" "} {(__('No one has checked anything in for %s yet.'), query)}{' '}
<Link label={__("Be the first")} navigate="/publish" /> <Link label={__('Be the first')} navigate="/publish" />
</span> </span>
</section> </section>
); );
@ -38,18 +38,14 @@ class FileListSearch extends React.PureComponent {
return ( return (
<div> <div>
{isSearching && {isSearching && !uris && <BusyMessage message={__('Looking up the Dewey Decimals')} />}
!uris && (
<BusyMessage message={__("Looking up the Dewey Decimals")} />
)}
{isSearching && {isSearching && uris && <BusyMessage message={__('Refreshing the Dewey Decimals')} />}
uris && <BusyMessage message={__("Refreshing the Dewey Decimals")} />}
{uris && uris.length {uris && uris.length
? uris.map( ? uris.map(
uri => uri =>
lbryuri.parse(uri).name[0] === "@" ? ( lbryuri.parse(uri).name[0] === '@' ? (
<ChannelTile key={uri} uri={uri} /> <ChannelTile key={uri} uri={uri} />
) : ( ) : (
<FileTile key={uri} uri={uri} /> <FileTile key={uri} uri={uri} />

View file

@ -1,12 +1,12 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { doFetchCostInfoForUri } from "redux/actions/cost_info"; import { doFetchCostInfoForUri } from 'redux/actions/cost_info';
import { import {
makeSelectCostInfoForUri, makeSelectCostInfoForUri,
makeSelectFetchingCostInfoForUri, makeSelectFetchingCostInfoForUri,
} from "redux/selectors/cost_info"; } from 'redux/selectors/cost_info';
import { makeSelectClaimForUri } from "redux/selectors/claims"; import { makeSelectClaimForUri } from 'redux/selectors/claims';
import FilePrice from "./view"; import FilePrice from './view';
const select = (state, props) => ({ const select = (state, props) => ({
costInfo: makeSelectCostInfoForUri(props.uri)(state), costInfo: makeSelectCostInfoForUri(props.uri)(state),

View file

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import { CreditAmount } from "component/common"; import { CreditAmount } from 'component/common';
class FilePrice extends React.PureComponent { class FilePrice extends React.PureComponent {
componentWillMount() { componentWillMount() {
@ -19,14 +19,12 @@ class FilePrice extends React.PureComponent {
} }
render() { render() {
const { costInfo, look = "indicator", showFullPrice = false } = this.props; const { costInfo, look = 'indicator', showFullPrice = false } = this.props;
const isEstimate = costInfo ? !costInfo.includesData : null; const isEstimate = costInfo ? !costInfo.includesData : null;
if (!costInfo) { if (!costInfo) {
return ( return <span className={`credit-amount credit-amount--${look}`}>???</span>;
<span className={`credit-amount credit-amount--${look}`}>???</span>
);
} }
return ( return (
@ -34,7 +32,7 @@ class FilePrice extends React.PureComponent {
label={false} label={false}
amount={costInfo.cost} amount={costInfo.cost}
isEstimate={isEstimate} isEstimate={isEstimate}
showFree={true} showFree
showFullPrice={showFullPrice} showFullPrice={showFullPrice}
/> />
); );

View file

@ -1,18 +1,12 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { doNavigate } from "redux/actions/navigation"; import { doNavigate } from 'redux/actions/navigation';
import { doResolveUri } from "redux/actions/content"; import { doResolveUri } from 'redux/actions/content';
import { import { makeSelectClaimForUri, makeSelectMetadataForUri } from 'redux/selectors/claims';
makeSelectClaimForUri, import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
makeSelectMetadataForUri, import { selectShowNsfw } from 'redux/selectors/settings';
} from "redux/selectors/claims"; import { makeSelectIsUriResolving, selectRewardContentClaimIds } from 'redux/selectors/content';
import { makeSelectFileInfoForUri } from "redux/selectors/file_info"; import FileTile from './view';
import { selectShowNsfw } from "redux/selectors/settings";
import {
makeSelectIsUriResolving,
selectRewardContentClaimIds,
} from "redux/selectors/content";
import FileTile from "./view";
const select = (state, props) => ({ const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),

View file

@ -1,15 +1,15 @@
import React from "react"; import React from 'react';
import * as icons from "constants/icons"; import * as icons from 'constants/icons';
import lbryuri from "lbryuri.js"; import lbryuri from 'lbryuri.js';
import CardMedia from "component/cardMedia"; import CardMedia from 'component/cardMedia';
import { TruncatedText } from "component/common.js"; import { TruncatedText } from 'component/common.js';
import FilePrice from "component/filePrice"; import FilePrice from 'component/filePrice';
import NsfwOverlay from "component/nsfwOverlay"; import NsfwOverlay from 'component/nsfwOverlay';
import Icon from "component/icon"; import Icon from 'component/icon';
class FileTile extends React.PureComponent { class FileTile extends React.PureComponent {
static SHOW_EMPTY_PUBLISH = "publish"; static SHOW_EMPTY_PUBLISH = 'publish';
static SHOW_EMPTY_PENDING = "pending"; static SHOW_EMPTY_PENDING = 'pending';
static defaultProps = { static defaultProps = {
showPrice: true, showPrice: true,
@ -36,11 +36,7 @@ class FileTile extends React.PureComponent {
} }
handleMouseOver() { handleMouseOver() {
if ( if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) {
this.props.obscureNsfw &&
this.props.metadata &&
this.props.metadata.nsfw
) {
this.setState({ this.setState({
showNsfwHelp: true, showNsfwHelp: true,
}); });
@ -73,59 +69,49 @@ class FileTile extends React.PureComponent {
const isClaimed = !!claim; const isClaimed = !!claim;
const isClaimable = lbryuri.isClaimable(uri); const isClaimable = lbryuri.isClaimable(uri);
const title = const title =
isClaimed && metadata && metadata.title isClaimed && metadata && metadata.title ? metadata.title : lbryuri.parse(uri).contentName;
? metadata.title const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null;
: lbryuri.parse(uri).contentName;
const thumbnail =
metadata && metadata.thumbnail ? metadata.thumbnail : null;
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
const isRewardContent = const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
claim && rewardedContentClaimIds.includes(claim.claim_id);
let onClick = () => navigate("/show", { uri }); let onClick = () => navigate('/show', { uri });
let name = ""; let name = '';
if (claim) { if (claim) {
name = claim.name; name = claim.name;
} }
let description = ""; let description = '';
if (isClaimed) { if (isClaimed) {
description = metadata && metadata.description; description = metadata && metadata.description;
} else if (isResolvingUri) { } else if (isResolvingUri) {
description = __("Loading..."); description = __('Loading...');
} else if (showEmpty === FileTile.SHOW_EMPTY_PUBLISH) { } else if (showEmpty === FileTile.SHOW_EMPTY_PUBLISH) {
onClick = () => navigate("/publish", {}); onClick = () => navigate('/publish', {});
description = ( description = (
<span className="empty"> <span className="empty">
{__("This location is unused.")}{" "} {__('This location is unused.')}{' '}
{isClaimable && ( {isClaimable && <span className="button-text">{__('Put something here!')}</span>}
<span className="button-text">{__("Put something here!")}</span>
)}
</span> </span>
); );
} else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) { } else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) {
description = ( description = <span className="empty">{__('This file is pending confirmation.')}</span>;
<span className="empty">
{__("This file is pending confirmation.")}
</span>
);
} }
return ( return (
<section <section
className={"file-tile card " + (obscureNsfw ? "card--obscured " : "")} className={`file-tile card ${obscureNsfw ? 'card--obscured ' : ''}`}
onMouseEnter={this.handleMouseOver.bind(this)} onMouseEnter={this.handleMouseOver.bind(this)}
onMouseLeave={this.handleMouseOut.bind(this)} onMouseLeave={this.handleMouseOut.bind(this)}
> >
<div onClick={onClick} className="card__link"> <div onClick={onClick} className="card__link">
<div className={"card__inner file-tile__row"}> <div className="card__inner file-tile__row">
<CardMedia title={title || name} thumbnail={thumbnail} /> <CardMedia title={title || name} thumbnail={thumbnail} />
<div className="file-tile__content"> <div className="file-tile__content">
<div className="card__title-primary"> <div className="card__title-primary">
<span className="card__indicators"> <span className="card__indicators">
{showPrice && <FilePrice uri={this.props.uri} />}{" "} {showPrice && <FilePrice uri={this.props.uri} />}{' '}
{isRewardContent && <Icon icon={icons.FEATURED} />}{" "} {isRewardContent && <Icon icon={icons.FEATURED} />}{' '}
{showLocal && fileInfo && <Icon icon={icons.LOCAL} />} {showLocal && fileInfo && <Icon icon={icons.LOCAL} />}
</span> </span>
<h3> <h3>
@ -134,9 +120,7 @@ class FileTile extends React.PureComponent {
</div> </div>
{description && ( {description && (
<div className="card__content card__subtext"> <div className="card__content card__subtext">
<TruncatedText lines={!showActions ? 3 : 2}> <TruncatedText lines={!showActions ? 3 : 2}>{description}</TruncatedText>
{description}
</TruncatedText>
</div> </div>
)} )}
</div> </div>

View file

@ -1,14 +1,14 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import FormField from "component/formField"; import FormField from 'component/formField';
import { Icon } from "component/common.js"; import { Icon } from 'component/common.js';
let formFieldCounter = 0; let formFieldCounter = 0;
export const formFieldNestedLabelTypes = ["radio", "checkbox"]; export const formFieldNestedLabelTypes = ['radio', 'checkbox'];
export function formFieldId() { export function formFieldId() {
return "form-field-" + ++formFieldCounter; return `form-field-${++formFieldCounter}`;
} }
export class Form extends React.PureComponent { export class Form extends React.PureComponent {
@ -26,11 +26,7 @@ export class Form extends React.PureComponent {
} }
render() { render() {
return ( return <form onSubmit={event => this.handleSubmit(event)}>{this.props.children}</form>;
<form onSubmit={event => this.handleSubmit(event)}>
{this.props.children}
</form>
);
} }
} }
@ -50,7 +46,7 @@ export class FormRow extends React.PureComponent {
this._field = null; this._field = null;
this._fieldRequiredText = __("This field is required"); this._fieldRequiredText = __('This field is required');
this.state = this.getStateFromProps(props); this.state = this.getStateFromProps(props);
} }
@ -63,11 +59,9 @@ export class FormRow extends React.PureComponent {
return { return {
isError: !!props.errorMessage, isError: !!props.errorMessage,
errorMessage: errorMessage:
typeof props.errorMessage === "string" typeof props.errorMessage === 'string'
? props.errorMessage ? props.errorMessage
: props.errorMessage instanceof Error : props.errorMessage instanceof Error ? props.errorMessage.toString() : '',
? props.errorMessage.toString()
: "",
}; };
} }
@ -85,7 +79,7 @@ export class FormRow extends React.PureComponent {
clearError(text) { clearError(text) {
this.setState({ this.setState({
isError: false, isError: false,
errorMessage: "", errorMessage: '',
}); });
} }
@ -116,9 +110,7 @@ export class FormRow extends React.PureComponent {
render() { render() {
const fieldProps = Object.assign({}, this.props), const fieldProps = Object.assign({}, this.props),
elementId = formFieldId(), elementId = formFieldId(),
renderLabelInFormField = formFieldNestedLabelTypes.includes( renderLabelInFormField = formFieldNestedLabelTypes.includes(this.props.type);
this.props.type
);
if (!renderLabelInFormField) { if (!renderLabelInFormField) {
delete fieldProps.label; delete fieldProps.label;
@ -128,28 +120,24 @@ export class FormRow extends React.PureComponent {
delete fieldProps.isFocus; delete fieldProps.isFocus;
return ( return (
<div <div className={`form-row${this.state.isFocus ? ' form-row--focus' : ''}`}>
className={"form-row" + (this.state.isFocus ? " form-row--focus" : "")}
>
{this.props.label && !renderLabelInFormField ? ( {this.props.label && !renderLabelInFormField ? (
<div <div
className={ className={`form-row__label-row ${
"form-row__label-row " + this.props.labelPrefix ? 'form-row__label-row--prefix' : ''
(this.props.labelPrefix ? "form-row__label-row--prefix" : "") }`}
}
> >
<label <label
htmlFor={elementId} htmlFor={elementId}
className={ className={`form-field__label ${
"form-field__label " + this.state.isError ? 'form-field__label--error' : ' '
(this.state.isError ? "form-field__label--error" : " ") }`}
}
> >
{this.props.label} {this.props.label}
</label> </label>
</div> </div>
) : ( ) : (
"" ''
)} )}
<FormField <FormField
ref={ref => { ref={ref => {
@ -163,12 +151,12 @@ export class FormRow extends React.PureComponent {
{!this.state.isError && this.props.helper ? ( {!this.state.isError && this.props.helper ? (
<div className="form-field__helper">{this.props.helper}</div> <div className="form-field__helper">{this.props.helper}</div>
) : ( ) : (
"" ''
)} )}
{this.state.isError ? ( {this.state.isError ? (
<div className="form-field__error">{this.state.errorMessage}</div> <div className="form-field__error">{this.state.errorMessage}</div>
) : ( ) : (
"" ''
)} )}
</div> </div>
); );
@ -178,16 +166,14 @@ export class FormRow extends React.PureComponent {
export const Submit = props => { export const Submit = props => {
const { title, label, icon, disabled } = props; const { title, label, icon, disabled } = props;
const className = const className = `${'button-block' +
"button-block" + ' button-primary' +
" button-primary" + ' button-set-item' +
" button-set-item" + ' button--submit'}${disabled ? ' disabled' : ''}`;
" button--submit" +
(disabled ? " disabled" : "");
const content = ( const content = (
<span className="button__content"> <span className="button__content">
{"icon" in props ? <Icon icon={icon} fixed={true} /> : null} {'icon' in props ? <Icon icon={icon} fixed /> : null}
{label ? <span className="button-label">{label}</span> : null} {label ? <span className="button-label">{label}</span> : null}
</span> </span>
); );

View file

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import FormField from "./view"; import FormField from './view';
export default connect(null, null, null, { withRef: true })(FormField); export default connect(null, null, null, { withRef: true })(FormField);

View file

@ -1,11 +1,11 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import FileSelector from "component/file-selector.js"; import FileSelector from 'component/file-selector.js';
import SimpleMDE from "react-simplemde-editor"; import SimpleMDE from 'react-simplemde-editor';
import { formFieldNestedLabelTypes, formFieldId } from "../form"; import { formFieldNestedLabelTypes, formFieldId } from '../form';
import style from "react-simplemde-editor/dist/simplemde.min.css"; import style from 'react-simplemde-editor/dist/simplemde.min.css';
const formFieldFileSelectorTypes = ["file", "directory"]; const formFieldFileSelectorTypes = ['file', 'directory'];
class FormField extends React.PureComponent { class FormField extends React.PureComponent {
static propTypes = { static propTypes = {
@ -14,10 +14,7 @@ class FormField extends React.PureComponent {
postfix: PropTypes.string, postfix: PropTypes.string,
hasError: PropTypes.bool, hasError: PropTypes.bool,
trim: PropTypes.bool, trim: PropTypes.bool,
regexp: PropTypes.oneOfType([ regexp: PropTypes.oneOfType([PropTypes.instanceOf(RegExp), PropTypes.string]),
PropTypes.instanceOf(RegExp),
PropTypes.string,
]),
}; };
static defaultProps = { static defaultProps = {
@ -27,7 +24,7 @@ class FormField extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this._fieldRequiredText = __("This field is required"); this._fieldRequiredText = __('This field is required');
this._type = null; this._type = null;
this._element = null; this._element = null;
this._extraElementProps = {}; this._extraElementProps = {};
@ -39,22 +36,22 @@ class FormField extends React.PureComponent {
} }
componentWillMount() { componentWillMount() {
if (["text", "number", "radio", "checkbox"].includes(this.props.type)) { if (['text', 'number', 'radio', 'checkbox'].includes(this.props.type)) {
this._element = "input"; this._element = 'input';
this._type = this.props.type; this._type = this.props.type;
} else if (this.props.type == "text-number") { } else if (this.props.type == 'text-number') {
this._element = "input"; this._element = 'input';
this._type = "text"; this._type = 'text';
} else if (this.props.type == "SimpleMDE") { } else if (this.props.type == 'SimpleMDE') {
this._element = SimpleMDE; this._element = SimpleMDE;
this._type = "textarea"; this._type = 'textarea';
this._extraElementProps.options = { this._extraElementProps.options = {
placeholder: this.props.placeholder, 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)) { } else if (formFieldFileSelectorTypes.includes(this.props.type)) {
this._element = "input"; this._element = 'input';
this._type = "hidden"; this._type = 'hidden';
} else { } else {
// Non <input> field, e.g. <select>, <textarea> // Non <input> field, e.g. <select>, <textarea>
this._element = this.props.type; this._element = this.props.type;
@ -66,7 +63,7 @@ class FormField extends React.PureComponent {
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX * We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
* https://github.com/facebook/react/issues/3468 * https://github.com/facebook/react/issues/3468
*/ */
if (this.props.type == "directory") { if (this.props.type == 'directory') {
this.refs.field.webkitdirectory = true; this.refs.field.webkitdirectory = true;
} }
} }
@ -75,7 +72,7 @@ class FormField extends React.PureComponent {
this.refs.field.value = path; this.refs.field.value = path;
if (this.props.onChange) { if (this.props.onChange) {
// Updating inputs programmatically doesn't generate an event, so we have to make our own // Updating inputs programmatically doesn't generate an event, so we have to make our own
const event = new Event("change", { bubbles: true }); const event = new Event('change', { bubbles: true });
this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target
this.props.onChange(event); this.props.onChange(event);
} }
@ -91,20 +88,17 @@ class FormField extends React.PureComponent {
clearError() { clearError() {
this.setState({ this.setState({
isError: false, isError: false,
errorMessage: "", errorMessage: '',
}); });
} }
getValue() { getValue() {
if (this.props.type == "checkbox") { if (this.props.type == 'checkbox') {
return this.refs.field.checked; return this.refs.field.checked;
} else if (this.props.type == "SimpleMDE") { } else if (this.props.type == 'SimpleMDE') {
return this.refs.field.simplemde.value(); return this.refs.field.simplemde.value();
} else {
return this.props.trim
? this.refs.field.value.trim()
: this.refs.field.value;
} }
return this.props.trim ? this.refs.field.value.trim() : this.refs.field.value;
} }
getSelectedElement() { getSelectedElement() {
@ -116,9 +110,9 @@ class FormField extends React.PureComponent {
} }
validate() { validate() {
if ("regexp" in this.props) { if ('regexp' in this.props) {
if (!this.getValue().match(this.props.regexp)) { if (!this.getValue().match(this.props.regexp)) {
this.showError(__("Invalid format.")); this.showError(__('Invalid format.'));
} else { } else {
this.clearError(); this.clearError();
} }
@ -133,8 +127,7 @@ class FormField extends React.PureComponent {
render() { render() {
// Pass all unhandled props to the field element // Pass all unhandled props to the field element
const otherProps = Object.assign({}, this.props), const otherProps = Object.assign({}, this.props),
isError = isError = this.state.isError !== null ? this.state.isError : this.props.hasError,
this.state.isError !== null ? this.state.isError : this.props.hasError,
elementId = this.props.elementId ? this.props.elementId : formFieldId(), elementId = this.props.elementId ? this.props.elementId : formFieldId(),
renderElementInsideLabel = renderElementInsideLabel =
this.props.label && formFieldNestedLabelTypes.includes(this.props.type); this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
@ -158,13 +151,8 @@ class FormField extends React.PureComponent {
placeholder={this.props.placeholder} placeholder={this.props.placeholder}
onBlur={() => this.validate()} onBlur={() => this.validate()}
onFocus={() => this.props.onFocus && this.props.onFocus()} onFocus={() => this.props.onFocus && this.props.onFocus()}
className={ className={`form-field__input form-field__input-${this.props.type} ${this.props.className ||
"form-field__input form-field__input-" + ''}${isError ? 'form-field__input--error' : ''}`}
this.props.type +
" " +
(this.props.className || "") +
(isError ? "form-field__input--error" : "")
}
{...otherProps} {...otherProps}
{...this._extraElementProps} {...this._extraElementProps}
> >
@ -173,19 +161,13 @@ class FormField extends React.PureComponent {
); );
return ( return (
<div className={"form-field form-field--" + this.props.type}> <div className={`form-field form-field--${this.props.type}`}>
{this.props.prefix ? ( {this.props.prefix ? <span className="form-field__prefix">{this.props.prefix}</span> : ''}
<span className="form-field__prefix">{this.props.prefix}</span>
) : (
""
)}
{element} {element}
{renderElementInsideLabel && ( {renderElementInsideLabel && (
<label <label
htmlFor={elementId} htmlFor={elementId}
className={ className={`form-field__label ${isError ? 'form-field__label--error' : ''}`}
"form-field__label " + (isError ? "form-field__label--error" : "")
}
> >
{this.props.label} {this.props.label}
</label> </label>
@ -194,20 +176,18 @@ class FormField extends React.PureComponent {
<FileSelector <FileSelector
type={this.props.type} type={this.props.type}
onFileChosen={this.handleFileChosen.bind(this)} onFileChosen={this.handleFileChosen.bind(this)}
{...(this.props.defaultValue {...(this.props.defaultValue ? { initPath: this.props.defaultValue } : {})}
? { initPath: this.props.defaultValue }
: {})}
/> />
) : null} ) : null}
{this.props.postfix ? ( {this.props.postfix ? (
<span className="form-field__postfix">{this.props.postfix}</span> <span className="form-field__postfix">{this.props.postfix}</span>
) : ( ) : (
"" ''
)} )}
{isError && this.state.errorMessage ? ( {isError && this.state.errorMessage ? (
<div className="form-field__error">{this.state.errorMessage}</div> <div className="form-field__error">{this.state.errorMessage}</div>
) : ( ) : (
"" ''
)} )}
</div> </div>
); );

View file

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import FormFieldPrice from "./view"; import FormFieldPrice from './view';
export default connect(null, null)(FormFieldPrice); export default connect(null, null)(FormFieldPrice);

View file

@ -1,18 +1,13 @@
import React from "react"; import React from 'react';
import FormField from "component/formField"; import FormField from 'component/formField';
class FormFieldPrice extends React.PureComponent { class FormFieldPrice extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
amount: amount: props.defaultValue && props.defaultValue.amount ? props.defaultValue.amount : '',
props.defaultValue && props.defaultValue.amount
? props.defaultValue.amount
: "",
currency: currency:
props.defaultValue && props.defaultValue.currency props.defaultValue && props.defaultValue.currency ? props.defaultValue.currency : 'LBC',
? props.defaultValue.currency
: "LBC",
}; };
} }
@ -45,24 +40,20 @@ class FormFieldPrice extends React.PureComponent {
name="amount" name="amount"
min={min} min={min}
placeholder={placeholder || null} placeholder={placeholder || null}
step="any" //Unfortunately, you cannot set a step without triggering validation that enforces a multiple of the step step="any" // Unfortunately, you cannot set a step without triggering validation that enforces a multiple of the step
onChange={event => this.handleFeeAmountChange(event)} onChange={event => this.handleFeeAmountChange(event)}
defaultValue={ defaultValue={defaultValue && defaultValue.amount ? defaultValue.amount : ''}
defaultValue && defaultValue.amount ? defaultValue.amount : ""
}
className="form-field__input--inline" className="form-field__input--inline"
/> />
<FormField <FormField
type="select" type="select"
name="currency" name="currency"
onChange={event => this.handleFeeCurrencyChange(event)} onChange={event => this.handleFeeCurrencyChange(event)}
defaultValue={ defaultValue={defaultValue && defaultValue.currency ? defaultValue.currency : ''}
defaultValue && defaultValue.currency ? defaultValue.currency : ""
}
className="form-field__input--inline" className="form-field__input--inline"
> >
<option value="LBC">{__("LBRY Credits (LBC)")}</option> <option value="LBC">{__('LBRY Credits (LBC)')}</option>
<option value="USD">{__("US Dollars")}</option> <option value="USD">{__('US Dollars')}</option>
</FormField> </FormField>
</span> </span>
); );

View file

@ -1,19 +1,12 @@
import React from "react"; import React from 'react';
import { formatCredits } from "util/formatCredits"; import { formatCredits } from 'util/formatCredits';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { import { selectIsBackDisabled, selectIsForwardDisabled } from 'redux/selectors/navigation';
selectIsBackDisabled, import { selectBalance } from 'redux/selectors/wallet';
selectIsForwardDisabled, import { doNavigate, doHistoryBack, doHistoryForward } from 'redux/actions/navigation';
} from "redux/selectors/navigation"; import Header from './view';
import { selectBalance } from "redux/selectors/wallet"; import { selectIsUpgradeAvailable } from 'redux/selectors/app';
import { import { doDownloadUpgrade } from 'redux/actions/app';
doNavigate,
doHistoryBack,
doHistoryForward,
} from "redux/actions/navigation";
import Header from "./view";
import { selectIsUpgradeAvailable } from "redux/selectors/app";
import { doDownloadUpgrade } from "redux/actions/app";
const select = state => ({ const select = state => ({
isBackDisabled: selectIsBackDisabled(state), isBackDisabled: selectIsBackDisabled(state),

View file

@ -1,6 +1,6 @@
import React from "react"; import React from 'react';
import Link from "component/link"; import Link from 'component/link';
import WunderBar from "component/wunderbar"; import WunderBar from 'component/wunderbar';
export const Header = props => { export const Header = props => {
const { const {
@ -21,7 +21,7 @@ export const Header = props => {
disabled={isBackDisabled} disabled={isBackDisabled}
button="alt button--flat" button="alt button--flat"
icon="icon-arrow-left" icon="icon-arrow-left"
title={__("Back")} title={__('Back')}
/> />
</div> </div>
<div className="header__item"> <div className="header__item">
@ -30,23 +30,23 @@ export const Header = props => {
disabled={isForwardDisabled} disabled={isForwardDisabled}
button="alt button--flat" button="alt button--flat"
icon="icon-arrow-right" icon="icon-arrow-right"
title={__("Forward")} title={__('Forward')}
/> />
</div> </div>
<div className="header__item"> <div className="header__item">
<Link <Link
onClick={() => navigate("/discover")} onClick={() => navigate('/discover')}
button="alt button--flat" button="alt button--flat"
icon="icon-home" icon="icon-home"
title={__("Discover Content")} title={__('Discover Content')}
/> />
</div> </div>
<div className="header__item"> <div className="header__item">
<Link <Link
onClick={() => navigate("/subscriptions")} onClick={() => navigate('/subscriptions')}
button="alt button--flat" button="alt button--flat"
icon="icon-at" icon="icon-at"
title={__("My Subscriptions")} title={__('My Subscriptions')}
/> />
</div> </div>
<div className="header__item header__item--wunderbar"> <div className="header__item header__item--wunderbar">
@ -54,36 +54,36 @@ export const Header = props => {
</div> </div>
<div className="header__item"> <div className="header__item">
<Link <Link
onClick={() => navigate("/wallet")} onClick={() => navigate('/wallet')}
button="text" button="text"
className="no-underline" className="no-underline"
icon="icon-bank" icon="icon-bank"
label={balance} label={balance}
title={__("Wallet")} title={__('Wallet')}
/> />
</div> </div>
<div className="header__item"> <div className="header__item">
<Link <Link
onClick={() => navigate("/publish")} onClick={() => navigate('/publish')}
button="primary button--flat" button="primary button--flat"
icon="icon-upload" icon="icon-upload"
label={__("Publish")} label={__('Publish')}
/> />
</div> </div>
<div className="header__item"> <div className="header__item">
<Link <Link
onClick={() => navigate("/downloaded")} onClick={() => navigate('/downloaded')}
button="alt button--flat" button="alt button--flat"
icon="icon-folder" icon="icon-folder"
title={__("Downloads and Publishes")} title={__('Downloads and Publishes')}
/> />
</div> </div>
<div className="header__item"> <div className="header__item">
<Link <Link
onClick={() => navigate("/settings")} onClick={() => navigate('/settings')}
button="alt button--flat" button="alt button--flat"
icon="icon-gear" icon="icon-gear"
title={__("Settings")} title={__('Settings')}
/> />
</div> </div>
{isUpgradeAvailable && ( {isUpgradeAvailable && (
@ -91,7 +91,7 @@ export const Header = props => {
onClick={() => downloadUpgrade()} onClick={() => downloadUpgrade()}
button="primary button--flat" button="primary button--flat"
icon="icon-arrow-up" icon="icon-arrow-up"
label={__("Upgrade App")} label={__('Upgrade App')}
/> />
)} )}
</header> </header>

View file

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import Icon from "./view"; import Icon from './view';
export default connect(null, null)(Icon); export default connect(null, null)(Icon);

View file

@ -1,6 +1,6 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import * as icons from "constants/icons"; import * as icons from 'constants/icons';
export default class Icon extends React.PureComponent { export default class Icon extends React.PureComponent {
static propTypes = { static propTypes = {
@ -15,17 +15,17 @@ export default class Icon extends React.PureComponent {
getIconClass() { getIconClass() {
const { icon } = this.props; const { icon } = this.props;
return icon.startsWith("icon-") ? icon : "icon-" + icon; return icon.startsWith('icon-') ? icon : `icon-${icon}`;
} }
getIconTitle() { getIconTitle() {
switch (this.props.icon) { switch (this.props.icon) {
case icons.FEATURED: case icons.FEATURED:
return __("Watch this and earn rewards."); return __('Watch this and earn rewards.');
case icons.LOCAL: case icons.LOCAL:
return __("You have a copy of this file."); return __('You have a copy of this file.');
default: default:
return ""; return '';
} }
} }
@ -33,8 +33,7 @@ export default class Icon extends React.PureComponent {
const className = this.getIconClass(), const className = this.getIconClass(),
title = this.getIconTitle(); title = this.getIconTitle();
const spanClassName = const spanClassName = `icon ${className}${this.props.fixed ? ' icon-fixed-width ' : ''}`;
"icon " + className + (this.props.fixed ? " icon-fixed-width " : "");
return <span className={spanClassName} title={title} />; return <span className={spanClassName} title={title} />;
} }

View file

@ -1,10 +1,7 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { import { selectUserInvitees, selectUserInviteStatusIsPending } from 'redux/selectors/user';
selectUserInvitees, import InviteList from './view';
selectUserInviteStatusIsPending,
} from "redux/selectors/user";
import InviteList from "./view";
const select = state => ({ const select = state => ({
invitees: selectUserInvitees(state), invitees: selectUserInvitees(state),

View file

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import { Icon } from "component/common"; import { Icon } from 'component/common';
import RewardLink from "component/rewardLink"; import RewardLink from 'component/rewardLink';
import rewards from "rewards.js"; import rewards from 'rewards.js';
class InviteList extends React.PureComponent { class InviteList extends React.PureComponent {
render() { render() {
@ -14,7 +14,7 @@ class InviteList extends React.PureComponent {
return ( return (
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__("Invite History")}</h3> <h3>{__('Invite History')}</h3>
</div> </div>
<div className="card__content"> <div className="card__content">
{invitees.length === 0 && ( {invitees.length === 0 && (
@ -24,38 +24,33 @@ class InviteList extends React.PureComponent {
<table className="table-standard table-stretch"> <table className="table-standard table-stretch">
<thead> <thead>
<tr> <tr>
<th>{__("Invitee Email")}</th> <th>{__('Invitee Email')}</th>
<th className="text-center">{__("Invite Status")}</th> <th className="text-center">{__('Invite Status')}</th>
<th className="text-center">{__("Reward")}</th> <th className="text-center">{__('Reward')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{invitees.map((invitee, index) => { {invitees.map((invitee, index) => (
return (
<tr key={index}> <tr key={index}>
<td>{invitee.email}</td> <td>{invitee.email}</td>
<td className="text-center"> <td className="text-center">
{invitee.invite_accepted ? ( {invitee.invite_accepted ? (
<Icon icon="icon-check" /> <Icon icon="icon-check" />
) : ( ) : (
<span className="empty">{__("unused")}</span> <span className="empty">{__('unused')}</span>
)} )}
</td> </td>
<td className="text-center"> <td className="text-center">
{invitee.invite_reward_claimed ? ( {invitee.invite_reward_claimed ? (
<Icon icon="icon-check" /> <Icon icon="icon-check" />
) : invitee.invite_reward_claimable ? ( ) : invitee.invite_reward_claimable ? (
<RewardLink <RewardLink label={__('claim')} reward_type={rewards.TYPE_REFERRAL} />
label={__("claim")}
reward_type={rewards.TYPE_REFERRAL}
/>
) : ( ) : (
<span className="empty">{__("unclaimable")}</span> <span className="empty">{__('unclaimable')}</span>
)} )}
</td> </td>
</tr> </tr>
); ))}
})}
</tbody> </tbody>
</table> </table>
)} )}
@ -63,7 +58,7 @@ class InviteList extends React.PureComponent {
<div className="card__content"> <div className="card__content">
<div className="help"> <div className="help">
{__( {__(
"The maximum number of invite rewards is currently limited. Invite reward can only be claimed if the invitee passes the humanness test." 'The maximum number of invite rewards is currently limited. Invite reward can only be claimed if the invitee passes the humanness test.'
)} )}
</div> </div>
</div> </div>

View file

@ -1,15 +1,15 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import InviteNew from "./view"; import InviteNew from './view';
import { import {
selectUserInvitesRemaining, selectUserInvitesRemaining,
selectUserInviteNewIsPending, selectUserInviteNewIsPending,
selectUserInviteNewErrorMessage, selectUserInviteNewErrorMessage,
} from "redux/selectors/user"; } from 'redux/selectors/user';
import rewards from "rewards"; import rewards from 'rewards';
import { makeSelectRewardAmountByType } from "redux/selectors/rewards"; import { makeSelectRewardAmountByType } from 'redux/selectors/rewards';
import { doUserInviteNew } from "redux/actions/user"; import { doUserInviteNew } from 'redux/actions/user';
const select = state => { const select = state => {
const selectReward = makeSelectRewardAmountByType(); const selectReward = makeSelectRewardAmountByType();

View file

@ -1,13 +1,13 @@
import React from "react"; import React from 'react';
import { BusyMessage, CreditAmount } from "component/common"; import { BusyMessage, CreditAmount } from 'component/common';
import { Form, FormRow, Submit } from "component/form.js"; import { Form, FormRow, Submit } from 'component/form.js';
class FormInviteNew extends React.PureComponent { class FormInviteNew extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
email: "", email: '',
}; };
} }
@ -39,7 +39,7 @@ class FormInviteNew extends React.PureComponent {
}} }}
/> />
<div className="form-row-submit"> <div className="form-row-submit">
<Submit label={__("Send Invite")} disabled={isPending} /> <Submit label={__('Send Invite')} disabled={isPending} />
</div> </div>
</Form> </Form>
); );
@ -61,7 +61,7 @@ class InviteNew extends React.PureComponent {
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<CreditAmount amount={rewardAmount} /> <CreditAmount amount={rewardAmount} />
<h3>{__("Invite a Friend")}</h3> <h3>{__('Invite a Friend')}</h3>
</div> </div>
{/* {/*
<div className="card__content"> <div className="card__content">
@ -71,16 +71,8 @@ class InviteNew extends React.PureComponent {
<p className="empty">{__("You have no invites.")}</p>} <p className="empty">{__("You have no invites.")}</p>}
</div> */} </div> */}
<div className="card__content"> <div className="card__content">
<p> <p>{__("Or an enemy. Or your cousin Jerry, who you're kind of unsure about.")}</p>
{__( <FormInviteNew errorMessage={errorMessage} inviteNew={inviteNew} isPending={isPending} />
"Or an enemy. Or your cousin Jerry, who you're kind of unsure about."
)}
</p>
<FormInviteNew
errorMessage={errorMessage}
inviteNew={inviteNew}
isPending={isPending}
/>
</div> </div>
</section> </section>
); );

View file

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { doNavigate } from "redux/actions/navigation"; import { doNavigate } from 'redux/actions/navigation';
import Link from "./view"; import Link from './view';
const perform = dispatch => ({ const perform = dispatch => ({
doNavigate: (path, params) => dispatch(doNavigate(path, params)), doNavigate: (path, params) => dispatch(doNavigate(path, params)),

View file

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import Icon from "component/icon"; import Icon from 'component/icon';
const Link = props => { const Link = props => {
const { const {
@ -20,15 +20,15 @@ const Link = props => {
} = props; } = props;
const combinedClassName = const combinedClassName =
(className || "") + (className || '') +
(!className && !button ? "button-text" : "") + // Non-button links get the same look as text buttons (!className && !button ? 'button-text' : '') + // Non-button links get the same look as text buttons
(button ? " button-block button-" + button + " button-set-item" : "") + (button ? ` button-block button-${button} button-set-item` : '') +
(disabled ? " disabled" : ""); (disabled ? ' disabled' : '');
const onClick = const onClick =
!props.onClick && navigate !props.onClick && navigate
? e => { ? event => {
e.stopPropagation(); event.stopPropagation();
doNavigate(navigate, navigateParams || {}); doNavigate(navigate, navigateParams || {});
} }
: props.onClick; : props.onClick;
@ -38,27 +38,23 @@ const Link = props => {
content = children; content = children;
} else { } else {
content = ( content = (
<span {...("button" in props ? { className: "button__content" } : {})}> <span {...('button' in props ? { className: 'button__content' } : {})}>
{icon ? <Icon icon={icon} fixed={true} /> : null} {icon ? <Icon icon={icon} fixed /> : null}
{label ? <span className="link-label">{label}</span> : null} {label ? <span className="link-label">{label}</span> : null}
{iconRight ? <Icon icon={iconRight} fixed={true} /> : null} {iconRight ? <Icon icon={iconRight} fixed /> : null}
</span> </span>
); );
} }
const linkProps = { const linkProps = {
className: combinedClassName, className: combinedClassName,
href: href || "javascript:;", href: href || 'javascript:;',
title, title,
onClick, onClick,
style, style,
}; };
return span ? ( return span ? <span {...linkProps}>{content}</span> : <a {...linkProps}>{content}</a>;
<span {...linkProps}>{content}</span>
) : (
<a {...linkProps}>{content}</a>
);
}; };
export default Link; export default Link;

View file

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import LinkTransaction from "./view"; import LinkTransaction from './view';
export default connect(null, null)(LinkTransaction); export default connect(null, null)(LinkTransaction);

View file

@ -1,11 +1,11 @@
import React from "react"; import React from 'react';
import Link from "component/link"; import Link from 'component/link';
const LinkTransaction = props => { const LinkTransaction = props => {
const { id } = props; const { id } = props;
const linkProps = Object.assign({}, props); const linkProps = Object.assign({}, props);
linkProps.href = "https://explorer.lbry.io/#!/transaction/" + id; linkProps.href = `https://explorer.lbry.io/#!/transaction/${id}`;
linkProps.label = id.substr(0, 7); linkProps.label = id.substr(0, 7);
return <Link {...linkProps} />; return <Link {...linkProps} />;

View file

@ -1,8 +1,8 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import lbry from "../lbry.js"; import lbry from '../lbry.js';
import { BusyMessage, Icon } from "./common.js"; import { BusyMessage, Icon } from './common.js';
import Link from "component/link"; import Link from 'component/link';
class LoadScreen extends React.PureComponent { class LoadScreen extends React.PureComponent {
static propTypes = { static propTypes = {
@ -26,7 +26,7 @@ class LoadScreen extends React.PureComponent {
}; };
render() { render() {
const imgSrc = lbry.imagePath("lbry-white-485x160.png"); const imgSrc = lbry.imagePath('lbry-white-485x160.png');
return ( return (
<div className="load-screen"> <div className="load-screen">
<img src={imgSrc} alt="LBRY" /> <img src={imgSrc} alt="LBRY" />
@ -37,15 +37,14 @@ class LoadScreen extends React.PureComponent {
) : ( ) : (
<span> <span>
<Icon icon="icon-warning" /> <Icon icon="icon-warning" />
{" " + this.props.message} {` ${this.props.message}`}
</span> </span>
)} )}
</h3> </h3>
<span <span
className={ className={`load-screen__details ${
"load-screen__details " + this.props.isWarning ? 'load-screen__details--warning' : ''
(this.props.isWarning ? "load-screen__details--warning" : "") }`}
}
> >
{this.props.details} {this.props.details}
</span> </span>

View file

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { Icon } from "./common.js"; import { Icon } from './common.js';
import Link from "component/link"; import Link from 'component/link';
export class DropDownMenuItem extends React.PureComponent { export class DropDownMenuItem extends React.PureComponent {
static propTypes = { static propTypes = {
@ -12,22 +12,22 @@ export class DropDownMenuItem extends React.PureComponent {
}; };
static defaultProps = { static defaultProps = {
iconPosition: "left", iconPosition: 'left',
}; };
render() { render() {
var icon = this.props.icon ? <Icon icon={this.props.icon} fixed /> : null; const icon = this.props.icon ? <Icon icon={this.props.icon} fixed /> : null;
return ( return (
<a <a
className="menu__menu-item" className="menu__menu-item"
onClick={this.props.onClick} onClick={this.props.onClick}
href={this.props.href || "javascript:"} href={this.props.href || 'javascript:'}
label={this.props.label} label={this.props.label}
> >
{this.props.iconPosition == "left" ? icon : null} {this.props.iconPosition == 'left' ? icon : null}
{this.props.label} {this.props.label}
{this.props.iconPosition == "left" ? null : icon} {this.props.iconPosition == 'left' ? null : icon}
</a> </a>
); );
} }
@ -47,7 +47,7 @@ export class DropDownMenu extends React.PureComponent {
componentWillUnmount() { componentWillUnmount() {
if (this._isWindowClickBound) { if (this._isWindowClickBound) {
window.removeEventListener("click", this.handleWindowClick, false); window.removeEventListener('click', this.handleWindowClick, false);
} }
} }
@ -57,7 +57,7 @@ export class DropDownMenu extends React.PureComponent {
}); });
if (!this.state.menuOpen && !this._isWindowClickBound) { if (!this.state.menuOpen && !this._isWindowClickBound) {
this._isWindowClickBound = true; this._isWindowClickBound = true;
window.addEventListener("click", this.handleWindowClick, false); window.addEventListener('click', this.handleWindowClick, false);
e.stopPropagation(); e.stopPropagation();
} }
return false; return false;
@ -70,12 +70,9 @@ export class DropDownMenu extends React.PureComponent {
}); });
} }
/*this will force "this" to always be the class, even when passed to an event listener*/ /* this will force "this" to always be the class, even when passed to an event listener */
handleWindowClick = e => { handleWindowClick = e => {
if ( if (this.state.menuOpen && (!this._menuDiv || !this._menuDiv.contains(e.target))) {
this.state.menuOpen &&
(!this._menuDiv || !this._menuDiv.contains(e.target))
) {
this.setState({ this.setState({
menuOpen: false, menuOpen: false,
}); });
@ -85,7 +82,7 @@ export class DropDownMenu extends React.PureComponent {
render() { render() {
if (!this.state.menuOpen && this._isWindowClickBound) { if (!this.state.menuOpen && this._isWindowClickBound) {
this._isWindowClickBound = false; this._isWindowClickBound = false;
window.removeEventListener("click", this.handleWindowClick, false); window.removeEventListener('click', this.handleWindowClick, false);
} }
return ( return (
<div className="menu-container"> <div className="menu-container">

View file

@ -1,10 +1,10 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { doNavigate } from "redux/actions/navigation"; import { doNavigate } from 'redux/actions/navigation';
import NsfwOverlay from "./view"; import NsfwOverlay from './view';
const perform = dispatch => ({ const perform = dispatch => ({
navigateSettings: () => dispatch(doNavigate("/settings")), navigateSettings: () => dispatch(doNavigate('/settings')),
}); });
export default connect(null, perform)(NsfwOverlay); export default connect(null, perform)(NsfwOverlay);

View file

@ -1,21 +1,17 @@
import React from "react"; import React from 'react';
import Link from "component/link"; import Link from 'component/link';
const NsfwOverlay = props => { const NsfwOverlay = props => (
return (
<div className="card-overlay"> <div className="card-overlay">
<p> <p>
{__( {__('This content is Not Safe For Work. To view adult content, please change your')}{' '}
"This content is Not Safe For Work. To view adult content, please change your"
)}{" "}
<Link <Link
className="button-text" className="button-text"
onClick={() => props.navigateSettings()} onClick={() => props.navigateSettings()}
label={__("Settings")} label={__('Settings')}
/>. />.
</p> </p>
</div> </div>
); );
};
export default NsfwOverlay; export default NsfwOverlay;

View file

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import PublishForm from "./view"; import PublishForm from './view';
import { selectBalance } from "redux/selectors/wallet"; import { selectBalance } from 'redux/selectors/wallet';
const select = state => ({ const select = state => ({
balance: selectBalance(state), balance: selectBalance(state),

View file

@ -1,15 +1,15 @@
import React from "react"; import React from 'react';
import lbryuri from "lbryuri"; import lbryuri from 'lbryuri';
import { FormRow } from "component/form.js"; import { FormRow } from 'component/form.js';
import { BusyMessage } from "component/common"; import { BusyMessage } from 'component/common';
import Link from "component/link"; import Link from 'component/link';
class ChannelSection extends React.PureComponent { class ChannelSection extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
newChannelName: "@", newChannelName: '@',
newChannelBid: 10, newChannelBid: 10,
addingChannel: false, addingChannel: false,
}; };
@ -17,7 +17,7 @@ class ChannelSection extends React.PureComponent {
handleChannelChange(event) { handleChannelChange(event) {
const channel = event.target.value; const channel = event.target.value;
if (channel === "new") this.setState({ addingChannel: true }); if (channel === 'new') this.setState({ addingChannel: true });
else { else {
this.setState({ addingChannel: false }); this.setState({ addingChannel: false });
this.props.handleChannelChange(event.target.value); this.props.handleChannelChange(event.target.value);
@ -25,21 +25,17 @@ class ChannelSection extends React.PureComponent {
} }
handleNewChannelNameChange(event) { handleNewChannelNameChange(event) {
const newChannelName = event.target.value.startsWith("@") const newChannelName = event.target.value.startsWith('@')
? event.target.value ? event.target.value
: "@" + event.target.value; : `@${event.target.value}`;
if ( if (newChannelName.length > 1 && !lbryuri.isValidName(newChannelName.substr(1), false)) {
newChannelName.length > 1 &&
!lbryuri.isValidName(newChannelName.substr(1), false)
) {
this.refs.newChannelName.showError( this.refs.newChannelName.showError(
__("LBRY channel names must contain only letters, numbers and dashes.") __('LBRY channel names must contain only letters, numbers and dashes.')
); );
return; return;
} else {
this.refs.newChannelName.clearError();
} }
this.refs.newChannelName.clearError();
this.setState({ this.setState({
newChannelName, newChannelName,
@ -57,9 +53,7 @@ class ChannelSection extends React.PureComponent {
const { newChannelBid } = this.state; const { newChannelBid } = this.state;
if (newChannelBid > balance) { if (newChannelBid > balance) {
this.refs.newChannelName.showError( this.refs.newChannelName.showError(__('Unable to create channel due to insufficient funds.'));
__("Unable to create channel due to insufficient funds.")
);
return; return;
} }
@ -85,19 +79,17 @@ class ChannelSection extends React.PureComponent {
this.setState({ this.setState({
creatingChannel: false, creatingChannel: false,
}); });
this.refs.newChannelName.showError( this.refs.newChannelName.showError(__('Unable to create channel due to an internal error.'));
__("Unable to create channel due to an internal error.")
);
}; };
this.props.createChannel(newChannelName, amount).then(success, failure); this.props.createChannel(newChannelName, amount).then(success, failure);
} }
render() { render() {
const lbcInputHelp = __( const lbcInputHelp = __(
"This LBC remains yours. It is a deposit to reserve the name and can be undone at any time." 'This LBC remains yours. It is a deposit to reserve the name and can be undone at any time.'
); );
const channel = this.state.addingChannel ? "new" : this.props.channel; const channel = this.state.addingChannel ? 'new' : this.props.channel;
const { fetchingChannels, channels = [] } = this.props; const { fetchingChannels, channels = [] } = this.props;
const channelSelector = ( const channelSelector = (
@ -109,7 +101,7 @@ class ChannelSection extends React.PureComponent {
value={channel} value={channel}
> >
<option key="anonymous" value="anonymous"> <option key="anonymous" value="anonymous">
{__("Anonymous")} {__('Anonymous')}
</option> </option>
{channels.map(({ name }) => ( {channels.map(({ name }) => (
<option key={name} value={name}> <option key={name} value={name}>
@ -117,7 +109,7 @@ class ChannelSection extends React.PureComponent {
</option> </option>
))} ))}
<option key="new" value="new"> <option key="new" value="new">
{__("New channel...")} {__('New channel...')}
</option> </option>
</FormRow> </FormRow>
); );
@ -125,12 +117,10 @@ class ChannelSection extends React.PureComponent {
return ( return (
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h4>{__("Channel Name")}</h4> <h4>{__('Channel Name')}</h4>
<div className="card__subtitle"> <div className="card__subtitle">
{__( {__('This is a username or handle that your content can be found under.')}{' '}
"This is a username or handle that your content can be found under." {__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}
)}{" "}
{__("Ex. @Marvel, @TheBeatles, @BooksByJoe")}
</div> </div>
</div> </div>
<div className="card__content"> <div className="card__content">
@ -143,13 +133,13 @@ class ChannelSection extends React.PureComponent {
{this.state.addingChannel && ( {this.state.addingChannel && (
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
label={__("Name")} label={__('Name')}
type="text" type="text"
onChange={this.handleNewChannelNameChange.bind(this)} onChange={this.handleNewChannelNameChange.bind(this)}
value={this.state.newChannelName} value={this.state.newChannelName}
/> />
<FormRow <FormRow
label={__("Deposit")} label={__('Deposit')}
postfix="LBC" postfix="LBC"
step="any" step="any"
min="0" min="0"
@ -163,9 +153,7 @@ class ChannelSection extends React.PureComponent {
<Link <Link
button="primary" button="primary"
label={ label={
!this.state.creatingChannel !this.state.creatingChannel ? __('Create channel') : __('Creating channel...')
? __("Create channel")
: __("Creating channel...")
} }
onClick={this.handleCreateChannelClick.bind(this)} onClick={this.handleCreateChannelClick.bind(this)}
disabled={this.state.creatingChannel} disabled={this.state.creatingChannel}

View file

@ -1,44 +1,44 @@
import React from "react"; import React from 'react';
import lbry from "lbry"; import lbry from 'lbry';
import lbryuri from "lbryuri"; import lbryuri from 'lbryuri';
import FormField from "component/formField"; import FormField from 'component/formField';
import { Form, FormRow, Submit } from "component/form.js"; import { Form, FormRow, Submit } from 'component/form.js';
import Link from "component/link"; import Link from 'component/link';
import FormFieldPrice from "component/formFieldPrice"; import FormFieldPrice from 'component/formFieldPrice';
import Modal from "modal/modal"; import Modal from 'modal/modal';
import { BusyMessage } from "component/common"; import { BusyMessage } from 'component/common';
import ChannelSection from "./internal/channelSection"; import ChannelSection from './internal/channelSection';
class PublishForm extends React.PureComponent { class PublishForm extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this._requiredFields = ["name", "bid", "meta_title", "tosAgree"]; this._requiredFields = ['name', 'bid', 'meta_title', 'tosAgree'];
this._defaultCopyrightNotice = "All rights reserved."; this._defaultCopyrightNotice = 'All rights reserved.';
this._defaultPaidPrice = 0.01; this._defaultPaidPrice = 0.01;
this.state = { this.state = {
id: null, id: null,
uri: null, uri: null,
rawName: "", rawName: '',
name: "", name: '',
bid: 10, bid: 10,
hasFile: false, hasFile: false,
feeAmount: "", feeAmount: '',
feeCurrency: "LBC", feeCurrency: 'LBC',
channel: "anonymous", channel: 'anonymous',
newChannelName: "@", newChannelName: '@',
newChannelBid: 10, newChannelBid: 10,
meta_title: "", meta_title: '',
meta_thumbnail: "", meta_thumbnail: '',
meta_description: "", meta_description: '',
meta_language: "en", meta_language: 'en',
meta_nsfw: "0", meta_nsfw: '0',
licenseType: "", licenseType: '',
copyrightNotice: this._defaultCopyrightNotice, copyrightNotice: this._defaultCopyrightNotice,
otherLicenseDescription: "", otherLicenseDescription: '',
otherLicenseUrl: "", otherLicenseUrl: '',
tosAgree: false, tosAgree: false,
prefillDone: false, prefillDone: false,
uploadProgress: 0.0, uploadProgress: 0.0,
@ -50,7 +50,7 @@ class PublishForm extends React.PureComponent {
isFee: false, isFee: false,
customUrl: false, customUrl: false,
source: null, source: null,
mode: "publish", mode: 'publish',
}; };
} }
@ -65,7 +65,7 @@ class PublishForm extends React.PureComponent {
const { bid } = this.state; const { bid } = this.state;
if (bid > balance) { if (bid > balance) {
this.handlePublishError({ message: "insufficient funds" }); this.handlePublishError({ message: 'insufficient funds' });
return; return;
} }
@ -74,16 +74,16 @@ class PublishForm extends React.PureComponent {
submitting: true, submitting: true,
}); });
let checkFields = this._requiredFields; const checkFields = this._requiredFields;
if (!this.myClaimExists()) { if (!this.myClaimExists()) {
checkFields.unshift("file"); checkFields.unshift('file');
} }
let missingFieldFound = false; let missingFieldFound = false;
for (let fieldName of checkFields) { for (const fieldName of checkFields) {
const field = this.refs[fieldName]; const field = this.refs[fieldName];
if (field) { if (field) {
if (field.getValue() === "" || field.getValue() === false) { if (field.getValue() === '' || field.getValue() === false) {
field.showRequiredError(); field.showRequiredError();
if (!missingFieldFound) { if (!missingFieldFound) {
field.focus(); field.focus();
@ -102,10 +102,10 @@ class PublishForm extends React.PureComponent {
return; return;
} }
let metadata = {}; const metadata = {};
for (let metaField of ["title", "description", "thumbnail", "language"]) { for (const metaField of ['title', 'description', 'thumbnail', 'language']) {
const value = this.state["meta_" + metaField]; const value = this.state[`meta_${metaField}`];
if (value) { if (value) {
metadata[metaField] = value; metadata[metaField] = value;
} }
@ -115,19 +115,19 @@ class PublishForm extends React.PureComponent {
metadata.licenseUrl = this.getLicenseUrl(); metadata.licenseUrl = this.getLicenseUrl();
metadata.nsfw = !!parseInt(this.state.meta_nsfw); metadata.nsfw = !!parseInt(this.state.meta_nsfw);
var doPublish = () => { const doPublish = () => {
var publishArgs = { const publishArgs = {
name: this.state.name, name: this.state.name,
bid: parseFloat(this.state.bid), bid: parseFloat(this.state.bid),
metadata: metadata, metadata,
...(this.state.channel != "new" && this.state.channel != "anonymous" ...(this.state.channel != 'new' && this.state.channel != 'anonymous'
? { channel_name: this.state.channel } ? { channel_name: this.state.channel }
: {}), : {}),
}; };
const { source } = this.state; const { source } = this.state;
if (this.refs.file.getValue() !== "") { if (this.refs.file.getValue() !== '') {
publishArgs.file_path = this.refs.file.getValue(); publishArgs.file_path = this.refs.file.getValue();
} else if (source) { } else if (source) {
publishArgs.sources = source; publishArgs.sources = source;
@ -145,7 +145,7 @@ class PublishForm extends React.PureComponent {
metadata.fee = { metadata.fee = {
currency: this.state.feeCurrency, currency: this.state.feeCurrency,
amount: parseFloat(this.state.feeAmount), amount: parseFloat(this.state.feeAmount),
address: address, address,
}; };
doPublish(); doPublish();
@ -157,18 +157,18 @@ class PublishForm extends React.PureComponent {
handlePublishStarted() { handlePublishStarted() {
this.setState({ this.setState({
modal: "publishStarted", modal: 'publishStarted',
}); });
} }
handlePublishStartedConfirmed() { handlePublishStartedConfirmed() {
this.props.navigate("/published"); this.props.navigate('/published');
} }
handlePublishError(error) { handlePublishError(error) {
this.setState({ this.setState({
submitting: false, submitting: false,
modal: "error", modal: 'error',
errorMessage: error.message, errorMessage: error.message,
}); });
} }
@ -220,13 +220,11 @@ class PublishForm extends React.PureComponent {
myClaimInfo() { myClaimInfo() {
const { id } = this.state; const { id } = this.state;
return Object.values(this.props.myClaims).find( return Object.values(this.props.myClaims).find(claim => claim.claim_id === id);
claim => claim.claim_id === id
);
} }
handleNameChange(event) { handleNameChange(event) {
var rawName = event.target.value; const rawName = event.target.value;
this.setState({ this.setState({
customUrl: Boolean(rawName.length), customUrl: Boolean(rawName.length),
}); });
@ -237,33 +235,31 @@ class PublishForm extends React.PureComponent {
nameChanged(rawName) { nameChanged(rawName) {
if (!rawName) { if (!rawName) {
this.setState({ this.setState({
rawName: "", rawName: '',
name: "", name: '',
uri: "", uri: '',
prefillDone: false, prefillDone: false,
mode: "publish", mode: 'publish',
}); });
return; return;
} }
if (!lbryuri.isValidName(rawName, false)) { if (!lbryuri.isValidName(rawName, false)) {
this.refs.name.showError( this.refs.name.showError(__('LBRY names must contain only letters, numbers and dashes.'));
__("LBRY names must contain only letters, numbers and dashes.")
);
return; return;
} }
let channel = ""; let channel = '';
if (this.state.channel !== "anonymous") channel = this.state.channel; if (this.state.channel !== 'anonymous') channel = this.state.channel;
const name = rawName.toLowerCase(); const name = rawName.toLowerCase();
const uri = lbryuri.build({ contentName: name, channelName: channel }); const uri = lbryuri.build({ contentName: name, channelName: channel });
this.setState({ this.setState({
rawName: rawName, rawName,
name: name, name,
prefillDone: false, prefillDone: false,
mode: "publish", mode: 'publish',
uri, uri,
}); });
@ -282,26 +278,18 @@ class PublishForm extends React.PureComponent {
const { claim_id, name, channel_name, amount } = claimInfo; const { claim_id, name, channel_name, amount } = claimInfo;
const { source, metadata } = claimInfo.value.stream; const { source, metadata } = claimInfo.value.stream;
const { const { license, licenseUrl, title, thumbnail, description, language, nsfw } = metadata;
license,
licenseUrl,
title,
thumbnail,
description,
language,
nsfw,
} = metadata;
let newState = { const newState = {
id: claim_id, id: claim_id,
channel: channel_name || "anonymous", channel: channel_name || 'anonymous',
bid: amount, bid: amount,
meta_title: title, meta_title: title,
meta_thumbnail: thumbnail, meta_thumbnail: thumbnail,
meta_description: description, meta_description: description,
meta_language: language, meta_language: language,
meta_nsfw: nsfw, meta_nsfw: nsfw,
mode: "edit", mode: 'edit',
prefillDone: true, prefillDone: true,
rawName: name, rawName: name,
name, name,
@ -309,21 +297,18 @@ class PublishForm extends React.PureComponent {
}; };
if (license == this._defaultCopyrightNotice) { if (license == this._defaultCopyrightNotice) {
newState.licenseType = "copyright"; newState.licenseType = 'copyright';
newState.copyrightNotice = this._defaultCopyrightNotice; newState.copyrightNotice = this._defaultCopyrightNotice;
} else { } else {
// If the license URL or description matches one of the drop-down options, use that // If the license URL or description matches one of the drop-down options, use that
let licenseType = "other"; // Will be overridden if we find a match let licenseType = 'other'; // Will be overridden if we find a match
for (let option of this._meta_license.getOptions()) { for (const option of this._meta_license.getOptions()) {
if ( if (option.getAttribute('data-url') === licenseUrl || option.text === license) {
option.getAttribute("data-url") === licenseUrl ||
option.text === license
) {
licenseType = option.value; licenseType = option.value;
} }
} }
if (licenseType == "other") { if (licenseType == 'other') {
newState.otherLicenseDescription = license; newState.otherLicenseDescription = license;
newState.otherLicenseUrl = licenseUrl; newState.otherLicenseUrl = licenseUrl;
} }
@ -349,10 +334,7 @@ class PublishForm extends React.PureComponent {
handleFeePrefChange(feeEnabled) { handleFeePrefChange(feeEnabled) {
this.setState({ this.setState({
isFee: feeEnabled, isFee: feeEnabled,
feeAmount: feeAmount: this.state.feeAmount == '' ? this._defaultPaidPrice : this.state.feeAmount,
this.state.feeAmount == ""
? this._defaultPaidPrice
: this.state.feeAmount,
}); });
} }
@ -363,7 +345,7 @@ class PublishForm extends React.PureComponent {
* more complex logic and the final value is determined at submit time. * more complex logic and the final value is determined at submit time.
*/ */
this.setState({ this.setState({
["meta_" + event.target.name]: event.target.value, [`meta_${event.target.name}`]: event.target.value,
}); });
} }
@ -399,7 +381,7 @@ class PublishForm extends React.PureComponent {
handleChannelChange(channelName) { handleChannelChange(channelName) {
this.setState({ this.setState({
mode: "publish", mode: 'publish',
channel: channelName, channel: channelName,
}); });
const nameChanged = () => this.nameChanged(this.state.rawName); const nameChanged = () => this.nameChanged(this.state.rawName);
@ -414,9 +396,9 @@ class PublishForm extends React.PureComponent {
getLicense() { getLicense() {
switch (this.state.licenseType) { switch (this.state.licenseType) {
case "copyright": case 'copyright':
return this.state.copyrightNotice; return this.state.copyrightNotice;
case "other": case 'other':
return this.state.otherLicenseDescription; return this.state.otherLicenseDescription;
default: default:
return this._meta_license.getSelectedElement().text; return this._meta_license.getSelectedElement().text;
@ -425,12 +407,12 @@ class PublishForm extends React.PureComponent {
getLicenseUrl() { getLicenseUrl() {
switch (this.state.licenseType) { switch (this.state.licenseType) {
case "copyright": case 'copyright':
return ""; return '';
case "other": case 'other':
return this.state.otherLicenseUrl; return this.state.otherLicenseUrl;
default: default:
return this._meta_license.getSelectedElement().getAttribute("data-url"); return this._meta_license.getSelectedElement().getAttribute('data-url');
} }
} }
@ -450,8 +432,8 @@ class PublishForm extends React.PureComponent {
const { mode } = this.state; const { mode } = this.state;
if (this.refs.file.getValue()) { if (this.refs.file.getValue()) {
this.setState({ hasFile: true }); this.setState({ hasFile: true });
if (!this.state.customUrl && mode !== "edit") { if (!this.state.customUrl && mode !== 'edit') {
let fileName = this._getFileName(this.refs.file.getValue()); const fileName = this._getFileName(this.refs.file.getValue());
this.nameChanged(fileName); this.nameChanged(fileName);
} }
} else { } else {
@ -460,11 +442,11 @@ class PublishForm extends React.PureComponent {
} }
_getFileName(fileName) { _getFileName(fileName) {
const path = require("path"); const path = require('path');
const extension = path.extname(fileName); const extension = path.extname(fileName);
fileName = path.basename(fileName, extension); fileName = path.basename(fileName, extension);
fileName = fileName.replace(lbryuri.REGEXP_INVALID_URI, ""); fileName = fileName.replace(lbryuri.REGEXP_INVALID_URI, '');
return fileName; return fileName;
} }
@ -474,23 +456,20 @@ class PublishForm extends React.PureComponent {
const claim = this.claim(); const claim = this.claim();
if (prefillDone) { if (prefillDone) {
return __("Existing claim data was prefilled"); return __('Existing claim data was prefilled');
} }
if (uri && resolvingUris.indexOf(uri) !== -1 && claim === undefined) { if (uri && resolvingUris.indexOf(uri) !== -1 && claim === undefined) {
return __("Checking..."); return __('Checking...');
} else if (!name) { } else if (!name) {
return __("Select a URL for this publish."); return __('Select a URL for this publish.');
} else if (!claim) { } else if (!claim) {
return __("This URL is unused."); return __('This URL is unused.');
} else if (this.myClaimExists() && !prefillDone) { } else if (this.myClaimExists() && !prefillDone) {
return ( return (
<span> <span>
{__("You already have a claim with this name.")}{" "} {__('You already have a claim with this name.')}{' '}
<Link <Link label={__('Edit existing claim')} onClick={() => this.handleEditClaim()} />
label={__("Edit existing claim")}
onClick={() => this.handleEditClaim()}
/>
</span> </span>
); );
} else if (claim) { } else if (claim) {
@ -504,7 +483,7 @@ class PublishForm extends React.PureComponent {
)} )}
</span> </span>
); );
} else { }
return ( return (
<span> <span>
{__( {__(
@ -515,9 +494,7 @@ class PublishForm extends React.PureComponent {
</span> </span>
); );
} }
} else { return '';
return "";
}
} }
closeModal() { closeModal() {
@ -529,14 +506,12 @@ class PublishForm extends React.PureComponent {
render() { render() {
const { mode, submitting } = this.state; const { mode, submitting } = this.state;
const lbcInputHelp = __( const lbcInputHelp = __('This LBC remains yours and the deposit can be undone at any time.');
"This LBC remains yours and the deposit can be undone at any time."
);
let submitLabel = !submitting ? __("Publish") : __("Publishing..."); let submitLabel = !submitting ? __('Publish') : __('Publishing...');
if (mode === "edit") { if (mode === 'edit') {
submitLabel = !submitting ? __("Update") : __("Updating..."); submitLabel = !submitting ? __('Update') : __('Updating...');
} }
return ( return (
@ -544,10 +519,8 @@ class PublishForm extends React.PureComponent {
<Form onSubmit={this.handleSubmit.bind(this)}> <Form onSubmit={this.handleSubmit.bind(this)}>
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h4>{__("Content")}</h4> <h4>{__('Content')}</h4>
<div className="card__subtitle"> <div className="card__subtitle">{__('What are you publishing?')}</div>
{__("What are you publishing?")}
</div>
</div> </div>
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
@ -571,7 +544,7 @@ class PublishForm extends React.PureComponent {
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
ref="meta_title" ref="meta_title"
label={__("Title")} label={__('Title')}
type="text" type="text"
name="title" name="title"
value={this.state.meta_title} value={this.state.meta_title}
@ -584,7 +557,7 @@ class PublishForm extends React.PureComponent {
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
type="text" type="text"
label={__("Thumbnail URL")} label={__('Thumbnail URL')}
name="thumbnail" name="thumbnail"
value={this.state.meta_thumbnail} value={this.state.meta_thumbnail}
placeholder="http://spee.ch/mylogo" placeholder="http://spee.ch/mylogo"
@ -596,11 +569,11 @@ class PublishForm extends React.PureComponent {
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
type="SimpleMDE" type="SimpleMDE"
label={__("Description")} label={__('Description')}
ref="meta_description" ref="meta_description"
name="description" name="description"
value={this.state.meta_description} value={this.state.meta_description}
placeholder={__("Description of your content")} placeholder={__('Description of your content')}
onChange={text => { onChange={text => {
this.handleDescriptionChanged(text); this.handleDescriptionChanged(text);
}} }}
@ -608,7 +581,7 @@ class PublishForm extends React.PureComponent {
</div> </div>
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
label={__("Language")} label={__('Language')}
type="select" type="select"
value={this.state.meta_language} value={this.state.meta_language}
name="language" name="language"
@ -616,19 +589,19 @@ class PublishForm extends React.PureComponent {
this.handleMetadataChange(event); this.handleMetadataChange(event);
}} }}
> >
<option value="en">{__("English")}</option> <option value="en">{__('English')}</option>
<option value="zh">{__("Chinese")}</option> <option value="zh">{__('Chinese')}</option>
<option value="fr">{__("French")}</option> <option value="fr">{__('French')}</option>
<option value="de">{__("German")}</option> <option value="de">{__('German')}</option>
<option value="jp">{__("Japanese")}</option> <option value="jp">{__('Japanese')}</option>
<option value="ru">{__("Russian")}</option> <option value="ru">{__('Russian')}</option>
<option value="es">{__("Spanish")}</option> <option value="es">{__('Spanish')}</option>
</FormRow> </FormRow>
</div> </div>
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
type="select" type="select"
label={__("Maturity")} label={__('Maturity')}
value={this.state.meta_nsfw} value={this.state.meta_nsfw}
name="nsfw" name="nsfw"
onChange={event => { onChange={event => {
@ -636,8 +609,8 @@ class PublishForm extends React.PureComponent {
}} }}
> >
{/* <option value=""></option> */} {/* <option value=""></option> */}
<option value="0">{__("All Ages")}</option> <option value="0">{__('All Ages')}</option>
<option value="1">{__("Adults Only")}</option> <option value="1">{__('Adults Only')}</option>
</FormRow> </FormRow>
</div> </div>
</div> </div>
@ -646,14 +619,12 @@ class PublishForm extends React.PureComponent {
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h4>{__("Price")}</h4> <h4>{__('Price')}</h4>
<div className="card__subtitle"> <div className="card__subtitle">{__('How much does this content cost?')}</div>
{__("How much does this content cost?")}
</div>
</div> </div>
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
label={__("Free")} label={__('Free')}
type="radio" type="radio"
name="isFree" name="isFree"
onChange={() => this.handleFeePrefChange(false)} onChange={() => this.handleFeePrefChange(false)}
@ -662,27 +633,26 @@ class PublishForm extends React.PureComponent {
<FormField <FormField
type="radio" type="radio"
name="isFree" name="isFree"
label={!this.state.isFee ? __("Choose price...") : __("Price ")} label={!this.state.isFee ? __('Choose price...') : __('Price ')}
onChange={() => { onChange={() => {
this.handleFeePrefChange(true); this.handleFeePrefChange(true);
}} }}
checked={this.state.isFee} checked={this.state.isFee}
/> />
<span className={!this.state.isFee ? "hidden" : ""}> <span className={!this.state.isFee ? 'hidden' : ''}>
<FormFieldPrice <FormFieldPrice
min="0" min="0"
defaultValue={{ defaultValue={{
amount: this._defaultPaidPrice, amount: this._defaultPaidPrice,
currency: "LBC", currency: 'LBC',
}} }}
onChange={val => this.handleFeeChange(val)} onChange={val => this.handleFeeChange(val)}
/> />
</span> </span>
{this.state.isFee && {this.state.isFee && this.state.feeCurrency.toUpperCase() != 'LBC' ? (
this.state.feeCurrency.toUpperCase() != "LBC" ? (
<div className="form-field__helper"> <div className="form-field__helper">
{__( {__(
"All content fees are charged in LBC. For non-LBC payment methods, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase." 'All content fees are charged in LBC. For non-LBC payment methods, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.'
)} )}
</div> </div>
) : null} ) : null}
@ -690,7 +660,7 @@ class PublishForm extends React.PureComponent {
</section> </section>
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h4>{__("License")}</h4> <h4>{__('License')}</h4>
</div> </div>
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
@ -703,61 +673,51 @@ class PublishForm extends React.PureComponent {
this.handleLicenseTypeChange(event); this.handleLicenseTypeChange(event);
}} }}
> >
<option>{__("None")}</option> <option>{__('None')}</option>
<option value="publicDomain">{__("Public Domain")}</option> <option value="publicDomain">{__('Public Domain')}</option>
<option <option
value="cc-by" value="cc-by"
data-url="https://creativecommons.org/licenses/by/4.0/legalcode" data-url="https://creativecommons.org/licenses/by/4.0/legalcode"
> >
{__("Creative Commons Attribution 4.0 International")} {__('Creative Commons Attribution 4.0 International')}
</option> </option>
<option <option
value="cc-by-sa" value="cc-by-sa"
data-url="https://creativecommons.org/licenses/by-sa/4.0/legalcode" data-url="https://creativecommons.org/licenses/by-sa/4.0/legalcode"
> >
{__( {__('Creative Commons Attribution-ShareAlike 4.0 International')}
"Creative Commons Attribution-ShareAlike 4.0 International"
)}
</option> </option>
<option <option
value="cc-by-nd" value="cc-by-nd"
data-url="https://creativecommons.org/licenses/by-nd/4.0/legalcode" data-url="https://creativecommons.org/licenses/by-nd/4.0/legalcode"
> >
{__( {__('Creative Commons Attribution-NoDerivatives 4.0 International')}
"Creative Commons Attribution-NoDerivatives 4.0 International"
)}
</option> </option>
<option <option
value="cc-by-nc" value="cc-by-nc"
data-url="https://creativecommons.org/licenses/by-nc/4.0/legalcode" data-url="https://creativecommons.org/licenses/by-nc/4.0/legalcode"
> >
{__( {__('Creative Commons Attribution-NonCommercial 4.0 International')}
"Creative Commons Attribution-NonCommercial 4.0 International"
)}
</option> </option>
<option <option
value="cc-by-nc-sa" value="cc-by-nc-sa"
data-url="https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode" data-url="https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode"
> >
{__( {__('Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International')}
"Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International"
)}
</option> </option>
<option <option
value="cc-by-nc-nd" value="cc-by-nc-nd"
data-url="https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode" data-url="https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode"
> >
{__( {__('Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International')}
"Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International"
)}
</option> </option>
<option value="copyright">{__("Copyrighted...")}</option> <option value="copyright">{__('Copyrighted...')}</option>
<option value="other">{__("Other...")}</option> <option value="other">{__('Other...')}</option>
</FormRow> </FormRow>
{this.state.licenseType == "copyright" ? ( {this.state.licenseType == 'copyright' ? (
<FormRow <FormRow
label={__("Copyright notice")} label={__('Copyright notice')}
type="text" type="text"
name="copyright-notice" name="copyright-notice"
value={this.state.copyrightNotice} value={this.state.copyrightNotice}
@ -767,9 +727,9 @@ class PublishForm extends React.PureComponent {
/> />
) : null} ) : null}
{this.state.licenseType == "other" ? ( {this.state.licenseType == 'other' ? (
<FormRow <FormRow
label={__("License description")} label={__('License description')}
type="text" type="text"
name="other-license-description" name="other-license-description"
value={this.state.otherLicenseDescription} value={this.state.otherLicenseDescription}
@ -779,9 +739,9 @@ class PublishForm extends React.PureComponent {
/> />
) : null} ) : null}
{this.state.licenseType == "other" ? ( {this.state.licenseType == 'other' ? (
<FormRow <FormRow
label={__("License URL")} label={__('License URL')}
type="text" type="text"
name="other-license-url" name="other-license-url"
value={this.state.otherLicenseUrl} value={this.state.otherLicenseUrl}
@ -801,23 +761,18 @@ class PublishForm extends React.PureComponent {
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h4>{__("Content URL")}</h4> <h4>{__('Content URL')}</h4>
<div className="card__subtitle"> <div className="card__subtitle">
{__( {__(
"This is the exact address where people find your content (ex. lbry://myvideo)." 'This is the exact address where people find your content (ex. lbry://myvideo).'
)}{" "} )}{' '}
<Link <Link label={__('Learn more')} href="https://lbry.io/faq/naming" />.
label={__("Learn more")}
href="https://lbry.io/faq/naming"
/>.
</div> </div>
</div> </div>
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
prefix={`lbry://${ prefix={`lbry://${
this.state.channel === "anonymous" this.state.channel === 'anonymous' ? '' : `${this.state.channel}/`
? ""
: `${this.state.channel}/`
}`} }`}
type="text" type="text"
ref="name" ref="name"
@ -835,7 +790,7 @@ class PublishForm extends React.PureComponent {
ref="bid" ref="bid"
type="number" type="number"
step="any" step="any"
label={__("Deposit")} label={__('Deposit')}
postfix="LBC" postfix="LBC"
onChange={event => { onChange={event => {
this.handleBidChange(event); this.handleBidChange(event);
@ -847,23 +802,23 @@ class PublishForm extends React.PureComponent {
/> />
</div> </div>
) : ( ) : (
"" ''
)} )}
</section> </section>
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h4>{__("Terms of Service")}</h4> <h4>{__('Terms of Service')}</h4>
</div> </div>
<div className="card__content"> <div className="card__content">
<FormRow <FormRow
ref="tosAgree" ref="tosAgree"
label={ label={
<span> <span>
{__("I agree to the")}{" "} {__('I agree to the')}{' '}
<Link <Link
href="https://www.lbry.io/termsofservice" href="https://www.lbry.io/termsofservice"
label={__("LBRY terms of service")} label={__('LBRY terms of service')}
/> />
</span> </span>
} }
@ -878,36 +833,27 @@ class PublishForm extends React.PureComponent {
<div className="card-series-submit"> <div className="card-series-submit">
<Submit <Submit
label={ label={!this.state.submitting ? __('Publish') : __('Publishing...')}
!this.state.submitting ? __("Publish") : __("Publishing...")
}
disabled={ disabled={
this.props.balance <= 0 || this.props.balance <= 0 ||
this.state.submitting || this.state.submitting ||
(this.state.uri && (this.state.uri && this.props.resolvingUris.indexOf(this.state.uri) !== -1) ||
this.props.resolvingUris.indexOf(this.state.uri) !== -1) || (this.claim() && !this.topClaimIsMine() && this.state.bid <= this.topClaimValue())
(this.claim() &&
!this.topClaimIsMine() &&
this.state.bid <= this.topClaimValue())
} }
/> />
<Link <Link button="cancel" onClick={this.props.back} label={__('Cancel')} />
button="cancel"
onClick={this.props.back}
label={__("Cancel")}
/>
</div> </div>
</Form> </Form>
<Modal <Modal
isOpen={this.state.modal == "publishStarted"} isOpen={this.state.modal == 'publishStarted'}
contentLabel={__("File published")} contentLabel={__('File published')}
onConfirmed={event => { onConfirmed={event => {
this.handlePublishStartedConfirmed(event); this.handlePublishStartedConfirmed(event);
}} }}
> >
<p> <p>
{__("Your file has been published to LBRY at the address")}{" "} {__('Your file has been published to LBRY at the address')}{' '}
<code>{this.state.uri}</code>! <code>{this.state.uri}</code>!
</p> </p>
<p> <p>
@ -917,15 +863,14 @@ class PublishForm extends React.PureComponent {
</p> </p>
</Modal> </Modal>
<Modal <Modal
isOpen={this.state.modal == "error"} isOpen={this.state.modal == 'error'}
contentLabel={__("Error publishing file")} contentLabel={__('Error publishing file')}
onConfirmed={event => { onConfirmed={event => {
this.closeModal(event); this.closeModal(event);
}} }}
> >
{__( {__('The following error occurred when attempting to publish your file')}:{' '}
"The following error occurred when attempting to publish your file" {this.state.errorMessage}
)}: {this.state.errorMessage}
</Modal> </Modal>
</main> </main>
); );

View file

@ -1,16 +1,13 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { import {
makeSelectClaimRewardError, makeSelectClaimRewardError,
makeSelectRewardByType, makeSelectRewardByType,
makeSelectIsRewardClaimPending, makeSelectIsRewardClaimPending,
} from "redux/selectors/rewards"; } from 'redux/selectors/rewards';
import { doNavigate } from "redux/actions/navigation"; import { doNavigate } from 'redux/actions/navigation';
import { import { doClaimRewardType, doClaimRewardClearError } from 'redux/actions/rewards';
doClaimRewardType, import RewardLink from './view';
doClaimRewardClearError,
} from "redux/actions/rewards";
import RewardLink from "./view";
const makeSelect = () => { const makeSelect = () => {
const selectIsPending = makeSelectIsRewardClaimPending(); const selectIsPending = makeSelectIsRewardClaimPending();

View file

@ -1,33 +1,23 @@
import React from "react"; import React from 'react';
import Modal from "modal/modal"; import Modal from 'modal/modal';
import Link from "component/link"; import Link from 'component/link';
const RewardLink = props => { const RewardLink = props => {
const { const { reward, button, claimReward, clearError, errorMessage, label, isPending } = props;
reward,
button,
claimReward,
clearError,
errorMessage,
label,
isPending,
} = props;
return ( return (
<div className="reward-link"> <div className="reward-link">
<Link <Link
button={button} button={button}
disabled={isPending} disabled={isPending}
label={ label={isPending ? __('Claiming...') : label || __('Claim Reward')}
isPending ? __("Claiming...") : label ? label : __("Claim Reward")
}
onClick={() => { onClick={() => {
claimReward(reward); claimReward(reward);
}} }}
/> />
{errorMessage ? ( {errorMessage ? (
<Modal <Modal
isOpen={true} isOpen
contentLabel="Reward Claim Error" contentLabel="Reward Claim Error"
className="error-modal" className="error-modal"
onConfirmed={() => { onConfirmed={() => {
@ -37,7 +27,7 @@ const RewardLink = props => {
{errorMessage} {errorMessage}
</Modal> </Modal>
) : ( ) : (
"" ''
)} )}
</div> </div>
); );

View file

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { selectClaimedRewards } from "redux/selectors/rewards"; import { selectClaimedRewards } from 'redux/selectors/rewards';
import RewardListClaimed from "./view"; import RewardListClaimed from './view';
const select = state => ({ const select = state => ({
rewards: selectClaimedRewards(state), rewards: selectClaimedRewards(state),

View file

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import LinkTransaction from "component/linkTransaction"; import LinkTransaction from 'component/linkTransaction';
const RewardListClaimed = props => { const RewardListClaimed = props => {
const { rewards } = props; const { rewards } = props;
@ -17,27 +17,23 @@ const RewardListClaimed = props => {
<table className="table-standard table-stretch"> <table className="table-standard table-stretch">
<thead> <thead>
<tr> <tr>
<th>{__("Title")}</th> <th>{__('Title')}</th>
<th>{__("Amount")}</th> <th>{__('Amount')}</th>
<th>{__("Transaction")}</th> <th>{__('Transaction')}</th>
<th>{__("Date")}</th> <th>{__('Date')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{rewards.map(reward => { {rewards.map(reward => (
return (
<tr key={reward.id}> <tr key={reward.id}>
<td>{reward.reward_title}</td> <td>{reward.reward_title}</td>
<td>{reward.reward_amount}</td> <td>{reward.reward_amount}</td>
<td> <td>
<LinkTransaction id={reward.transaction_id} /> <LinkTransaction id={reward.transaction_id} />
</td> </td>
<td> <td>{reward.created_at.replace('Z', ' ').replace('T', ' ')}</td>
{reward.created_at.replace("Z", " ").replace("T", " ")}
</td>
</tr> </tr>
); ))}
})}
</tbody> </tbody>
</table> </table>
</div> </div>

View file

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { selectUnclaimedRewardValue } from "redux/selectors/rewards"; import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
import RewardSummary from "./view"; import RewardSummary from './view';
const select = state => ({ const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state), unclaimedRewardAmount: selectUnclaimedRewardValue(state),

View file

@ -1,7 +1,7 @@
// @flow // @flow
import React from "react"; import React from 'react';
import Link from "component/link"; import Link from 'component/link';
import { CreditAmount } from "component/common"; import { CreditAmount } from 'component/common';
type Props = { type Props = {
unclaimedRewardAmount: number, unclaimedRewardAmount: number,
@ -13,29 +13,20 @@ const RewardSummary = (props: Props) => {
return ( return (
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__("Rewards")}</h3> <h3>{__('Rewards')}</h3>
</div> </div>
<div className="card__content"> <div className="card__content">
{unclaimedRewardAmount > 0 ? ( {unclaimedRewardAmount > 0 ? (
<p> <p>
{__("You have")}{" "} {__('You have')} <CreditAmount amount={unclaimedRewardAmount} precision={8} />{' '}
<CreditAmount amount={unclaimedRewardAmount} precision={8} />{" "} {__('in unclaimed rewards')}.
{__("in unclaimed rewards")}.
</p> </p>
) : ( ) : (
<p> <p>{__('There are no rewards available at this time, please check back later')}.</p>
{__(
"There are no rewards available at this time, please check back later"
)}.
</p>
)} )}
</div> </div>
<div className="card__actions"> <div className="card__actions">
<Link <Link button="primary" navigate="/rewards" label={__('Claim Rewards')} />
button="primary"
navigate="/rewards"
label={__("Claim Rewards")}
/>
</div> </div>
</section> </section>
); );

View file

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import RewardTile from "./view"; import RewardTile from './view';
export default connect(null, null)(RewardTile); export default connect(null, null)(RewardTile);

View file

@ -1,8 +1,8 @@
import React from "react"; import React from 'react';
import { CreditAmount, Icon } from "component/common"; import { CreditAmount, Icon } from 'component/common';
import RewardLink from "component/rewardLink"; import RewardLink from 'component/rewardLink';
import Link from "component/link"; import Link from 'component/link';
import rewards from "rewards"; import rewards from 'rewards';
const RewardTile = props => { const RewardTile = props => {
const { reward } = props; const { reward } = props;
@ -19,12 +19,12 @@ const RewardTile = props => {
<div className="card__content">{reward.reward_description}</div> <div className="card__content">{reward.reward_description}</div>
<div className="card__actions "> <div className="card__actions ">
{reward.reward_type == rewards.TYPE_REFERRAL && ( {reward.reward_type == rewards.TYPE_REFERRAL && (
<Link button="alt" navigate="/invite" label={__("Go To Invites")} /> <Link button="alt" navigate="/invite" label={__('Go To Invites')} />
)} )}
{reward.reward_type !== rewards.TYPE_REFERRAL && {reward.reward_type !== rewards.TYPE_REFERRAL &&
(claimed ? ( (claimed ? (
<span> <span>
<Icon icon="icon-check" /> {__("Reward claimed.")} <Icon icon="icon-check" /> {__('Reward claimed.')}
</span> </span>
) : ( ) : (
<RewardLink button="alt" reward_type={reward.reward_type} /> <RewardLink button="alt" reward_type={reward.reward_type} />

View file

@ -1,10 +1,7 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import Router from "./view.jsx"; import Router from './view.jsx';
import { import { selectCurrentPage, selectCurrentParams } from 'redux/selectors/navigation.js';
selectCurrentPage,
selectCurrentParams,
} from "redux/selectors/navigation.js";
const select = state => ({ const select = state => ({
params: selectCurrentParams(state), params: selectCurrentParams(state),

View file

@ -1,23 +1,23 @@
import React from "react"; import React from 'react';
import SettingsPage from "page/settings"; import SettingsPage from 'page/settings';
import HelpPage from "page/help"; import HelpPage from 'page/help';
import ReportPage from "page/report.js"; import ReportPage from 'page/report.js';
import WalletPage from "page/wallet"; import WalletPage from 'page/wallet';
import GetCreditsPage from "../../page/getCredits"; import GetCreditsPage from '../../page/getCredits';
import SendReceivePage from "page/sendCredits"; import SendReceivePage from 'page/sendCredits';
import ShowPage from "page/show"; import ShowPage from 'page/show';
import PublishPage from "page/publish"; import PublishPage from 'page/publish';
import DiscoverPage from "page/discover"; import DiscoverPage from 'page/discover';
import RewardsPage from "page/rewards"; import RewardsPage from 'page/rewards';
import FileListDownloaded from "page/fileListDownloaded"; import FileListDownloaded from 'page/fileListDownloaded';
import FileListPublished from "page/fileListPublished"; import FileListPublished from 'page/fileListPublished';
import TransactionHistoryPage from "page/transactionHistory"; import TransactionHistoryPage from 'page/transactionHistory';
import ChannelPage from "page/channel"; import ChannelPage from 'page/channel';
import SearchPage from "page/search"; import SearchPage from 'page/search';
import AuthPage from "page/auth"; import AuthPage from 'page/auth';
import InvitePage from "page/invite"; import InvitePage from 'page/invite';
import BackupPage from "page/backup"; import BackupPage from 'page/backup';
import SubscriptionsPage from "page/subscriptions"; import SubscriptionsPage from 'page/subscriptions';
const route = (page, routesMap) => { const route = (page, routesMap) => {
const component = routesMap[page]; const component = routesMap[page];

View file

@ -1,15 +1,15 @@
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { import {
createShapeShift, createShapeShift,
shapeShiftInit, shapeShiftInit,
getCoinStats, getCoinStats,
clearShapeShift, clearShapeShift,
getActiveShift, getActiveShift,
} from "redux/actions/shape_shift"; } from 'redux/actions/shape_shift';
import { doShowSnackBar } from "redux/actions/app"; import { doShowSnackBar } from 'redux/actions/app';
import { selectReceiveAddress } from "redux/selectors/wallet"; import { selectReceiveAddress } from 'redux/selectors/wallet';
import { selectShapeShift } from "redux/selectors/shape_shift"; import { selectShapeShift } from 'redux/selectors/shape_shift';
import ShapeShift from "./view"; import ShapeShift from './view';
const select = state => ({ const select = state => ({
receiveAddress: selectReceiveAddress(state), receiveAddress: selectReceiveAddress(state),

View file

@ -1,11 +1,11 @@
// @flow // @flow
import * as React from "react"; import * as React from 'react';
import QRCode from "qrcode.react"; import QRCode from 'qrcode.react';
import * as statuses from "constants/shape_shift"; import * as statuses from 'constants/shape_shift';
import Address from "component/address"; import Address from 'component/address';
import Link from "component/link"; import Link from 'component/link';
import type { Dispatch } from "redux/actions/shape_shift"; import type { Dispatch } from 'redux/actions/shape_shift';
import ShiftMarketInfo from "./market_info"; import ShiftMarketInfo from './market_info';
type Props = { type Props = {
shiftState: ?string, shiftState: ?string,
@ -78,10 +78,10 @@ class ActiveShapeShift extends React.PureComponent<Props> {
{shiftState === statuses.NO_DEPOSITS && ( {shiftState === statuses.NO_DEPOSITS && (
<div> <div>
<p> <p>
Send up to{" "} Send up to{' '}
<span className="credit-amount--bold"> <span className="credit-amount--bold">
{originCoinDepositMax} {shiftCoinType} {originCoinDepositMax} {shiftCoinType}
</span>{" "} </span>{' '}
to the address below. to the address below.
</p> </p>
<ShiftMarketInfo <ShiftMarketInfo
@ -104,41 +104,32 @@ class ActiveShapeShift extends React.PureComponent<Props> {
{shiftState === statuses.RECEIVED && ( {shiftState === statuses.RECEIVED && (
<div className="card__content--extra-vertical-space"> <div className="card__content--extra-vertical-space">
<p> <p>
{__( {__('ShapeShift has received your payment! Sending the funds to your LBRY wallet.')}
"ShapeShift has received your payment! Sending the funds to your LBRY wallet."
)}
</p> </p>
<span className="help"> <span className="help">{__('This can take a while, especially with BTC.')}</span>
{__("This can take a while, especially with BTC.")}
</span>
</div> </div>
)} )}
{shiftState === statuses.COMPLETE && ( {shiftState === statuses.COMPLETE && (
<div className="card__content--extra-vertical-space"> <div className="card__content--extra-vertical-space">
<p> <p>{__('Transaction complete! You should see the new LBC in your wallet.')}</p>
{__(
"Transaction complete! You should see the new LBC in your wallet."
)}
</p>
</div> </div>
)} )}
<div className="card__actions card__actions--only-vertical"> <div className="card__actions card__actions--only-vertical">
<Link <Link
button={shiftState === statuses.COMPLETE ? "primary" : "alt"} button={shiftState === statuses.COMPLETE ? 'primary' : 'alt'}
onClick={clearShapeShift} onClick={clearShapeShift}
label={ label={
shiftState === statuses.COMPLETE || shiftState === statuses.COMPLETE || shiftState === statuses.RECEIVED
shiftState === statuses.RECEIVED ? __('Done')
? __("Done") : __('Cancel')
: __("Cancel")
} }
/> />
{shiftOrderId && ( {shiftOrderId && (
<span className="shapeshift__link"> <span className="shapeshift__link">
<Link <Link
button="text" button="text"
label={__("View the status on Shapeshift.io")} label={__('View the status on Shapeshift.io')}
href={`https://shapeshift.io/#/status/${shiftOrderId}`} href={`https://shapeshift.io/#/status/${shiftOrderId}`}
/> />
</span> </span>
@ -147,8 +138,8 @@ class ActiveShapeShift extends React.PureComponent<Props> {
shiftReturnAddress && ( shiftReturnAddress && (
<div className="shapeshift__actions-help"> <div className="shapeshift__actions-help">
<span className="help"> <span className="help">
If the transaction doesn't go through, ShapeShift will return If the transaction doesn't go through, ShapeShift will return your {shiftCoinType}{' '}
your {shiftCoinType} back to {shiftReturnAddress} back to {shiftReturnAddress}
</span> </span>
</div> </div>
)} )}

View file

@ -1,9 +1,9 @@
import React from "react"; import React from 'react';
import Link from "component/link"; import Link from 'component/link';
import { getExampleAddress } from "util/shape_shift"; import { getExampleAddress } from 'util/shape_shift';
import { Submit, FormRow } from "component/form"; import { Submit, FormRow } from 'component/form';
import type { ShapeShiftFormValues, Dispatch } from "redux/actions/shape_shift"; import type { ShapeShiftFormValues, Dispatch } from 'redux/actions/shape_shift';
import ShiftMarketInfo from "./market_info"; import ShiftMarketInfo from './market_info';
type ShapeShiftFormErrors = { type ShapeShiftFormErrors = {
returnAddress?: string, returnAddress?: string,
@ -50,7 +50,7 @@ export default (props: Props) => {
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-field"> <div className="form-field">
<span>{__("Exchange")} </span> <span>{__('Exchange')} </span>
<select <select
className="form-field__input form-field__input-select" className="form-field__input form-field__input-select"
name="originCoin" name="originCoin"
@ -65,7 +65,7 @@ export default (props: Props) => {
</option> </option>
))} ))}
</select> </select>
<span> {__("for LBC")}</span> <span> {__('for LBC')}</span>
<div className="shapeshift__tx-info"> <div className="shapeshift__tx-info">
{!updating && {!updating &&
originCoinDepositMax && ( originCoinDepositMax && (
@ -84,7 +84,7 @@ export default (props: Props) => {
type="text" type="text"
name="returnAddress" name="returnAddress"
placeholder={getExampleAddress(originCoin)} placeholder={getExampleAddress(originCoin)}
label={__("Return address")} label={__('Return address')}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
value={values.returnAddress} value={values.returnAddress}
@ -93,14 +93,13 @@ export default (props: Props) => {
/> />
<span className="help"> <span className="help">
<span> <span>
({__("optional but recommended")}) {__("We will return your")}{" "} ({__('optional but recommended')}) {__('We will return your')} {originCoin}{' '}
{originCoin}{" "}
{__("to this address if the transaction doesn't go through.")} {__("to this address if the transaction doesn't go through.")}
</span> </span>
</span> </span>
<div className="card__actions card__actions--only-vertical"> <div className="card__actions card__actions--only-vertical">
<Submit <Submit
label={__("Begin Conversion")} label={__('Begin Conversion')}
disabled={isSubmitting || !!Object.keys(errors).length} disabled={isSubmitting || !!Object.keys(errors).length}
/> />
</div> </div>

View file

@ -1,5 +1,5 @@
// @flow // @flow
import React from "react"; import React from 'react';
type Props = { type Props = {
shapeShiftRate: ?number, shapeShiftRate: ?number,
@ -21,13 +21,13 @@ export default (props: Props) => {
return ( return (
<div> <div>
<span className="help"> <span className="help">
{__("Receive")} {shapeShiftRate} LBC {__('Receive')} {shapeShiftRate} LBC
{" / "} {' / '}
{"1"} {originCoin} {__("less")} {originCoinDepositFee} LBC {__("fee")}. {'1'} {originCoin} {__('less')} {originCoinDepositFee} LBC {__('fee')}.
<br /> <br />
{__("Exchange max")}: {originCoinDepositMax} {originCoin} {__('Exchange max')}: {originCoinDepositMax} {originCoin}
<br /> <br />
{__("Exchange min")}: {originCoinDepositMin} {originCoin} {__('Exchange min')}: {originCoinDepositMin} {originCoin}
</span> </span>
</div> </div>
); );

View file

@ -1,18 +1,18 @@
// @flow // @flow
import * as React from "react"; import * as React from 'react';
import { shell } from "electron"; import { shell } from 'electron';
import { Formik } from "formik"; import { Formik } from 'formik';
import classnames from "classnames"; import classnames from 'classnames';
import * as statuses from "constants/shape_shift"; import * as statuses from 'constants/shape_shift';
import { validateShapeShiftForm } from "util/shape_shift"; import { validateShapeShiftForm } from 'util/shape_shift';
import Link from "component/link"; import Link from 'component/link';
import Spinner from "component/common/spinner"; import Spinner from 'component/common/spinner';
import { BusyMessage } from "component/common"; import { BusyMessage } from 'component/common';
import ShapeShiftForm from "./internal/form"; import ShapeShiftForm from './internal/form';
import ActiveShapeShift from "./internal/active-shift"; import ActiveShapeShift from './internal/active-shift';
import type { ShapeShiftState } from "redux/reducers/shape_shift"; import type { ShapeShiftState } from 'redux/reducers/shape_shift';
import type { Dispatch, ShapeShiftFormValues } from "redux/actions/shape_shift"; import type { Dispatch, ShapeShiftFormValues } from 'redux/actions/shape_shift';
type Props = { type Props = {
shapeShift: ShapeShiftState, shapeShift: ShapeShiftState,
@ -27,10 +27,7 @@ type Props = {
class ShapeShift extends React.PureComponent<Props> { class ShapeShift extends React.PureComponent<Props> {
componentDidMount() { componentDidMount() {
const { const { shapeShiftInit, shapeShift: { hasActiveShift, shiftSupportedCoins } } = this.props;
shapeShiftInit,
shapeShift: { hasActiveShift, shiftSupportedCoins },
} = this.props;
if (!hasActiveShift && !shiftSupportedCoins.length) { if (!hasActiveShift && !shiftSupportedCoins.length) {
// calls shapeshift to see list of supported coins for shifting // calls shapeshift to see list of supported coins for shifting
@ -70,8 +67,8 @@ class ShapeShift extends React.PureComponent<Props> {
const initialFormValues: ShapeShiftFormValues = { const initialFormValues: ShapeShiftFormValues = {
receiveAddress, receiveAddress,
originCoin: "BTC", originCoin: 'BTC',
returnAddress: "", returnAddress: '',
}; };
return ( return (
@ -80,19 +77,17 @@ class ShapeShift extends React.PureComponent<Props> {
// if the markup below changes for the initial render (form.jsx) there will be content jumping // if the markup below changes for the initial render (form.jsx) there will be content jumping
// the styling in shapeshift.scss will need to be updated to the correct min-height // the styling in shapeshift.scss will need to be updated to the correct min-height
<section <section
className={classnames("card shapeshift__wrapper", { className={classnames('card shapeshift__wrapper', {
"shapeshift__initial-wrapper": loading, 'shapeshift__initial-wrapper': loading,
})} })}
> >
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__("Convert Crypto to LBC")}</h3> <h3>{__('Convert Crypto to LBC')}</h3>
<p className="help"> <p className="help">
{__("Powered by ShapeShift. Read our FAQ")}{" "} {__('Powered by ShapeShift. Read our FAQ')}{' '}
<Link href="https://lbry.io/faq/shapeshift">{__("here")}</Link>. <Link href="https://lbry.io/faq/shapeshift">{__('here')}</Link>.
{hasActiveShift && {hasActiveShift &&
shiftState !== "complete" && ( shiftState !== 'complete' && <span>{__('This will update automatically.')}</span>}
<span>{__("This will update automatically.")}</span>
)}
</p> </p>
</div> </div>

View file

@ -1,8 +1,8 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { doRemoveSnackBarSnack } from "redux/actions/app"; import { doRemoveSnackBarSnack } from 'redux/actions/app';
import { selectSnackBarSnacks } from "redux/selectors/app"; import { selectSnackBarSnacks } from 'redux/selectors/app';
import SnackBar from "./view"; import SnackBar from './view';
const perform = dispatch => ({ const perform = dispatch => ({
removeSnack: () => dispatch(doRemoveSnackBarSnack()), removeSnack: () => dispatch(doRemoveSnackBarSnack()),

View file

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import Link from "component/link"; import Link from 'component/link';
class SnackBar extends React.PureComponent { class SnackBar extends React.PureComponent {
constructor(props) { constructor(props) {
@ -13,7 +13,7 @@ class SnackBar extends React.PureComponent {
const { snacks, removeSnack } = this.props; const { snacks, removeSnack } = this.props;
if (!snacks.length) { if (!snacks.length) {
this._hideTimeout = null; //should be unmounting anyway, but be safe? this._hideTimeout = null; // should be unmounting anyway, but be safe?
return null; return null;
} }
@ -32,11 +32,7 @@ class SnackBar extends React.PureComponent {
{message} {message}
{linkText && {linkText &&
linkTarget && ( linkTarget && (
<Link <Link navigate={linkTarget} className="snack-bar__action" label={linkText} />
navigate={linkTarget}
className="snack-bar__action"
label={linkText}
/>
)} )}
</div> </div>
); );

View file

@ -1,19 +1,14 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { import { selectCurrentModal, selectDaemonVersionMatched } from 'redux/selectors/app';
selectCurrentModal, import { doCheckDaemonVersion } from 'redux/actions/app';
selectDaemonVersionMatched, import SplashScreen from './view';
} from "redux/selectors/app";
import { doCheckDaemonVersion } from "redux/actions/app";
import SplashScreen from "./view";
const select = state => { const select = state => ({
return {
modal: selectCurrentModal(state), modal: selectCurrentModal(state),
daemonVersionMatched: selectDaemonVersionMatched(state), daemonVersionMatched: selectDaemonVersionMatched(state),
}; });
};
const perform = dispatch => ({ const perform = dispatch => ({
checkDaemonVersion: () => dispatch(doCheckDaemonVersion()), checkDaemonVersion: () => dispatch(doCheckDaemonVersion()),

View file

@ -1,11 +1,11 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import lbry from "lbry.js"; import lbry from 'lbry.js';
import LoadScreen from "../load_screen.js"; import LoadScreen from '../load_screen.js';
import ModalIncompatibleDaemon from "modal/modalIncompatibleDaemon"; import ModalIncompatibleDaemon from 'modal/modalIncompatibleDaemon';
import ModalUpgrade from "modal/modalUpgrade"; import ModalUpgrade from 'modal/modalUpgrade';
import ModalDownloading from "modal/modalDownloading"; import ModalDownloading from 'modal/modalDownloading';
import * as modals from "constants/modal_types"; import * as modals from 'constants/modal_types';
export class SplashScreen extends React.PureComponent { export class SplashScreen extends React.PureComponent {
static propTypes = { static propTypes = {
@ -17,8 +17,8 @@ export class SplashScreen extends React.PureComponent {
super(props); super(props);
this.state = { this.state = {
details: __("Starting daemon"), details: __('Starting daemon'),
message: __("Connecting"), message: __('Connecting'),
isRunning: false, isRunning: false,
isLagging: false, isLagging: false,
}; };
@ -32,19 +32,19 @@ export class SplashScreen extends React.PureComponent {
_updateStatusCallback(status) { _updateStatusCallback(status) {
const startupStatus = status.startup_status; const startupStatus = status.startup_status;
if (startupStatus.code == "started") { if (startupStatus.code == 'started') {
// Wait until we are able to resolve a name before declaring // Wait until we are able to resolve a name before declaring
// that we are done. // that we are done.
// TODO: This is a hack, and the logic should live in the daemon // TODO: This is a hack, and the logic should live in the daemon
// to give us a better sense of when we are actually started // to give us a better sense of when we are actually started
this.setState({ this.setState({
message: __("Testing Network"), message: __('Testing Network'),
details: __("Waiting for name resolution"), details: __('Waiting for name resolution'),
isLagging: false, isLagging: false,
isRunning: true, isRunning: true,
}); });
lbry.resolve({ uri: "lbry://one" }).then(() => { lbry.resolve({ uri: 'lbry://one' }).then(() => {
// Only leave the load screen if the daemon version matched; // Only leave the load screen if the daemon version matched;
// otherwise we'll notify the user at the end of the load screen. // otherwise we'll notify the user at the end of the load screen.
@ -54,24 +54,18 @@ export class SplashScreen extends React.PureComponent {
}); });
return; return;
} }
if ( if (status.blockchain_status && status.blockchain_status.blocks_behind > 0) {
status.blockchain_status &&
status.blockchain_status.blocks_behind > 0
) {
const format = const format =
status.blockchain_status.blocks_behind == 1 status.blockchain_status.blocks_behind == 1 ? '%s block behind' : '%s blocks behind';
? "%s block behind"
: "%s blocks behind";
this.setState({ this.setState({
message: __("Blockchain Sync"), message: __('Blockchain Sync'),
details: __(format, status.blockchain_status.blocks_behind), details: __(format, status.blockchain_status.blocks_behind),
isLagging: startupStatus.is_lagging, isLagging: startupStatus.is_lagging,
}); });
} else { } else {
this.setState({ this.setState({
message: __("Network Loading"), message: __('Network Loading'),
details: details: startupStatus.message + (startupStatus.is_lagging ? '' : '...'),
startupStatus.message + (startupStatus.is_lagging ? "" : "..."),
isLagging: startupStatus.is_lagging, isLagging: startupStatus.is_lagging,
}); });
} }
@ -90,9 +84,9 @@ export class SplashScreen extends React.PureComponent {
.catch(() => { .catch(() => {
this.setState({ this.setState({
isLagging: true, isLagging: true,
message: __("Connection Failure"), message: __('Connection Failure'),
details: __( details: __(
"Try closing all LBRY processes and starting again. If this still happens, your anti-virus software or firewall may be preventing LBRY from connecting. Contact hello@lbry.io if you think this is a software bug." 'Try closing all LBRY processes and starting again. If this still happens, your anti-virus software or firewall may be preventing LBRY from connecting. Contact hello@lbry.io if you think this is a software bug.'
), ),
}); });
}); });
@ -104,19 +98,13 @@ export class SplashScreen extends React.PureComponent {
return ( return (
<div> <div>
<LoadScreen <LoadScreen message={message} details={details} isWarning={isLagging} />
message={message}
details={details}
isWarning={isLagging}
/>
{/* Temp hack: don't show any modals on splash screen daemon is running; {/* Temp hack: don't show any modals on splash screen daemon is running;
daemon doesn't let you quit during startup, so the "Quit" buttons daemon doesn't let you quit during startup, so the "Quit" buttons
in the modals won't work. */} in the modals won't work. */}
{modal == "incompatibleDaemon" && {modal == 'incompatibleDaemon' && isRunning && <ModalIncompatibleDaemon />}
isRunning && <ModalIncompatibleDaemon />}
{modal == modals.UPGRADE && isRunning && <ModalUpgrade />} {modal == modals.UPGRADE && isRunning && <ModalUpgrade />}
{modal == modals.DOWNLOADING && {modal == modals.DOWNLOADING && isRunning && <ModalDownloading />}
isRunning && <ModalDownloading />}
</div> </div>
); );
} }

View file

@ -1,11 +1,8 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { import { selectCurrentPage, selectHeaderLinks } from 'redux/selectors/navigation';
selectCurrentPage, import { doNavigate } from 'redux/actions/navigation';
selectHeaderLinks, import SubHeader from './view';
} from "redux/selectors/navigation";
import { doNavigate } from "redux/actions/navigation";
import SubHeader from "./view";
const select = (state, props) => ({ const select = (state, props) => ({
currentPage: selectCurrentPage(state), currentPage: selectCurrentPage(state),

View file

@ -1,20 +1,18 @@
import React from "react"; import React from 'react';
import Link from "component/link"; import Link from 'component/link';
import classnames from "classnames"; import classnames from 'classnames';
const SubHeader = props => { const SubHeader = props => {
const { subLinks, currentPage, navigate, fullWidth, smallMargin } = props; const { subLinks, currentPage, navigate, fullWidth, smallMargin } = props;
const links = []; const links = [];
for (let link of Object.keys(subLinks)) { for (const link of Object.keys(subLinks)) {
links.push( links.push(
<Link <Link
onClick={event => navigate(`/${link}`, event)} onClick={event => navigate(`/${link}`, event)}
key={link} key={link}
className={ className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected'}
link == currentPage ? "sub-header-selected" : "sub-header-unselected"
}
> >
{subLinks[link]} {subLinks[link]}
</Link> </Link>
@ -23,9 +21,9 @@ const SubHeader = props => {
return ( return (
<nav <nav
className={classnames("sub-header", { className={classnames('sub-header', {
"sub-header--full-width": fullWidth, 'sub-header--full-width': fullWidth,
"sub-header--small-margin": smallMargin, 'sub-header--small-margin': smallMargin,
})} })}
> >
{links} {links}

View file

@ -1,10 +1,8 @@
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
doChannelSubscribe, import { selectSubscriptions } from 'redux/selectors/subscriptions';
doChannelUnsubscribe,
} from "redux/actions/subscriptions"; import SubscribeButton from './view';
import { selectSubscriptions } from "redux/selectors/subscriptions";;
import SubscribeButton from "./view";
const select = (state, props) => ({ const select = (state, props) => ({
subscriptions: selectSubscriptions(state), subscriptions: selectSubscriptions(state),
@ -12,5 +10,5 @@ const select = (state, props) => ({
export default connect(select, { export default connect(select, {
doChannelSubscribe, doChannelSubscribe,
doChannelUnsubscribe doChannelUnsubscribe,
})(SubscribeButton); })(SubscribeButton);

View file

@ -1,36 +1,27 @@
import React from "react"; import React from 'react';
import Link from "component/link"; import Link from 'component/link';
export default ({
channelName,
uri,
subscriptions,
doChannelSubscribe,
doChannelUnsubscribe
}) => {
export default ({ channelName, uri, subscriptions, doChannelSubscribe, doChannelUnsubscribe }) => {
const isSubscribed = const isSubscribed =
subscriptions subscriptions.map(subscription => subscription.channelName).indexOf(channelName) !== -1;
.map(subscription => subscription.channelName)
.indexOf(channelName) !== -1;
const subscriptionHandler = isSubscribed const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
? doChannelUnsubscribe
: doChannelSubscribe;
const subscriptionLabel = isSubscribed ? __("Unsubscribe") : __("Subscribe"); const subscriptionLabel = isSubscribed ? __('Unsubscribe') : __('Subscribe');
return channelName && uri ? ( return channelName && uri ? (
<div className="card__actions"> <div className="card__actions">
<Link <Link
iconRight={isSubscribed ? "" : "at"} iconRight={isSubscribed ? '' : 'at'}
button={isSubscribed ? "alt" : "primary"} button={isSubscribed ? 'alt' : 'primary'}
label={subscriptionLabel} label={subscriptionLabel}
onClick={() => subscriptionHandler({ onClick={() =>
subscriptionHandler({
channelName, channelName,
uri, uri,
})} })
}
/> />
</div> </div>
) : null; ) : null;
} };

View file

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { selectThemePath } from "redux/selectors/settings.js"; import { selectThemePath } from 'redux/selectors/settings.js';
import Theme from "./view"; import Theme from './view';
const select = state => ({ const select = state => ({
themePath: selectThemePath(state), themePath: selectThemePath(state),

View file

@ -1,4 +1,4 @@
import React from "react"; import React from 'react';
const Theme = props => { const Theme = props => {
const { themePath } = props; const { themePath } = props;
@ -7,14 +7,7 @@ const Theme = props => {
return null; return null;
} }
return ( return <link href={themePath} rel="stylesheet" type="text/css" media="screen,print" />;
<link
href={themePath}
rel="stylesheet"
type="text/css"
media="screen,print"
/>
);
}; };
export default Theme; export default Theme;

View file

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
export class ToolTip extends React.PureComponent { export class ToolTip extends React.PureComponent {
static propTypes = { static propTypes = {
@ -29,7 +29,7 @@ export class ToolTip extends React.PureComponent {
render() { render() {
return ( return (
<span className={"tooltip " + (this.props.className || "")}> <span className={`tooltip ${this.props.className || ''}`}>
<a <a
className="tooltip__link" className="tooltip__link"
onClick={() => { onClick={() => {
@ -39,9 +39,7 @@ export class ToolTip extends React.PureComponent {
{this.props.label} {this.props.label}
</a> </a>
<div <div
className={ className={`tooltip__body ${this.state.showTooltip ? '' : ' hidden'}`}
"tooltip__body " + (this.state.showTooltip ? "" : " hidden")
}
onMouseOut={() => { onMouseOut={() => {
this.handleTooltipMouseOut(); this.handleTooltipMouseOut();
}} }}

View file

@ -1,10 +1,10 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { doNavigate } from "redux/actions/navigation"; import { doNavigate } from 'redux/actions/navigation';
import { doOpenModal } from "redux/actions/app"; import { doOpenModal } from 'redux/actions/app';
import { selectClaimedRewardsByTransactionId } from "redux/selectors/rewards"; import { selectClaimedRewardsByTransactionId } from 'redux/selectors/rewards';
import { selectAllMyClaimsByOutpoint } from "redux/selectors/claims"; import { selectAllMyClaimsByOutpoint } from 'redux/selectors/claims';
import TransactionList from "./view"; import TransactionList from './view';
const select = state => ({ const select = state => ({
rewards: selectClaimedRewardsByTransactionId(state), rewards: selectClaimedRewardsByTransactionId(state),

View file

@ -1,10 +1,10 @@
import React from "react"; import React from 'react';
import LinkTransaction from "component/linkTransaction"; import LinkTransaction from 'component/linkTransaction';
import { CreditAmount } from "component/common"; import { CreditAmount } from 'component/common';
import DateTime from "component/dateTime"; import DateTime from 'component/dateTime';
import Link from "component/link"; import Link from 'component/link';
import lbryuri from "lbryuri"; import lbryuri from 'lbryuri';
import * as txnTypes from "constants/transaction_types"; import * as txnTypes from 'constants/transaction_types';
class TransactionListItem extends React.PureComponent { class TransactionListItem extends React.PureComponent {
abandonClaim() { abandonClaim() {
@ -16,21 +16,10 @@ class TransactionListItem extends React.PureComponent {
getLink(type) { getLink(type) {
if (type == txnTypes.TIP) { if (type == txnTypes.TIP) {
return ( return (
<Link <Link onClick={this.abandonClaim.bind(this)} icon="icon-unlock-alt" title={__('Unlock')} />
onClick={this.abandonClaim.bind(this)}
icon="icon-unlock-alt"
title={__("Unlock")}
/>
);
} else {
return (
<Link
onClick={this.abandonClaim.bind(this)}
icon="icon-trash"
title={__("Revoke")}
/>
); );
} }
return <Link onClick={this.abandonClaim.bind(this)} icon="icon-trash" title={__('Revoke')} />;
} }
capitalize(string) { capitalize(string) {
@ -51,9 +40,9 @@ class TransactionListItem extends React.PureComponent {
} = transaction; } = transaction;
const dateFormat = { const dateFormat = {
month: "short", month: 'short',
day: "numeric", day: 'numeric',
year: "numeric", year: 'numeric',
}; };
return ( return (
@ -61,41 +50,25 @@ class TransactionListItem extends React.PureComponent {
<td> <td>
{date ? ( {date ? (
<div> <div>
<DateTime <DateTime date={date} show={DateTime.SHOW_DATE} formatOptions={dateFormat} />
date={date}
show={DateTime.SHOW_DATE}
formatOptions={dateFormat}
/>
<div className="meta"> <div className="meta">
<DateTime date={date} show={DateTime.SHOW_TIME} /> <DateTime date={date} show={DateTime.SHOW_TIME} />
</div> </div>
</div> </div>
) : ( ) : (
<span className="empty">{__("Pending")}</span> <span className="empty">{__('Pending')}</span>
)} )}
</td> </td>
<td> <td>
<CreditAmount <CreditAmount amount={amount} look="plain" label={false} showPlus precision={8} />
amount={amount}
look="plain"
label={false}
showPlus={true}
precision={8}
/>
<br /> <br />
{fee != 0 && ( {fee != 0 && <CreditAmount amount={fee} look="fee" label={false} precision={8} />}
<CreditAmount amount={fee} look="fee" label={false} precision={8} />
)}
</td> </td>
<td> <td>
{this.capitalize(type)} {isRevokeable && this.getLink(type)} {this.capitalize(type)} {isRevokeable && this.getLink(type)}
</td> </td>
<td> <td>
{reward && ( {reward && <Link navigate="/rewards">{__('Reward: %s', reward.reward_title)}</Link>}
<Link navigate="/rewards">
{__("Reward: %s", reward.reward_title)}
</Link>
)}
{name && {name &&
claimId && ( claimId && (
<Link <Link

View file

@ -1,9 +1,9 @@
import React from "react"; import React from 'react';
import TransactionListItem from "./internal/TransactionListItem"; import TransactionListItem from './internal/TransactionListItem';
import FormField from "component/formField"; import FormField from 'component/formField';
import Link from "component/link"; import Link from 'component/link';
import * as icons from "constants/icons"; import * as icons from 'constants/icons';
import * as modals from "constants/modal_types"; import * as modals from 'constants/modal_types';
class TransactionList extends React.PureComponent { class TransactionList extends React.PureComponent {
constructor(props) { constructor(props) {
@ -39,48 +39,38 @@ class TransactionList extends React.PureComponent {
render() { render() {
const { emptyMessage, rewards, transactions } = this.props; const { emptyMessage, rewards, transactions } = this.props;
let transactionList = transactions.filter( const transactionList = transactions.filter(this.filterTransaction.bind(this));
this.filterTransaction.bind(this)
);
return ( return (
<div> <div>
{(transactionList.length || this.state.filter) && ( {(transactionList.length || this.state.filter) && (
<span className="sort-section"> <span className="sort-section">
{__("Filter")}{" "} {__('Filter')}{' '}
<FormField <FormField type="select" onChange={this.handleFilterChanged.bind(this)}>
type="select" <option value="">{__('All')}</option>
onChange={this.handleFilterChanged.bind(this)} <option value="spend">{__('Spends')}</option>
> <option value="receive">{__('Receives')}</option>
<option value="">{__("All")}</option> <option value="publish">{__('Publishes')}</option>
<option value="spend">{__("Spends")}</option> <option value="channel">{__('Channels')}</option>
<option value="receive">{__("Receives")}</option> <option value="tip">{__('Tips')}</option>
<option value="publish">{__("Publishes")}</option> <option value="support">{__('Supports')}</option>
<option value="channel">{__("Channels")}</option> <option value="update">{__('Updates')}</option>
<option value="tip">{__("Tips")}</option> </FormField>{' '}
<option value="support">{__("Supports")}</option> <Link href="https://lbry.io/faq/transaction-types" icon={icons.HELP_CIRCLE} />
<option value="update">{__("Updates")}</option>
</FormField>{" "}
<Link
href="https://lbry.io/faq/transaction-types"
icon={icons.HELP_CIRCLE}
/>
</span> </span>
)} )}
{!transactionList.length && ( {!transactionList.length && (
<div className="empty"> <div className="empty">{emptyMessage || __('No transactions to list.')}</div>
{emptyMessage || __("No transactions to list.")}
</div>
)} )}
{Boolean(transactionList.length) && ( {Boolean(transactionList.length) && (
<table className="table-standard table-transactions table-stretch"> <table className="table-standard table-transactions table-stretch">
<thead> <thead>
<tr> <tr>
<th>{__("Date")}</th> <th>{__('Date')}</th>
<th>{__("Amount (Fee)")}</th> <th>{__('Amount (Fee)')}</th>
<th>{__("Type")} </th> <th>{__('Type')} </th>
<th>{__("Details")} </th> <th>{__('Details')} </th>
<th>{__("Transaction")}</th> <th>{__('Transaction')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View file

@ -1,14 +1,14 @@
import React from "react"; import React from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { doFetchTransactions } from "redux/actions/wallet"; import { doFetchTransactions } from 'redux/actions/wallet';
import { import {
selectBalance, selectBalance,
selectRecentTransactions, selectRecentTransactions,
selectHasTransactions, selectHasTransactions,
selectIsFetchingTransactions, selectIsFetchingTransactions,
} from "redux/selectors/wallet"; } from 'redux/selectors/wallet';
import TransactionListRecent from "./view"; import TransactionListRecent from './view';
const select = state => ({ const select = state => ({
fetchingTransactions: selectIsFetchingTransactions(state), fetchingTransactions: selectIsFetchingTransactions(state),

View file

@ -1,8 +1,8 @@
import React from "react"; import React from 'react';
import { BusyMessage } from "component/common"; import { BusyMessage } from 'component/common';
import Link from "component/link"; import Link from 'component/link';
import TransactionList from "component/transactionList"; import TransactionList from 'component/transactionList';
import * as icons from "constants/icons"; import * as icons from 'constants/icons';
class TransactionListRecent extends React.PureComponent { class TransactionListRecent extends React.PureComponent {
componentWillMount() { componentWillMount() {
@ -15,16 +15,14 @@ class TransactionListRecent extends React.PureComponent {
return ( return (
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__("Recent Transactions")}</h3> <h3>{__('Recent Transactions')}</h3>
</div> </div>
<div className="card__content"> <div className="card__content">
{fetchingTransactions && ( {fetchingTransactions && <BusyMessage message={__('Loading transactions')} />}
<BusyMessage message={__("Loading transactions")} />
)}
{!fetchingTransactions && ( {!fetchingTransactions && (
<TransactionList <TransactionList
transactions={transactions} transactions={transactions}
emptyMessage={__("You have no recent transactions.")} emptyMessage={__('You have no recent transactions.')}
/> />
)} )}
</div> </div>
@ -32,7 +30,7 @@ class TransactionListRecent extends React.PureComponent {
<div className="card__actions card__actions--bottom"> <div className="card__actions card__actions--bottom">
<Link <Link
navigate="/history" navigate="/history"
label={__("Full History")} label={__('Full History')}
icon={icons.HISTORY} icon={icons.HISTORY}
className="no-underline" className="no-underline"
button="text" button="text"

Some files were not shown because too many files have changed in this diff Show more