diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 91764cc37..609a7ce07 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.19.1 +current_version = 0.19.4rc1 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..2d095999a --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "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": "warn", + "import/no-amd": "warn", + "func-names": ["warn", "as-needed"] + } +} diff --git a/.gitignore b/.gitignore index e20de7b15..023767f02 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /lbry-app /lbry-venv /static/daemon/lbrynet* +/static/locales /daemon/build /daemon/venv /daemon/requirements.txt diff --git a/.lintstagedrc b/.lintstagedrc new file mode 100644 index 000000000..bd9f228ce --- /dev/null +++ b/.lintstagedrc @@ -0,0 +1,12 @@ +{ + "linters": { + "src/**/*.{js,jsx,scss,json}": [ + "prettier --write", + "git add" + ], + "src/**/*.{js,jsx}": [ + "eslint --fix", + "git add" + ] + } +} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..1d3fce725 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "trailingComma": "es5", + "printWidth": 100, + "singleQuote": true +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e66e97c3e..5909b74bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,40 @@ Web UI version numbers should always match the corresponding version of LBRY App * * +## [0.19.3] - 2017-12-30 + +### Changed + * Improved internal code structuring by adding linting integration -- developers only (#891) + * Improved developer documentation (#910) + + +### Removed + * Removed email verification reward (#914) + + +### Fixed + * Added snackbar text in place where it was coming up blank (#902) + + + +## [0.19.2] - 2017-12-22 + +### Added + * Added copy address button to the Wallet Address component on Send / Receive (#875) + * Link to creators’ channels on homepage (#869) + * Pause playing video when file is opened (#880) + * Add captcha to verification process (#897) + + +### Changed + * Contributor documentation (#879) + + +### Fixed + * Linux app categorization (#877) + + + ## [0.19.1] - 2017-12-13 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e7d0dce20..578abc5f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,166 @@ -## Contributing to LBRY +# Contribute to LBRY -https://lbry.io/faq/contributing +You found this page! That means you are well on your way to contributing to the LBRY app. This +application is primarily written in JavaScript and is built on [Electron](https://electronjs.org) +while utilizing [React](https://reactjs.org) and [Redux](https://redux.js.org) for UI and +application state. + +## TL;DR? + +* [Here](https://github.com/lbryio/lbry-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+no%3Aassignee) + is a list of help wanted issues. +* Comment on an issue to let us know if you are going to work on it, don't take an issue that + someone reserved less than 3 days ago +* Submit a pull request and get paid in LBC +* Don't hesitate to contact us with any questions or comments + +## Choose an Issue + +LBRY is an open source project and therefore is developed out in the open for everyone to see. What +you see here are the latest source code changes and issues. + +Since LBRY is based on a decentralized community, we believe that the app will be stronger if it +receives contributions from individuals outside the core team -- such as yourself! + +To make contributing as easy and rewarding of possible, we have instituted the following system: + +* Anyone can view all issues in the system by clicking on the + [Issues](https://github.com/lbryio/lbry-app/issues) button at the top of the page. Feel free to + add an issue if you think we have missed something (and you might earn some LBC in the process + because we do tip people for reporting bugs). +* Once on the [Issues](https://github.com/lbryio/lbry-app/issues) page, a potential contributor can + filter issues by the + [Help Wanted](https://github.com/lbryio/lbry-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+no%3Aassignee) + label to see a curated list of suggested issues with which community members can help. +* Every + [Help Wanted](https://github.com/lbryio/lbry-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+no%3Aassignee) + issue is ranked on a scale from zero to four. + +| Level | Description | +| --------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| [**level 0**](https://github.com/lbryio/lbry-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+label%3A%22level+0%22+no%3Aassignee) | Typos and text edits -- a tech-savvy non-programmer can fix these | +| [**level 1**](https://github.com/lbryio/lbry-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+label%3A%22level+1%22+no%3Aassignee) | Programming issues that require little knowledge of how the LBRY app works | +| [**level 2**](https://github.com/lbryio/lbry-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+label%3A%22level+2%22+no%3Aassignee) | Issues of average difficulty that require the developer to dig into how the app works a little bit | +| [**level 3**](https://github.com/lbryio/lbry-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+label%3A%22level+3%22+no%3Aassignee) | Issues that are likely too tricky to be level 2 or require more thinking outside of the box | +| [**level 4**](https://github.com/lbryio/lbry-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+label%3A%22level+4%22+no%3Aassignee) | Big features or really hard issues | + +The process of ranking issues is highly subjective. The purpose of sorting issues like this is to +give contributors a general idea about the type of issues they are looking at. It could very well be +the case that a level 1 issue is more difficult than a level 2, for instance. This system is meant +to help you find relevant issues, not to prevent you from working on issues that you otherwise +would. If these rankings don't work for you, feel free to ignore them. + +Although all contributions should have good UX, the [UX label, when applied in conjunction with Help Wanted](https://github.com/lbryio/lbry-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+label%3Aux+no%3Aassignee), indicates that the contributor ought to implement the feature in a creative way that specifically focuses on providing a good user experience. These issues often have no set instruction for how the experience should be and leave it to the contributor to figure out. This may be challenging for people who do not like UX, but also more fun and rewarding for those who do. + +## Develop + +The project comes with diverse tools for simplifying the development process and for providing +better code quality. It's recommended to make use of them thoroughly during ongoing development. + +We follow the well-known [Airbnb JavaScript Style Guide](http://airbnb.io/javascript/) for defining +our styling rules and code best practices. + +### Run + +LBRY app can be run for development by using the command: + +`yarn dev` + +This will launch the app and provide HMR (Hot Module Replacement). Any change made to the sources +will automatically reload the app without losing its state. + +### Lint + +Code linting is ensured by [ESLint](https://eslint.org/). + +You can lint all the project's sources at any time by running: + +`yarn lint` + +If you desire to lint a specific file or directory you can use `yarn eslint 'glob/pattern'`. + +In addition to those commands, staged files are automatically linted before commit. Please take the +time to fix all staged files' linting problems before committing or suppress them if necessary. + +If you want the linting problems to show up on your IDE or text editor, check out +[ESLint integrations](https://eslint.org/docs/user-guide/integrations). + +### Code Formatting + +Project's sources are formatted using [Prettier](https://prettier.io/). + +Staged files are automatically formatted before commit. + +You can also use the following command: + +`yarn format` + +for applying formatting rules to all project's code sources. For formatting a specific file or +directory use `yarn prettier 'glob/pattern'`. + +Editor integrations are available [here](https://prettier.io/docs/en/editors.html). + +### Debug + +There are a few tools integrated to the project that will ease the process of debugging: + +* [Chrome DevTools](https://developer.chrome.com/devtools) + * Also available for the main process as a [remote target](chrome://inspect/#devices). +* [Electron Devtron](https://electronjs.org/devtron) +* [React DevTools](https://github.com/facebook/react-devtools) +* [Redux DevTools](https://github.com/gaearon/redux-devtools) + +## Submit a Pull Request + +* After deciding what to work on, a potential contributor can + [fork](https://help.github.com/articles/fork-a-repo/) this repository, make his or her changes, + and submit a + [pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). A + contributor wanting to reserve an issue in advance can leave a comment saying that he or she is + working on it. Contributors should respect other people's efforts to complete issues in a timely + manner and, therefore, not begin working on anything reserved (or updated) within the last 3 days. + If someone has been officially assigned an issue via Github's assignment system, it is also not + available. Contributors are encouraged to ask if they have any questions about issue availability. +* Once the pull request is visible in the LBRY repo, a LBRY team member will review it and make sure + it is up to our standards. At this point, the contributor may have to change his or her code based + on our suggestions and comments. +* Then, upon a satisfactory review of the code, we will merge it and send the contributor a tip (in + LBC) for the contribution. + +We are dedicated to being fair and friendly in this process. In **general**, level 4 issues will be +paid more than level 3 issues which will be paid more than level 2, and so on. However, this is not +due to their labeling, but rather how difficult they end up being. Maybe an issue labeled "level 1" +turns out to be very difficult. In this case, we would be **more than happy** to tip accordingly. If +you do good work, we want you to be rewarded for it. + +Also, we are here to enable you. We want you to succeed, so do not hesitate to ask questions. If you +need some information or assistance in completing an issue, please let us know! That is what we are +here for-- pushing development forward. + +Lastly, don't feel limited by this list. Should LBRY have built-in Tor support? Add it! It's not in +the issue tracker, but maybe it's a good idea. Do you think the search layout is unintuitive? Change +it! We welcome all feedback and suggestions. That said, it may be the case that we do not wish to +incorporate your change if you don't check with us first (also, please check with us especially if +you are planning on adding Tor support :P). If you want to add a feature that is not listed in the +issue tracker, go ahead and [create an issue](https://github.com/lbryio/lbry-app/issues/new), and +say in the description that you would like to try to implement it yourself. This way we can tell you +in advance if we will accept your changes and we can point you in the right direction. + +# Tom's "Voice of the User" Wishlist + +[Anything marked with **both** "Help Wanted" and "Tom's 'Voice of the User' Wishlist"](https://github.com/lbryio/lbry-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22Tom%27s+%5C%22Voice+of+the+User%5C%22+Wishlist%22+label%3A%22help+wanted%22+no%3Aassignee) +will earn you an extra 50 LBC on top of what we would otherwise tip you. + +# Get in Touch + +| Name | Role | Discord | Email | +| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------ | +| [Liam](https://github.com/liamcardenas) | The application engineer in charge of community development. He is the person to contact with any development/contribution related questions. | liamsdouble | liam@lbry.io | +| [Tom](https://github.com/tzarebczan) | Community manager. He knows more than anyone about the app and all of its flaws. Reach out to him with any questions about how the app works, if a bug has been reported, or if a feature should be requested. | jiggytom | tom@lbry.io | +| [Sean](https://github.com/seanyesmunt) | An application engineer who focuses largely on UI/UX. If you have a design or implementation question, feel free to reach out to him. | sean | sean@lbry.io | + +Join our Discord [here](https://chat.lbry.io/). + +# More Information + +More information about contributing to LBRY [here](https://lbry.io/faq/contributing). diff --git a/README.md b/README.md index aed0caca0..be41950c6 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,119 @@ # LBRY App -This is a graphical browser for the decentralized content marketplace provided by the [LBRY](https://lbry.io) protocol. It is essentially the [lbry daemon](https://github.com/lbryio/lbry) bundled with a UI using [Electron](http://electron.atom.io/). +The LBRY app is a graphical browser for the decentralized content marketplace provided by the +[LBRY](https://lbry.io) protocol. It is essentially the +[lbry daemon](https://github.com/lbryio/lbry) bundled with a UI using +[Electron](http://electron.atom.io/). -![App Screenshot](https://lbry.io/img/lbry-ui.png) +![App screenshot](https://lbry.io/img/lbry-ui.png) ## Installing We provide installers for Windows, macOS, and Debian-based Linux. -| | Windows | macOS | Linux | -| --- | --- | --- | --- | -| Latest Stable Release | [Download](https://lbry.io/get/lbry.exe) | [Download](https://lbry.io/get/lbry.dmg) | [Download](https://lbry.io/get/lbry.deb) | -| Latest Prerelease | [Download](https://lbry.io/get/lbry.pre.exe) | [Download](https://lbry.io/get/lbry.pre.dmg) | [Download](https://lbry.io/get/lbry.pre.deb) | +| | Windows | macOS | Linux | +| --------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | +| Latest Stable Release | [Download](https://lbry.io/get/lbry.exe) | [Download](https://lbry.io/get/lbry.dmg) | [Download](https://lbry.io/get/lbry.deb) | +| Latest Prerelease | [Download](https://lbry.io/get/lbry.pre.exe) | [Download](https://lbry.io/get/lbry.pre.dmg) | [Download](https://lbry.io/get/lbry.pre.deb) | - -Our [releases page](https://github.com/lbryio/lbry-app/releases/latest) also contains the latest release, pre-releases, and past builds. +Our [releases page](https://github.com/lbryio/lbry-app/releases/latest) also contains the latest +release, pre-releases, and past builds. To install from source or make changes to the application, continue reading below. -## Development on Linux and macOS +## Getting Started + +These instructions will get you a copy of the project up and running on your local machine for +development and testing purposes. + +### Prerequisites + +* [Git](https://git-scm.com/downloads) +* [Node.js](https://nodejs.org/en/download/) +* [Yarn](https://yarnpkg.com/en/docs/install) +* `yarn --add-python-to-path install --global --production windows-build-tools` (Windows only) ### One-time Setup 1. Clone this repo 2. `DEPS=true ./build.sh` -This will download and install the LBRY app and its dependencies, including [the LBRY daemon](https://github.com/lbryio/lbry) and command line utilities like `node` and `yarn`. The LBRY app requires Node >= 6; if you have an earlier version of Node installed and want to keep it, you can use [nvm](https://github.com/creationix/nvm) to switch back and forth. +This will download and install the LBRY app and its dependencies, including +[the LBRY daemon](https://github.com/lbryio/lbry) and command line utilities like `node` and `yarn`. +The LBRY app requires Node >= 6; if you have an earlier version of Node installed and want to keep +it, you can use [nvm](https://github.com/creationix/nvm) to switch back and forth. -### Ongoing Development -Run `yarn dev` +#### Arch Linux and Other Non-Debian Distributions -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. +Running the build script with `DEPS=true` triggers a bash script with `apt-get` specific commands. +If you are using a distribution without `apt-get`, try running the script as: -### Packaging +`./build.sh` -Run `yarn dist` +You may also have to install the package [libsecret](https://wiki.gnome.org/Projects/Libsecret) if +it is not already installed. -We use [electron-builder](https://github.com/electron-userland/electron-builder) -to create distributable packages. +### Running -## Development on Windows +The app can be run from the sources using the following command: -### Windows Dependency -1. Download and install `git` from github.io (configure to use command prompt integration) -2. Download and install `npm` and `node` from nodejs.org -3. Download and install `python 2.7` from python.org -4. Download and Install `Microsoft Visual C++ Compiler for Python 2.7` from Microsoft -5. Download and install `.NET Framework 2.0 Software Development Kit (SDK) (x64)` from Microsoft (may need to extract setup.exe and install manually by running install.exe as Administrator) +`yarn dev` + +### On Windows + +#### Windows Dependency + +1. Download and install `git` from github.io + (configure to use command prompt integration) +2. Download and install `npm` and `node` from + nodejs.org +3. Download and install `python 2.7` from + python.org +4. Download and Install `Microsoft Visual C++ Compiler for Python 2.7` from + Microsoft +5. Download and install `.NET Framework 2.0 Software Development Kit (SDK) (x64)` from + Microsoft (may need + to extract setup.exe and install manually by running install.exe as Administrator) + +#### One-time Setup + +1. Open a command prompt as administrator and run the following: -### One-time Setup -1. Open command prompt as adminstrator and run the following: ``` npm install --global --production windows-build-tools exit ``` -2. Open command prompt in the root of the project and run the following: +2. Open a command prompt in the root of the project and run the following: + ``` python -m pip install -r build\requirements.txt npm install -g yarn yarn install -yarn dist +yarn build ``` -3. Download the lbry daemon and cli [binaries](https://github.com/lbryio/lbry/releases) and place them in `dist\` -### Building lbry-app -Run `yarn dist` +3. Download the lbry daemon and CLI [binaries](https://github.com/lbryio/lbry/releases) and place + them in `static\daemon`. -### Ongoing Development -Run `yarn dev` +### Build -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. +Run `yarn build`. + +We use [electron-builder](https://github.com/electron-userland/electron-builder) to create +distributable packages. + +## Contributing + +Please read [our contributing manual](CONTRIBUTING.md) for details on how to develop for the +project and the process of submitting pull requests. ## Internationalization -If you want to help translating the lbry-app, you can copy the `en.json` file in `/dist/locales/` and modify the values while leaving the keys as their original English strings. An example for this would be: `"Skip": "Überspringen",` Translations should automatically show up in options. +If you want to help to translate the lbry-app, you can copy the `en.json` file in `/dist/locales/` +and modify the values while leaving the keys as their original English strings. An example for this +would be: `"Skip": "Überspringen",` Translations should automatically show up in options. + +## License + +[MIT © LBRY](LICENSE) diff --git a/build/build.ps1 b/build/build.ps1 index 3fb9d90b8..730fd80af 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -26,7 +26,7 @@ dir static\daemon\ # verify that daemon binary is there rm daemon.zip # build electron app -yarn dist +yarn build dir dist # verify that binary was built/named correctly python build\upload_assets.py diff --git a/build/build.sh b/build/build.sh index 37a26b0a1..2273eff00 100755 --- a/build/build.sh +++ b/build/build.sh @@ -77,7 +77,7 @@ if [ "$FULL_BUILD" == "true" ]; then security unlock-keychain -p ${KEYCHAIN_PASSWORD} osx-build.keychain fi - yarn dist + yarn build # electron-build has a publish feature, but I had a hard time getting # it to reliably work and it also seemed difficult to configure. Not proud of diff --git a/src/renderer/extractLocals.js b/build/extractLocals.js similarity index 51% rename from src/renderer/extractLocals.js rename to build/extractLocals.js index b4437dbf5..8f48b1492 100644 --- a/src/renderer/extractLocals.js +++ b/build/extractLocals.js @@ -1,20 +1,21 @@ -var extract = require("i18n-extract"); +const extract = require("i18n-extract"); const fs = require("fs"); +const path = require("path"); -var dir = __dirname + "/../../dist/locales"; -var path = dir + "/en.json"; +const outputDir = `${__dirname}/../static/locales`; +const outputPath = `${outputDir}/en.json`; -if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir); } -fs.writeFile(path, "{}", "utf8", function(err) { +fs.writeFile(outputPath, "{}", "utf8", err => { if (err) { return console.log(err); } - var enLocale = require(path); + const enLocale = require(outputPath); - const keys = extract.extractFromFiles(["js/**/*.{js,jsx}"], { + const keys = extract.extractFromFiles("src/**/*.{js,jsx}", { marker: "__", }); @@ -22,21 +23,21 @@ fs.writeFile(path, "{}", "utf8", function(err) { reports = reports.concat(extract.findMissing(enLocale, keys)); if (reports.length > 0) { - fs.readFile(path, "utf8", function readFileCallback(err, data) { + fs.readFile(outputPath, "utf8", (err, data) => { if (err) { console.log(err); } else { localeObj = JSON.parse(data); - for (var i = 0; i < reports.length; i++) { + for (let i = 0; i < reports.length; i++) { // no need to care for other types than MISSING because starting file will always be empty if (reports[i].type === "MISSING") { localeObj[reports[i].key] = reports[i].key; } } - var json = JSON.stringify(localeObj, null, "\t"); //convert it back to json-string - fs.writeFile(path, json, "utf8", function callback(err) { + const json = JSON.stringify(localeObj, null, "\t"); // convert it back to json-string + fs.writeFile(outputPath, json, "utf8", err => { if (err) { throw err; } diff --git a/build/install_deps.sh b/build/install_deps.sh index 2103d27e3..29b7dd723 100755 --- a/build/install_deps.sh +++ b/build/install_deps.sh @@ -99,7 +99,7 @@ if ! cmd_exists yarn; then curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | $SUDO apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | $SUDO tee /etc/apt/sources.list.d/yarn.list $SUDO apt-get update - $SUDO apt-get install yarn + $SUDO apt-get -o Dpkg::Options::="--force-overwrite" install yarn elif $OSX; then brew install yarn else diff --git a/electron-builder.json b/electron-builder.json index acf8e77a2..9e0a8f86b 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -1,7 +1,7 @@ { "appId": "io.lbry.LBRY", "mac": { - "category": "public.app-category.entertainment", + "category": "public.app-category.entertainment" }, "dmg": { "iconSize": 128, @@ -34,6 +34,7 @@ ], "linux": { "target": "deb", + "category": "Video", "desktop": { "MimeType": "x-scheme-handler/lbry", "Exec": "/opt/LBRY/lbry %U" diff --git a/package.json b/package.json index ff83432b9..e5b01cd30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.19.1", + "version": "0.19.4rc1", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "homepage": "https://lbry.io/", "bugs": { @@ -14,21 +14,23 @@ "name": "LBRY Inc.", "email": "hello@lbry.io" }, + "main": "src/main/index.js", "scripts": { - "extract-langs": "node src/renderer/extractLocals.js", + "extract-langs": "node build/extractLocals.js", "dev": "electron-webpack dev", "compile": "electron-webpack && yarn extract-langs", - "dist": "yarn compile && electron-builder", - "dist:dir": "yarn dist -- --dir -c.compression=store -c.mac.identity=null", + "build": "yarn compile && electron-builder build", "postinstall": "electron-builder install-app-deps", - "precommit": "lint-staged" + "precommit": "lint-staged", + "lint": "eslint 'src/**/*.{js,jsx}' --fix", + "format": "prettier 'src/**/*.{js,jsx,scss,json}' --write" }, - "main": "src/main/index.js", "keywords": [ "lbry" ], "dependencies": { "amplitude-js": "^4.0.0", + "bluebird": "^3.5.1", "classnames": "^2.2.5", "electron-dl": "^1.6.0", "formik": "^0.10.4", @@ -64,18 +66,27 @@ "y18n": "^4.0.0" }, "devDependencies": { + "babel-eslint": "^8.0.3", "babel-plugin-module-resolver": "^3.0.0", "babel-plugin-react-require": "^3.0.0", "babel-polyfill": "^6.20.0", "babel-preset-env": "^1.6.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.18.0", - "bluebird": "^3.5.1", "devtron": "^1.4.0", "electron": "^1.7.9", - "electron-builder": "^19.48.2", + "electron-builder": "^19.49.0", "electron-devtools-installer": "^2.2.1", "electron-webpack": "^1.11.0", + "eslint": "^4.13.1", + "eslint-config-airbnb": "^16.1.0", + "eslint-config-prettier": "^2.9.0", + "eslint-import-resolver-webpack": "^0.8.3", + "eslint-plugin-flowtype": "^2.40.1", + "eslint-plugin-import": "^2.8.0", + "eslint-plugin-jsx-a11y": "^6.0.3", + "eslint-plugin-prettier": "^2.4.0", + "eslint-plugin-react": "^7.5.1", "flow-babel-webpack-plugin": "^1.1.0", "flow-bin": "^0.61.0", "flow-typed": "^2.2.3", @@ -98,12 +109,6 @@ "yarn": "^1.3" }, "license": "MIT", - "lint-staged": { - "src/**/*.{js,jsx}": [ - "prettier --trailing-comma es5 --write", - "git add" - ] - }, "lbrySettings": { "lbrynetDaemonVersion": "0.18.0", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-daemon-vDAEMONVER-OSNAME.zip" diff --git a/src/main/index.js b/src/main/index.js index e43a71d7f..30a78c3e6 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1,57 +1,39 @@ +/* eslint-disable no-console */ // Module imports -const {app, BrowserWindow, ipcMain, Menu, Tray, globalShortcut} = require('electron'); -const path = require('path'); -const url = require('url'); -const jayson = require('jayson'); -const semver = require('semver'); -const https = require('https'); -const keytar = require('keytar'); -// tree-kill has better cross-platform handling of -// killing a process. child-process.kill was unreliable -const kill = require('tree-kill'); -const child_process = require('child_process'); -const assert = require('assert'); +import Path from 'path'; +import Url from 'url'; +import Jayson from 'jayson'; +import Semver from 'semver'; +import Https from 'https'; +import Keytar from 'keytar'; +import ChildProcess from 'child_process'; +import Assert from 'assert'; +import { app, BrowserWindow, globalShortcut, ipcMain, Menu, Tray } from 'electron'; +import mainMenu from './menu/mainMenu'; +import contextMenu from './menu/contextMenu'; + const localVersion = app.getVersion(); -const setMenu = require('./menu/main-menu.js'); -export const contextMenu = require('./menu/context-menu'); // Debug configs const isDevelopment = process.env.NODE_ENV === 'development'; -if (isDevelopment) { - try - { - const { default: installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } = require('electron-devtools-installer'); - app.on('ready', () => { - [REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS].forEach(extension => { - installExtension(extension) - .then((name) => console.log(`Added Extension: ${name}`)) - .catch((err) => console.log('An error occurred: ', err)); - }); - }); - } - catch (err) - { - console.error(err) - } -} // Misc constants const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest'; -const DAEMON_PATH = process.env.LBRY_DAEMON || path.join(__static, 'daemon/lbrynet-daemon'); +const DAEMON_PATH = process.env.LBRY_DAEMON || Path.join(__static, 'daemon/lbrynet-daemon'); const rendererUrl = isDevelopment ? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}` : `file://${__dirname}/index.html`; -let client = jayson.client.http({ +const client = Jayson.client.http({ host: 'localhost', port: 5279, path: '/', - timeout: 1000 + timeout: 1000, }); // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. -let win; +let rendererWindow; // Also keep the daemon subprocess alive let daemonSubprocess; @@ -86,9 +68,8 @@ function processRequestedUri(uri) { if (process.platform === 'win32') { return uri.replace(/\/$/, '').replace('/#', '#'); - } else { - return uri; } + return uri; } /* @@ -97,81 +78,122 @@ function processRequestedUri(uri) { * when no windows are open. */ function openItem(fullPath) { - const subprocOptions = { - detached: true, - stdio: 'ignore', + const subprocOptions = { + detached: true, + stdio: 'ignore', + }; + + let child; + if (process.platform === 'darwin') { + child = ChildProcess.spawn('open', [fullPath], subprocOptions); + } else if (process.platform === 'linux') { + child = ChildProcess.spawn('xdg-open', [fullPath], subprocOptions); + } else if (process.platform === 'win32') { + child = ChildProcess.spawn(fullPath, Object.assign({}, subprocOptions, { shell: true })); + } + + // Causes child process reference to be garbage collected, allowing main process to exit + child.unref(); +} +/* + * Quits by first killing the daemon, the calling quitting for real. + */ +export function safeQuit() { + minimize = false; + 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(), }; + } - let child; - if (process.platform === 'darwin') { - child = child_process.spawn('open', [fullPath], subprocOptions); - } else if (process.platform === 'linux') { - child = child_process.spawn('xdg-open', [fullPath], subprocOptions); - } else if (process.platform === 'win32') { - child = child_process.spawn(fullPath, Object.assign({}, subprocOptions, {shell: true})); - } - - // Causes child process reference to be garbage collected, allowing main process to exit - child.unref(); + return [ + getToggleItem(), + { + label: 'Quit', + click: () => safeQuit(), + }, + ]; } -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 - } +// 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 { - const pgrepOut = child_process.spawnSync('pgrep', ['-x', name], {encoding: 'utf8'}).stdout; - return pgrepOut.match(/\d+/g); + console.log('How did update tray get called without a tray?'); } } -function createWindow () { +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}); + let windowConfiguration = { + backgroundColor: '#155B4A', + minWidth: 800, + minHeight: 600, + autoHideMenuBar: true, + }; - win.webContents.session.setUserAgent(`LBRY/${localVersion}`); + windowConfiguration = isDevelopment + ? { + ...windowConfiguration, + webPreferences: { + webSecurity: false, + }, + } + : windowConfiguration; - win.maximize() + let window = new BrowserWindow(windowConfiguration); + + window.webContents.session.setUserAgent(`LBRY/${localVersion}`); + + window.maximize(); if (isDevelopment) { - win.webContents.openDevTools(); + window.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); + 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); }); } - win.removeAllListeners(); + window.removeAllListeners(); - win.on('close', function(event) { + window.on('close', event => { if (minimize) { event.preventDefault(); - win.hide(); + window.hide(); } - }) + }); - win.on('closed', () => { - win = null - }) + window.on('closed', () => { + window = null; + }); - win.on("hide", () => { + window.on('hide', () => { // Checks what to show in the tray icon menu if (minimize) updateTray(); }); - win.on("show", () => { + window.on('show', () => { // Checks what to show in the tray icon menu if (minimize) updateTray(); }); - win.on("blur", () => { + window.on('blur', () => { // Checks what to show in the tray icon menu if (minimize) updateTray(); @@ -179,135 +201,57 @@ function createWindow () { globalShortcut.unregister('Alt+F4'); }); - win.on("focus", () => { + 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()); - win.webContents.send('window-is-focused', null); + window.webContents.send('window-is-focused', null); }); - // Menu bar - win.setAutoHideMenuBar(true); - win.setMenuBarVisibility(isDevelopment); - setMenu(); + mainMenu(); -}; + return window; +} -function createTray () { +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"); + iconPath = Path.join(__static, '/img/fav/macTemplate@2x.png'); } else { - iconPath = path.join(__static, "/img/fav/32x32.png"); + iconPath = Path.join(__static, '/img/fav/32x32.png'); } tray = new Tray(iconPath); - tray.setToolTip("LBRY App"); - tray.setTitle("LBRY"); + 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() - } - } - } + rendererWindow.show(); + }); } function handleOpenUriRequested(uri) { - if (!win) { + if (!rendererWindow) { // 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() + if (rendererWindow.isMinimized()) { + rendererWindow.restore(); + } else if (!rendererWindow.isVisible()) { + rendererWindow.show(); } - win.focus(); - win.webContents.send('open-uri-requested', processRequestedUri(uri)); + rendererWindow.focus(); + rendererWindow.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. - */ -export function safeQuit() { - minimize = false; - app.quit(); -} - /* * 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, @@ -318,20 +262,57 @@ function quitNow() { safeQuit(); } -const isSecondaryInstance = app.makeSingleInstance((argv) => { +function handleDaemonSubprocessExited() { + console.log('The daemon has exited.'); + daemonSubprocess = null; + if (!daemonStopRequested) { + // We didn't request to stop the daemon, so display a + // warning and schedule a quit. + // + // TODO: maybe it would be better to restart the daemon? + if (rendererWindow) { + console.log('Did not request daemon stop, so quitting in 5 seconds.'); + rendererWindow.loadURL(`file://${__static}/warning.html`); + setTimeout(quitNow, 5000); + } else { + console.log('Did not request daemon stop, so quitting.'); + quitNow(); + } + } +} + +function launchDaemon() { + Assert(!daemonSubprocess, 'Tried to launch daemon twice'); + + console.log('Launching daemon:', DAEMON_PATH); + daemonSubprocess = ChildProcess.spawn(DAEMON_PATH); + // Need to handle the data event instead of attaching to + // process.stdout because the latter doesn't work. I believe on + // windows it buffers stdout and we don't get any meaningful output + daemonSubprocess.stdout.on('data', buf => { + console.log(String(buf).trim()); + }); + daemonSubprocess.stderr.on('data', buf => { + console.log(String(buf).trim()); + }); + daemonSubprocess.on('exit', handleDaemonSubprocessExited); +} + +const isSecondaryInstance = app.makeSingleInstance(argv => { if (argv.length >= 2) { handleOpenUriRequested(argv[1]); // This will handle restoring and focusing the window - } else if (win) { - if (win.isMinimized()) { - win.restore(); - } else if (!win.isVisible()) { - win.show(); + } else if (rendererWindow) { + if (rendererWindow.isMinimized()) { + rendererWindow.restore(); + } else if (!rendererWindow.isVisible()) { + rendererWindow.show(); } - win.focus(); + rendererWindow.focus(); } }); -if (isSecondaryInstance) { // We're not in the original process, so quit +if (isSecondaryInstance) { + // We're not in the original process, so quit quitNow(); } @@ -339,101 +320,29 @@ function launchDaemonIfNotRunning() { // Check if the daemon is already running. If we get // an error its because its not running console.log('Checking for lbrynet daemon'); - client.request( - 'status', [], - function (err, res) { - if (err) { - console.log('lbrynet daemon needs to be launched') - launchDaemon(); - } else { - console.log('lbrynet daemon is already running') - } + client.request('status', [], err => { + if (err) { + console.log('lbrynet daemon needs to be launched'); + launchDaemon(); + } else { + console.log('lbrynet daemon is already running'); } - ); -} - -/* - * Last resort for killing unresponsive daemon instances. - * Looks for any processes called "lbrynet-daemon" and - * tries to force kill them. - */ -function forceKillAllDaemonsAndQuit() { - console.log('Attempting to force kill any running lbrynet-daemon instances...'); - - const daemonPids = getPidsForProcessName('lbrynet-daemon'); - if (!daemonPids) { - console.log('No lbrynet-daemon found running.'); - quitNow(); - } else { - console.log(`Found ${daemonPids.length} running daemon instances. Attempting to force kill...`); - - for (const pid of daemonPids) { - let daemonKillAttemptsComplete = 0; - kill(pid, 'SIGKILL', (err) => { - daemonKillAttemptsComplete++; - if (err) { - console.log(`Failed to force kill daemon task with pid ${pid}. Error message: ${err.message}`); - } else { - console.log(`Force killed daemon task with pid ${pid}.`); - } - if (daemonKillAttemptsComplete >= daemonPids.length - 1) { - quitNow(); - } - }); - } - } -} - -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]); +} + +// Taken from webtorrent-desktop +function checkLinuxTraySupport(cb) { + // Check that we're on Ubuntu (or another debian system) and that we have + // libappindicator1. + ChildProcess.exec('dpkg --get-selections libappindicator1', (err, stdout) => { + if (err) return cb(err); + // Unfortunately there's no cleaner way, as far as I can tell, to check + // whether a debian package is installed: + if (stdout.endsWith('\tinstall\n')) { + return cb(null); + } + return cb(new Error('debian package not installed')); + }); } // When a quit is attempted, this is called. It attempts to shutdown the daemon, @@ -442,12 +351,12 @@ function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) { function doShutdown() { console.log('Shutting down daemon'); daemonStopRequested = true; - client.request('daemon_stop', [], (err, res) => { + client.request('daemon_stop', [], err => { if (err) { console.log(`received error when stopping lbrynet-daemon. Error message: ${err.message}\n`); console.log('You will need to manually kill the daemon.'); } else { - console.log('Successfully stopped daemon via RPC call.') + console.log('Successfully stopped daemon via RPC call.'); quitNow(); } }); @@ -466,20 +375,79 @@ function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) { // If not, we should wait until the daemon is closed before we start the install. } -// Taken from webtorrent-desktop -function checkLinuxTraySupport (cb) { - // Check that we're on Ubuntu (or another debian system) and that we have - // libappindicator1. - child_process.exec('dpkg --get-selections libappindicator1', function (err, stdout) { - if (err) return cb(err) - // Unfortunately there's no cleaner way, as far as I can tell, to check - // whether a debian package is installed: - if (stdout.endsWith('\tinstall\n')) { - cb(null) - } else { - cb(new Error('debian package not installed')) - } - }) +if (isDevelopment) { + import('devtron') + .then(({ install }) => { + install(); + console.log('Added Extension: Devtron'); + }) + .catch(error => { + console.error(error); + }); + import('electron-devtools-installer') + .then(({ default: installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS }) => { + app.on('ready', () => { + [REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS].forEach(extension => { + installExtension(extension) + .then(name => console.log(`Added Extension: ${name}`)) + .catch(err => console.log('An error occurred: ', err)); + }); + }); + }) + .catch(error => { + console.error(error); + }); +} + +app.setAsDefaultProtocolClient('lbry'); + +app.on('ready', () => { + launchDaemonIfNotRunning(); + if (process.platform === 'linux') { + checkLinuxTraySupport(err => { + if (!err) createTray(); + else minimize = false; + }); + } else { + createTray(); + } + rendererWindow = createWindow(); +}); + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('before-quit', event => { + if (!readyToQuit) { + // We need to shutdown the daemon before we're ready to actually quit. This + // event will be triggered re-entrantly once preparation is done. + event.preventDefault(); + shutdownDaemonAndQuit(); + } else { + console.log('Quitting.'); + } +}); + +app.on('activate', () => { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (rendererWindow === null) { + createWindow(); + } +}); + +if (process.platform === 'darwin') { + app.on('open-url', (event, uri) => { + handleOpenUriRequested(uri); + }); +} else if (process.argv.length >= 2) { + handleOpenUriRequested(process.argv[1]); } ipcMain.on('upgrade', (event, installerPath) => { @@ -490,15 +458,17 @@ ipcMain.on('upgrade', (event, installerPath) => { openItem(installerPath); }); - if (win) { - win.loadURL(`file://${__static}/upgrade.html`); + if (rendererWindow) { + rendererWindow.loadURL(`file://${__static}/upgrade.html`); } shutdownDaemonAndQuit(true); // wait for daemon to shut down before upgrading // what to do if no shutdown in a long time? console.log('Update downloaded to', installerPath); - console.log('The app will close, and you will be prompted to install the latest version of LBRY.'); + console.log( + '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.'); }); @@ -512,43 +482,49 @@ ipcMain.on('version-info-requested', () => { const opts = { headers: { 'User-Agent': `LBRY/${localVersion}`, - } + }, }; - const req = https.get(Object.assign(opts, url.parse(LATEST_RELEASE_API_URL)), (res) => { - res.on('data', (data) => { + const req = Https.get(Object.assign(opts, Url.parse(LATEST_RELEASE_API_URL)), res => { + res.on('data', data => { result += data; }); res.on('end', () => { const tagName = JSON.parse(result).tag_name; - const [_, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/); + const [, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/); if (!remoteVersion) { - if (win) { - win.webContents.send('version-info-received', null); + if (rendererWindow) { + rendererWindow.webContents.send('version-info-received', null); } } else { - const upgradeAvailable = semver.gt(formatRc(remoteVersion), formatRc(localVersion)); - if (win) { - win.webContents.send('version-info-received', {remoteVersion, localVersion, upgradeAvailable}); + const upgradeAvailable = Semver.gt(formatRc(remoteVersion), formatRc(localVersion)); + if (rendererWindow) { + rendererWindow.webContents.send('version-info-received', { + remoteVersion, + localVersion, + upgradeAvailable, + }); } } - }) + }); }); - req.on('error', (err) => { + req.on('error', err => { console.log('Failed to get current version from GitHub. Error:', err); - if (win) { - win.webContents.send('version-info-received', null); + if (rendererWindow) { + rendererWindow.webContents.send('version-info-received', null); } }); }); -ipcMain.on('get-auth-token', (event) => { - keytar.getPassword("LBRY", "auth_token").then(token => { - event.sender.send('auth-token-response', token ? token.toString().trim() : null) +ipcMain.on('get-auth-token', event => { + Keytar.getPassword('LBRY', 'auth_token').then(token => { + event.sender.send('auth-token-response', token ? token.toString().trim() : null); }); }); ipcMain.on('set-auth-token', (event, token) => { - keytar.setPassword("LBRY", "auth_token", token ? token.toString().trim() : null); + Keytar.setPassword('LBRY', 'auth_token', token ? token.toString().trim() : null); }); + +export { contextMenu }; diff --git a/src/main/menu/context-menu.js b/src/main/menu/context-menu.js deleted file mode 100644 index f38de5d1c..000000000 --- a/src/main/menu/context-menu.js +++ /dev/null @@ -1,36 +0,0 @@ -const {Menu} = require('electron'); -const electron = require('electron'); -const app = electron.app; - -const contextMenuTemplate = [ - { - role: 'cut', - }, - { - role: 'copy', - }, - { - role: 'paste', - }, -]; - -module.exports = { - showContextMenu(win, posX, posY, showDevItems) { - let template = contextMenuTemplate.slice(); - if (showDevItems) { - template.push({ - type: 'separator', - }); - template.push( - { - label: 'Inspect Element', - click() { - win.inspectElement(posX, posY); - } - } - ); - } - - Menu.buildFromTemplate(template).popup(win); - }, -}; diff --git a/src/main/menu/contextMenu.js b/src/main/menu/contextMenu.js new file mode 100644 index 000000000..e24885ec2 --- /dev/null +++ b/src/main/menu/contextMenu.js @@ -0,0 +1,20 @@ +import { Menu } from 'electron'; + +const contextMenuTemplate = [{ role: 'cut' }, { role: 'copy' }, { role: 'paste' }]; + +export default (win, posX, posY, showDevItems) => { + const template = contextMenuTemplate.slice(); + if (showDevItems) { + template.push({ + type: 'separator', + }); + template.push({ + label: 'Inspect Element', + click() { + win.inspectElement(posX, posY); + }, + }); + } + + Menu.buildFromTemplate(template).popup(win); +}; diff --git a/src/main/menu/main-menu.js b/src/main/menu/mainMenu.js similarity index 63% rename from src/main/menu/main-menu.js rename to src/main/menu/mainMenu.js index 7b5854d75..879ce8206 100644 --- a/src/main/menu/main-menu.js +++ b/src/main/menu/mainMenu.js @@ -1,5 +1,5 @@ -const { app, shell, Menu } = require('electron'); -const { safeQuit } = require('../index.js'); +import { app, Menu, shell } from 'electron'; +import { safeQuit } from '../index'; const baseTemplate = [ { @@ -7,10 +7,10 @@ const baseTemplate = [ submenu: [ { label: 'Quit', - accelerator: "CommandOrControl+Q", + accelerator: 'CommandOrControl+Q', click: () => safeQuit(), }, - ] + ], }, { label: 'Edit', @@ -36,32 +36,32 @@ const baseTemplate = [ { role: 'selectall', }, - ] + ], }, { label: 'View', submenu: [ { - role: 'reload' + role: 'reload', }, { label: 'Developer', submenu: [ { - role: 'forcereload' + role: 'forcereload', }, { - role: 'toggledevtools' + role: 'toggledevtools', }, - ] + ], }, { - type: 'separator' + type: 'separator', }, { - role: 'togglefullscreen' - } - ] + role: 'togglefullscreen', + }, + ], }, { role: 'help', @@ -72,34 +72,34 @@ const baseTemplate = [ if (focusedWindow) { focusedWindow.webContents.send('open-menu', '/help'); } - } + }, }, { label: 'Frequently Asked Questions', - click(item, focusedWindow){ - shell.openExternal('https://lbry.io/faq') - } + click() { + shell.openExternal('https://lbry.io/faq'); + }, }, { - type: 'separator' + type: 'separator', }, { label: 'Report Issue', - click(item, focusedWindow){ + click() { shell.openExternal('https://lbry.io/faq/contributing#report-a-bug'); - } + }, }, { - type: 'separator' + type: 'separator', }, { label: 'Developer API Guide', - click(item, focusedWindow){ - shell.openExternal('https://lbry.io/quickstart') - } + click() { + shell.openExternal('https://lbry.io/quickstart'); + }, }, - ] - } + ], + }, ]; const macOSAppMenuTemplate = { @@ -126,11 +126,13 @@ const macOSAppMenuTemplate = { { role: 'quit', }, - ] + ], }; -module.exports = () => { - let template = baseTemplate.slice(); - (process.platform === 'darwin') && template.unshift(macOSAppMenuTemplate); - Menu.setApplicationMenu(Menu.buildFromTemplate(template)); +export default () => { + const template = baseTemplate.slice(); + if (process.platform === 'darwin') { + template.unshift(macOSAppMenuTemplate); + } + Menu.setApplicationMenu(Menu.buildFromTemplate(template)); }; diff --git a/src/renderer/app.js b/src/renderer/app.js index e249810f9..75752fac7 100644 --- a/src/renderer/app.js +++ b/src/renderer/app.js @@ -1,29 +1,40 @@ -import store from "store.js"; -import { remote } from "electron"; +import store from 'store'; +import { remote } from 'electron'; +import Path from 'path'; +import y18n from 'y18n'; -const env = process.env.NODE_ENV || "production"; -const config = { - ...require(`./config/${env}`), -}; -const i18n = require("y18n")({ - directory: remote.app.getAppPath() + "/locales", +const env = process.env.NODE_ENV || 'production'; +const i18n = y18n({ + directory: Path.join(remote.app.getAppPath(), '/../static/locales').replace(/\\/g, '\\\\'), updateFiles: false, - locale: "en", + locale: 'en', }); + const logs = []; const app = { - env: env, - config: config, - store: store, - i18n: i18n, - logs: logs, - log: function(message) { + env, + store, + i18n, + logs, + log(message) { logs.push(message); }, }; -window.__ = i18n.__; -window.__n = i18n.__n; +// Workaround for https://github.com/electron-userland/electron-webpack/issues/52 +if (env !== 'development') { + window.staticResourcesPath = Path.join(remote.app.getAppPath(), '../static').replace( + /\\/g, + '\\\\' + ); +} else { + window.staticResourcesPath = ''; +} +// eslint-disable-next-line no-underscore-dangle +global.__ = i18n.__; +// eslint-disable-next-line no-underscore-dangle +global.__n = i18n.__n; global.app = app; -module.exports = app; + +export default app; diff --git a/src/renderer/component/address/index.js b/src/renderer/component/address/index.js index 00bec962e..b861b75c5 100644 --- a/src/renderer/component/address/index.js +++ b/src/renderer/component/address/index.js @@ -1,6 +1,6 @@ -import { connect } from "react-redux"; -import { doShowSnackBar } from "redux/actions/app"; -import Address from "./view"; +import { connect } from 'react-redux'; +import { doShowSnackBar } from 'redux/actions/app'; +import Address from './view'; export default connect(null, { doShowSnackBar, diff --git a/src/renderer/component/address/view.jsx b/src/renderer/component/address/view.jsx index 96049eb35..65ea563c0 100644 --- a/src/renderer/component/address/view.jsx +++ b/src/renderer/component/address/view.jsx @@ -1,8 +1,8 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { clipboard } from "electron"; -import Link from "component/link"; -import classnames from "classnames"; +import React from 'react'; +import PropTypes from 'prop-types'; +import { clipboard } from 'electron'; +import Link from 'component/link'; +import classnames from 'classnames'; export default class Address extends React.PureComponent { static propTypes = { @@ -21,8 +21,8 @@ export default class Address extends React.PureComponent { return (
{ @@ -32,7 +32,7 @@ export default class Address extends React.PureComponent { this._inputElem.select(); }} readOnly="readonly" - value={address || ""} + value={address || ''} /> {showCopyButton && ( @@ -41,7 +41,7 @@ export default class Address extends React.PureComponent { icon="clipboard" onClick={() => { clipboard.writeText(address); - doShowSnackBar({ message: __("Address copied") }); + doShowSnackBar({ message: __('Address copied') }); }} /> diff --git a/src/renderer/component/app/index.js b/src/renderer/component/app/index.js index 66b50bb71..c7173f606 100644 --- a/src/renderer/component/app/index.js +++ b/src/renderer/component/app/index.js @@ -1,14 +1,14 @@ -import React from "react"; -import { connect } from "react-redux"; +import React from 'react'; +import { connect } from 'react-redux'; import { selectPageTitle, selectHistoryIndex, selectActiveHistoryEntry, -} from "redux/selectors/navigation"; -import { selectUser } from "redux/selectors/user"; -import { doAlertError } from "redux/actions/app"; -import { doRecordScroll } from "redux/actions/navigation"; -import App from "./view"; +} from 'redux/selectors/navigation'; +import { selectUser } from 'redux/selectors/user'; +import { doAlertError } from 'redux/actions/app'; +import { doRecordScroll } from 'redux/actions/navigation'; +import App from './view'; const select = (state, props) => ({ pageTitle: selectPageTitle(state), diff --git a/src/renderer/component/app/view.jsx b/src/renderer/component/app/view.jsx index 11e66e252..fb43b6574 100644 --- a/src/renderer/component/app/view.jsx +++ b/src/renderer/component/app/view.jsx @@ -1,10 +1,10 @@ -import React from "react"; -import Router from "component/router/index"; -import Header from "component/header"; -import Theme from "component/theme"; -import ModalRouter from "modal/modalRouter"; -import ReactModal from "react-modal"; -import throttle from "util/throttle"; +import React from 'react'; +import Router from 'component/router/index'; +import Header from 'component/header'; +import Theme from 'component/theme'; +import ModalRouter from 'modal/modalRouter'; +import ReactModal from 'react-modal'; +import throttle from 'util/throttle'; class App extends React.PureComponent { constructor() { @@ -15,25 +15,25 @@ class App extends React.PureComponent { componentWillMount() { const { alertError } = this.props; - document.addEventListener("unhandledError", event => { + document.addEventListener('unhandledError', event => { alertError(event.detail); }); } componentDidMount() { const { recordScroll } = this.props; - const mainContent = document.getElementById("main-content"); + const mainContent = document.getElementById('main-content'); this.mainContent = mainContent; const scrollListener = () => recordScroll(this.mainContent.scrollTop); - this.mainContent.addEventListener("scroll", throttle(scrollListener, 750)); + this.mainContent.addEventListener('scroll', throttle(scrollListener, 750)); - ReactModal.setAppElement("#window"); //fuck this + ReactModal.setAppElement('#window'); // fuck this } componentWillUnmount() { - this.mainContent.removeEventListener("scroll", this.scrollListener); + this.mainContent.removeEventListener('scroll', this.scrollListener); } componentWillReceiveProps(props) { @@ -50,7 +50,7 @@ class App extends React.PureComponent { } setTitleFromProps(props) { - window.document.title = props.pageTitle || "LBRY"; + window.document.title = props.pageTitle || 'LBRY'; } render() { diff --git a/src/renderer/component/cardMedia/index.js b/src/renderer/component/cardMedia/index.js index 3616b0331..1f7988712 100644 --- a/src/renderer/component/cardMedia/index.js +++ b/src/renderer/component/cardMedia/index.js @@ -1,6 +1,6 @@ -import React from "react"; -import { connect } from "react-redux"; -import CardMedia from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import CardMedia from './view'; const select = state => ({}); const perform = dispatch => ({}); diff --git a/src/renderer/component/cardMedia/view.jsx b/src/renderer/component/cardMedia/view.jsx index d0f45f4ac..06f80f1d7 100644 --- a/src/renderer/component/cardMedia/view.jsx +++ b/src/renderer/component/cardMedia/view.jsx @@ -1,18 +1,18 @@ -import React from "react"; +import React from 'react'; class CardMedia extends React.PureComponent { static AUTO_THUMB_CLASSES = [ - "purple", - "red", - "pink", - "indigo", - "blue", - "light-blue", - "cyan", - "teal", - "green", - "yellow", - "orange", + 'purple', + 'red', + 'pink', + 'indigo', + 'blue', + 'light-blue', + 'cyan', + 'teal', + 'green', + 'yellow', + 'orange', ]; componentWillMount() { @@ -29,12 +29,7 @@ class CardMedia extends React.PureComponent { const atClass = this.state.autoThumbClass; if (thumbnail) { - return ( -
- ); + return
; } return ( @@ -42,8 +37,8 @@ class CardMedia extends React.PureComponent {
{title && title - .replace(/\s+/g, "") - .substring(0, Math.min(title.replace(" ", "").length, 5)) + .replace(/\s+/g, '') + .substring(0, Math.min(title.replace(' ', '').length, 5)) .toUpperCase()}
diff --git a/src/renderer/component/cardVerify/index.js b/src/renderer/component/cardVerify/index.js index 53bdf05b9..390622f3d 100644 --- a/src/renderer/component/cardVerify/index.js +++ b/src/renderer/component/cardVerify/index.js @@ -1,7 +1,7 @@ -import React from "react"; -import { connect } from "react-redux"; -import { selectUserEmail } from "redux/selectors/user"; -import CardVerify from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { selectUserEmail } from 'redux/selectors/user'; +import CardVerify from './view'; const select = state => ({ email: selectUserEmail(state), diff --git a/src/renderer/component/cardVerify/view.jsx b/src/renderer/component/cardVerify/view.jsx index 4494cbf6c..4690b61d2 100644 --- a/src/renderer/component/cardVerify/view.jsx +++ b/src/renderer/component/cardVerify/view.jsx @@ -1,6 +1,6 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Link from "component/link"; +import React from 'react'; +import PropTypes from 'prop-types'; +import Link from 'component/link'; let scriptLoading = false; let scriptLoaded = false; @@ -48,8 +48,8 @@ class CardVerify extends React.Component { scriptLoading = true; - const script = document.createElement("script"); - script.src = "https://checkout.stripe.com/checkout.js"; + const script = document.createElement('script'); + script.src = 'https://checkout.stripe.com/checkout.js'; script.async = 1; this.loadPromise = (() => { @@ -69,12 +69,8 @@ class CardVerify extends React.Component { }; }); const wrappedPromise = new Promise((accept, cancel) => { - promise.then( - () => (canceled ? cancel({ isCanceled: true }) : accept()) - ); - promise.catch( - error => (canceled ? cancel({ isCanceled: true }) : cancel(error)) - ); + promise.then(() => (canceled ? cancel({ isCanceled: true }) : accept())); + promise.catch(error => (canceled ? cancel({ isCanceled: true }) : cancel(error))); }); return { @@ -85,9 +81,7 @@ class CardVerify extends React.Component { }; })(); - this.loadPromise.promise - .then(this.onScriptLoaded) - .catch(this.onScriptError); + this.loadPromise.promise.then(this.onScriptLoaded).catch(this.onScriptError); document.body.appendChild(script); } @@ -119,7 +113,7 @@ class CardVerify extends React.Component { }; onScriptError = (...args) => { - throw new Error("Unable to load credit validation script."); + throw new Error('Unable to load credit validation script.'); }; onClosed = () => { @@ -139,10 +133,10 @@ class CardVerify extends React.Component { CardVerify.stripeHandler.open({ allowRememberMe: false, closed: this.onClosed, - description: __("Confirm Identity"), + description: __('Confirm Identity'), email: this.props.email, - locale: "auto", - panelLabel: "Verify", + locale: 'auto', + panelLabel: 'Verify', token: this.props.token, zipCode: true, }); @@ -151,9 +145,7 @@ class CardVerify extends React.Component { onClick = () => { if (scriptDidError) { try { - throw new Error( - "Tried to call onClick, but StripeCheckout failed to load" - ); + throw new Error('Tried to call onClick, but StripeCheckout failed to load'); } catch (x) {} } else if (CardVerify.stripeHandler) { this.showStripeDialog(); @@ -168,9 +160,7 @@ class CardVerify extends React.Component { button="alt" label={this.props.label} icon="icon-lock" - disabled={ - this.props.disabled || this.state.open || this.hasPendingClick - } + disabled={this.props.disabled || this.state.open || this.hasPendingClick} onClick={this.onClick.bind(this)} /> ); diff --git a/src/renderer/component/channelTile/index.js b/src/renderer/component/channelTile/index.js index 4c86e8b14..51a4835f6 100644 --- a/src/renderer/component/channelTile/index.js +++ b/src/renderer/component/channelTile/index.js @@ -1,11 +1,11 @@ -import React from "react"; -import { connect } from "react-redux"; -import { makeSelectClaimForUri } from "redux/selectors/claims"; -import { doNavigate } from "redux/actions/navigation"; -import { doResolveUri } from "redux/actions/content"; -import { makeSelectTotalItemsForChannel } from "redux/selectors/content"; -import { makeSelectIsUriResolving } from "redux/selectors/content"; -import ChannelTile from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; +import { doNavigate } from 'redux/actions/navigation'; +import { doResolveUri } from 'redux/actions/content'; +import { makeSelectTotalItemsForChannel } from 'redux/selectors/content'; +import { makeSelectIsUriResolving } from 'redux/selectors/content'; +import ChannelTile from './view'; const select = (state, props) => ({ claim: makeSelectClaimForUri(props.uri)(state), diff --git a/src/renderer/component/channelTile/view.jsx b/src/renderer/component/channelTile/view.jsx index e4a4ed8be..73efed63a 100644 --- a/src/renderer/component/channelTile/view.jsx +++ b/src/renderer/component/channelTile/view.jsx @@ -1,6 +1,6 @@ -import React from "react"; -import CardMedia from "component/cardMedia"; -import { TruncatedText, BusyMessage } from "component/common.js"; +import React from 'react'; +import CardMedia from 'component/cardMedia'; +import { TruncatedText, BusyMessage } from 'component/common.js'; class ChannelTile extends React.PureComponent { componentDidMount() { @@ -26,12 +26,12 @@ class ChannelTile extends React.PureComponent { channelId = claim.claim_id; } - let onClick = () => navigate("/show", { uri }); + const onClick = () => navigate('/show', { uri }); return (
-
+
{channelName && }
@@ -40,19 +40,15 @@ class ChannelTile extends React.PureComponent {
- {isResolvingUri && ( - - )} + {isResolvingUri && } {totalItems > 0 && ( - This is a channel with {totalItems}{" "} - {totalItems === 1 ? " item" : " items"} inside of it. + This is a channel with {totalItems} {totalItems === 1 ? ' item' : ' items'}{' '} + inside of it. )} {!isResolvingUri && - !totalItems && ( - This is an empty channel. - )} + !totalItems && This is an empty channel.}
diff --git a/src/renderer/component/common.js b/src/renderer/component/common.js index 2be86b00b..629d09f57 100644 --- a/src/renderer/component/common.js +++ b/src/renderer/component/common.js @@ -1,27 +1,7 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { formatCredits, formatFullPrice } from "util/formatCredits"; -import lbry from "../lbry.js"; - -//component/icon.js -export class Icon extends React.PureComponent { - static propTypes = { - icon: PropTypes.string.isRequired, - className: PropTypes.string, - fixed: PropTypes.bool, - }; - - render() { - const { fixed, className } = this.props; - const spanClassName = - "icon " + - ("fixed" in this.props ? "icon-fixed-width " : "") + - this.props.icon + - " " + - (this.props.className || ""); - return ; - } -} +import React from 'react'; +import PropTypes from 'prop-types'; +import { formatCredits, formatFullPrice } from 'util/formatCredits'; +import lbry from '../lbry.js'; export class TruncatedText extends React.PureComponent { static propTypes = { @@ -34,10 +14,7 @@ export class TruncatedText extends React.PureComponent { render() { return ( - + {this.props.children} ); @@ -73,14 +50,14 @@ export class CreditAmount extends React.PureComponent { showFree: PropTypes.bool, showFullPrice: PropTypes.bool, showPlus: PropTypes.bool, - look: PropTypes.oneOf(["indicator", "plain", "fee"]), + look: PropTypes.oneOf(['indicator', 'plain', 'fee']), }; static defaultProps = { precision: 2, label: true, showFree: false, - look: "indicator", + look: 'indicator', showFullPrice: false, showPlus: false, }; @@ -90,46 +67,43 @@ export class CreditAmount extends React.PureComponent { const { amount, precision, showFullPrice } = this.props; let formattedAmount; - let fullPrice = formatFullPrice(amount, 2); + const fullPrice = formatFullPrice(amount, 2); if (showFullPrice) { formattedAmount = fullPrice; } else { formattedAmount = amount > 0 && amount < minimumRenderableAmount - ? "<" + minimumRenderableAmount + ? `<${minimumRenderableAmount}` : formatCredits(amount, precision); } let amountText; if (this.props.showFree && parseFloat(this.props.amount) === 0) { - amountText = __("free"); + amountText = __('free'); } else { if (this.props.label) { const label = - typeof this.props.label === "string" + typeof this.props.label === 'string' ? this.props.label - : parseFloat(amount) == 1 ? __("credit") : __("credits"); + : parseFloat(amount) == 1 ? __('credit') : __('credits'); - amountText = formattedAmount + " " + label; + amountText = `${formattedAmount} ${label}`; } else { amountText = formattedAmount; } if (this.props.showPlus && amount > 0) { - amountText = "+" + amountText; + amountText = `+${amountText}`; } } return ( - + {amountText} {this.props.isEstimate ? ( * @@ -155,7 +129,7 @@ export class Thumbnail extends React.PureComponent { constructor(props) { super(props); - this._defaultImageUri = lbry.imagePath("default-thumb.svg"); + this._defaultImageUri = lbry.imagePath('default-thumb.svg'); this._maxLoadTime = 10000; this._isMounted = false; @@ -180,7 +154,7 @@ export class Thumbnail extends React.PureComponent { } render() { - const className = this.props.className ? this.props.className : "", + const className = this.props.className ? this.props.className : '', otherProps = Object.assign({}, this.props); delete otherProps.className; return ( diff --git a/src/renderer/component/common/spinner.jsx b/src/renderer/component/common/spinner.jsx index 8863459c8..14938f9b9 100644 --- a/src/renderer/component/common/spinner.jsx +++ b/src/renderer/component/common/spinner.jsx @@ -1,16 +1,14 @@ -import React from "react"; -import classnames from "classnames"; +import React from 'react'; +import classnames from 'classnames'; -export default ({ dark, className }) => { - return ( -
- ); -}; +export default ({ dark, className }) => ( +
+); diff --git a/src/renderer/component/dateTime/index.js b/src/renderer/component/dateTime/index.js index dbef1e030..06009a4ca 100644 --- a/src/renderer/component/dateTime/index.js +++ b/src/renderer/component/dateTime/index.js @@ -1,14 +1,11 @@ -import React from "react"; -import { connect } from "react-redux"; -import { makeSelectBlockDate } from "redux/selectors/wallet"; -import { doFetchBlock } from "redux/actions/wallet"; -import DateTime from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { makeSelectBlockDate } from 'redux/selectors/wallet'; +import { doFetchBlock } from 'redux/actions/wallet'; +import DateTime from './view'; const select = (state, props) => ({ - date: - !props.date && props.block - ? makeSelectBlockDate(props.block)(state) - : props.date, + date: !props.date && props.block ? makeSelectBlockDate(props.block)(state) : props.date, }); const perform = dispatch => ({ diff --git a/src/renderer/component/dateTime/view.jsx b/src/renderer/component/dateTime/view.jsx index b5dff1239..83f7fa755 100644 --- a/src/renderer/component/dateTime/view.jsx +++ b/src/renderer/component/dateTime/view.jsx @@ -1,15 +1,15 @@ -import React from "react"; +import React from 'react'; class DateTime extends React.PureComponent { - static SHOW_DATE = "date"; - static SHOW_TIME = "time"; - static SHOW_BOTH = "both"; + static SHOW_DATE = 'date'; + static SHOW_TIME = 'time'; + static SHOW_BOTH = 'both'; static defaultProps = { formatOptions: { - month: "long", - day: "numeric", - year: "numeric", + month: 'long', + day: 'numeric', + year: 'numeric', }, }; @@ -37,12 +37,12 @@ class DateTime extends React.PureComponent { {date && (show == DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) && - date.toLocaleDateString([locale, "en-US"], formatOptions)} - {show == DateTime.SHOW_BOTH && " "} + date.toLocaleDateString([locale, 'en-US'], formatOptions)} + {show == DateTime.SHOW_BOTH && ' '} {date && (show == DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) && date.toLocaleTimeString()} - {!date && "..."} + {!date && '...'} ); } diff --git a/src/renderer/component/file-selector.js b/src/renderer/component/file-selector.js index adc481782..18284268b 100644 --- a/src/renderer/component/file-selector.js +++ b/src/renderer/component/file-selector.js @@ -1,16 +1,17 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React from 'react'; +import PropTypes from 'prop-types'; + +const { remote } = require('electron'); -const { remote } = require("electron"); class FileSelector extends React.PureComponent { static propTypes = { - type: PropTypes.oneOf(["file", "directory"]), + type: PropTypes.oneOf(['file', 'directory']), initPath: PropTypes.string, onFileChosen: PropTypes.func, }; static defaultProps = { - type: "file", + type: 'file', }; constructor(props) { @@ -27,10 +28,7 @@ class FileSelector extends React.PureComponent { handleButtonClick() { remote.dialog.showOpenDialog( { - properties: - this.props.type == "file" - ? ["openFile"] - : ["openDirectory", "createDirectory"], + properties: this.props.type == 'file' ? ['openFile'] : ['openDirectory', 'createDirectory'], }, paths => { if (!paths) { @@ -40,7 +38,7 @@ class FileSelector extends React.PureComponent { const path = paths[0]; this.setState({ - path: path, + path, }); if (this.props.onFileChosen) { this.props.onFileChosen(path); @@ -59,12 +57,10 @@ class FileSelector extends React.PureComponent { > - {this.props.type == "file" - ? __("Choose File") - : __("Choose Directory")} + {this.props.type == 'file' ? __('Choose File') : __('Choose Directory')} - {" "} + {' '}
diff --git a/src/renderer/component/fileActions/index.js b/src/renderer/component/fileActions/index.js index 53030756f..b848342c0 100644 --- a/src/renderer/component/fileActions/index.js +++ b/src/renderer/component/fileActions/index.js @@ -1,14 +1,14 @@ -import React from "react"; -import { connect } from "react-redux"; -import { makeSelectFileInfoForUri } from "redux/selectors/file_info"; -import { makeSelectCostInfoForUri } from "redux/selectors/cost_info"; -import { doOpenModal } from "redux/actions/app"; -import { makeSelectClaimIsMine } from "redux/selectors/claims"; -import FileActions from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { makeSelectFileInfoForUri } from 'redux/selectors/file_info'; +import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info'; +import { doOpenModal } from 'redux/actions/app'; +import { makeSelectClaimIsMine } from 'redux/selectors/claims'; +import FileActions from './view'; const select = (state, props) => ({ fileInfo: makeSelectFileInfoForUri(props.uri)(state), - /*availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix*/ + /* availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix */ costInfo: makeSelectCostInfoForUri(props.uri)(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state), }); diff --git a/src/renderer/component/fileActions/view.jsx b/src/renderer/component/fileActions/view.jsx index a382191ee..b2d83f3b9 100644 --- a/src/renderer/component/fileActions/view.jsx +++ b/src/renderer/component/fileActions/view.jsx @@ -1,7 +1,7 @@ -import React from "react"; -import Link from "component/link"; -import FileDownloadLink from "component/fileDownloadLink"; -import * as modals from "constants/modal_types"; +import React from 'react'; +import Link from 'component/link'; +import FileDownloadLink from 'component/fileDownloadLink'; +import * as modals from 'constants/modal_types'; class FileActions extends React.PureComponent { render() { @@ -17,7 +17,7 @@ class FileActions extends React.PureComponent { openModal(modals.CONFIRM_FILE_REMOVE, { uri })} /> @@ -28,22 +28,22 @@ class FileActions extends React.PureComponent { icon="icon-flag" href={`https://lbry.io/dmca?claim_id=${claimId}`} className="no-underline" - label={__("report")} + label={__('report')} /> )} {claimIsMine && ( ({ claim: makeSelectClaimForUri(props.uri)(state), diff --git a/src/renderer/component/fileCard/view.jsx b/src/renderer/component/fileCard/view.jsx index a7ec6a5fa..e38bf06c2 100644 --- a/src/renderer/component/fileCard/view.jsx +++ b/src/renderer/component/fileCard/view.jsx @@ -1,14 +1,14 @@ -import React from "react"; -import lbryuri from "lbryuri.js"; -import CardMedia from "component/cardMedia"; -import Link from "component/link"; -import { TruncatedText } from "component/common"; -import Icon from "component/icon"; -import FilePrice from "component/filePrice"; -import UriIndicator from "component/uriIndicator"; -import NsfwOverlay from "component/nsfwOverlay"; -import TruncatedMarkdown from "component/truncatedMarkdown"; -import * as icons from "constants/icons"; +import React from 'react'; +import lbryuri from 'lbryuri.js'; +import CardMedia from 'component/cardMedia'; +import Link from 'component/link'; +import { TruncatedText } from 'component/common'; +import Icon from 'component/icon'; +import FilePrice from 'component/filePrice'; +import UriIndicator from 'component/uriIndicator'; +import NsfwOverlay from 'component/nsfwOverlay'; +import TruncatedMarkdown from 'component/truncatedMarkdown'; +import * as icons from 'constants/icons'; class FileCard extends React.PureComponent { constructor(props) { @@ -59,35 +59,27 @@ class FileCard extends React.PureComponent { const uri = lbryuri.normalize(this.props.uri); const title = metadata && metadata.title ? metadata.title : uri; - const thumbnail = - metadata && metadata.thumbnail ? metadata.thumbnail : null; + const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - const isRewardContent = - claim && rewardedContentClaimIds.includes(claim.claim_id); + const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); - let description = ""; + let description = ''; if (isResolvingUri && !claim) { - description = __("Loading..."); + description = __('Loading...'); } else if (metadata && metadata.description) { description = metadata.description; } else if (claim === null) { - description = __("This address contains no content."); + description = __('This address contains no content.'); } return (
- navigate("/show", { uri })} - className="card__link" - > + navigate('/show', { uri })} className="card__link">
@@ -95,12 +87,12 @@ class FileCard extends React.PureComponent {
- {" "} - {isRewardContent && }{" "} - {fileInfo && } + {' '} + {isRewardContent && }{' '} + {fileInfo && } - +
diff --git a/src/renderer/component/fileDetails/index.js b/src/renderer/component/fileDetails/index.js index fb29fe042..9b877dec9 100644 --- a/src/renderer/component/fileDetails/index.js +++ b/src/renderer/component/fileDetails/index.js @@ -1,13 +1,13 @@ -import React from "react"; -import { connect } from "react-redux"; +import React from 'react'; +import { connect } from 'react-redux'; import { makeSelectClaimForUri, makeSelectContentTypeForUri, makeSelectMetadataForUri, -} from "redux/selectors/claims"; -import FileDetails from "./view"; -import { doOpenFileInFolder } from "redux/actions/file_info"; -import { makeSelectFileInfoForUri } from "redux/selectors/file_info"; +} from 'redux/selectors/claims'; +import FileDetails from './view'; +import { doOpenFileInFolder } from 'redux/actions/file_info'; +import { makeSelectFileInfoForUri } from 'redux/selectors/file_info'; const select = (state, props) => ({ claim: makeSelectClaimForUri(props.uri)(state), diff --git a/src/renderer/component/fileDetails/view.jsx b/src/renderer/component/fileDetails/view.jsx index 55afed97d..9b6753484 100644 --- a/src/renderer/component/fileDetails/view.jsx +++ b/src/renderer/component/fileDetails/view.jsx @@ -1,27 +1,20 @@ -import React from "react"; -import ReactMarkdown from "react-markdown"; -import lbry from "lbry.js"; -import FileActions from "component/fileActions"; -import Link from "component/link"; -import DateTime from "component/dateTime"; +import React from 'react'; +import ReactMarkdown from 'react-markdown'; +import lbry from 'lbry.js'; +import FileActions from 'component/fileActions'; +import Link from 'component/link'; +import DateTime from 'component/dateTime'; -const path = require("path"); +const path = require('path'); class FileDetails extends React.PureComponent { render() { - const { - claim, - contentType, - fileInfo, - metadata, - openFolder, - uri, - } = this.props; + const { claim, contentType, fileInfo, metadata, openFolder, uri } = this.props; if (!claim || !metadata) { return (
- {__("Empty claim or metadata info.")} + {__('Empty claim or metadata info.')}
); } @@ -29,9 +22,7 @@ class FileDetails extends React.PureComponent { const { description, language, license } = metadata; const mediaType = lbry.getMediaType(contentType); - const downloadPath = fileInfo - ? path.normalize(fileInfo.download_path) - : null; + const downloadPath = fileInfo ? path.normalize(fileInfo.download_path) : null; return (
@@ -40,33 +31,31 @@ class FileDetails extends React.PureComponent {
- + - + - + {downloadPath && ( - + )} diff --git a/src/renderer/component/fileDownloadLink/index.js b/src/renderer/component/fileDownloadLink/index.js index edc52cffc..e13b4b3d4 100644 --- a/src/renderer/component/fileDownloadLink/index.js +++ b/src/renderer/component/fileDownloadLink/index.js @@ -1,20 +1,20 @@ -import React from "react"; -import { connect } from "react-redux"; +import React from 'react'; +import { connect } from 'react-redux'; import { makeSelectFileInfoForUri, makeSelectDownloadingForUri, makeSelectLoadingForUri, -} from "redux/selectors/file_info"; -import { makeSelectCostInfoForUri } from "redux/selectors/cost_info"; -import { doFetchAvailability } from "redux/actions/availability"; -import { doOpenFileInShell } from "redux/actions/file_info"; -import { doPurchaseUri, doStartDownload } from "redux/actions/content"; -import { doPause } from "redux/actions/media"; -import FileDownloadLink from "./view"; +} from 'redux/selectors/file_info'; +import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info'; +import { doFetchAvailability } from 'redux/actions/availability'; +import { doOpenFileInShell } from 'redux/actions/file_info'; +import { doPurchaseUri, doStartDownload } from 'redux/actions/content'; +import { doPause } from 'redux/actions/media'; +import FileDownloadLink from './view'; const select = (state, props) => ({ fileInfo: makeSelectFileInfoForUri(props.uri)(state), - /*availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix*/ + /* availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix */ downloading: makeSelectDownloadingForUri(props.uri)(state), costInfo: makeSelectCostInfoForUri(props.uri)(state), loading: makeSelectLoadingForUri(props.uri)(state), diff --git a/src/renderer/component/fileDownloadLink/view.jsx b/src/renderer/component/fileDownloadLink/view.jsx index d2349059b..57818da51 100644 --- a/src/renderer/component/fileDownloadLink/view.jsx +++ b/src/renderer/component/fileDownloadLink/view.jsx @@ -1,6 +1,7 @@ -import React from "react"; -import { Icon, BusyMessage } from "component/common"; -import Link from "component/link"; +import React from 'react'; +import { BusyMessage } from 'component/common'; +import Icon from 'component/icon'; +import Link from 'component/link'; class FileDownloadLink extends React.PureComponent { componentWillMount() { @@ -55,9 +56,7 @@ class FileDownloadLink extends React.PureComponent { fileInfo && fileInfo.written_bytes ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0, - label = fileInfo - ? progress.toFixed(0) + __("% complete") - : __("Connecting..."), + label = fileInfo ? progress.toFixed(0) + __('% complete') : __('Connecting...'), labelWithIcon = ( @@ -69,7 +68,7 @@ class FileDownloadLink extends React.PureComponent {
{labelWithIcon}
@@ -78,24 +77,23 @@ class FileDownloadLink extends React.PureComponent { ); } else if (fileInfo === null && !downloading) { if (!costInfo) { - return ; - } else { - return ( - { - purchaseUri(uri); - }} - /> - ); + return ; } + return ( + { + purchaseUri(uri); + }} + /> + ); } else if (fileInfo && fileInfo.download_path) { return ( ({}); diff --git a/src/renderer/component/fileList/view.jsx b/src/renderer/component/fileList/view.jsx index 10dd2d2d7..ba1ba296c 100644 --- a/src/renderer/component/fileList/view.jsx +++ b/src/renderer/component/fileList/view.jsx @@ -1,23 +1,23 @@ -import React from "react"; -import lbryuri from "lbryuri.js"; -import FormField from "component/formField"; -import FileTile from "component/fileTile"; -import { BusyMessage } from "component/common.js"; +import React from 'react'; +import lbryuri from 'lbryuri.js'; +import FormField from 'component/formField'; +import FileTile from 'component/fileTile'; +import { BusyMessage } from 'component/common.js'; class FileList extends React.PureComponent { constructor(props) { super(props); this.state = { - sortBy: "date", + sortBy: 'date', }; this._sortFunctions = { - date: function(fileInfos) { + date(fileInfos) { return fileInfos.slice().reverse(); }, - title: function(fileInfos) { - return fileInfos.slice().sort(function(fileInfo1, fileInfo2) { + title(fileInfos) { + return fileInfos.slice().sort((fileInfo1, fileInfo2) => { const title1 = fileInfo1.value ? fileInfo1.value.stream.metadata.title.toLowerCase() : fileInfo1.name; @@ -28,25 +28,21 @@ class FileList extends React.PureComponent { return -1; } else if (title1 > title2) { return 1; - } else { - return 0; } + return 0; }); }, - filename: function(fileInfos) { - return fileInfos - .slice() - .sort(function({ file_name: fileName1 }, { file_name: fileName2 }) { - const fileName1Lower = fileName1.toLowerCase(); - const fileName2Lower = fileName2.toLowerCase(); - if (fileName1Lower < fileName2Lower) { - return -1; - } else if (fileName2Lower > fileName1Lower) { - return 1; - } else { - return 0; - } - }); + filename(fileInfos) { + return fileInfos.slice().sort(({ file_name: fileName1 }, { file_name: fileName2 }) => { + const fileName1Lower = fileName1.toLowerCase(); + const fileName2Lower = fileName2.toLowerCase(); + if (fileName1Lower < fileName2Lower) { + return -1; + } else if (fileName2Lower > fileName1Lower) { + return 1; + } + return 0; + }); }, }; } @@ -54,9 +50,8 @@ class FileList extends React.PureComponent { getChannelSignature(fileInfo) { if (fileInfo.value) { return fileInfo.value.publisherSignature.certificateId; - } else { - return fileInfo.metadata.publisherSignature.certificateId; } + return fileInfo.metadata.publisherSignature.certificateId; } handleSortChanged(event) { @@ -71,7 +66,7 @@ class FileList extends React.PureComponent { const content = []; this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => { - let uriParams = {}; + const uriParams = {}; if (fileInfo.channel_name) { uriParams.channelName = fileInfo.channel_name; @@ -89,7 +84,7 @@ class FileList extends React.PureComponent { uri={uri} showPrice={false} showLocal={false} - showActions={true} + showActions showEmpty={this.props.fileTileShowEmpty} /> ); @@ -98,10 +93,10 @@ class FileList extends React.PureComponent {
{fetching && } - {__("Sort by")}{" "} + {__('Sort by')}{' '} - - + + {content} diff --git a/src/renderer/component/fileListSearch/index.js b/src/renderer/component/fileListSearch/index.js index cd6e087f6..ec06231af 100644 --- a/src/renderer/component/fileListSearch/index.js +++ b/src/renderer/component/fileListSearch/index.js @@ -1,11 +1,8 @@ -import React from "react"; -import { connect } from "react-redux"; -import { doSearch } from "redux/actions/search"; -import { - selectIsSearching, - makeSelectSearchUris, -} from "redux/selectors/search"; -import FileListSearch from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { doSearch } from 'redux/actions/search'; +import { selectIsSearching, makeSelectSearchUris } from 'redux/selectors/search'; +import FileListSearch from './view'; const select = (state, props) => ({ isSearching: selectIsSearching(state), diff --git a/src/renderer/component/fileListSearch/view.jsx b/src/renderer/component/fileListSearch/view.jsx index 00a759d5e..afe5d4f36 100644 --- a/src/renderer/component/fileListSearch/view.jsx +++ b/src/renderer/component/fileListSearch/view.jsx @@ -1,9 +1,9 @@ -import React from "react"; -import FileTile from "component/fileTile"; -import ChannelTile from "component/channelTile"; -import Link from "component/link"; -import { BusyMessage } from "component/common.js"; -import lbryuri from "lbryuri"; +import React from 'react'; +import FileTile from 'component/fileTile'; +import ChannelTile from 'component/channelTile'; +import Link from 'component/link'; +import { BusyMessage } from 'component/common.js'; +import lbryuri from 'lbryuri'; const SearchNoResults = props => { const { query } = props; @@ -11,8 +11,8 @@ const SearchNoResults = props => { return (
- {(__("No one has checked anything in for %s yet."), query)}{" "} - + {(__('No one has checked anything in for %s yet.'), query)}{' '} +
); @@ -38,18 +38,14 @@ class FileListSearch extends React.PureComponent { return (
- {isSearching && - !uris && ( - - )} + {isSearching && !uris && } - {isSearching && - uris && } + {isSearching && uris && } {uris && uris.length ? uris.map( uri => - lbryuri.parse(uri).name[0] === "@" ? ( + lbryuri.parse(uri).name[0] === '@' ? ( ) : ( diff --git a/src/renderer/component/filePrice/index.js b/src/renderer/component/filePrice/index.js index 7460b0a4e..93eaee79d 100644 --- a/src/renderer/component/filePrice/index.js +++ b/src/renderer/component/filePrice/index.js @@ -1,12 +1,12 @@ -import React from "react"; -import { connect } from "react-redux"; -import { doFetchCostInfoForUri } from "redux/actions/cost_info"; +import React from 'react'; +import { connect } from 'react-redux'; +import { doFetchCostInfoForUri } from 'redux/actions/cost_info'; import { makeSelectCostInfoForUri, makeSelectFetchingCostInfoForUri, -} from "redux/selectors/cost_info"; -import { makeSelectClaimForUri } from "redux/selectors/claims"; -import FilePrice from "./view"; +} from 'redux/selectors/cost_info'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; +import FilePrice from './view'; const select = (state, props) => ({ costInfo: makeSelectCostInfoForUri(props.uri)(state), diff --git a/src/renderer/component/filePrice/view.jsx b/src/renderer/component/filePrice/view.jsx index a2b5067df..5a5947f14 100644 --- a/src/renderer/component/filePrice/view.jsx +++ b/src/renderer/component/filePrice/view.jsx @@ -1,5 +1,5 @@ -import React from "react"; -import { CreditAmount } from "component/common"; +import React from 'react'; +import { CreditAmount } from 'component/common'; class FilePrice extends React.PureComponent { componentWillMount() { @@ -19,14 +19,12 @@ class FilePrice extends React.PureComponent { } render() { - const { costInfo, look = "indicator", showFullPrice = false } = this.props; + const { costInfo, look = 'indicator', showFullPrice = false } = this.props; const isEstimate = costInfo ? !costInfo.includesData : null; if (!costInfo) { - return ( - ??? - ); + return ???; } return ( @@ -34,7 +32,7 @@ class FilePrice extends React.PureComponent { label={false} amount={costInfo.cost} isEstimate={isEstimate} - showFree={true} + showFree showFullPrice={showFullPrice} /> ); diff --git a/src/renderer/component/fileTile/index.js b/src/renderer/component/fileTile/index.js index f352b1633..601a94a7e 100644 --- a/src/renderer/component/fileTile/index.js +++ b/src/renderer/component/fileTile/index.js @@ -1,18 +1,12 @@ -import React from "react"; -import { connect } from "react-redux"; -import { doNavigate } from "redux/actions/navigation"; -import { doResolveUri } from "redux/actions/content"; -import { - makeSelectClaimForUri, - makeSelectMetadataForUri, -} from "redux/selectors/claims"; -import { makeSelectFileInfoForUri } from "redux/selectors/file_info"; -import { selectShowNsfw } from "redux/selectors/settings"; -import { - makeSelectIsUriResolving, - selectRewardContentClaimIds, -} from "redux/selectors/content"; -import FileTile from "./view"; +import React from 'react'; +import { connect } from 'react-redux'; +import { doNavigate } from 'redux/actions/navigation'; +import { doResolveUri } from 'redux/actions/content'; +import { makeSelectClaimForUri, makeSelectMetadataForUri } from 'redux/selectors/claims'; +import { makeSelectFileInfoForUri } from 'redux/selectors/file_info'; +import { selectShowNsfw } from 'redux/selectors/settings'; +import { makeSelectIsUriResolving, selectRewardContentClaimIds } from 'redux/selectors/content'; +import FileTile from './view'; const select = (state, props) => ({ claim: makeSelectClaimForUri(props.uri)(state), diff --git a/src/renderer/component/fileTile/view.jsx b/src/renderer/component/fileTile/view.jsx index fbf3c9110..231cfc293 100644 --- a/src/renderer/component/fileTile/view.jsx +++ b/src/renderer/component/fileTile/view.jsx @@ -1,15 +1,15 @@ -import React from "react"; -import * as icons from "constants/icons"; -import lbryuri from "lbryuri.js"; -import CardMedia from "component/cardMedia"; -import { TruncatedText } from "component/common.js"; -import FilePrice from "component/filePrice"; -import NsfwOverlay from "component/nsfwOverlay"; -import Icon from "component/icon"; +import React from 'react'; +import * as icons from 'constants/icons'; +import lbryuri from 'lbryuri.js'; +import CardMedia from 'component/cardMedia'; +import { TruncatedText } from 'component/common.js'; +import FilePrice from 'component/filePrice'; +import NsfwOverlay from 'component/nsfwOverlay'; +import Icon from 'component/icon'; class FileTile extends React.PureComponent { - static SHOW_EMPTY_PUBLISH = "publish"; - static SHOW_EMPTY_PENDING = "pending"; + static SHOW_EMPTY_PUBLISH = 'publish'; + static SHOW_EMPTY_PENDING = 'pending'; static defaultProps = { showPrice: true, @@ -36,11 +36,7 @@ class FileTile extends React.PureComponent { } handleMouseOver() { - if ( - this.props.obscureNsfw && - this.props.metadata && - this.props.metadata.nsfw - ) { + if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { this.setState({ showNsfwHelp: true, }); @@ -73,59 +69,49 @@ class FileTile extends React.PureComponent { const isClaimed = !!claim; const isClaimable = lbryuri.isClaimable(uri); const title = - isClaimed && metadata && metadata.title - ? metadata.title - : lbryuri.parse(uri).contentName; - const thumbnail = - metadata && metadata.thumbnail ? metadata.thumbnail : null; + isClaimed && metadata && metadata.title ? metadata.title : lbryuri.parse(uri).contentName; + const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - const isRewardContent = - claim && rewardedContentClaimIds.includes(claim.claim_id); + const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); - let onClick = () => navigate("/show", { uri }); + let onClick = () => navigate('/show', { uri }); - let name = ""; + let name = ''; if (claim) { name = claim.name; } - let description = ""; + let description = ''; if (isClaimed) { description = metadata && metadata.description; } else if (isResolvingUri) { - description = __("Loading..."); + description = __('Loading...'); } else if (showEmpty === FileTile.SHOW_EMPTY_PUBLISH) { - onClick = () => navigate("/publish", {}); + onClick = () => navigate('/publish', {}); description = ( - {__("This location is unused.")}{" "} - {isClaimable && ( - {__("Put something here!")} - )} + {__('This location is unused.')}{' '} + {isClaimable && {__('Put something here!')}} ); } else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) { - description = ( - - {__("This file is pending confirmation.")} - - ); + description = {__('This file is pending confirmation.')}; } return (
-
+
- {showPrice && }{" "} - {isRewardContent && }{" "} + {showPrice && }{' '} + {isRewardContent && }{' '} {showLocal && fileInfo && }

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

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