resolve merge conflicts

This commit is contained in:
Travis Eden 2018-01-05 18:57:24 -05:00
commit 7bc2f888f1
302 changed files with 7127 additions and 7496 deletions

View file

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.19.1
current_version = 0.19.4rc1
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)((?P<release>[a-z]+)(?P<candidate>\d+))?

33
.eslintrc.json Normal file
View file

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

1
.gitignore vendored
View file

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

12
.lintstagedrc Normal file
View file

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

5
.prettierrc.json Normal file
View file

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

View file

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

View file

@ -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).

112
README.md
View file

@ -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 <a href="https://git-for-windows.github.io/">github.io<a> (configure to use command prompt integration)
2. Download and install `npm` and `node` from <a href="https://nodejs.org/en/download/current/">nodejs.org<a>
3. Download and install `python 2.7` from <a href="https://www.python.org/downloads/windows/">python.org</a>
4. Download and Install `Microsoft Visual C++ Compiler for Python 2.7` from <a href="https://www.microsoft.com/en-us/download/confirmation.aspx?id=44266">Microsoft<a>
5. Download and install `.NET Framework 2.0 Software Development Kit (SDK) (x64)` from <a href="https://www.microsoft.com/en-gb/download/details.aspx?id=15354">Microsoft<a> (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 <a href="https://git-for-windows.github.io/">github.io<a>
(configure to use command prompt integration)
2. Download and install `npm` and `node` from
<a href="https://nodejs.org/en/download/current/">nodejs.org<a>
3. Download and install `python 2.7` from
<a href="https://www.python.org/downloads/windows/">python.org</a>
4. Download and Install `Microsoft Visual C++ Compiler for Python 2.7` from
<a href="https://www.microsoft.com/en-us/download/confirmation.aspx?id=44266">Microsoft<a>
5. Download and install `.NET Framework 2.0 Software Development Kit (SDK) (x64)` from
<a href="https://www.microsoft.com/en-gb/download/details.aspx?id=15354">Microsoft<a> (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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,36 +0,0 @@
const {Menu} = require('electron');
const electron = require('electron');
const app = electron.app;
const contextMenuTemplate = [
{
role: 'cut',
},
{
role: 'copy',
},
{
role: 'paste',
},
];
module.exports = {
showContextMenu(win, posX, posY, showDevItems) {
let template = contextMenuTemplate.slice();
if (showDevItems) {
template.push({
type: 'separator',
});
template.push(
{
label: 'Inspect Element',
click() {
win.inspectElement(posX, posY);
}
}
);
}
Menu.buildFromTemplate(template).popup(win);
},
};

View file

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

View file

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

View file

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

View file

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

View file

@ -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 (
<div className="form-field form-field--address">
<input
className={classnames("input-copyable", {
"input-copyable--with-copy-btn": showCopyButton,
className={classnames('input-copyable', {
'input-copyable--with-copy-btn': showCopyButton,
})}
type="text"
ref={input => {
@ -32,7 +32,7 @@ export default class Address extends React.PureComponent {
this._inputElem.select();
}}
readOnly="readonly"
value={address || ""}
value={address || ''}
/>
{showCopyButton && (
<span className="header__item">
@ -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') });
}}
/>
</span>

View file

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

View file

@ -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() {

View file

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

View file

@ -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 (
<div
className="card__media"
style={{ backgroundImage: "url('" + thumbnail + "')" }}
/>
);
return <div className="card__media" style={{ backgroundImage: `url('${thumbnail}')` }} />;
}
return (
@ -42,8 +37,8 @@ class CardMedia extends React.PureComponent {
<div className="card__autothumb__text">
{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()}
</div>
</div>

View file

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

View file

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

View file

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

View file

@ -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 (
<section className="file-tile card">
<div onClick={onClick} className="card__link">
<div className={"card__inner file-tile__row"}>
<div className="card__inner file-tile__row">
{channelName && <CardMedia title={channelName} thumbnail={null} />}
<div className="file-tile__content">
<div className="card__title-primary">
@ -40,19 +40,15 @@ class ChannelTile extends React.PureComponent {
</h3>
</div>
<div className="card__content card__subtext">
{isResolvingUri && (
<BusyMessage message={__("Resolving channel")} />
)}
{isResolvingUri && <BusyMessage message={__('Resolving channel')} />}
{totalItems > 0 && (
<span>
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.
</span>
)}
{!isResolvingUri &&
!totalItems && (
<span className="empty">This is an empty channel.</span>
)}
!totalItems && <span className="empty">This is an empty channel.</span>}
</div>
</div>
</div>

View file

@ -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 <span className={spanClassName} />;
}
}
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 (
<span
className="truncated-text"
style={{ WebkitLineClamp: this.props.lines }}
>
<span className="truncated-text" style={{ WebkitLineClamp: this.props.lines }}>
{this.props.children}
</span>
);
@ -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 (
<span
className={`credit-amount credit-amount--${this.props.look}`}
title={fullPrice}
>
<span className={`credit-amount credit-amount--${this.props.look}`} title={fullPrice}>
<span>{amountText}</span>
{this.props.isEstimate ? (
<span
className="credit-amount__estimate"
title={__("This is an estimate and does not include data fees")}
title={__('This is an estimate and does not include data fees')}
>
*
</span>
@ -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 (

View file

@ -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 (
<div
className={classnames(
"spinner",
{
"spinner--dark": dark,
},
className
)}
/>
);
};
export default ({ dark, className }) => (
<div
className={classnames(
'spinner',
{
'spinner--dark': dark,
},
className
)}
/>
);

View file

@ -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 => ({

View file

@ -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 {
<span>
{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 && '...'}
</span>
);
}

View file

@ -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 {
>
<span className="button__content">
<span className="button-label">
{this.props.type == "file"
? __("Choose File")
: __("Choose Directory")}
{this.props.type == 'file' ? __('Choose File') : __('Choose Directory')}
</span>
</span>
</button>{" "}
</button>{' '}
<span className="file-selector__path">
<input
className="input-copyable"
@ -76,7 +72,7 @@ class FileSelector extends React.PureComponent {
this._inputElem.select();
}}
readOnly="readonly"
value={this.state.path || __("No File Chosen")}
value={this.state.path || __('No File Chosen')}
/>
</span>
</div>

View file

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

View file

@ -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 {
<Link
button="text"
icon="icon-trash"
label={__("Remove")}
label={__('Remove')}
className="no-underline"
onClick={() => 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')}
/>
)}
<Link
button="primary"
icon="icon-gift"
label={__("Support")}
label={__('Support')}
navigate="/show"
className="card__action--right"
navigateParams={{ uri, tab: "tip" }}
navigateParams={{ uri, tab: 'tip' }}
/>
{claimIsMine && (
<Link
button="alt"
icon="icon-edit"
label={__("Edit")}
label={__('Edit')}
navigate="/publish"
className="card__action--right"
navigateParams={{ id: claimId }}

View file

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

View file

@ -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 (
<section
className={
"card card--small card--link " +
(obscureNsfw ? "card--obscured " : "")
}
className={`card card--small card--link ${obscureNsfw ? 'card--obscured ' : ''}`}
onMouseEnter={this.handleMouseOver.bind(this)}
onMouseLeave={this.handleMouseOut.bind(this)}
>
<div className="card__inner">
<Link
onClick={() => navigate("/show", { uri })}
className="card__link"
>
<Link onClick={() => navigate('/show', { uri })} className="card__link">
<CardMedia title={title} thumbnail={thumbnail} />
<div className="card__title-identity">
<div className="card__title" title={title}>
@ -95,12 +87,12 @@ class FileCard extends React.PureComponent {
</div>
<div className="card__subtitle">
<span className="card__indicators card--file-subtitle">
<FilePrice uri={uri} />{" "}
{isRewardContent && <Icon icon={icons.FEATURED} />}{" "}
{fileInfo && <Icon icon={icons.LOCAL} />}
<FilePrice uri={uri} />{' '}
{isRewardContent && <Icon icon={icons.FEATURED} leftPad />}{' '}
{fileInfo && <Icon icon={icons.LOCAL} leftPad />}
</span>
<span className="card--file-subtitle">
<UriIndicator uri={uri} link={true} span={true} smallCard />
<UriIndicator uri={uri} link span smallCard />
</span>
</div>
</div>

View file

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

View file

@ -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 (
<div className="card__content">
<span className="empty">{__("Empty claim or metadata info.")}</span>
<span className="empty">{__('Empty claim or metadata info.')}</span>
</div>
);
}
@ -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 (
<div>
@ -40,33 +31,31 @@ class FileDetails extends React.PureComponent {
<div className="divider__horizontal" />
<div className="card__content card__subtext card__subtext--allow-newlines">
<ReactMarkdown
source={description || ""}
escapeHtml={true}
disallowedTypes={["Heading", "HtmlInline", "HtmlBlock"]}
source={description || ''}
escapeHtml
disallowedTypes={['Heading', 'HtmlInline', 'HtmlBlock']}
/>
</div>
<div className="card__content">
<table className="table-standard table-stretch">
<tbody>
<tr>
<td>{__("Content-Type")}</td>
<td>{__('Content-Type')}</td>
<td>{mediaType}</td>
</tr>
<tr>
<td>{__("Language")}</td>
<td>{__('Language')}</td>
<td>{language}</td>
</tr>
<tr>
<td>{__("License")}</td>
<td>{__('License')}</td>
<td>{license}</td>
</tr>
{downloadPath && (
<tr>
<td>{__("Downloaded to")}</td>
<td>{__('Downloaded to')}</td>
<td>
<Link onClick={() => openFolder(downloadPath)}>
{downloadPath}
</Link>
<Link onClick={() => openFolder(downloadPath)}>{downloadPath}</Link>
</td>
</tr>
)}

View file

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

View file

@ -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 = (
<span className="button__content">
<Icon icon="icon-download" />
@ -69,7 +68,7 @@ class FileDownloadLink extends React.PureComponent {
<div className="faux-button-block file-download button-set-item">
<div
className="faux-button-block file-download__overlay"
style={{ width: progress + "%" }}
style={{ width: `${progress}%` }}
>
{labelWithIcon}
</div>
@ -78,24 +77,23 @@ class FileDownloadLink extends React.PureComponent {
);
} else if (fileInfo === null && !downloading) {
if (!costInfo) {
return <BusyMessage message={__("Fetching cost info")} />;
} else {
return (
<Link
button="text"
label={__("Download")}
icon="icon-download"
className="no-underline"
onClick={() => {
purchaseUri(uri);
}}
/>
);
return <BusyMessage message={__('Fetching cost info')} />;
}
return (
<Link
button="text"
label={__('Download')}
icon="icon-download"
className="no-underline"
onClick={() => {
purchaseUri(uri);
}}
/>
);
} else if (fileInfo && fileInfo.download_path) {
return (
<Link
label={__("Open")}
label={__('Open')}
button="text"
icon="icon-external-link-square"
className="no-underline"

View file

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

View file

@ -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 {
<section className="file-list__header">
{fetching && <BusyMessage />}
<span className="sort-section">
{__("Sort by")}{" "}
{__('Sort by')}{' '}
<FormField type="select" onChange={this.handleSortChanged.bind(this)}>
<option value="date">{__("Date")}</option>
<option value="title">{__("Title")}</option>
<option value="date">{__('Date')}</option>
<option value="title">{__('Title')}</option>
</FormField>
</span>
{content}

View file

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

View file

@ -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 (
<section>
<span className="empty">
{(__("No one has checked anything in for %s yet."), query)}{" "}
<Link label={__("Be the first")} navigate="/publish" />
{(__('No one has checked anything in for %s yet.'), query)}{' '}
<Link label={__('Be the first')} navigate="/publish" />
</span>
</section>
);
@ -38,18 +38,14 @@ class FileListSearch extends React.PureComponent {
return (
<div>
{isSearching &&
!uris && (
<BusyMessage message={__("Looking up the Dewey Decimals")} />
)}
{isSearching && !uris && <BusyMessage message={__('Looking up the Dewey Decimals')} />}
{isSearching &&
uris && <BusyMessage message={__("Refreshing the Dewey Decimals")} />}
{isSearching && uris && <BusyMessage message={__('Refreshing the Dewey Decimals')} />}
{uris && uris.length
? uris.map(
uri =>
lbryuri.parse(uri).name[0] === "@" ? (
lbryuri.parse(uri).name[0] === '@' ? (
<ChannelTile key={uri} uri={uri} />
) : (
<FileTile key={uri} uri={uri} />

View file

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

View file

@ -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 (
<span className={`credit-amount credit-amount--${look}`}>???</span>
);
return <span className={`credit-amount credit-amount--${look}`}>???</span>;
}
return (
@ -34,7 +32,7 @@ class FilePrice extends React.PureComponent {
label={false}
amount={costInfo.cost}
isEstimate={isEstimate}
showFree={true}
showFree
showFullPrice={showFullPrice}
/>
);

View file

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

View file

@ -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 = (
<span className="empty">
{__("This location is unused.")}{" "}
{isClaimable && (
<span className="button-text">{__("Put something here!")}</span>
)}
{__('This location is unused.')}{' '}
{isClaimable && <span className="button-text">{__('Put something here!')}</span>}
</span>
);
} else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) {
description = (
<span className="empty">
{__("This file is pending confirmation.")}
</span>
);
description = <span className="empty">{__('This file is pending confirmation.')}</span>;
}
return (
<section
className={"file-tile card " + (obscureNsfw ? "card--obscured " : "")}
className={`file-tile card ${obscureNsfw ? 'card--obscured ' : ''}`}
onMouseEnter={this.handleMouseOver.bind(this)}
onMouseLeave={this.handleMouseOut.bind(this)}
>
<div onClick={onClick} className="card__link">
<div className={"card__inner file-tile__row"}>
<div className="card__inner file-tile__row">
<CardMedia title={title || name} thumbnail={thumbnail} />
<div className="file-tile__content">
<div className="card__title-primary">
<span className="card__indicators">
{showPrice && <FilePrice uri={this.props.uri} />}{" "}
{isRewardContent && <Icon icon={icons.FEATURED} />}{" "}
{showPrice && <FilePrice uri={this.props.uri} />}{' '}
{isRewardContent && <Icon icon={icons.FEATURED} />}{' '}
{showLocal && fileInfo && <Icon icon={icons.LOCAL} />}
</span>
<h3>
@ -134,9 +120,7 @@ class FileTile extends React.PureComponent {
</div>
{description && (
<div className="card__content card__subtext">
<TruncatedText lines={!showActions ? 3 : 2}>
{description}
</TruncatedText>
<TruncatedText lines={!showActions ? 3 : 2}>{description}</TruncatedText>
</div>
)}
</div>

View file

@ -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 (
<form onSubmit={event => this.handleSubmit(event)}>
{this.props.children}
</form>
);
return <form onSubmit={event => this.handleSubmit(event)}>{this.props.children}</form>;
}
}
@ -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 (
<div
className={"form-row" + (this.state.isFocus ? " form-row--focus" : "")}
>
<div className={`form-row${this.state.isFocus ? ' form-row--focus' : ''}`}>
{this.props.label && !renderLabelInFormField ? (
<div
className={
"form-row__label-row " +
(this.props.labelPrefix ? "form-row__label-row--prefix" : "")
}
className={`form-row__label-row ${
this.props.labelPrefix ? 'form-row__label-row--prefix' : ''
}`}
>
<label
htmlFor={elementId}
className={
"form-field__label " +
(this.state.isError ? "form-field__label--error" : " ")
}
className={`form-field__label ${
this.state.isError ? 'form-field__label--error' : ' '
}`}
>
{this.props.label}
</label>
</div>
) : (
""
''
)}
<FormField
ref={ref => {
@ -163,12 +151,12 @@ export class FormRow extends React.PureComponent {
{!this.state.isError && this.props.helper ? (
<div className="form-field__helper">{this.props.helper}</div>
) : (
""
''
)}
{this.state.isError ? (
<div className="form-field__error">{this.state.errorMessage}</div>
) : (
""
''
)}
</div>
);
@ -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 = (
<span className="button__content">
{"icon" in props ? <Icon icon={icon} fixed={true} /> : null}
{'icon' in props ? <Icon icon={icon} fixed /> : null}
{label ? <span className="button-label">{label}</span> : null}
</span>
);

View file

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

View file

@ -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 <input> field, e.g. <select>, <textarea>
this._element = this.props.type;
@ -66,7 +63,7 @@ class FormField extends React.PureComponent {
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
* https://github.com/facebook/react/issues/3468
*/
if (this.props.type == "directory") {
if (this.props.type == 'directory') {
this.refs.field.webkitdirectory = true;
}
}
@ -75,7 +72,7 @@ class FormField extends React.PureComponent {
this.refs.field.value = path;
if (this.props.onChange) {
// Updating inputs programmatically doesn't generate an event, so we have to make our own
const event = new Event("change", { bubbles: true });
const event = new Event('change', { bubbles: true });
this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target
this.props.onChange(event);
}
@ -91,20 +88,17 @@ class FormField extends React.PureComponent {
clearError() {
this.setState({
isError: false,
errorMessage: "",
errorMessage: '',
});
}
getValue() {
if (this.props.type == "checkbox") {
if (this.props.type == 'checkbox') {
return this.refs.field.checked;
} else if (this.props.type == "SimpleMDE") {
} else if (this.props.type == 'SimpleMDE') {
return this.refs.field.simplemde.value();
} else {
return this.props.trim
? this.refs.field.value.trim()
: this.refs.field.value;
}
return this.props.trim ? this.refs.field.value.trim() : this.refs.field.value;
}
getSelectedElement() {
@ -116,9 +110,9 @@ class FormField extends React.PureComponent {
}
validate() {
if ("regexp" in this.props) {
if ('regexp' in this.props) {
if (!this.getValue().match(this.props.regexp)) {
this.showError(__("Invalid format."));
this.showError(__('Invalid format.'));
} else {
this.clearError();
}
@ -133,8 +127,7 @@ class FormField extends React.PureComponent {
render() {
// Pass all unhandled props to the field element
const otherProps = Object.assign({}, this.props),
isError =
this.state.isError !== null ? this.state.isError : this.props.hasError,
isError = this.state.isError !== null ? this.state.isError : this.props.hasError,
elementId = this.props.elementId ? this.props.elementId : formFieldId(),
renderElementInsideLabel =
this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
@ -158,13 +151,8 @@ class FormField extends React.PureComponent {
placeholder={this.props.placeholder}
onBlur={() => this.validate()}
onFocus={() => this.props.onFocus && this.props.onFocus()}
className={
"form-field__input form-field__input-" +
this.props.type +
" " +
(this.props.className || "") +
(isError ? "form-field__input--error" : "")
}
className={`form-field__input form-field__input-${this.props.type} ${this.props.className ||
''}${isError ? 'form-field__input--error' : ''}`}
{...otherProps}
{...this._extraElementProps}
>
@ -173,19 +161,13 @@ class FormField extends React.PureComponent {
);
return (
<div className={"form-field form-field--" + this.props.type}>
{this.props.prefix ? (
<span className="form-field__prefix">{this.props.prefix}</span>
) : (
""
)}
<div className={`form-field form-field--${this.props.type}`}>
{this.props.prefix ? <span className="form-field__prefix">{this.props.prefix}</span> : ''}
{element}
{renderElementInsideLabel && (
<label
htmlFor={elementId}
className={
"form-field__label " + (isError ? "form-field__label--error" : "")
}
className={`form-field__label ${isError ? 'form-field__label--error' : ''}`}
>
{this.props.label}
</label>
@ -194,20 +176,18 @@ class FormField extends React.PureComponent {
<FileSelector
type={this.props.type}
onFileChosen={this.handleFileChosen.bind(this)}
{...(this.props.defaultValue
? { initPath: this.props.defaultValue }
: {})}
{...(this.props.defaultValue ? { initPath: this.props.defaultValue } : {})}
/>
) : null}
{this.props.postfix ? (
<span className="form-field__postfix">{this.props.postfix}</span>
) : (
""
''
)}
{isError && this.state.errorMessage ? (
<div className="form-field__error">{this.state.errorMessage}</div>
) : (
""
''
)}
</div>
);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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