Upgrade daemon to 0.19.0 #1032
46 changed files with 2661 additions and 1314 deletions
|
@ -4,7 +4,7 @@
|
||||||
"airbnb",
|
"airbnb",
|
||||||
"plugin:import/electron",
|
"plugin:import/electron",
|
||||||
"plugin:flowtype/recommended",
|
"plugin:flowtype/recommended",
|
||||||
"plugin:prettier/recommended"
|
"prettier"
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
[ignore]
|
[ignore]
|
||||||
.*/node_modules/**
|
|
||||||
|
|
||||||
[include]
|
[include]
|
||||||
|
|
||||||
[libs]
|
[libs]
|
||||||
flow-typed
|
./flow-typed
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
||||||
/node_modules
|
/node_modules
|
||||||
/dist
|
/dist
|
||||||
/build/daemon.ver
|
/build/daemon.ver
|
||||||
|
/build/daemon.zip
|
||||||
/build/venv
|
/build/venv
|
||||||
*.pyc
|
*.pyc
|
||||||
/static/daemon/lbrynet*
|
/static/daemon/lbrynet*
|
||||||
|
|
76
.travis.yml
76
.travis.yml
|
@ -1,28 +1,50 @@
|
||||||
env:
|
matrix:
|
||||||
global:
|
include:
|
||||||
- TRANSIFEX_USER: api
|
- os: osx
|
||||||
- secure: u6gwnZlPGJLnvpoPPCpUZ+jBPajQuIW1+aq6UGW57z54AUjTAECxaYpqcGTGtDBjYark/yeiso887wP/EmJva7hMHeNMf6uLqwzP3YFsIv/Iv+9c1f4MXbJNgOrEKN834o/BdkD4ifi9CCiH9uPPVYGPx1bvfaxGpcHmQXdW4F4S0uj+jePB257mt+afGiNlz9wET6kWJKNNZf/4BNmefldVNq7h6oTSLsyO1TBhDcvSjatpKIwmXUNQfSUTFWvrtpWUB/m/IzitGuUtrt82vU2fPl7tuH6BHNrNp58MINjFzXXJLC+mMybb2UBDIAuc3+k7vj4J0U2rkcTloxDNCKNmYa1jBogOKBRgGp98Ct7E0V2vuLGAPniUbvBcCGK1wwed7uwDjsz3YNCGxUEcyyWc3OVDgN/up4+gXHxkh9FTpZy8Q3rSZx4Lwj700impBUQIVh/5p7Vgv+bSUdOeVRAMlcP9yT83jX50w9LkJMfICFPNv1tOZ3/SOnnB+JdW/ahpplFI4Z68/fBLttZTeaNcU4f28oJvPer8Wll+Elx5kxwLqLbwVUFNlxTxY1LYnPB7SPjGxrFNy3mVTRq5Pxp1hMiTZF4TlapkfHgR+gEzk2wpcJGmub70tW2baZaJF0jDBWIh7GXV+EGve53BKDhpX2Z6jTK0gkhrSUW1MT8=
|
osx_image: xcode9.2
|
||||||
- secure: h1r9Qzv2xHRQl7nDHcscB4qDv7KlF/ncgHko1YuoY4oLZipBV8mzQXDmn3nlMKwaKOe1/Tty/bjoZexkWict4cwKBzU7/1HtJeMa6nxRICuS6DiVhLUNGZEddK6jQLxeEZFxkFPSCZyjybPWtasF8f8jd0lqqLIL4/FcIVV56aRCKAsUwCbedxi8Vnc19l74xjaQIK82xBFYOQPK078OBovk9DDOnicTjMulUo3/pKEZD1njSdcEhfSRv+MFE+31B/a6lpoLo7twPlyzLMfpo30NlEzIN0TeMAk44e4PV6DYg0wntC2GJ21p4BqMnDGocwnZwm7gpjflzUZdW6hF0esGLcqOdbyJLUb3rNX9AzQmn0p9KwDC3S80peZFxSiuLJGL8eivceVDUK/jwWinu3OHDJ/eO5iMDm9odm2ALemPtrDTSlRNT8HzNCY9PQTU9Dhdm4Q/dGDsRPWibFJSJ/qGKhVgadk2CUEAPua0hB1zZ556PkTGx4R1JDscgFDAkgemzgKl4Z/4qK3xGDoEtz1HmBlvQtn+B/PuhA2essADj0iTDiItxb7AYTA7EzsHEcRMmrbYarZ3Eh2onWy2GOpAGRN5Xl9cBIDbibcSC6BLI1m2PcLABpP7DhUX4bJbsVNSiGesHEU7o9Dgn8Ig09eHW/8F9i0VVoGUZXxKMJ8=
|
language: node_js
|
||||||
|
node_js: "9"
|
||||||
os: linux
|
env:
|
||||||
dist: xenial
|
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||||
|
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||||
branches:
|
- os: linux
|
||||||
only:
|
services: docker
|
||||||
- "/^v\\d+\\.\\d+\\.\\d+$/"
|
language: generic
|
||||||
|
before_cache:
|
||||||
install:
|
- rm -rf $HOME/.cache/electron-builder/wine
|
||||||
- rvm install 2.3.1
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
- $HOME/.cache/electron
|
||||||
|
- $HOME/.cache/electron-builder
|
||||||
|
before_install:
|
||||||
|
- |
|
||||||
|
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||||
|
mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v2.3.1/git-lfs-$([ "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-2.3.1.tar.gz | tar -xz -C /tmp/git-lfs --strip-components 1
|
||||||
|
export PATH="/tmp/git-lfs:$PATH"
|
||||||
|
fi
|
||||||
|
before_script:
|
||||||
|
- git lfs pull
|
||||||
script:
|
script:
|
||||||
- rvm use 2.3.1 && gem install danger --version '~> 4.0' && danger
|
- |
|
||||||
- FULL_BUILD=true ./build.sh
|
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
|
||||||
|
docker run --rm \
|
||||||
sudo: required
|
--env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') \
|
||||||
after_success:
|
-v ${PWD}:/project \
|
||||||
- pip install virtualenv
|
-v ~/.cache/electron:/root/.cache/electron \
|
||||||
- virtualenv ~/env
|
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
|
||||||
- source ~/env/bin/activate
|
electronuserland/builder:wine \
|
||||||
- pip install transifex-client
|
/bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn build --linux --win"
|
||||||
- sudo echo $'[https://www.transifex.com]\nhostname = https://www.transifex.com\nusername= '"$TRANSIFEX_USER"$'\npassword = '"$TRANSIFEX_PASSWORD"$'\ntoken = '"$TRANSIFEX_API_TOKEN"$'\n' > ~/.transifexrc
|
else
|
||||||
- tx push -s
|
yarn build
|
||||||
|
fi
|
||||||
|
branches:
|
||||||
|
except:
|
||||||
|
- "/^v\\d+\\.\\d+\\.\\d+$/"
|
||||||
|
addons:
|
||||||
|
artifacts:
|
||||||
|
working_dir: dist
|
||||||
|
paths:
|
||||||
|
- $(git ls-files -o dist/{*.dmg,*.exe,*.AppImage} | tr "\n" ":")
|
||||||
|
target_paths:
|
||||||
|
- /commit-${TRAVIS_COMMIT:0:7}_build-${TRAVIS_BUILD_NUMBER}_tag-${TRAVIS_TAG}
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -8,7 +8,10 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
*
|
* Save app state when closing to tray ([#968](https://github.com/lbryio/lbry-app/issues/968))
|
||||||
|
* Added startup-troubleshooting FAQ URL to daemon error ([#1039](https://github.com/lbryio/lbry-app/pull/1039))
|
||||||
|
* Added ability to export wallet transactions to JSON and CSV format ([#976](https://github.com/lbryio/lbry-app/pull/976))
|
||||||
|
* Add Rewards FAQ to LBRY app ([#1041](https://github.com/lbryio/lbry-app/pull/1041))
|
||||||
*
|
*
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -16,8 +19,10 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
||||||
*
|
*
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
*
|
* Fixed sort by date of published content ([#986](https://github.com/lbryio/lbry-app/issues/986))
|
||||||
*
|
* Fix night mode start time, set to 9PM (#1050)
|
||||||
|
* Fix night mode start time, set to 9PM ([#1050](https://github.com/lbryio/lbry-app/issues/1050))
|
||||||
|
* Disable drag and drop of files into the app ([#1045](https://github.com/lbryio/lbry-app/pull/1045))
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
*
|
*
|
||||||
|
|
|
@ -43,32 +43,6 @@ fi
|
||||||
|
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
####################
|
|
||||||
# daemon and cli #
|
|
||||||
####################
|
|
||||||
echo -e "\033[0;32mGrabbing Daemon and CLI\x1b[m"
|
|
||||||
if $OSX; then
|
|
||||||
OSNAME="macos"
|
|
||||||
else
|
|
||||||
OSNAME="linux"
|
|
||||||
fi
|
|
||||||
DAEMON_VER=$(node -e "console.log(require(\"$ROOT/package.json\").lbrySettings.lbrynetDaemonVersion)")
|
|
||||||
DAEMON_URL_TEMPLATE=$(node -e "console.log(require(\"$ROOT/package.json\").lbrySettings.lbrynetDaemonUrlTemplate)")
|
|
||||||
DAEMON_URL=$(echo ${DAEMON_URL_TEMPLATE//DAEMONVER/$DAEMON_VER} | sed "s/OSNAME/$OSNAME/g")
|
|
||||||
DAEMON_VER_PATH="$BUILD_DIR/daemon.ver"
|
|
||||||
echo "$DAEMON_VER_PATH"
|
|
||||||
if [[ ! -f $DAEMON_VER_PATH || ! -f $ROOT/static/daemon/lbrynet-daemon || "$(< "$DAEMON_VER_PATH")" != "$DAEMON_VER" ]]; then
|
|
||||||
curl -sL -o "$BUILD_DIR/daemon.zip" "$DAEMON_URL"
|
|
||||||
unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/static/daemon/"
|
|
||||||
rm "$BUILD_DIR/daemon.zip"
|
|
||||||
echo "$DAEMON_VER" > "$DAEMON_VER_PATH"
|
|
||||||
else
|
|
||||||
echo -e "\033[4;31mAlready have daemon version $DAEMON_VER, skipping download\x1b[m"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# Build the app #
|
# Build the app #
|
||||||
###################
|
###################
|
||||||
|
@ -79,23 +53,6 @@ if [ "$FULL_BUILD" == "true" ]; then
|
||||||
|
|
||||||
yarn build
|
yarn build
|
||||||
|
|
||||||
# Workaround: TeamCity expects the dmg to be in dist/mac, but in the new electron-builder
|
|
||||||
# it's put directly in dist/ (the right way to solve this is to update the TeamCity config)
|
|
||||||
if $OSX; then
|
|
||||||
cp dist/*.dmg dist/mac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# this, but it seemed better to write my own.
|
|
||||||
VENV="$BUILD_DIR/venv"
|
|
||||||
if [ -d "$VENV" ]; then
|
|
||||||
rm -rf "$VENV"
|
|
||||||
fi
|
|
||||||
virtualenv "$VENV"
|
|
||||||
"$VENV/bin/pip" install -r "$BUILD_DIR/requirements.txt"
|
|
||||||
"$VENV/bin/python" "$BUILD_DIR/upload_assets.py"
|
|
||||||
|
|
||||||
echo -e '\033[0;32mBuild and packaging complete.\x1b[m'
|
echo -e '\033[0;32mBuild and packaging complete.\x1b[m'
|
||||||
else
|
else
|
||||||
echo -e 'Build complete. Run \033[1;31myarn dev\x1b[m to launch the app'
|
echo -e 'Build complete. Run \033[1;31myarn dev\x1b[m to launch the app'
|
||||||
|
|
47
build/downloadDaemon.js
Normal file
47
build/downloadDaemon.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/* eslint-disable no-console,import/no-commonjs */
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const packageJSON = require('../package.json');
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
const axios = require('axios');
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
const decompress = require('decompress');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
const daemonURLTemplate = packageJSON.lbrySettings.lbrynetDaemonUrlTemplate;
|
||||||
|
const daemonVersion = packageJSON.lbrySettings.lbrynetDaemonVersion;
|
||||||
|
let currentPlatform = os.platform();
|
||||||
|
if (currentPlatform === 'darwin') currentPlatform = 'macos';
|
||||||
|
if (currentPlatform === 'win32') currentPlatform = 'windows';
|
||||||
|
|
||||||
|
const daemonURL = daemonURLTemplate
|
||||||
|
.replace(/DAEMONVER/g, daemonVersion)
|
||||||
|
.replace(/OSNAME/g, currentPlatform);
|
||||||
|
const tmpZipPath = 'build/daemon.zip';
|
||||||
|
|
||||||
|
console.log('\x1b[34minfo\x1b[0m Downloading daemon...');
|
||||||
|
axios
|
||||||
|
.request({
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
url: daemonURL,
|
||||||
|
method: 'get',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/zip',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
fs.writeFileSync(tmpZipPath, result.data);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
decompress(tmpZipPath, 'static/daemon', {
|
||||||
|
filter: file =>
|
||||||
|
path.basename(file.path).replace(path.extname(file.path), '') === 'lbrynet-daemon',
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('\x1b[32msuccess\x1b[0m Daemon downloaded!');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`\x1b[31merror\x1b[0m Daemon download failed due to: \x1b[35m${error}\x1b[0m`);
|
||||||
|
});
|
|
@ -1,11 +1,13 @@
|
||||||
{
|
{
|
||||||
"appId": "io.lbry.LBRY",
|
"appId": "io.lbry.LBRY",
|
||||||
"publish": {
|
"productName": "LBRY",
|
||||||
|
"publish": [{
|
||||||
"provider": "s3",
|
"provider": "s3",
|
||||||
"bucket": "releases.lbry.io",
|
"bucket": "releases.lbry.io",
|
||||||
"path": "app/latest"
|
"path": "app/latest"
|
||||||
},
|
}, "github"],
|
||||||
"mac": {
|
"mac": {
|
||||||
|
"target": "dmg",
|
||||||
"category": "public.app-category.entertainment"
|
"category": "public.app-category.entertainment"
|
||||||
},
|
},
|
||||||
"dmg": {
|
"dmg": {
|
||||||
|
@ -28,36 +30,24 @@
|
||||||
"width": 500,
|
"width": 500,
|
||||||
"height": 300
|
"height": 300
|
||||||
},
|
},
|
||||||
"background": "build/background.png"
|
"background": "./build/background.png"
|
||||||
},
|
},
|
||||||
"protocols": [
|
"protocols": [
|
||||||
{
|
{
|
||||||
"name": "lbry",
|
"name": "LBRY URI",
|
||||||
"schemes": ["lbry"],
|
"schemes": ["lbry"],
|
||||||
"role": "Viewer"
|
"role": "Viewer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": "deb",
|
"target": "AppImage",
|
||||||
"category": "Video;AudioVideo;",
|
"category": "AudioVideo;Video",
|
||||||
"desktop": {
|
"desktop": {
|
||||||
"MimeType": "x-scheme-handler/lbry",
|
"MimeType": "x-scheme-handler/lbry;"
|
||||||
"Exec": "/opt/LBRY/lbry %U"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deb": {
|
|
||||||
"depends": [
|
|
||||||
"gconf2",
|
|
||||||
"gconf-service",
|
|
||||||
"libnotify4",
|
|
||||||
"libappindicator1",
|
|
||||||
"libxtst6",
|
|
||||||
"libnss3",
|
|
||||||
"libsecret-1-0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"perMachine": true
|
"perMachine": true
|
||||||
},
|
},
|
||||||
"artifactName": "${productName}_${version}_${arch}.${ext}"
|
"artifactName": "${productName}_${version}.${ext}"
|
||||||
}
|
}
|
||||||
|
|
56
package.json
56
package.json
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "LBRY",
|
"name": "lbry-app",
|
||||||
"version": "0.20.0",
|
"version": "0.20.0",
|
||||||
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
||||||
"homepage": "https://lbry.io/",
|
"homepage": "https://lbry.io/",
|
||||||
|
@ -21,7 +21,8 @@
|
||||||
"compile": "electron-webpack && yarn extract-langs",
|
"compile": "electron-webpack && yarn extract-langs",
|
||||||
"build": "yarn compile && electron-builder build",
|
"build": "yarn compile && electron-builder build",
|
||||||
"build:dir": "yarn build -- --dir -c.compression=store -c.mac.identity=null",
|
"build:dir": "yarn build -- --dir -c.compression=store -c.mac.identity=null",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"release": "yarn compile && electron-builder build",
|
||||||
|
"postinstall": "electron-builder install-app-deps && node build/downloadDaemon.js",
|
||||||
"lint": "eslint 'src/**/*.{js,jsx}' --fix",
|
"lint": "eslint 'src/**/*.{js,jsx}' --fix",
|
||||||
"format": "prettier 'src/**/*.{js,jsx,scss,json}' --write"
|
"format": "prettier 'src/**/*.{js,jsx,scss,json}' --write"
|
||||||
},
|
},
|
||||||
|
@ -33,16 +34,17 @@
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"country-data": "^0.0.31",
|
"country-data": "^0.0.31",
|
||||||
"electron-dl": "^1.6.0",
|
"electron-dl": "^1.6.0",
|
||||||
|
"electron-is-dev": "^0.3.0",
|
||||||
"electron-log": "^2.2.12",
|
"electron-log": "^2.2.12",
|
||||||
"electron-publisher-s3": "^19.47.0",
|
"electron-publisher-s3": "^20.2.0",
|
||||||
"electron-updater": "^2.18.2",
|
"electron-updater": "^2.21.0",
|
||||||
"find-process": "^1.1.0",
|
"find-process": "^1.1.0",
|
||||||
"formik": "^0.10.4",
|
"formik": "^0.10.4",
|
||||||
"from2": "^2.3.0",
|
"from2": "^2.3.0",
|
||||||
"install": "^0.10.2",
|
"install": "^0.10.2",
|
||||||
"jayson": "^2.0.2",
|
"jayson": "^2.0.2",
|
||||||
"jshashes": "^1.0.7",
|
"jshashes": "^1.0.7",
|
||||||
"keytar-prebuild": "^4.0.4",
|
"keytar-prebuild": "^4.1.1",
|
||||||
"localforage": "^1.5.0",
|
"localforage": "^1.5.0",
|
||||||
"mixpanel-browser": "^2.17.1",
|
"mixpanel-browser": "^2.17.1",
|
||||||
"moment": "^2.20.1",
|
"moment": "^2.20.1",
|
||||||
|
@ -72,43 +74,41 @@
|
||||||
"y18n": "^4.0.0"
|
"y18n": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^8.0.3",
|
"axios": "^0.18.0",
|
||||||
"babel-plugin-module-resolver": "^3.0.0",
|
"babel-eslint": "^8.2.2",
|
||||||
"babel-plugin-react-require": "^3.0.0",
|
"babel-plugin-module-resolver": "^3.1.0",
|
||||||
"babel-polyfill": "^6.20.0",
|
"babel-polyfill": "^6.20.0",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-preset-env": "^1.6.1",
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-preset-react": "^6.24.1",
|
||||||
"babel-preset-stage-2": "^6.18.0",
|
"babel-preset-stage-2": "^6.18.0",
|
||||||
|
"decompress": "^4.2.0",
|
||||||
"devtron": "^1.4.0",
|
"devtron": "^1.4.0",
|
||||||
"electron": "^1.7.11",
|
"electron": "^1.8.3",
|
||||||
"electron-builder": "^19.55.2",
|
"electron-builder": "^20.3.1",
|
||||||
"electron-devtools-installer": "^2.2.1",
|
"electron-devtools-installer": "^2.2.3",
|
||||||
"electron-webpack": "^1.11.0",
|
"electron-webpack": "^1.13.0",
|
||||||
"eslint": "^4.13.1",
|
"eslint": "^4.18.2",
|
||||||
"eslint-config-airbnb": "^16.1.0",
|
"eslint-config-airbnb": "^16.1.0",
|
||||||
"eslint-config-prettier": "^2.9.0",
|
"eslint-config-prettier": "^2.9.0",
|
||||||
"eslint-import-resolver-webpack": "^0.8.3",
|
"eslint-import-resolver-webpack": "^0.8.4",
|
||||||
"eslint-plugin-flowtype": "^2.40.1",
|
"eslint-plugin-flowtype": "^2.46.1",
|
||||||
"eslint-plugin-import": "^2.8.0",
|
"eslint-plugin-import": "^2.9.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||||
"eslint-plugin-prettier": "^2.4.0",
|
"eslint-plugin-prettier": "^2.6.0",
|
||||||
"eslint-plugin-react": "^7.5.1",
|
"eslint-plugin-react": "^7.7.0",
|
||||||
"flow-babel-webpack-plugin": "^1.1.0",
|
"flow-babel-webpack-plugin": "^1.1.1",
|
||||||
"flow-bin": "^0.61.0",
|
"flow-bin": "^0.66.0",
|
||||||
"flow-typed": "^2.2.3",
|
"flow-typed": "^2.3.0",
|
||||||
"husky": "^0.14.3",
|
"husky": "^0.14.3",
|
||||||
"i18n-extract": "^0.5.1",
|
"i18n-extract": "^0.5.1",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lint-staged": "^6.0.0",
|
"lint-staged": "^7.0.0",
|
||||||
"node-loader": "^0.6.0",
|
"node-loader": "^0.6.0",
|
||||||
"node-sass": "^4.7.2",
|
"node-sass": "^4.7.2",
|
||||||
"prettier": "^1.4.2",
|
"prettier": "^1.11.1",
|
||||||
"sass-loader": "^6.0.6",
|
"sass-loader": "^6.0.7",
|
||||||
"webpack": "^3.10.0",
|
"webpack": "^3.10.0",
|
||||||
"webpack-build-notifier": "^0.1.18"
|
"webpack-build-notifier": "^0.1.23"
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
"webpack/webpack-sources": "1.0.1"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6",
|
"node": ">=6",
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
import { app, Menu, Tray as ElectronTray } from 'electron';
|
|
||||||
import path from 'path';
|
|
||||||
import createWindow from './createWindow';
|
|
||||||
|
|
||||||
export default class Tray {
|
|
||||||
window;
|
|
||||||
updateAttachedWindow;
|
|
||||||
tray;
|
|
||||||
|
|
||||||
constructor(window, updateAttachedWindow) {
|
|
||||||
this.window = window;
|
|
||||||
this.updateAttachedWindow = updateAttachedWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
create() {
|
|
||||||
let iconPath;
|
|
||||||
switch (process.platform) {
|
|
||||||
case 'darwin': {
|
|
||||||
iconPath = path.join(__static, '/img/tray/mac/trayTemplate.png');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'win32': {
|
|
||||||
iconPath = path.join(__static, '/img/tray/windows/tray.ico');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
iconPath = path.join(__static, '/img/tray/default/tray.png');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tray = new ElectronTray(iconPath);
|
|
||||||
|
|
||||||
this.tray.on('double-click', () => {
|
|
||||||
if (!this.window || this.window.isDestroyed()) {
|
|
||||||
this.window = createWindow();
|
|
||||||
this.updateAttachedWindow(this.window);
|
|
||||||
} else {
|
|
||||||
this.window.show();
|
|
||||||
this.window.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.tray.setToolTip('LBRY App');
|
|
||||||
|
|
||||||
const template = [
|
|
||||||
{
|
|
||||||
label: `Open ${app.getName()}`,
|
|
||||||
click: () => {
|
|
||||||
if (!this.window || this.window.isDestroyed()) {
|
|
||||||
this.window = createWindow();
|
|
||||||
this.updateAttachedWindow(this.window);
|
|
||||||
} else {
|
|
||||||
this.window.show();
|
|
||||||
this.window.focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ role: 'quit' },
|
|
||||||
];
|
|
||||||
const contextMenu = Menu.buildFromTemplate(template);
|
|
||||||
this.tray.setContextMenu(contextMenu);
|
|
||||||
}
|
|
||||||
}
|
|
41
src/main/createTray.js
Normal file
41
src/main/createTray.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { app, Menu, Tray } from 'electron';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default window => {
|
||||||
|
let iconPath;
|
||||||
|
switch (process.platform) {
|
||||||
|
case 'darwin': {
|
||||||
|
iconPath = path.join(__static, '/img/tray/mac/trayTemplate.png');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'win32': {
|
||||||
|
iconPath = path.join(__static, '/img/tray/windows/tray.ico');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
iconPath = path.join(__static, '/img/tray/default/tray.png');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tray = new Tray(iconPath);
|
||||||
|
|
||||||
|
tray.on('double-click', () => {
|
||||||
|
window.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
tray.setToolTip('LBRY App');
|
||||||
|
|
||||||
|
const template = [
|
||||||
|
{
|
||||||
|
label: `Open ${app.getName()}`,
|
||||||
|
click: () => {
|
||||||
|
window.show();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ role: 'quit' },
|
||||||
|
];
|
||||||
|
const contextMenu = Menu.buildFromTemplate(template);
|
||||||
|
tray.setContextMenu(contextMenu);
|
||||||
|
|
||||||
|
return tray;
|
||||||
|
};
|
|
@ -1,8 +1,9 @@
|
||||||
import { app, BrowserWindow, dialog } from 'electron';
|
import { app, BrowserWindow, dialog } from 'electron';
|
||||||
|
import isDev from 'electron-is-dev';
|
||||||
import setupBarMenu from './menu/setupBarMenu';
|
import setupBarMenu from './menu/setupBarMenu';
|
||||||
import setupContextMenu from './menu/setupContextMenu';
|
import setupContextMenu from './menu/setupContextMenu';
|
||||||
|
|
||||||
export default deepLinkingURIArg => {
|
export default appState => {
|
||||||
let windowConfiguration = {
|
let windowConfiguration = {
|
||||||
backgroundColor: '#155B4A',
|
backgroundColor: '#155B4A',
|
||||||
minWidth: 800,
|
minWidth: 800,
|
||||||
|
@ -12,20 +13,18 @@ export default deepLinkingURIArg => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Disable renderer process's webSecurity on development to enable CORS.
|
// Disable renderer process's webSecurity on development to enable CORS.
|
||||||
windowConfiguration =
|
windowConfiguration = isDev
|
||||||
process.env.NODE_ENV === 'development'
|
? {
|
||||||
? {
|
...windowConfiguration,
|
||||||
...windowConfiguration,
|
webPreferences: {
|
||||||
webPreferences: {
|
webSecurity: false,
|
||||||
webSecurity: false,
|
},
|
||||||
},
|
}
|
||||||
}
|
: windowConfiguration;
|
||||||
: windowConfiguration;
|
|
||||||
|
|
||||||
const rendererURL =
|
const rendererURL = isDev
|
||||||
process.env.NODE_ENV === 'development'
|
? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`
|
||||||
? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`
|
: `file://${__dirname}/index.html`;
|
||||||
: `file://${__dirname}/index.html`;
|
|
||||||
|
|
||||||
let window = new BrowserWindow(windowConfiguration);
|
let window = new BrowserWindow(windowConfiguration);
|
||||||
|
|
||||||
|
@ -35,11 +34,7 @@ export default deepLinkingURIArg => {
|
||||||
|
|
||||||
let deepLinkingURI;
|
let deepLinkingURI;
|
||||||
// Protocol handler for win32
|
// Protocol handler for win32
|
||||||
if (
|
if (process.platform === 'win32' && String(process.argv[1]).startsWith('lbry')) {
|
||||||
!deepLinkingURIArg &&
|
|
||||||
process.platform === 'win32' &&
|
|
||||||
String(process.argv[1]).startsWith('lbry')
|
|
||||||
) {
|
|
||||||
// Keep only command line / deep linked arguments
|
// Keep only command line / deep linked arguments
|
||||||
// Windows normalizes URIs when they're passed in from other apps. On Windows, this tries to
|
// Windows normalizes URIs when they're passed in from other apps. On Windows, this tries to
|
||||||
// restore the original URI that was typed.
|
// restore the original URI that was typed.
|
||||||
|
@ -48,15 +43,16 @@ export default deepLinkingURIArg => {
|
||||||
// - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as
|
// - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as
|
||||||
// an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well.
|
// an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well.
|
||||||
deepLinkingURI = process.argv[1].replace(/\/$/, '').replace('/#', '#');
|
deepLinkingURI = process.argv[1].replace(/\/$/, '').replace('/#', '#');
|
||||||
} else {
|
|
||||||
deepLinkingURI = deepLinkingURIArg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupBarMenu();
|
setupBarMenu();
|
||||||
setupContextMenu(window);
|
setupContextMenu(window);
|
||||||
|
|
||||||
window.on('closed', () => {
|
window.on('close', event => {
|
||||||
window = null;
|
if (!appState.isQuitting) {
|
||||||
|
event.preventDefault();
|
||||||
|
window.hide();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.on('focus', () => {
|
window.on('focus', () => {
|
||||||
|
@ -87,7 +83,7 @@ export default deepLinkingURIArg => {
|
||||||
window.webContents.on('did-finish-load', () => {
|
window.webContents.on('did-finish-load', () => {
|
||||||
window.webContents.send('open-uri-requested', deepLinkingURI, true);
|
window.webContents.send('open-uri-requested', deepLinkingURI, true);
|
||||||
window.webContents.session.setUserAgent(`LBRY/${app.getVersion()}`);
|
window.webContents.session.setUserAgent(`LBRY/${app.getVersion()}`);
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (isDev) {
|
||||||
window.webContents.openDevTools();
|
window.webContents.openDevTools();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,8 +7,9 @@ import url from 'url';
|
||||||
import https from 'https';
|
import https from 'https';
|
||||||
import { shell, app, ipcMain, dialog } from 'electron';
|
import { shell, app, ipcMain, dialog } from 'electron';
|
||||||
import { autoUpdater } from 'electron-updater';
|
import { autoUpdater } from 'electron-updater';
|
||||||
|
import isDev from 'electron-is-dev';
|
||||||
import Daemon from './Daemon';
|
import Daemon from './Daemon';
|
||||||
import Tray from './Tray';
|
import createTray from './createTray';
|
||||||
import createWindow from './createWindow';
|
import createWindow from './createWindow';
|
||||||
|
|
||||||
autoUpdater.autoDownload = true;
|
autoUpdater.autoDownload = true;
|
||||||
|
@ -32,11 +33,7 @@ let rendererWindow;
|
||||||
let tray;
|
let tray;
|
||||||
let daemon;
|
let daemon;
|
||||||
|
|
||||||
let isQuitting;
|
const appState = {};
|
||||||
|
|
||||||
const updateRendererWindow = window => {
|
|
||||||
rendererWindow = window;
|
|
||||||
};
|
|
||||||
|
|
||||||
const installExtensions = async () => {
|
const installExtensions = async () => {
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies,global-require
|
// eslint-disable-next-line import/no-extraneous-dependencies,global-require
|
||||||
|
@ -64,28 +61,27 @@ app.on('ready', async () => {
|
||||||
daemon = new Daemon();
|
daemon = new Daemon();
|
||||||
daemon.on('exit', () => {
|
daemon.on('exit', () => {
|
||||||
daemon = null;
|
daemon = null;
|
||||||
if (!isQuitting) {
|
if (!appState.isQuitting) {
|
||||||
dialog.showErrorBox(
|
dialog.showErrorBox(
|
||||||
'Daemon has Exited',
|
'Daemon has Exited',
|
||||||
'The daemon may have encountered an unexpected error, or another daemon instance is already running.'
|
'The daemon may have encountered an unexpected error, or another daemon instance is already running. \n\n' +
|
||||||
|
'For more information please visit: \n' +
|
||||||
|
'https://lbry.io/faq/startup-troubleshooting'
|
||||||
);
|
);
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
daemon.launch();
|
daemon.launch();
|
||||||
}
|
}
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (isDev) {
|
||||||
await installExtensions();
|
await installExtensions();
|
||||||
}
|
}
|
||||||
rendererWindow = createWindow();
|
rendererWindow = createWindow(appState);
|
||||||
tray = new Tray(rendererWindow, updateRendererWindow);
|
tray = createTray(rendererWindow);
|
||||||
tray.create();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
// On macOS it's common to re-create a window in the app when the
|
rendererWindow.show();
|
||||||
// dock icon is clicked and there are no other windows open.
|
|
||||||
if (!rendererWindow) rendererWindow = createWindow();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('will-quit', event => {
|
app.on('will-quit', event => {
|
||||||
|
@ -117,7 +113,7 @@ app.on('will-quit', event => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isQuitting = true;
|
appState.isQuitting = true;
|
||||||
if (daemon) daemon.quit();
|
if (daemon) daemon.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -126,18 +122,13 @@ app.on('will-finish-launching', () => {
|
||||||
// Protocol handler for macOS
|
// Protocol handler for macOS
|
||||||
app.on('open-url', (event, URL) => {
|
app.on('open-url', (event, URL) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (rendererWindow && !rendererWindow.isDestroyed()) {
|
rendererWindow.webContents.send('open-uri-requested', URL);
|
||||||
rendererWindow.webContents.send('open-uri-requested', URL);
|
rendererWindow.show();
|
||||||
rendererWindow.show();
|
|
||||||
rendererWindow.focus();
|
|
||||||
} else {
|
|
||||||
rendererWindow = createWindow(URL);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('before-quit', () => {
|
||||||
// Subscribe to event so the app doesn't quit when closing the window.
|
appState.isQuitting = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('upgrade', (event, installerPath) => {
|
ipcMain.on('upgrade', (event, installerPath) => {
|
||||||
|
@ -224,7 +215,7 @@ ipcMain.on('set-auth-token', (event, token) => {
|
||||||
|
|
||||||
process.on('uncaughtException', error => {
|
process.on('uncaughtException', error => {
|
||||||
dialog.showErrorBox('Error Encountered', `Caught error: ${error}`);
|
dialog.showErrorBox('Error Encountered', `Caught error: ${error}`);
|
||||||
isQuitting = true;
|
appState.isQuitting = true;
|
||||||
if (daemon) daemon.quit();
|
if (daemon) daemon.quit();
|
||||||
app.exit(1);
|
app.exit(1);
|
||||||
});
|
});
|
||||||
|
@ -240,14 +231,8 @@ const isSecondInstance = app.makeSingleInstance(argv => {
|
||||||
URI = argv[1].replace(/\/$/, '').replace('/#', '#');
|
URI = argv[1].replace(/\/$/, '').replace('/#', '#');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rendererWindow && !rendererWindow.isDestroyed()) {
|
rendererWindow.webContents.send('open-uri-requested', URI);
|
||||||
rendererWindow.webContents.send('open-uri-requested', URI);
|
rendererWindow.show();
|
||||||
|
|
||||||
rendererWindow.show();
|
|
||||||
rendererWindow.focus();
|
|
||||||
} else {
|
|
||||||
rendererWindow = createWindow(URI);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isSecondInstance) {
|
if (isSecondInstance) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { Menu, BrowserWindow } from 'electron';
|
import { Menu, BrowserWindow } from 'electron';
|
||||||
|
import isDev from 'electron-is-dev';
|
||||||
|
|
||||||
export default (rendererWindow: BrowserWindow) => {
|
export default (rendererWindow: BrowserWindow) => {
|
||||||
rendererWindow.webContents.on('context-menu', (e, params) => {
|
rendererWindow.webContents.on('context-menu', (e, params) => {
|
||||||
|
@ -17,7 +18,7 @@ export default (rendererWindow: BrowserWindow) => {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (isDev) {
|
||||||
template.push(...developmentTemplateAddition);
|
template.push(...developmentTemplateAddition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
// @flow
|
// @flow
|
||||||
import mixpanel from 'mixpanel-browser';
|
import mixpanel from 'mixpanel-browser';
|
||||||
|
import Lbryio from 'lbryio';
|
||||||
|
import isDev from 'electron-is-dev';
|
||||||
|
|
||||||
mixpanel.init('691723e855cabb9d27a7a79002216967');
|
if (isDev) {
|
||||||
|
mixpanel.init('691723e855cabb9d27a7a79002216967');
|
||||||
|
} else {
|
||||||
|
mixpanel.init('af5c6b8110068fa4f5c4600c81f05e60');
|
||||||
|
}
|
||||||
|
|
||||||
type Analytics = {
|
type Analytics = {
|
||||||
track: (string, ?Object) => void,
|
track: (string, ?Object) => void,
|
||||||
setUser: (Object) => void,
|
setUser: Object => void,
|
||||||
toggle: (boolean, ?boolean) => void
|
toggle: (boolean, ?boolean) => void,
|
||||||
}
|
apiLog: (string, string, string) => void,
|
||||||
|
};
|
||||||
|
|
||||||
let analyticsEnabled: boolean = false;
|
let analyticsEnabled: boolean = false;
|
||||||
|
|
||||||
const analytics: Analytics = {
|
const analytics: Analytics = {
|
||||||
track: (name: string, payload: ?Object): void => {
|
track: (name: string, payload: ?Object): void => {
|
||||||
if(analyticsEnabled) {
|
if (analyticsEnabled) {
|
||||||
if(payload) {
|
if (payload) {
|
||||||
mixpanel.track(name, payload);
|
mixpanel.track(name, payload);
|
||||||
} else {
|
} else {
|
||||||
mixpanel.track(name);
|
mixpanel.track(name);
|
||||||
|
@ -22,21 +29,30 @@ const analytics: Analytics = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setUser: (user: Object): void => {
|
setUser: (user: Object): void => {
|
||||||
if(user.id) {
|
if (user.id) {
|
||||||
mixpanel.identify(user.id);
|
mixpanel.identify(user.id);
|
||||||
}
|
}
|
||||||
if(user.primary_email) {
|
if (user.primary_email) {
|
||||||
mixpanel.people.set({
|
mixpanel.people.set({
|
||||||
"$email": user.primary_email
|
$email: user.primary_email,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggle: (enabled: boolean, logDisabled: ?boolean): void => {
|
toggle: (enabled: boolean, logDisabled: ?boolean): void => {
|
||||||
if(!enabled && logDisabled) {
|
if (!enabled && logDisabled) {
|
||||||
mixpanel.track('DISABLED');
|
mixpanel.track('DISABLED');
|
||||||
}
|
}
|
||||||
analyticsEnabled = enabled;
|
analyticsEnabled = enabled;
|
||||||
}
|
},
|
||||||
}
|
apiLog: (uri: string, outpoint: string, claimId: string): void => {
|
||||||
|
if (analyticsEnabled) {
|
||||||
|
Lbryio.call('file', 'view', {
|
||||||
|
uri,
|
||||||
|
outpoint,
|
||||||
|
claim_id: claimId,
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default analytics;
|
export default analytics;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import store from 'store';
|
||||||
import { remote } from 'electron';
|
import { remote } from 'electron';
|
||||||
import Path from 'path';
|
import Path from 'path';
|
||||||
import y18n from 'y18n';
|
import y18n from 'y18n';
|
||||||
|
import isDev from 'electron-is-dev';
|
||||||
|
|
||||||
const env = process.env.NODE_ENV || 'production';
|
const env = process.env.NODE_ENV || 'production';
|
||||||
const i18n = y18n({
|
const i18n = y18n({
|
||||||
|
@ -22,7 +23,7 @@ const app = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Workaround for https://github.com/electron-userland/electron-webpack/issues/52
|
// Workaround for https://github.com/electron-userland/electron-webpack/issues/52
|
||||||
if (env !== 'development') {
|
if (!isDev) {
|
||||||
window.staticResourcesPath = Path.join(remote.app.getAppPath(), '../static').replace(
|
window.staticResourcesPath = Path.join(remote.app.getAppPath(), '../static').replace(
|
||||||
/\\/g,
|
/\\/g,
|
||||||
'\\\\'
|
'\\\\'
|
||||||
|
|
67
src/renderer/component/file-exporter.js
Normal file
67
src/renderer/component/file-exporter.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Link from 'component/link';
|
||||||
|
import parseData from 'util/parseData';
|
||||||
|
import * as icons from 'constants/icons';
|
||||||
|
const { remote } = require('electron');
|
||||||
|
|
||||||
|
class FileExporter extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
data: PropTypes.array,
|
||||||
|
title: PropTypes.string,
|
||||||
|
label: PropTypes.string,
|
||||||
|
defaultPath: PropTypes.string,
|
||||||
|
onFileCreated: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFileCreation(filename, data) {
|
||||||
|
const { onFileCreated } = this.props;
|
||||||
|
fs.writeFile(filename, data, err => {
|
||||||
|
if (err) throw err;
|
||||||
|
// Do something after creation
|
||||||
|
onFileCreated && onFileCreated(filename);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleButtonClick() {
|
||||||
|
const { title, defaultPath, data } = this.props;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
title,
|
||||||
|
defaultPath,
|
||||||
|
filters: [{ name: 'JSON', extensions: ['json'] }, { name: 'CSV', extensions: ['csv'] }],
|
||||||
|
};
|
||||||
|
|
||||||
|
remote.dialog.showSaveDialog(options, filename => {
|
||||||
|
// User hit cancel so do nothing:
|
||||||
|
if (!filename) return;
|
||||||
|
// Get extension and remove initial dot
|
||||||
|
const format = path.extname(filename).replace(/\./g, '');
|
||||||
|
// Parse data to string with the chosen format
|
||||||
|
const parsed = parseData(data, format);
|
||||||
|
// Write file
|
||||||
|
parsed && this.handleFileCreation(filename, parsed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { title, label } = this.props;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
button="primary"
|
||||||
|
icon={icons.DOWNLOAD}
|
||||||
|
title={title || __('Export')}
|
||||||
|
label={label || __('Export')}
|
||||||
|
onClick={() => this.handleButtonClick()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileExporter;
|
|
@ -9,12 +9,33 @@ class FileList extends React.PureComponent {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
sortBy: 'date',
|
sortBy: 'dateNew',
|
||||||
};
|
};
|
||||||
|
|
||||||
this._sortFunctions = {
|
this._sortFunctions = {
|
||||||
date(fileInfos) {
|
dateNew(fileInfos) {
|
||||||
return fileInfos.slice().reverse();
|
return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
|
||||||
|
const height1 = fileInfo1.height;
|
||||||
|
const height2 = fileInfo2.height;
|
||||||
|
if (height1 > height2) {
|
||||||
|
return -1;
|
||||||
|
} else if (height1 < height2) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dateOld(fileInfos) {
|
||||||
|
return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
|
||||||
|
const height1 = fileInfo1.height;
|
||||||
|
const height2 = fileInfo2.height;
|
||||||
|
if (height1 < height2) {
|
||||||
|
return -1;
|
||||||
|
} else if (height1 > height2) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
title(fileInfos) {
|
title(fileInfos) {
|
||||||
return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
|
return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
|
||||||
|
@ -95,7 +116,8 @@ class FileList extends React.PureComponent {
|
||||||
<span className="sort-section">
|
<span className="sort-section">
|
||||||
{__('Sort by')}{' '}
|
{__('Sort by')}{' '}
|
||||||
<FormField type="select" onChange={this.handleSortChanged.bind(this)}>
|
<FormField type="select" onChange={this.handleSortChanged.bind(this)}>
|
||||||
<option value="date">{__('Date')}</option>
|
<option value="dateNew">{__('Newest First')}</option>
|
||||||
|
<option value="dateOld">{__('Oldest First')}</option>
|
||||||
<option value="title">{__('Title')}</option>
|
<option value="title">{__('Title')}</option>
|
||||||
</FormField>
|
</FormField>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -14,6 +14,10 @@ const RewardSummary = (props: Props) => {
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card__title-primary">
|
<div className="card__title-primary">
|
||||||
<h3>{__('Rewards')}</h3>
|
<h3>{__('Rewards')}</h3>
|
||||||
|
<p className="help">
|
||||||
|
{__('Read our')} <Link href="https://lbry.io/faq/rewards">{__('FAQ')}</Link>{' '}
|
||||||
|
{__('to learn more about LBRY Rewards')}.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
{unclaimedRewardAmount > 0 ? (
|
{unclaimedRewardAmount > 0 ? (
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import TransactionListItem from './internal/TransactionListItem';
|
import TransactionListItem from './internal/TransactionListItem';
|
||||||
import FormField from 'component/formField';
|
import FormField from 'component/formField';
|
||||||
import Link from 'component/link';
|
import Link from 'component/link';
|
||||||
|
import FileExporter from 'component/file-exporter.js';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
import * as modals from 'constants/modal_types';
|
import * as modals from 'constants/modal_types';
|
||||||
|
|
||||||
|
@ -43,6 +44,13 @@ class TransactionList extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{Boolean(transactionList.length) && (
|
||||||
|
<FileExporter
|
||||||
|
data={transactionList}
|
||||||
|
title={__('Export Transactions')}
|
||||||
|
label={__('Export')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{(transactionList.length || this.state.filter) && (
|
{(transactionList.length || this.state.filter) && (
|
||||||
<span className="sort-section">
|
<span className="sort-section">
|
||||||
{__('Filter')}{' '}
|
{__('Filter')}{' '}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const { remote } = require('electron');
|
import { remote } from 'electron';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Thumbnail } from 'component/common';
|
import { Thumbnail } from 'component/common';
|
||||||
import player from 'render-media';
|
import player from 'render-media';
|
||||||
|
@ -21,33 +20,21 @@ class VideoPlayer extends React.PureComponent {
|
||||||
this.togglePlayListener = this.togglePlay.bind(this);
|
this.togglePlayListener = this.togglePlay.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(next) {
|
|
||||||
const el = this.refs.media.children[0];
|
|
||||||
if (!this.props.paused && next.paused && !el.paused) el.pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const container = this.refs.media;
|
const container = this.media;
|
||||||
const {
|
const { contentType, changeVolume, volume, position, claim } = this.props;
|
||||||
contentType,
|
|
||||||
downloadPath,
|
|
||||||
mediaType,
|
|
||||||
changeVolume,
|
|
||||||
volume,
|
|
||||||
position,
|
|
||||||
claim,
|
|
||||||
uri,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const loadedMetadata = e => {
|
const loadedMetadata = () => {
|
||||||
this.setState({ hasMetadata: true, startedPlaying: true });
|
this.setState({ hasMetadata: true, startedPlaying: true });
|
||||||
this.refs.media.children[0].play();
|
this.media.children[0].play();
|
||||||
};
|
};
|
||||||
const renderMediaCallback = err => {
|
|
||||||
if (err) this.setState({ unplayable: true });
|
const renderMediaCallback = error => {
|
||||||
|
if (error) this.setState({ unplayable: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle fullscreen change for the Windows platform
|
// Handle fullscreen change for the Windows platform
|
||||||
const win32FullScreenChange = e => {
|
const win32FullScreenChange = () => {
|
||||||
const win = remote.BrowserWindow.getFocusedWindow();
|
const win = remote.BrowserWindow.getFocusedWindow();
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
win.setMenu(document.webkitIsFullScreen ? null : remote.Menu.getApplicationMenu());
|
win.setMenu(document.webkitIsFullScreen ? null : remote.Menu.getApplicationMenu());
|
||||||
|
@ -61,13 +48,13 @@ class VideoPlayer extends React.PureComponent {
|
||||||
player.append(
|
player.append(
|
||||||
this.file(),
|
this.file(),
|
||||||
container,
|
container,
|
||||||
{ autoplay: false, controls: true },
|
{ autoplay: true, controls: true },
|
||||||
renderMediaCallback.bind(this)
|
renderMediaCallback.bind(this)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('keydown', this.togglePlayListener);
|
document.addEventListener('keydown', this.togglePlayListener);
|
||||||
const mediaElement = this.refs.media.children[0];
|
const mediaElement = this.media.children[0];
|
||||||
if (mediaElement) {
|
if (mediaElement) {
|
||||||
mediaElement.currentTime = position || 0;
|
mediaElement.currentTime = position || 0;
|
||||||
mediaElement.addEventListener('play', () => this.props.doPlay());
|
mediaElement.addEventListener('play', () => this.props.doPlay());
|
||||||
|
@ -87,29 +74,38 @@ class VideoPlayer extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(next) {
|
||||||
|
const el = this.media.children[0];
|
||||||
|
if (!this.props.paused && next.paused && !el.paused) el.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const { contentType, downloadCompleted } = this.props;
|
||||||
|
const { startedPlaying } = this.state;
|
||||||
|
|
||||||
|
if (this.playableType() && !startedPlaying && downloadCompleted) {
|
||||||
|
const container = this.media.children[0];
|
||||||
|
|
||||||
|
if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
|
||||||
|
this.renderAudio(this.media, true);
|
||||||
|
} else {
|
||||||
|
player.render(this.file(), container, {
|
||||||
|
autoplay: true,
|
||||||
|
controls: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('keydown', this.togglePlayListener);
|
document.removeEventListener('keydown', this.togglePlayListener);
|
||||||
const mediaElement = this.refs.media.children[0];
|
const mediaElement = this.media.children[0];
|
||||||
if (mediaElement) {
|
if (mediaElement) {
|
||||||
mediaElement.removeEventListener('click', this.togglePlayListener);
|
mediaElement.removeEventListener('click', this.togglePlayListener);
|
||||||
}
|
}
|
||||||
this.props.doPause();
|
this.props.doPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAudio(container, autoplay) {
|
|
||||||
if (container.firstChild) {
|
|
||||||
container.firstChild.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear the container
|
|
||||||
const { downloadPath } = this.props;
|
|
||||||
const audio = document.createElement('audio');
|
|
||||||
audio.autoplay = autoplay;
|
|
||||||
audio.controls = true;
|
|
||||||
audio.src = downloadPath;
|
|
||||||
container.appendChild(audio);
|
|
||||||
}
|
|
||||||
|
|
||||||
togglePlay(event) {
|
togglePlay(event) {
|
||||||
// ignore all events except click and spacebar keydown, or input events in a form control
|
// ignore all events except click and spacebar keydown, or input events in a form control
|
||||||
if (
|
if (
|
||||||
|
@ -119,7 +115,7 @@ class VideoPlayer extends React.PureComponent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const mediaElement = this.refs.media.children[0];
|
const mediaElement = this.media.children[0];
|
||||||
if (mediaElement) {
|
if (mediaElement) {
|
||||||
if (!mediaElement.paused) {
|
if (!mediaElement.paused) {
|
||||||
mediaElement.pause();
|
mediaElement.pause();
|
||||||
|
@ -129,24 +125,6 @@ class VideoPlayer extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
const { contentType, downloadCompleted } = this.props;
|
|
||||||
const { startedPlaying } = this.state;
|
|
||||||
|
|
||||||
if (this.playableType() && !startedPlaying && downloadCompleted) {
|
|
||||||
const container = this.refs.media.children[0];
|
|
||||||
|
|
||||||
if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
|
|
||||||
this.renderAudio(this.refs.media, true);
|
|
||||||
} else {
|
|
||||||
player.render(this.file(), container, {
|
|
||||||
autoplay: true,
|
|
||||||
controls: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file() {
|
file() {
|
||||||
const { downloadPath, filename } = this.props;
|
const { downloadPath, filename } = this.props;
|
||||||
|
|
||||||
|
@ -162,14 +140,26 @@ class VideoPlayer extends React.PureComponent {
|
||||||
return ['audio', 'video'].indexOf(mediaType) !== -1;
|
return ['audio', 'video'].indexOf(mediaType) !== -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderAudio(container, autoplay) {
|
||||||
|
if (container.firstChild) {
|
||||||
|
container.firstChild.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the container
|
||||||
|
const { downloadPath } = this.props;
|
||||||
|
const audio = document.createElement('audio');
|
||||||
|
audio.autoplay = autoplay;
|
||||||
|
audio.controls = true;
|
||||||
|
audio.src = downloadPath;
|
||||||
|
container.appendChild(audio);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { mediaType, poster } = this.props;
|
const { mediaType, poster } = this.props;
|
||||||
const { hasMetadata, unplayable } = this.state;
|
const { hasMetadata, unplayable } = this.state;
|
||||||
const noMetadataMessage = 'Waiting for metadata.';
|
const noMetadataMessage = 'Waiting for metadata.';
|
||||||
const unplayableMessage = "Sorry, looks like we can't play this file.";
|
const unplayableMessage = "Sorry, looks like we can't play this file.";
|
||||||
|
|
||||||
const needsMetadata = this.playableType();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{['audio', 'application'].indexOf(mediaType) !== -1 &&
|
{['audio', 'application'].indexOf(mediaType) !== -1 &&
|
||||||
|
@ -179,7 +169,12 @@ class VideoPlayer extends React.PureComponent {
|
||||||
!hasMetadata &&
|
!hasMetadata &&
|
||||||
!unplayable && <LoadingScreen status={noMetadataMessage} />}
|
!unplayable && <LoadingScreen status={noMetadataMessage} />}
|
||||||
{unplayable && <LoadingScreen status={unplayableMessage} spinner={false} />}
|
{unplayable && <LoadingScreen status={unplayableMessage} spinner={false} />}
|
||||||
<div ref="media" className="media" />
|
<div
|
||||||
|
ref={container => {
|
||||||
|
this.media = container;
|
||||||
|
}}
|
||||||
|
className="media"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,6 +164,10 @@ export const CLEAR_SHAPE_SHIFT = 'CLEAR_SHAPE_SHIFT';
|
||||||
export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
|
export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
|
||||||
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
|
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
|
||||||
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
|
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
|
||||||
|
export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST';
|
||||||
|
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
|
||||||
|
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
|
||||||
|
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
|
||||||
|
|
||||||
// Video controls
|
// Video controls
|
||||||
export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
|
export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
|
||||||
|
|
|
@ -3,3 +3,4 @@ export const LOCAL = 'folder';
|
||||||
export const FILE = 'file';
|
export const FILE = 'file';
|
||||||
export const HISTORY = 'history';
|
export const HISTORY = 'history';
|
||||||
export const HELP_CIRCLE = 'question-circle';
|
export const HELP_CIRCLE = 'question-circle';
|
||||||
|
export const DOWNLOAD = 'download';
|
||||||
|
|
|
@ -2,7 +2,7 @@ export const CONFIRM_FILE_REMOVE = 'confirmFileRemove';
|
||||||
export const INCOMPATIBLE_DAEMON = 'incompatibleDaemon';
|
export const INCOMPATIBLE_DAEMON = 'incompatibleDaemon';
|
||||||
export const FILE_TIMEOUT = 'file_timeout';
|
export const FILE_TIMEOUT = 'file_timeout';
|
||||||
export const DOWNLOADING = 'downloading';
|
export const DOWNLOADING = 'downloading';
|
||||||
export const AUTO_UPDATE_DOWNLOADED = "auto_update_downloaded";
|
export const AUTO_UPDATE_DOWNLOADED = 'auto_update_downloaded';
|
||||||
export const AUTO_UPDATE_CONFIRM = 'auto_update_confirm';
|
export const AUTO_UPDATE_CONFIRM = 'auto_update_confirm';
|
||||||
export const ERROR = 'error';
|
export const ERROR = 'error';
|
||||||
export const INSUFFICIENT_CREDITS = 'insufficient_credits';
|
export const INSUFFICIENT_CREDITS = 'insufficient_credits';
|
||||||
|
|
|
@ -8,10 +8,14 @@ import lbry from 'lbry';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { doConditionalAuthNavigate, doDaemonReady, doShowSnackBar, doAutoUpdate } from 'redux/actions/app';
|
import {
|
||||||
import { doUpdateIsNightAsync } from 'redux/actions/settings';
|
doConditionalAuthNavigate,
|
||||||
|
doDaemonReady,
|
||||||
|
doShowSnackBar,
|
||||||
|
doAutoUpdate,
|
||||||
|
} from 'redux/actions/app';
|
||||||
import { doNavigate } from 'redux/actions/navigation';
|
import { doNavigate } from 'redux/actions/navigation';
|
||||||
import { doDownloadLanguages } from 'redux/actions/settings';
|
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
|
||||||
import { doUserEmailVerify } from 'redux/actions/user';
|
import { doUserEmailVerify } from 'redux/actions/user';
|
||||||
import 'scss/all.scss';
|
import 'scss/all.scss';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
@ -20,12 +24,7 @@ import analytics from './analytics';
|
||||||
|
|
||||||
const { autoUpdater } = remote.require('electron-updater');
|
const { autoUpdater } = remote.require('electron-updater');
|
||||||
|
|
||||||
autoUpdater.logger = remote.require("electron-log");
|
autoUpdater.logger = remote.require('electron-log');
|
||||||
|
|
||||||
window.addEventListener('contextmenu', event => {
|
|
||||||
contextMenu(remote.getCurrentWindow(), event.x, event.y, app.env === 'development');
|
|
||||||
event.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
|
ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
|
||||||
if (uri && uri.startsWith('lbry://')) {
|
if (uri && uri.startsWith('lbry://')) {
|
||||||
|
@ -62,6 +61,12 @@ ipcRenderer.on('window-is-focused', () => {
|
||||||
dock.setBadge('');
|
dock.setBadge('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener('dragover', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
document.addEventListener('drop', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
document.addEventListener('click', event => {
|
document.addEventListener('click', event => {
|
||||||
let { target } = event;
|
let { target } = event;
|
||||||
while (target && target !== document) {
|
while (target && target !== document) {
|
||||||
|
@ -91,22 +96,21 @@ document.addEventListener('click', event => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
autoUpdater.on("update-downloaded", () => {
|
autoUpdater.on('update-downloaded', () => {
|
||||||
|
app.store.dispatch(doAutoUpdate());
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('update-available', () => {
|
||||||
|
console.log('Update available');
|
||||||
|
});
|
||||||
|
autoUpdater.on('update-not-available', () => {
|
||||||
|
console.log('Update not available');
|
||||||
|
});
|
||||||
|
autoUpdater.on('update-downloaded', () => {
|
||||||
|
console.log('Update downloaded');
|
||||||
app.store.dispatch(doAutoUpdate());
|
app.store.dispatch(doAutoUpdate());
|
||||||
});
|
});
|
||||||
|
|
||||||
if (["win32", "darwin"].includes(process.platform)) {
|
|
||||||
autoUpdater.on("update-available", () => {
|
|
||||||
console.log("Update available");
|
|
||||||
});
|
|
||||||
autoUpdater.on("update-not-available", () => {
|
|
||||||
console.log("Update not available");
|
|
||||||
});
|
|
||||||
autoUpdater.on("update-downloaded", () => {
|
|
||||||
console.log("Update downloaded");
|
|
||||||
app.store.dispatch(doAutoUpdate());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
app.store.dispatch(doUpdateIsNightAsync());
|
app.store.dispatch(doUpdateIsNightAsync());
|
||||||
app.store.dispatch(doDownloadLanguages());
|
app.store.dispatch(doDownloadLanguages());
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ jsonrpc.call = (
|
||||||
return response.json().then(json => {
|
return response.json().then(json => {
|
||||||
let error;
|
let error;
|
||||||
if (json.error) {
|
if (json.error) {
|
||||||
error = new Error(json.error);
|
error = new Error(json.error.message);
|
||||||
} else {
|
} else {
|
||||||
error = new Error('Protocol error with unknown response signature');
|
error = new Error('Protocol error with unknown response signature');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { connect } from "react-redux";
|
import { connect } from 'react-redux';
|
||||||
import { doCloseModal, doAutoUpdateDeclined } from "redux/actions/app";
|
import { doCloseModal, doAutoUpdateDeclined } from 'redux/actions/app';
|
||||||
import ModalAutoUpdateConfirm from "./view";
|
import ModalAutoUpdateConfirm from './view';
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
closeModal: () => dispatch(doCloseModal()),
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Modal } from "modal/modal";
|
import { Modal } from 'modal/modal';
|
||||||
import { Line } from "rc-progress";
|
import { Line } from 'rc-progress';
|
||||||
import Link from "component/link/index";
|
import Link from 'component/link/index';
|
||||||
|
|
||||||
const { ipcRenderer } = require("electron");
|
const { ipcRenderer } = require('electron');
|
||||||
|
|
||||||
class ModalAutoUpdateConfirm extends React.PureComponent {
|
class ModalAutoUpdateConfirm extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
|
@ -13,11 +13,11 @@ class ModalAutoUpdateConfirm extends React.PureComponent {
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
type="confirm"
|
type="confirm"
|
||||||
contentLabel={__("Update Downloaded")}
|
contentLabel={__('Update Downloaded')}
|
||||||
confirmButtonLabel={__("Upgrade")}
|
confirmButtonLabel={__('Upgrade')}
|
||||||
abortButtonLabel={__("Not now")}
|
abortButtonLabel={__('Not now')}
|
||||||
onConfirmed={() => {
|
onConfirmed={() => {
|
||||||
ipcRenderer.send("autoUpdateAccepted");
|
ipcRenderer.send('autoUpdateAccepted');
|
||||||
}}
|
}}
|
||||||
onAborted={() => {
|
onAborted={() => {
|
||||||
declineAutoUpdate();
|
declineAutoUpdate();
|
||||||
|
@ -25,12 +25,8 @@ class ModalAutoUpdateConfirm extends React.PureComponent {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<section>
|
<section>
|
||||||
<h3 className="text-center">{__("LBRY Update Ready")}</h3>
|
<h3 className="text-center">{__('LBRY Update Ready')}</h3>
|
||||||
<p>
|
<p>{__('Your LBRY update is ready. Restart LBRY now to use it!')}</p>
|
||||||
{__(
|
|
||||||
'Your LBRY update is ready. Restart LBRY now to use it!'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="meta text-center">
|
<p className="meta text-center">
|
||||||
{__('Want to know what has changed?')} See the{' '}
|
{__('Want to know what has changed?')} See the{' '}
|
||||||
<Link label={__('release notes')} href="https://github.com/lbryio/lbry-app/releases" />.
|
<Link label={__('release notes')} href="https://github.com/lbryio/lbry-app/releases" />.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { connect } from "react-redux";
|
import { connect } from 'react-redux';
|
||||||
import { doCloseModal, doAutoUpdateDeclined } from "redux/actions/app";
|
import { doCloseModal, doAutoUpdateDeclined } from 'redux/actions/app';
|
||||||
import ModalAutoUpdateDownloaded from "./view";
|
import ModalAutoUpdateDownloaded from './view';
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
closeModal: () => dispatch(doCloseModal()),
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Modal } from "modal/modal";
|
import { Modal } from 'modal/modal';
|
||||||
import { Line } from "rc-progress";
|
import { Line } from 'rc-progress';
|
||||||
import Link from "component/link/index";
|
import Link from 'component/link/index';
|
||||||
|
|
||||||
const { ipcRenderer } = require("electron");
|
const { ipcRenderer } = require('electron');
|
||||||
|
|
||||||
class ModalAutoUpdateDownloaded extends React.PureComponent {
|
class ModalAutoUpdateDownloaded extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
|
@ -13,20 +13,20 @@ class ModalAutoUpdateDownloaded extends React.PureComponent {
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
type="confirm"
|
type="confirm"
|
||||||
contentLabel={__("Update Downloaded")}
|
contentLabel={__('Update Downloaded')}
|
||||||
confirmButtonLabel={__("Use it Now")}
|
confirmButtonLabel={__('Use it Now')}
|
||||||
abortButtonLabel={__("Upgrade on Close")}
|
abortButtonLabel={__('Upgrade on Close')}
|
||||||
onConfirmed={() => {
|
onConfirmed={() => {
|
||||||
ipcRenderer.send("autoUpdateAccepted");
|
ipcRenderer.send('autoUpdateAccepted');
|
||||||
}}
|
}}
|
||||||
onAborted={() => {
|
onAborted={() => {
|
||||||
declineAutoUpdate();
|
declineAutoUpdate();
|
||||||
ipcRenderer.send("autoUpdateDeclined");
|
ipcRenderer.send('autoUpdateDeclined');
|
||||||
closeModal();
|
closeModal();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<section>
|
<section>
|
||||||
<h3 className="text-center">{__("LBRY Leveled Up")}</h3>
|
<h3 className="text-center">{__('LBRY Leveled Up')}</h3>
|
||||||
<p>
|
<p>
|
||||||
{__(
|
{__(
|
||||||
'A new version of LBRY has been released, downloaded, and is ready for you to use pending a restart.'
|
'A new version of LBRY has been released, downloaded, and is ready for you to use pending a restart.'
|
||||||
|
|
|
@ -30,7 +30,12 @@ class ModalEmailCollection extends React.PureComponent {
|
||||||
<Modal type="custom" isOpen contentLabel="Email">
|
<Modal type="custom" isOpen contentLabel="Email">
|
||||||
<section>
|
<section>
|
||||||
<h3 className="modal__header">Can We Stay In Touch?</h3>
|
<h3 className="modal__header">Can We Stay In Touch?</h3>
|
||||||
{this.renderInner()}
|
<div className="card__content">{this.renderInner()}</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<div className="help">
|
||||||
|
{`${__('Your email may be used to sync usage data across devices.')} `}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -68,7 +68,7 @@ export class AuthPage extends React.PureComponent {
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<div className="help">
|
<div className="help">
|
||||||
{`${__(
|
{`${__(
|
||||||
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards.'
|
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards and may be used to sync usage data across devices.'
|
||||||
)} `}
|
)} `}
|
||||||
<Link onClick={() => navigate('/discover')} label={__('Return home')} />.
|
<Link onClick={() => navigate('/discover')} label={__('Return home')} />.
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { doFetchFileInfo } from 'redux/actions/file_info';
|
||||||
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||||
import { selectRewardContentClaimIds } from 'redux/selectors/content';
|
import { selectRewardContentClaimIds } from 'redux/selectors/content';
|
||||||
import { doFetchCostInfoForUri } from 'redux/actions/cost_info';
|
import { doFetchCostInfoForUri } from 'redux/actions/cost_info';
|
||||||
|
import { checkSubscriptionLatest } from 'redux/actions/subscriptions';
|
||||||
import {
|
import {
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
makeSelectContentTypeForUri,
|
makeSelectContentTypeForUri,
|
||||||
|
@ -13,6 +14,7 @@ import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
|
||||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||||
import FilePage from './view';
|
import FilePage from './view';
|
||||||
import { makeSelectCurrentParam } from 'redux/selectors/navigation';
|
import { makeSelectCurrentParam } from 'redux/selectors/navigation';
|
||||||
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
@ -23,12 +25,15 @@ const select = (state, props) => ({
|
||||||
tab: makeSelectCurrentParam('tab')(state),
|
tab: makeSelectCurrentParam('tab')(state),
|
||||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||||
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||||
|
subscriptions: selectSubscriptions(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||||
|
checkSubscriptionLatest: (subscription, uri) =>
|
||||||
|
dispatch(checkSubscriptionLatest(subscription, uri)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(FilePage);
|
export default connect(select, perform)(FilePage);
|
||||||
|
|
|
@ -17,6 +17,7 @@ class FilePage extends React.PureComponent {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.fetchFileInfo(this.props);
|
this.fetchFileInfo(this.props);
|
||||||
this.fetchCostInfo(this.props);
|
this.fetchCostInfo(this.props);
|
||||||
|
this.checkSubscriptionLatest(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
@ -35,6 +36,28 @@ class FilePage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkSubscriptionLatest(props) {
|
||||||
|
if (
|
||||||
|
props.subscriptions
|
||||||
|
.map(subscription => subscription.channelName)
|
||||||
|
.indexOf(props.claim.channel_name) !== -1
|
||||||
|
) {
|
||||||
|
props.checkSubscriptionLatest(
|
||||||
|
{
|
||||||
|
channelName: props.claim.channel_name,
|
||||||
|
uri: buildURI(
|
||||||
|
{
|
||||||
|
contentName: props.claim.channel_name,
|
||||||
|
claimId: props.claim.value.publisherSignature.certificateId,
|
||||||
|
},
|
||||||
|
false
|
||||||
|
),
|
||||||
|
},
|
||||||
|
buildURI({ contentName: props.claim.name, claimId: props.claim.claim_id }, false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
claim,
|
claim,
|
||||||
|
|
|
@ -14,6 +14,8 @@ class SettingsPage extends React.PureComponent {
|
||||||
this.state = {
|
this.state = {
|
||||||
clearingCache: false,
|
clearingCache: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.onAutomaticDarkModeChange = this.onAutomaticDarkModeChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCache() {
|
clearCache() {
|
||||||
|
@ -62,11 +64,16 @@ class SettingsPage extends React.PureComponent {
|
||||||
|
|
||||||
onThemeChange(event) {
|
onThemeChange(event) {
|
||||||
const { value } = event.target;
|
const { value } = event.target;
|
||||||
|
|
||||||
|
if (value === 'dark') {
|
||||||
|
this.onAutomaticDarkModeChange(false);
|
||||||
|
}
|
||||||
|
|
||||||
this.props.setClientSetting(settings.THEME, value);
|
this.props.setClientSetting(settings.THEME, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAutomaticDarkModeChange(event) {
|
onAutomaticDarkModeChange(value) {
|
||||||
this.props.setClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED, event.target.checked);
|
this.props.setClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onInstantPurchaseEnabledChange(enabled) {
|
onInstantPurchaseEnabledChange(enabled) {
|
||||||
|
@ -143,6 +150,7 @@ class SettingsPage extends React.PureComponent {
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="main--single-column">
|
<main className="main--single-column">
|
||||||
<SubHeader />
|
<SubHeader />
|
||||||
|
@ -300,7 +308,9 @@ class SettingsPage extends React.PureComponent {
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
onChange={this.onShareDataChange.bind(this)}
|
onChange={this.onShareDataChange.bind(this)}
|
||||||
defaultChecked={daemonSettings.share_usage_data}
|
defaultChecked={daemonSettings.share_usage_data}
|
||||||
label={__('Help make LBRY better by contributing diagnostic data about my usage')}
|
label={__(
|
||||||
|
'Help make LBRY better by contributing analytics and diagnostic data and about my usage'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -325,8 +335,9 @@ class SettingsPage extends React.PureComponent {
|
||||||
|
|
||||||
<FormRow
|
<FormRow
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
onChange={this.onAutomaticDarkModeChange.bind(this)}
|
disabled={theme === 'dark'}
|
||||||
defaultChecked={automaticDarkModeEnabled}
|
onChange={e => this.onAutomaticDarkModeChange(e.target.checked)}
|
||||||
|
checked={automaticDarkModeEnabled}
|
||||||
label={__('Automatic dark mode (9pm to 8am)')}
|
label={__('Automatic dark mode (9pm to 8am)')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
/* eslint-disable import/no-commonjs */
|
import isDev from 'electron-is-dev';
|
||||||
|
import Lbry from 'lbry';
|
||||||
|
import path from 'path';
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import { ipcRenderer, remote } from 'electron';
|
import { ipcRenderer, remote } from 'electron';
|
||||||
import Lbry from 'lbry';
|
|
||||||
import Path from 'path';
|
|
||||||
import { doFetchRewardedContent } from 'redux/actions/content';
|
import { doFetchRewardedContent } from 'redux/actions/content';
|
||||||
import { doFetchFileInfosAndPublishedClaims } from 'redux/actions/file_info';
|
import { doFetchFileInfosAndPublishedClaims } from 'redux/actions/file_info';
|
||||||
import { doAuthNavigate } from 'redux/actions/navigation';
|
import { doAuthNavigate } from 'redux/actions/navigation';
|
||||||
import { doFetchDaemonSettings } from 'redux/actions/settings';
|
import { doFetchDaemonSettings } from 'redux/actions/settings';
|
||||||
import { doAuthenticate } from 'redux/actions/user';
|
import { doAuthenticate } from 'redux/actions/user';
|
||||||
import { doBalanceSubscribe } from 'redux/actions/wallet';
|
import { doBalanceSubscribe } from 'redux/actions/wallet';
|
||||||
import { doPause } from "redux/actions/media";
|
import { doPause } from 'redux/actions/media';
|
||||||
|
import { doCheckSubscriptions } from 'redux/actions/subscriptions';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
selectCurrentModal,
|
selectCurrentModal,
|
||||||
selectIsUpgradeSkipped,
|
selectIsUpgradeSkipped,
|
||||||
selectRemoteVersion,
|
|
||||||
selectUpdateUrl,
|
selectUpdateUrl,
|
||||||
selectUpgradeDownloadItem,
|
selectUpgradeDownloadItem,
|
||||||
selectUpgradeDownloadPath,
|
selectUpgradeDownloadPath,
|
||||||
selectUpgradeFilename,
|
selectUpgradeFilename,
|
||||||
selectAutoUpdateDeclined,
|
selectAutoUpdateDeclined,
|
||||||
} from 'redux/selectors/app';
|
} from 'redux/selectors/app';
|
||||||
|
import { lbrySettings as config } from 'package.json';
|
||||||
|
|
||||||
const { autoUpdater } = remote.require('electron-updater');
|
const { autoUpdater } = remote.require('electron-updater');
|
||||||
const { download } = remote.require('electron-dl');
|
const { download } = remote.require('electron-dl');
|
||||||
const Fs = remote.require('fs');
|
const Fs = remote.require('fs');
|
||||||
const { lbrySettings: config } = require('package.json');
|
|
||||||
|
|
||||||
const CHECK_UPGRADE_INTERVAL = 10 * 60 * 1000;
|
const CHECK_UPGRADE_INTERVAL = 10 * 60 * 1000;
|
||||||
|
|
||||||
|
@ -84,23 +84,19 @@ export function doDownloadUpgradeRequested() {
|
||||||
|
|
||||||
const autoUpdateDeclined = selectAutoUpdateDeclined(state);
|
const autoUpdateDeclined = selectAutoUpdateDeclined(state);
|
||||||
|
|
||||||
if (['win32', 'darwin'].includes(process.platform)) { // electron-updater behavior
|
if (autoUpdateDeclined) {
|
||||||
if (autoUpdateDeclined) {
|
// The user declined an update before, so show the "confirm" dialog
|
||||||
// The user declined an update before, so show the "confirm" dialog
|
dispatch({
|
||||||
dispatch({
|
type: ACTIONS.OPEN_MODAL,
|
||||||
type: ACTIONS.OPEN_MODAL,
|
data: { modal: MODALS.AUTO_UPDATE_CONFIRM },
|
||||||
data: { modal: MODALS.AUTO_UPDATE_CONFIRM },
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
// The user was never shown the original update dialog (e.g. because they were
|
||||||
// The user was never shown the original update dialog (e.g. because they were
|
// watching a video). So show the initial "update downloaded" dialog.
|
||||||
// watching a video). So show the inital "update downloaded" dialog.
|
dispatch({
|
||||||
dispatch({
|
type: ACTIONS.OPEN_MODAL,
|
||||||
type: ACTIONS.OPEN_MODAL,
|
data: { modal: MODALS.AUTO_UPDATE_DOWNLOADED },
|
||||||
data: { modal: MODALS.AUTO_UPDATE_DOWNLOADED },
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
} else { // Old behavior for Linux
|
|
||||||
dispatch(doDownloadUpgrade());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -109,7 +105,7 @@ export function doDownloadUpgrade() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
// Make a new directory within temp directory so the filename is guaranteed to be available
|
// Make a new directory within temp directory so the filename is guaranteed to be available
|
||||||
const dir = Fs.mkdtempSync(remote.app.getPath('temp') + Path.sep);
|
const dir = Fs.mkdtempSync(remote.app.getPath('temp') + path.sep);
|
||||||
const upgradeFilename = selectUpgradeFilename(state);
|
const upgradeFilename = selectUpgradeFilename(state);
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -127,7 +123,7 @@ export function doDownloadUpgrade() {
|
||||||
type: ACTIONS.UPGRADE_DOWNLOAD_COMPLETED,
|
type: ACTIONS.UPGRADE_DOWNLOAD_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
downloadItem,
|
downloadItem,
|
||||||
path: Path.join(dir, upgradeFilename),
|
path: path.join(dir, upgradeFilename),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -145,8 +141,7 @@ export function doDownloadUpgrade() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doAutoUpdate() {
|
export function doAutoUpdate() {
|
||||||
return function(dispatch, getState) {
|
return dispatch => {
|
||||||
const state = getState();
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.AUTO_UPDATE_DOWNLOADED,
|
type: ACTIONS.AUTO_UPDATE_DOWNLOADED,
|
||||||
});
|
});
|
||||||
|
@ -159,12 +154,11 @@ export function doAutoUpdate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doAutoUpdateDeclined() {
|
export function doAutoUpdateDeclined() {
|
||||||
return function(dispatch, getState) {
|
return dispatch => {
|
||||||
const state = getState();
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.AUTO_UPDATE_DECLINED,
|
type: ACTIONS.AUTO_UPDATE_DECLINED,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doCancelUpgrade() {
|
export function doCancelUpgrade() {
|
||||||
|
@ -181,6 +175,7 @@ export function doCancelUpgrade() {
|
||||||
try {
|
try {
|
||||||
upgradeDownloadItem.cancel();
|
upgradeDownloadItem.cancel();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error(err);
|
console.error(err);
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
@ -197,47 +192,11 @@ export function doCheckUpgradeAvailable() {
|
||||||
type: ACTIONS.CHECK_UPGRADE_START,
|
type: ACTIONS.CHECK_UPGRADE_START,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (["win32", "darwin"].includes(process.platform)) {
|
const autoUpdateDeclined = selectAutoUpdateDeclined(state);
|
||||||
// On Windows and Mac, updates happen silently through
|
|
||||||
// electron-updater.
|
|
||||||
const autoUpdateDeclined = selectAutoUpdateDeclined(state);
|
|
||||||
|
|
||||||
if (!autoUpdateDeclined) {
|
if (!autoUpdateDeclined && !isDev) {
|
||||||
autoUpdater.checkForUpdates();
|
autoUpdater.checkForUpdates();
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = ({ remoteVersion, upgradeAvailable }) => {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.CHECK_UPGRADE_SUCCESS,
|
|
||||||
data: {
|
|
||||||
upgradeAvailable,
|
|
||||||
remoteVersion,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
upgradeAvailable &&
|
|
||||||
!selectCurrentModal(state) &&
|
|
||||||
(!selectIsUpgradeSkipped(state) || remoteVersion !== selectRemoteVersion(state))
|
|
||||||
) {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.OPEN_MODAL,
|
|
||||||
data: {
|
|
||||||
modal: MODALS.UPGRADE,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fail = () => {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.CHECK_UPGRADE_FAIL,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Lbry.getAppVersionInfo().then(success, fail);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,6 +255,7 @@ export function doDaemonReady() {
|
||||||
dispatch(doCheckUpgradeAvailable());
|
dispatch(doCheckUpgradeAvailable());
|
||||||
}
|
}
|
||||||
dispatch(doCheckUpgradeSubscribe());
|
dispatch(doCheckUpgradeSubscribe());
|
||||||
|
dispatch(doCheckSubscriptions());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Lbryio from 'lbryio';
|
||||||
import { normalizeURI, buildURI } from 'lbryURI';
|
import { normalizeURI, buildURI } from 'lbryURI';
|
||||||
import { doAlertError, doOpenModal } from 'redux/actions/app';
|
import { doAlertError, doOpenModal } from 'redux/actions/app';
|
||||||
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||||
|
import { setSubscriptionLatest } from 'redux/actions/subscriptions';
|
||||||
import { selectBadgeNumber } from 'redux/selectors/app';
|
import { selectBadgeNumber } from 'redux/selectors/app';
|
||||||
import { selectMyClaimsRaw } from 'redux/selectors/claims';
|
import { selectMyClaimsRaw } from 'redux/selectors/claims';
|
||||||
import { selectResolvingUris } from 'redux/selectors/content';
|
import { selectResolvingUris } from 'redux/selectors/content';
|
||||||
|
@ -21,6 +22,7 @@ import { selectBalance } from 'redux/selectors/wallet';
|
||||||
import batchActions from 'util/batchActions';
|
import batchActions from 'util/batchActions';
|
||||||
import setBadge from 'util/setBadge';
|
import setBadge from 'util/setBadge';
|
||||||
import setProgressBar from 'util/setProgressBar';
|
import setProgressBar from 'util/setProgressBar';
|
||||||
|
import analytics from 'analytics';
|
||||||
|
|
||||||
const DOWNLOAD_POLL_INTERVAL = 250;
|
const DOWNLOAD_POLL_INTERVAL = 250;
|
||||||
|
|
||||||
|
@ -226,11 +228,7 @@ export function doDownloadFile(uri, streamInfo) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch(doStartDownload(uri, streamInfo.outpoint));
|
dispatch(doStartDownload(uri, streamInfo.outpoint));
|
||||||
|
|
||||||
Lbryio.call('file', 'view', {
|
analytics.apiLog(uri, streamInfo.output, streamInfo.claim_id);
|
||||||
uri,
|
|
||||||
outpoint: streamInfo.outpoint,
|
|
||||||
claim_id: streamInfo.claim_id,
|
|
||||||
}).catch(() => {});
|
|
||||||
|
|
||||||
dispatch(doClaimEligiblePurchaseRewards());
|
dispatch(doClaimEligiblePurchaseRewards());
|
||||||
};
|
};
|
||||||
|
@ -288,7 +286,7 @@ export function doLoadVideo(uri) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doPurchaseUri(uri) {
|
export function doPurchaseUri(uri, specificCostInfo) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const balance = selectBalance(state);
|
const balance = selectBalance(state);
|
||||||
|
@ -321,7 +319,7 @@ export function doPurchaseUri(uri) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const costInfo = makeSelectCostInfoForUri(uri)(state);
|
const costInfo = makeSelectCostInfoForUri(uri)(state) || specificCostInfo;
|
||||||
const { cost } = costInfo;
|
const { cost } = costInfo;
|
||||||
|
|
||||||
if (cost > balance) {
|
if (cost > balance) {
|
||||||
|
@ -358,6 +356,25 @@ export function doFetchClaimsByChannel(uri, page) {
|
||||||
const claimResult = result[uri] || {};
|
const claimResult = result[uri] || {};
|
||||||
const { claims_in_channel: claimsInChannel, returned_page: returnedPage } = claimResult;
|
const { claims_in_channel: claimsInChannel, returned_page: returnedPage } = claimResult;
|
||||||
|
|
||||||
|
if (claimsInChannel && claimsInChannel.length) {
|
||||||
|
const latest = claimsInChannel[0];
|
||||||
|
dispatch(
|
||||||
|
setSubscriptionLatest(
|
||||||
|
{
|
||||||
|
channelName: latest.channel_name,
|
||||||
|
uri: buildURI(
|
||||||
|
{
|
||||||
|
contentName: latest.channel_name,
|
||||||
|
claimId: latest.value.publisherSignature.certificateId,
|
||||||
|
},
|
||||||
|
false
|
||||||
|
),
|
||||||
|
},
|
||||||
|
buildURI({ contentName: latest.name, claimId: latest.claim_id }, false)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED,
|
type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -56,6 +56,20 @@ export function doGetThemes() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doUpdateIsNight() {
|
||||||
|
const momentNow = moment();
|
||||||
|
return {
|
||||||
|
type: ACTIONS.UPDATE_IS_NIGHT,
|
||||||
|
data: {
|
||||||
|
isNight: (() => {
|
||||||
|
const startNightMoment = moment('21:00', 'HH:mm');
|
||||||
|
const endNightMoment = moment('8:00', 'HH:mm');
|
||||||
|
return !(momentNow.isAfter(endNightMoment) && momentNow.isBefore(startNightMoment));
|
||||||
|
})(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function doUpdateIsNightAsync() {
|
export function doUpdateIsNightAsync() {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch(doUpdateIsNight());
|
dispatch(doUpdateIsNight());
|
||||||
|
@ -66,19 +80,6 @@ export function doUpdateIsNightAsync() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doUpdateIsNight() {
|
|
||||||
const momentNow = moment();
|
|
||||||
return {
|
|
||||||
type: ACTIONS.UPDATE_IS_NIGHT,
|
|
||||||
data: { isNight: (() => {
|
|
||||||
const startNightMoment = moment('19:00', 'HH:mm');
|
|
||||||
const endNightMoment = moment('8:00', 'HH:mm');
|
|
||||||
return !(momentNow.isAfter(endNightMoment) && momentNow.isBefore(startNightMoment));
|
|
||||||
})()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function doDownloadLanguage(langFile) {
|
export function doDownloadLanguage(langFile) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
const destinationPath = `${app.i18n.directory}/${langFile}`;
|
const destinationPath = `${app.i18n.directory}/${langFile}`;
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import type { Subscription, Dispatch } from 'redux/reducers/subscriptions';
|
import type { Subscription, Dispatch, SubscriptionState } from 'redux/reducers/subscriptions';
|
||||||
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
|
import Lbry from 'lbry';
|
||||||
|
import { doPurchaseUri } from 'redux/actions/content';
|
||||||
|
import { doNavigate } from 'redux/actions/navigation';
|
||||||
|
import { buildURI } from 'lbryURI';
|
||||||
|
|
||||||
|
const CHECK_SUBSCRIPTIONS_INTERVAL = 10 * 60 * 1000;
|
||||||
|
|
||||||
export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) =>
|
export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -14,5 +21,113 @@ export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: D
|
||||||
data: subscription,
|
data: subscription,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const doCheckSubscriptions = () => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: () => SubscriptionState
|
||||||
|
) => {
|
||||||
|
const checkSubscriptionsTimer = setInterval(
|
||||||
|
() =>
|
||||||
|
selectSubscriptions(getState()).map((subscription: Subscription) =>
|
||||||
|
dispatch(doCheckSubscription(subscription))
|
||||||
|
),
|
||||||
|
CHECK_SUBSCRIPTIONS_INTERVAL
|
||||||
|
);
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.CHECK_SUBSCRIPTIONS_SUBSCRIBE,
|
||||||
|
data: { checkSubscriptionsTimer },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const doCheckSubscription = (subscription: Subscription) => (dispatch: Dispatch) => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
|
||||||
|
data: subscription,
|
||||||
|
});
|
||||||
|
|
||||||
|
Lbry.claim_list_by_channel({ uri: subscription.uri, page: 1 }).then(result => {
|
||||||
|
const claimResult = result[subscription.uri] || {};
|
||||||
|
const { claims_in_channel: claimsInChannel } = claimResult;
|
||||||
|
|
||||||
|
const count = subscription.latest
|
||||||
|
? claimsInChannel.reduce(
|
||||||
|
(prev, cur, index) =>
|
||||||
|
buildURI({ contentName: cur.name, claimId: cur.claim_id }, false) ===
|
||||||
|
subscription.latest
|
||||||
|
? index
|
||||||
|
: prev,
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
if (count !== 0) {
|
||||||
|
if (!claimsInChannel[0].value.stream.metadata.fee) {
|
||||||
|
dispatch(
|
||||||
|
doPurchaseUri(
|
||||||
|
buildURI(
|
||||||
|
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
|
||||||
|
false
|
||||||
|
),
|
||||||
|
{ cost: 0 }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const notif = new window.Notification(subscription.channelName, {
|
||||||
|
body: `Posted ${claimsInChannel[0].value.stream.metadata.title}${
|
||||||
|
count > 1 ? ` and ${count - 1} other new items` : ''
|
||||||
|
}${count < 0 ? ' and 9+ other new items' : ''}`,
|
||||||
|
silent: false,
|
||||||
|
});
|
||||||
|
notif.onclick = () => {
|
||||||
|
dispatch(
|
||||||
|
doNavigate('/show', {
|
||||||
|
uri: buildURI(
|
||||||
|
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
|
||||||
|
true
|
||||||
|
),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//$FlowIssue
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
|
||||||
|
data: subscription,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const checkSubscriptionLatest = (channel: Subscription, uri: string) => (
|
||||||
|
dispatch: Dispatch
|
||||||
|
) => {
|
||||||
|
Lbry.claim_list_by_channel({ uri: channel.uri, page: 1 }).then(result => {
|
||||||
|
const claimResult = result[channel.uri] || {};
|
||||||
|
const { claims_in_channel: claimsInChannel } = claimResult;
|
||||||
|
|
||||||
|
if (
|
||||||
|
claimsInChannel &&
|
||||||
|
claimsInChannel.length &&
|
||||||
|
buildURI(
|
||||||
|
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
|
||||||
|
false
|
||||||
|
) === uri
|
||||||
|
) {
|
||||||
|
dispatch(setSubscriptionLatest(channel, uri));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
|
||||||
|
dispatch: Dispatch
|
||||||
|
) =>
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
|
||||||
|
data: {
|
||||||
|
subscription,
|
||||||
|
uri,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const setHasFetchedSubscriptions = () => (dispatch: Dispatch) =>
|
export const setHasFetchedSubscriptions = () => (dispatch: Dispatch) =>
|
||||||
dispatch({ type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS });
|
dispatch({ type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS });
|
||||||
|
|
|
@ -93,7 +93,7 @@ reducers[ACTIONS.AUTO_UPDATE_DECLINED] = state => {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
autoUpdateDeclined: true,
|
autoUpdateDeclined: true,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.UPGRADE_DOWNLOAD_COMPLETED] = (state, action) =>
|
reducers[ACTIONS.UPGRADE_DOWNLOAD_COMPLETED] = (state, action) =>
|
||||||
Object.assign({}, state, {
|
Object.assign({}, state, {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { handleActions } from 'util/redux-utils';
|
||||||
export type Subscription = {
|
export type Subscription = {
|
||||||
channelName: string,
|
channelName: string,
|
||||||
uri: string,
|
uri: string,
|
||||||
|
latest: ?string,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Subscription redux types
|
// Subscription redux types
|
||||||
|
@ -28,7 +29,30 @@ type HasFetchedSubscriptions = {
|
||||||
type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS,
|
type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Action = doChannelSubscribe | doChannelUnsubscribe | HasFetchedSubscriptions;
|
type setSubscriptionLatest = {
|
||||||
|
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
|
||||||
|
data: {
|
||||||
|
subscription: Subscription,
|
||||||
|
uri: string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type CheckSubscriptionStarted = {
|
||||||
|
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
type CheckSubscriptionCompleted = {
|
||||||
|
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Action =
|
||||||
|
| doChannelSubscribe
|
||||||
|
| doChannelUnsubscribe
|
||||||
|
| HasFetchedSubscriptions
|
||||||
|
| setSubscriptionLatest
|
||||||
|
| CheckSubscriptionStarted
|
||||||
|
| CheckSubscriptionCompleted
|
||||||
|
| Function;
|
||||||
export type Dispatch = (action: Action) => any;
|
export type Dispatch = (action: Action) => any;
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
|
@ -70,6 +94,18 @@ export default handleActions(
|
||||||
...state,
|
...state,
|
||||||
hasFetchedSubscriptions: true,
|
hasFetchedSubscriptions: true,
|
||||||
}),
|
}),
|
||||||
|
[ACTIONS.SET_SUBSCRIPTION_LATEST]: (
|
||||||
|
state: SubscriptionState,
|
||||||
|
action: setSubscriptionLatest
|
||||||
|
): SubscriptionState => ({
|
||||||
|
...state,
|
||||||
|
subscriptions: state.subscriptions.map(
|
||||||
|
subscription =>
|
||||||
|
subscription.channelName === action.data.subscription.channelName
|
||||||
|
? { ...subscription, latest: action.data.uri }
|
||||||
|
: subscription
|
||||||
|
),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
defaultState
|
defaultState
|
||||||
);
|
);
|
||||||
|
|
|
@ -56,9 +56,15 @@ export const selectUpgradeDownloadPath = createSelector(selectState, state => st
|
||||||
|
|
||||||
export const selectUpgradeDownloadItem = createSelector(selectState, state => state.downloadItem);
|
export const selectUpgradeDownloadItem = createSelector(selectState, state => state.downloadItem);
|
||||||
|
|
||||||
export const selectAutoUpdateDownloaded = createSelector(selectState, state => state.autoUpdateDownloaded);
|
export const selectAutoUpdateDownloaded = createSelector(
|
||||||
|
selectState,
|
||||||
|
state => state.autoUpdateDownloaded
|
||||||
|
);
|
||||||
|
|
||||||
export const selectAutoUpdateDeclined = createSelector(selectState, state => state.autoUpdateDeclined);
|
export const selectAutoUpdateDeclined = createSelector(
|
||||||
|
selectState,
|
||||||
|
state => state.autoUpdateDeclined
|
||||||
|
);
|
||||||
|
|
||||||
export const selectModalsAllowed = createSelector(selectState, state => state.modalsAllowed);
|
export const selectModalsAllowed = createSelector(selectState, state => state.modalsAllowed);
|
||||||
|
|
||||||
|
|
38
src/renderer/util/parseData.js
Normal file
38
src/renderer/util/parseData.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Beautify JSON
|
||||||
|
const parseJson = data => JSON.stringify(data, null, '\t');
|
||||||
|
|
||||||
|
// No need for an external module:
|
||||||
|
// https://gist.github.com/btzr-io/55c3450ea3d709fc57540e762899fb85
|
||||||
|
const parseCsv = data => {
|
||||||
|
// Get items for header
|
||||||
|
const getHeaders = temp =>
|
||||||
|
Object.entries(temp)
|
||||||
|
.map(([key]) => key)
|
||||||
|
.join(',');
|
||||||
|
// Get rows content
|
||||||
|
const getData = list =>
|
||||||
|
list
|
||||||
|
.map(item => {
|
||||||
|
const row = Object.entries(item)
|
||||||
|
.map(([key, value]) => value)
|
||||||
|
.join(',');
|
||||||
|
return row;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
// Return CSV string
|
||||||
|
return `${getHeaders(data[0])} \n ${getData(data)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseData = (data, format) => {
|
||||||
|
// Check for validation
|
||||||
|
const valid = data && data[0] && format;
|
||||||
|
// Pick a format
|
||||||
|
const formats = {
|
||||||
|
csv: list => parseCsv(list),
|
||||||
|
json: list => parseJson(list),
|
||||||
|
};
|
||||||
|
// Return parsed data: JSON || CSV
|
||||||
|
return valid && formats[format] ? formats[format](data) : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default parseData;
|
|
@ -2,10 +2,10 @@ import { remote } from 'electron';
|
||||||
|
|
||||||
const application = remote.app;
|
const application = remote.app;
|
||||||
const { dock } = application;
|
const { dock } = application;
|
||||||
const win = remote.BrowserWindow.getFocusedWindow();
|
const browserWindow = remote.getCurrentWindow();
|
||||||
const setBadge = text => {
|
const setBadge = text => {
|
||||||
if (!dock) return;
|
if (!dock) return;
|
||||||
if (win.isFocused()) return;
|
if (browserWindow.isFocused()) return;
|
||||||
|
|
||||||
dock.setBadge(text);
|
dock.setBadge(text);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const FlowFlowPlugin = require('./flowtype-plugin');
|
const FlowFlowPlugin = require('./flowtype-plugin');
|
||||||
|
const isDev = require('electron-is-dev');
|
||||||
|
|
||||||
const ELECTRON_RENDERER_PROCESS_ROOT = path.resolve(__dirname, 'src/renderer/');
|
const ELECTRON_RENDERER_PROCESS_ROOT = path.resolve(__dirname, 'src/renderer/');
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (isDev) {
|
||||||
module.exports.plugins = [
|
module.exports.plugins = [
|
||||||
new FlowFlowPlugin({
|
new FlowFlowPlugin({
|
||||||
warn: true,
|
warn: true,
|
||||||
|
|
Loading…
Reference in a new issue