diff --git a/.eslintrc.json b/.eslintrc.json index fb260f101..bb482f3f2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -39,6 +39,7 @@ "no-return-assign": 0, "react/require-default-props": 0, "react/jsx-closing-tag-location": 0, - "jsx-a11y/no-noninteractive-element-to-interactive-role": 0 + "jsx-a11y/no-noninteractive-element-to-interactive-role": 0, + "class-methods-use-this": 0 } } diff --git a/.flowconfig b/.flowconfig index c2bb89028..ffc632dcf 100644 --- a/.flowconfig +++ b/.flowconfig @@ -17,10 +17,10 @@ module.name_mapper='^types\(.*\)$' -> '/src/renderer/types\1' module.name_mapper='^component\(.*\)$' -> '/src/renderer/component\1' module.name_mapper='^page\(.*\)$' -> '/src/renderer/page\1' module.name_mapper='^lbry\(.*\)$' -> '/src/renderer/lbry\1' -module.name_mapper='^rewards\(.*\)$' -> '/src/renderer/rewards\1' module.name_mapper='^modal\(.*\)$' -> '/src/renderer/modal\1' module.name_mapper='^app\(.*\)$' -> '/src/renderer/app\1' module.name_mapper='^native\(.*\)$' -> '/src/renderer/native\1' module.name_mapper='^analytics\(.*\)$' -> '/src/renderer/analytics\1' + [strict] diff --git a/.gitignore b/.gitignore index 4fc96cbb9..66302821f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store /node_modules /dist /static/daemon/lbrynet* diff --git a/.travis.yml b/.travis.yml index da95738cf..2a883d6f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,49 +1,76 @@ matrix: include: - - os: osx - env: TARGET=mac - osx_image: xcode9.2 - language: node_js - node_js: "9" - - os: linux - env: TARGET=windows - services: docker - language: node_js - node_js: "9" - - os: linux - env: TARGET=linux - language: node_js - node_js: "9" + - os: osx + env: TARGET=mac + osx_image: xcode9.2 + language: node_js + node_js: '9' + - os: linux + env: TARGET=windows + services: docker + language: node_js + node_js: '9' + - os: linux + env: TARGET=linux + language: node_js + node_js: '9' cache: false 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" - else - sudo apt-get -qq update - sudo apt-get install -y libsecret-1-dev - sudo apt-get install --no-install-recommends -y gcc-multilib g++-multilib - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.6.0 - export PATH="$HOME/.yarn/bin:$PATH" - fi +- | + unset TRAVIS_COMMIT_MESSAGE; + 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" + else + sudo apt-get -qq update + sudo apt-get install -y libsecret-1-dev + sudo apt-get install --no-install-recommends -y gcc-multilib g++-multilib + curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.6.0 + export PATH="$HOME/.yarn/bin:$PATH" + fi before_script: - - git lfs pull +- git lfs pull script: - - | - if [ "$TARGET" == "windows" ]; then - docker run --rm \ - --env-file <(env | grep -iE 'DEBUG|TARGET|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') \ - -v ${PWD}:/project \ - electronuserland/builder:wine \ - /bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn build --win --publish onTag"; - fi - - if [ "$TARGET" == "mac" ]; then yarn build --publish onTag; fi - - if [ "$TARGET" == "linux" ]; then yarn --link-duplicates --pure-lockfile && yarn build --linux --publish onTag; fi +- | + if [ "$TARGET" == "windows" ]; then + # Remove any special characters before adding to our list of ENVs + # https://github.com/electron-userland/electron-builder/issues/2450#issuecomment-421788155 + ENVS=`env | grep -iE '(DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_|TARGET)([A-Z]|_)*=' | sed -n '/^[^\t]/s/=.*//p' | sed '/^$/d' | sed 's/^/-e /g' | tr '\n' ' '` + docker run $ENVS --rm \ + -v ${PWD}:/project \ + electronuserland/builder:wine \ + /bin/bash -c "env | grep -v '\r' | grep -iE 'DEBUG|TARGET|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_|WIN_' && yarn --link-duplicates --pure-lockfile && yarn build --win --publish onTag"; + fi +- if [ "$TARGET" == "mac" ]; then yarn build --publish onTag; fi +- if [ "$TARGET" == "linux" ]; then yarn --link-duplicates --pure-lockfile && yarn + build --linux --publish onTag; fi addons: artifacts: working_dir: dist paths: - - $(git ls-files -o dist/{*.dmg,*.exe,*.deb} | tr "\n" ":") + - $(git ls-files -o dist/{*.dmg,*.exe,*.deb} | tr "\n" ":") target_paths: - - /app/build-${TRAVIS_BUILD_NUMBER}_commit-${TRAVIS_COMMIT:0:7}$([ ! -z ${TRAVIS_TAG} ] && echo _tag-${TRAVIS_TAG}) + - "/app/build-${TRAVIS_BUILD_NUMBER}_commit-${TRAVIS_COMMIT:0:7}$([ ! -z ${TRAVIS_TAG} + ] && echo _tag-${TRAVIS_TAG})" +env: + global: + - ARTIFACTS_REGION=us-east-1 + - ARTIFACTS_BUCKET=build.lbry.io + - DANGER_GITHUB_API_TOKEN=f8b612049cbda985c251e18ae36c7a2b9c6c92df + - ARTIFACTS_DEBUG=1 + - WIN_CSC_LINK=https://s3.amazonaws.com/files.lbry.io/cert/win-csc-2018-2019-08.p12 + - CSC_LINK=https://s3.amazonaws.com/files.lbry.io/cert/osx-cert.p12 + #AWS_SECRET_ACCESS_KEY + - secure: aF0dZ1ub0r6X5SaTH7YYRpV8cl2gkU2tA7y2tkvGZQL0+Vv2FvkhwaXREa9IM9djm7r3W7I8vEekk6Skn300kIBwQ04COxgu+Oeqjsc2a529wmF9AN5EwKExKkVQFIjMjpMBmd/fE05PD29C0vULdtIMX8uhDe5zFE+tGD1QmwKVZ+55oCxJmt7vsQwQr46s/BsPODrZhvSUPWTUG4HSsc5DJwe/XYY+35vWQsnpCkBVabAZg+lnOpqLioRNzTsBX1oSkqTX9uvOC6fN+NlpbJWetzXsBciFcMHQCx9UHxu+Ibn3W3V8FVYRuFLAinhIg+JoVQHU8mH60ggjizATpgx12riP9e/Uwho5x5bgIYmnIBf4AHqupPv40iC/THMvsS08hCQHicu+/WiAvvN8wNQ1sqpj0+RTreB+4/qdRdwTs70t4jNV83cP+nAuekMCC7uYQ08GLmFNaa+x8NgDZa3JQv7EmXU0ALWVPeJ9UEV+dhEs5iI+7bLEhTUOuvBBWo17F4pst3pfS1j7md1x4wKFRWyP7IFmjK+L1KiAH94c/LIz2Nlp5yCDfbcwU5W/wCHYAxu5c8qnAlkxeub0GpxtZ66j6oOehk1lsHYxaZcBxyn7XhM1A6Mjxgy9OG0WGyYun8iH3aa4WcY0BO21l7je75s7l0qNSRS80st8kUE= + #AWS_ACCESS_KEY_ID + - secure: HHlUGaEjk0qsW9qb/93jdFhJypiDuJ0e6Z4UClW3ciMfdZ22Ur4D0oL0mgVguigTjdkf1Yy4Z1kWtWvtvQ8DBI+mdOl5GTMpX5nMattcuK/4gFXmqz30S3c07IPkilGKkAT64JqRGT7AvQTaV/tXqNNN9BbWCTAJSnhfzYJFvZQjnSmAAQvbqzMWEEein53244fWbDio462T7vzfbEHJD1pVUSrAMrKDgFSrIKJf4GQHceUk+fqWBjAsWN8USboo88JSLXn5qhD3ZczsgtZ2gi2KTvdfrHfZ5SpOzwc2AGr8U1laQzzW8c8xsPJPmM/3dCri/clcXassa4t/+2INYTPhKbmaD9hvjFRRMpoNjMCmPRJOmojxit8tqzCrw4nSGdr3/Bt0JjpLq90mfyL83YYU4gJXF97KzTl0lCy/DFXjBiHA9riRJVaXn3CI6L0i1RLCJI+C/rT9ErFB767t0aQtXlIWSZQ+IqWaoUdp57t0e2EtbRcjK7IYIXLNgZSbR0bY/TS005D/xZkexCpAtcV9F6iqDYP3HbGyiQqC75se0MqpNrZAx1a7WsSnumOcmtuU6LRriwjM0GXEACfGLCp4w4LBzKPwrOOg4QOhewHKgxpUff8KMlfTZ+Z8B7NBNjKSPbxiMedmpsiWpPRy6VeyvwNz32PUpjdsF3tIYMk= + #GH_TOKEN + - secure: g0gSiOmCKFzxLFV//oogmex+MkumCcGJ8CtvLzojAw1qhI1P6dGRH06yuCgBNW4PQGbbrYR2tntGEeq66LZ1bU7BJ2bdkv5lBi15JPH4POpA3OSc88YebitKIyIBI2WHA3VNCkF1JEFvrfTUX+4PjP+9h6vZ9tdfzezNTpc70SrGkWMhaoe+F8u6jCL63VBpnncf0foz3WlqUFNoIdinQLYxI6S2QmHz1p1HToC+MOosBgWDufX2+6B/Efq7JDFZgx/zItFzR1OCafKx8rScUd4woAqK+8mt862IzzEbztTv6IPSdhx/hsbO7rHMcjDFk/MuDeC9TaaXqMvvKmLuvWKH+dy4BvIiJJ/WuDfzumrw+8BvTbcnXFXkEXTlkQnZD0otBZsmI4/OPH5vIGUmVIwBFXjPBcNq/Xwig8hRCxrdioEZPO4C17fMCdOn3gAx+GcMddfAIKnrlG/XK2jL8kCylL7++OEkedzYiHWA7p9eaJDjxYmIxKcfYTyF4VIMYS/wAC5W1zi1AjM5BiUhBGp/Vd+f3UUTOsqWOmS4A8cqhK/rbx4xgdwUuv2DoljeQtWd2xdZFr10L2ErmEIdfdtZBVyauaGOhhXUqpSAB5PAwSRNNWkZFCJXmqMHemih4jlfN/KS+rujh5dbyXz81BPjyxrDAziO4Q19rz7hmVI= + #ARTIFACTS_KEY + - secure: giJRkODdgvMpaLh58c3UKTy6j3IjUVQ2wVvLmECN8caVLuBnEp3U56rUT7sQPzeMoaF9goOLKXD1o+qKdGsLz2Dedi/3vFr3IXTgpYwO03eMkylYUDUwKXV1Fv14ME+wknw/Fshmty1XAc/RwlTaCks9usIRT9OE75HvV8ILVfLdSwwRR/TKFEdEDjAY/3ROVFsleDKBIe1AG/AHeN37Mwqfo39zpN5Zidm3HUegujsMsHtPQtUUCvCxiaiBtqIz79WQuiXjelLWrV53k+ulpptz4T6M8FO5jxBCL5HQMbYy4J8BhukAPHUcTVzarsDe1XEmP6xtClcAydMiHmzUBh9VbvRF8AKwGGO41g6OasgMaJ/eUlACGWTIS2AsqgkXCEgWRtswwI4edLgT/F74fKxGV82zRYLcmjWceGc9H6m2YfIIQZ2PNsDloFgTWa63oZ1sT20fWmNohD3IgVK21rjrCnMbp5/RfBdihR1+GsS8wMA15nhUW3SHfSHKV0009FZdDjxgmigjjmFt1/SM8YGZDijQc7DoUsrScR9n2JR+KVqEED3BYOviw0M0blb6b2dqK0A3+N5C1RGt8Uu8AAuAzdPDviL38m4n1UOdI1Tij1boSfr1USvfipPfQXrqSWHIY3RITMmjPREmcdj2dbhjoUEFaFfp/b3sJxUI3I4= + #ARTIFACTS_SECRET + - secure: RtPES82DBisaCkernDm10u8mzNc8K4EyyTJUCopcFFJHfFpleuJOoXNFZGXixjHXWTxqc5+IjLThBZFG6krTpD4ocftfkBNeVDXL91JsGRW6X4VBpKaJp1UJZZiYjpPZRJDwVuIb570WcX3xXmsvAwwdODkeXgBZoOw+LZIz4HV3OACi5iaWCx4+2WvQiaz/6mRkLlw9iY1m9gCGQXXUSWyY+bGWsOdGWpfp/VfzpX7mBJbcIYjvRYSnBtVUFkEzxHlaR/CFc7Jio45EUImhBKqKV8QGAiv9AowcWqQn8y2SuW8J66bkaZGbGpyEdsRQDJKUU5naRIFJ/002e4XjI2moBfSHHnFTMH/AawA4b+gCjein8jDiRugJw8AqC4KpG660SFJwC4A8IxfPcXWcykahrL6kJhs+NFbyxQEJIMUqP6UAAiPAuUr/Whq1F8x5b39pEibRZ3Vv0Bix/lUj4cWZYRBL1ckoUgSCeG7llkAdnxLuQKfDvTw+HFcWGq7yg/ye7H7CwpBJkSSOlnQ61nuucC82hVqBGoPGbFW3vjS0BqesLcaO7kwW/fdBnIxwLiWWHDWol0aLfQLEzjUzpS+tA1YYKvrG+yNMEeYtHEK9ZcltQD52uEtjK26DIY8tQU8PxMlXha6/XTOlt8MIcl8IPULmVWKT9k8LPTFQiXc= + #CSC_KEY_PASSWORD + - secure: TDKeF7/WGwR+di+JhNp29x7NPzVMO/q+72n58zB5goI1NUKaeKgjeWbfVYx9f+4G9a9/pdDVUfORt8i6OW8ZhhqYS4E8G5F56q+oX3nrjNqM8NqoK09ehZS/wdYGbenG9oTfXYenDdwusZV4Fq0BRRLjqAIXPQCKg3//MKseh/1fHDGVGXpYUimHRSCkwspbbfB/9Qw9KEBjweeXiAwB+5F+E7fPlVtqsIvtkkED1hKe0Z8HdECC6JTZ0ZHPDDFGV3aondXQDgUlfchnZ6HDdNDO5y/hPEj0laiZQ7BssenJ8Z7qjHc5O9AKXfG+6WFICHvtgjQ0+x6rk6gpvJcyI1x2+Kck/s0EcSkFY+Yz81BxdehIeKPn9U8LpGaFbtxsp01661yeaIpAqT/PqFsdj/kFXFT6gwZlGGPMBm2WgQR4A61qkOO1jokqz/z6CnY+MNeE8E1Fh4bFoZ0JwUJFJugoyDahHpVlLw5lZaSipJO6RZ1xjoZ3XGmxvtkM4dQ16xQ++Q8EgD878uCWn2jZ5YTQdKANfXYTKSiQfoEjLeX0T6I6GSdim4ZURjcolmGNMH/3jhISOXj+e2UkLc6jwO4Ek084o6ciJ2JjqEhXvXOCeRJ2I9cf2dEk7CvtbitiDly6XATo2FP4hqNdcNNWyj/jFvuTwFT6hzBqLk1BCBc= + #WIN_CSC_KEY_PASSWORD + - secure: LBwkNMw5paoxpeLcNvAbrSLghLH0kXmgctBkUK6hgqJ/6WReWKlERUCwWQp/NcAB5Nb8fUTV8hXz1UjeQG1HH65geRcxya9sf9xu446e2gRdYGfYk7AhZdIw97IlwTYv2tpnjejiVXIoClQjKRYtOkRC4BIGW+Ydun1hwEUXTVMCplOzPhpLrkJCIDDDdM4eJjWm6ZMshCKRlnt42sNdx63FHBAH72rqBnt2jd+ISIadASnWz0nQlN5aIvXR6cLZfSl8Qt/JDL+LXzWX+pNA4BegCtW3hBoLrW18QdNPxrvPCvKjRCC3LHuv+KEGbzGv/QkXg5f9BKZehIZ/hjYgxysjeHBI8yOfCoyrDafxSIso5ja2TtL9lLpW90gbkbtEFiaL2XOMUbQcwWQ6RShSoLZxgiAl6zUmrq3UtS30OjnjWgMj+WnU2BcgpmpPzU0Hz/fblX6/EnwXhPNRYZvm5JyhVRYLZypLWuV4XsWGuLbUOOrsYi7UvU+xo+3AaxbUW4A6XuQ/DUh0ZVNlpPGcVi7A2tx4/46Wm0oLwQ5NYVukHbEiHWZ4HuKqSionwtgG0T2a6/7JQdH7gb+/86z4YHmK89dd/aE84hBUGTB/pNvvtDBYBE3c60SzL0CFPJZrbz9E0gVhLfsHO+5/VCFIipgM8mYoVzsq+cONKbqUPCg= diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d1de1d17..bc69a40bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Added +* Allow typing of encryption password without clicking entry box ([#1977](https://github.com/lbryio/lbry-desktop/pull/1977)) ### Changed +* Make tooltip smarter ([#1979](https://github.com/lbryio/lbry-desktop/pull/1979)) + +### Fixed +* Invite table cutoff with large number of invites ([#1985](https://github.com/lbryio/lbry-desktop/pull/1985)) + + +## [0.25.1] - 2018-09-18 ### Fixed * Paragraph rendering now properly includes a margin for new paragraphs ([#1939](https://github.com/lbryio/lbry-desktop/pull/1939)) @@ -31,15 +39,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). * 3D File viewer features and performance/memory usage improvements ([#1870](https://github.com/lbryio/lbry-desktop/pull/1870)) * Desktop notification when publish is completed ([#1892](https://github.com/lbryio/lbry-desktop/pull/1892)) * FAQ to Publishing Area ([#1833](https://github.com/lbryio/lbry-desktop/pull/1833)) - * FAQ to wallet security area ([#1917](https://github.com/lbryio/lbry-desktop/pull/1917)) + * FAQ to wallet security area ([#1917](https://github.com/lbryio/lbry-desktop/pull/1917)) + ### Changed * Upgraded LBRY Protocol to [version 0.21.2](https://github.com/lbryio/lbry/releases/tag/v0.21.2) fixing a download bug. - * Searching now shows results by default, including direct lbry:// URL tile + * Searching now shows results by default, including direct lbry:// URL tile ([#1875](https://github.com/lbryio/lbry-desktop/pull/)) * Replaced checkboxes with toggles throughout app ([#1834](https://github.com/lbryio/lbry-desktop/pull/1834)) * Removed price tile when content is Free ([#1845](https://github.com/lbryio/lbry-desktop/pull/1845)) * Pass error message from spee.ch API during thumbnail upload ([#1840](https://github.com/lbryio/lbry-desktop/pull/1840)) * Use router pattern for rendering file viewer ([#1544](https://github.com/lbryio/lbry-desktop/pull/1544)) - * Missing word "to" added to the Bid Help Text (#1854) + * Missing word "to" added to the Bid Help Text ([#1854](https://github.com/lbryio/lbry-desktop/pull/1854)) * Updated to electron@2 ([#1858](https://github.com/lbryio/lbry-desktop/pull/1858)) ### Fixed diff --git a/package.json b/package.json index f34b0e865..502a8cd51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.25.0", + "version": "0.25.1", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "keywords": [ "lbry" @@ -30,7 +30,7 @@ "flow-defs": "flow-typed install", "release": "yarn compile && electron-builder build", "precommit": "lint-staged", - "preinstall": "yarn cache clean lbry-redux", + "preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc", "postinstall": "electron-builder install-app-deps && node build/downloadDaemon.js" }, "dependencies": { @@ -50,7 +50,8 @@ "formik": "^0.10.4", "hast-util-sanitize": "^1.1.2", "keytar": "^4.2.1", - "lbry-redux": "lbryio/lbry-redux#421321a78397251589e5a890f4caa95e79975e2b", + "lbry-redux": "lbryio/lbry-redux#c079b108c3bc4ba2b4fb85fb112b52cfc040c301", + "lbryinc": "lbryio/lbryinc#de7ff055605b02a24821f0f9bab1d206eb7f235d", "localforage": "^1.7.1", "mammoth": "^1.4.6", "mime": "^2.3.1", diff --git a/src/renderer/analytics.js b/src/renderer/analytics.js index 7ab84e448..68454249c 100644 --- a/src/renderer/analytics.js +++ b/src/renderer/analytics.js @@ -1,6 +1,6 @@ // @flow import mixpanel from 'mixpanel-browser'; -import Lbryio from 'lbryio'; +import { Lbryio } from 'lbryinc'; import isDev from 'electron-is-dev'; if (isDev) { diff --git a/src/renderer/app.js b/src/renderer/app.js index 9933b47eb..6d93deb25 100644 --- a/src/renderer/app.js +++ b/src/renderer/app.js @@ -38,4 +38,7 @@ global.__ = i18n.__; global.__n = i18n.__n; global.app = app; +// Lbryinc needs access to the redux store for dispatching auth-releated actions +global.store = app.store; + export default app; diff --git a/src/renderer/component/address/view.jsx b/src/renderer/component/address/view.jsx index f1c411c3b..aa252f920 100644 --- a/src/renderer/component/address/view.jsx +++ b/src/renderer/component/address/view.jsx @@ -4,9 +4,14 @@ import { clipboard } from 'electron'; import { FormRow } from 'component/common/form'; import Button from 'component/button'; import * as icons from 'constants/icons'; - +/* +noSnackbar added due to issue 1945 +https://github.com/lbryio/lbry-desktop/issues/1945 +"Snackbars and modals can't be displayed at the same time" +*/ type Props = { address: string, + noSnackbar: boolean, doNotify: ({ message: string, displayType: Array }) => void, }; @@ -20,7 +25,7 @@ export default class Address extends React.PureComponent { input: ?HTMLInputElement; render() { - const { address, doNotify } = this.props; + const { address, doNotify, noSnackbar } = this.props; return ( @@ -43,10 +48,12 @@ export default class Address extends React.PureComponent { icon={icons.CLIPBOARD} onClick={() => { clipboard.writeText(address); - doNotify({ - message: __('Address copied'), - displayType: ['snackbar'], - }); + if (!noSnackbar) { + doNotify({ + message: __('Address copied'), + displayType: ['snackbar'], + }); + } }} /> diff --git a/src/renderer/component/app/index.js b/src/renderer/component/app/index.js index d78487976..e68f66443 100644 --- a/src/renderer/component/app/index.js +++ b/src/renderer/component/app/index.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux'; import { selectPageTitle, selectHistoryIndex, selectActiveHistoryEntry } from 'lbry-redux'; import { doRecordScroll } from 'redux/actions/navigation'; -import { selectUser } from 'redux/selectors/user'; +import { selectUser } from 'lbryinc'; import { doAlertError } from 'redux/actions/app'; import App from './view'; @@ -17,4 +17,7 @@ const perform = dispatch => ({ recordScroll: scrollPosition => dispatch(doRecordScroll(scrollPosition)), }); -export default connect(select, perform)(App); +export default connect( + select, + perform +)(App); diff --git a/src/renderer/component/cardVerify/index.js b/src/renderer/component/cardVerify/index.js index 390622f3d..5a770a0e1 100644 --- a/src/renderer/component/cardVerify/index.js +++ b/src/renderer/component/cardVerify/index.js @@ -1,12 +1,14 @@ -import React from 'react'; import { connect } from 'react-redux'; -import { selectUserEmail } from 'redux/selectors/user'; +import { selectUserEmail } from 'lbryinc'; import CardVerify from './view'; const select = state => ({ email: selectUserEmail(state), }); -const perform = dispatch => ({}); +const perform = () => ({}); -export default connect(select, perform)(CardVerify); +export default connect( + select, + perform +)(CardVerify); diff --git a/src/renderer/component/channelTile/view.jsx b/src/renderer/component/channelTile/view.jsx index 4b3c0f744..304bdd663 100644 --- a/src/renderer/component/channelTile/view.jsx +++ b/src/renderer/component/channelTile/view.jsx @@ -79,7 +79,7 @@ class ChannelTile extends React.PureComponent { 'card__title--large': size === 'large', })} > - {channelName || uri} +
{ + constructor(props) { + super(props); + this.input = React.createRef(); + } + + componentDidMount() { + const { autoFocus } = this.props; + const input = this.input.current; + + if (input && autoFocus) { + input.focus(); + } + } + render() { const { render, @@ -40,6 +58,7 @@ export class FormField extends React.PureComponent { children, stretch, affixClass, + autoFocus, ...inputProps } = this.props; @@ -79,7 +98,7 @@ export class FormField extends React.PureComponent { } else if (type === 'checkbox') { input = ; } else { - input = ; + input = ; } } diff --git a/src/renderer/component/common/form-components/form-row.jsx b/src/renderer/component/common/form-components/form-row.jsx index 79f1b5cd0..e0d0959e1 100644 --- a/src/renderer/component/common/form-components/form-row.jsx +++ b/src/renderer/component/common/form-components/form-row.jsx @@ -9,6 +9,7 @@ type Props = { verticallyCentered?: boolean, stretch?: boolean, alignRight?: boolean, + centered?: boolean, }; export class FormRow extends React.PureComponent { @@ -17,7 +18,7 @@ export class FormRow extends React.PureComponent { }; render() { - const { children, padded, verticallyCentered, stretch, alignRight } = this.props; + const { children, padded, verticallyCentered, stretch, alignRight, centered } = this.props; return (
{ 'form-row--vertically-centered': verticallyCentered, 'form-row--stretch': stretch, 'form-row--right': alignRight, + 'form-row--centered': centered, })} > {children} diff --git a/src/renderer/component/common/icon.jsx b/src/renderer/component/common/icon.jsx index c331012e5..01db48c41 100644 --- a/src/renderer/component/common/icon.jsx +++ b/src/renderer/component/common/icon.jsx @@ -8,6 +8,7 @@ import Tooltip from 'component/common/tooltip'; // These are copied from `scss/vars`, can they both come from the same source? const RED_COLOR = '#e2495e'; const GREEN_COLOR = '#44b098'; +const BLUE_COLOR = '#49b2e2'; type Props = { icon: string, @@ -33,6 +34,8 @@ class IconComponent extends React.PureComponent { return RED_COLOR; case 'green': return GREEN_COLOR; + case 'blue': + return BLUE_COLOR; default: return undefined; } diff --git a/src/renderer/component/common/tooltip.jsx b/src/renderer/component/common/tooltip.jsx index 811f09fef..c54318098 100644 --- a/src/renderer/component/common/tooltip.jsx +++ b/src/renderer/component/common/tooltip.jsx @@ -16,8 +16,67 @@ class ToolTip extends React.PureComponent { direction: 'bottom', }; + constructor(props) { + super(props); + this.tooltip = React.createRef(); + this.state = { + direction: this.props.direction, + }; + } + + componentDidMount() { + this.handleVisibility(); + } + + getVisibility = () => { + const node = this.tooltip.current; + const rect = node.getBoundingClientRect(); + + // Get parent-container + const viewport = document.getElementById('content'); + + const visibility = { + top: rect.top >= 0, + left: rect.left >= 0, + right: rect.right <= viewport.offsetWidth, + bottom: rect.bottom <= viewport.offsetHeight, + }; + + return visibility; + }; + + invertDirection = () => { + // Get current direction + const { direction } = this.state; + // Inverted directions + const directions = { + top: 'bottom', + left: 'right', + right: 'left', + bottom: 'top', + }; + + const inverted = directions[direction]; + + // Update direction + if (inverted) { + this.setState({ direction: inverted }); + } + }; + + handleVisibility = () => { + const { direction } = this.state; + const visibility = this.getVisibility(); + + // Invert direction if tooltip is outside viewport bounds + if (!visibility[direction]) { + this.invertDirection(); + } + }; + render() { - const { children, label, body, icon, direction, onComponent } = this.props; + const { direction } = this.state; + const { children, label, body, icon, onComponent } = this.props; const tooltipContent = children || label; const bodyLength = body.length; @@ -25,6 +84,8 @@ class ToolTip extends React.PureComponent { return ( { > {tooltipContent} ( - - {props.children} + + {props.text} ); diff --git a/src/renderer/component/fileCard/index.js b/src/renderer/component/fileCard/index.js index 3754e3ffa..95a7f9a67 100644 --- a/src/renderer/component/fileCard/index.js +++ b/src/renderer/component/fileCard/index.js @@ -8,7 +8,10 @@ import { makeSelectClaimIsMine, } from 'lbry-redux'; import { doNavigate } from 'redux/actions/navigation'; -import { selectRewardContentClaimIds } from 'redux/selectors/content'; +import { + selectRewardContentClaimIds, + makeSelectContentPositionForUri, +} from 'redux/selectors/content'; import { selectShowNsfw } from 'redux/selectors/settings'; import { selectPendingPublish } from 'redux/selectors/publish'; import FileCard from './view'; @@ -32,12 +35,14 @@ const select = (state, props) => { rewardedContentClaimIds: selectRewardContentClaimIds(state, props), ...fileCardInfo, pending: !!pendingPublish, + position: makeSelectContentPositionForUri(props.uri)(state), }; }; const perform = dispatch => ({ navigate: (path, params) => dispatch(doNavigate(path, params)), resolveUri: uri => dispatch(doResolveUri(uri)), + clearHistoryUri: uri => dispatch(doClearContentHistoryUri(uri)), }); export default connect( diff --git a/src/renderer/component/fileCard/view.jsx b/src/renderer/component/fileCard/view.jsx index f8dd8582c..1ca015b89 100644 --- a/src/renderer/component/fileCard/view.jsx +++ b/src/renderer/component/fileCard/view.jsx @@ -5,13 +5,12 @@ import type { Claim, Metadata } from 'types/claim'; import CardMedia from 'component/cardMedia'; import TruncatedText from 'component/common/truncated-text'; import Icon from 'component/common/icon'; -import FilePrice from 'component/filePrice'; import UriIndicator from 'component/uriIndicator'; import * as icons from 'constants/icons'; import classnames from 'classnames'; -import { openCopyLinkMenu } from '../../util/contextMenu'; +import FilePrice from 'component/filePrice'; +import { openCopyLinkMenu } from 'util/contextMenu'; -// TODO: iron these out type Props = { uri: string, claim: ?Claim, @@ -21,11 +20,11 @@ type Props = { rewardedContentClaimIds: Array, obscureNsfw: boolean, claimIsMine: boolean, - showPrice: boolean, pending?: boolean, /* eslint-disable react/no-unused-prop-types */ resolveUri: string => void, isResolvingUri: boolean, + showPrice: boolean, /* eslint-enable react/no-unused-prop-types */ }; @@ -59,8 +58,8 @@ class FileCard extends React.PureComponent { rewardedContentClaimIds, obscureNsfw, claimIsMine, - showPrice, pending, + showPrice, } = this.props; const shouldHide = !claimIsMine && !pending && obscureNsfw && metadata && metadata.nsfw; @@ -94,7 +93,7 @@ class FileCard extends React.PureComponent {
- {title} +
{pending ?
Pending...
: } diff --git a/src/renderer/component/fileDownloadLink/index.js b/src/renderer/component/fileDownloadLink/index.js index 23ed6b3f7..6d4334e01 100644 --- a/src/renderer/component/fileDownloadLink/index.js +++ b/src/renderer/component/fileDownloadLink/index.js @@ -7,8 +7,7 @@ import { makeSelectClaimForUri, } from 'lbry-redux'; import { doOpenFileInShell } from 'redux/actions/file'; -import { doPurchaseUri, doStartDownload } from 'redux/actions/content'; -import { doPause } from 'redux/actions/media'; +import { doPurchaseUri, doStartDownload, doSetPlayingUri } from 'redux/actions/content'; import FileDownloadLink from './view'; const select = (state, props) => ({ @@ -24,7 +23,7 @@ const perform = dispatch => ({ openInShell: path => dispatch(doOpenFileInShell(path)), purchaseUri: uri => dispatch(doPurchaseUri(uri)), restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)), - doPause: () => dispatch(doPause()), + pause: () => dispatch(doSetPlayingUri(null)), }); export default connect( diff --git a/src/renderer/component/fileDownloadLink/view.jsx b/src/renderer/component/fileDownloadLink/view.jsx index bcb352eb2..cc58d657b 100644 --- a/src/renderer/component/fileDownloadLink/view.jsx +++ b/src/renderer/component/fileDownloadLink/view.jsx @@ -22,7 +22,7 @@ type Props = { restartDownload: (string, number) => void, openInShell: string => void, purchaseUri: string => void, - doPause: () => void, + pause: () => void, }; class FileDownloadLink extends React.PureComponent { @@ -50,14 +50,14 @@ class FileDownloadLink extends React.PureComponent { purchaseUri, costInfo, loading, - doPause, + pause, claim, } = this.props; const openFile = () => { if (fileInfo) { openInShell(fileInfo.download_path); - doPause(); + pause(); } }; diff --git a/src/renderer/component/fileList/view.jsx b/src/renderer/component/fileList/view.jsx index 9ab572663..b7863547c 100644 --- a/src/renderer/component/fileList/view.jsx +++ b/src/renderer/component/fileList/view.jsx @@ -145,7 +145,8 @@ class FileList extends React.PureComponent { name: claimName, claim_name: claimNameDownloaded, claim_id: claimId, - outpoint, + txid, + nout, } = fileInfo; const uriParams = {}; @@ -155,6 +156,7 @@ class FileList extends React.PureComponent { uriParams.contentName = name; uriParams.claimId = claimId; const uri = buildURI(uriParams); + const outpoint = `${txid}:${nout}`; // See https://github.com/lbryio/lbry-desktop/issues/1327 for discussion around using outpoint as the key content.push(); diff --git a/src/renderer/component/fileTile/view.jsx b/src/renderer/component/fileTile/view.jsx index 100d90f55..bfc650d71 100644 --- a/src/renderer/component/fileTile/view.jsx +++ b/src/renderer/component/fileTile/view.jsx @@ -127,7 +127,7 @@ class FileTile extends React.PureComponent { 'card__title--large': size === 'large', })} > - {title || name} +
{ 'card__subtext--large': size === 'large', })} > - {description} +
)} {!name && ( diff --git a/src/renderer/component/fileViewer/index.js b/src/renderer/component/fileViewer/index.js index c21e5f5c1..56e7fb69b 100644 --- a/src/renderer/component/fileViewer/index.js +++ b/src/renderer/component/fileViewer/index.js @@ -2,9 +2,8 @@ import { connect } from 'react-redux'; import * as settings from 'constants/settings'; import { doChangeVolume } from 'redux/actions/app'; import { selectVolume } from 'redux/selectors/app'; -import { doPlayUri, doSetPlayingUri } from 'redux/actions/content'; -import { doPlay, doPause, savePosition } from 'redux/actions/media'; -import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards'; +import { doPlayUri, doSetPlayingUri, savePosition } from 'redux/actions/content'; +import { doClaimEligiblePurchaseRewards } from 'lbryinc'; import { makeSelectMetadataForUri, makeSelectContentTypeForUri, @@ -16,8 +15,7 @@ import { selectSearchBarFocused, } from 'lbry-redux'; import { makeSelectClientSetting, selectShowNsfw } from 'redux/selectors/settings'; -import { selectMediaPaused, makeSelectMediaPositionForUri } from 'redux/selectors/media'; -import { selectPlayingUri } from 'redux/selectors/content'; +import { selectPlayingUri, makeSelectContentPositionForUri } from 'redux/selectors/content'; import { selectFileInfoErrors } from 'redux/selectors/file_info'; import FileViewer from './view'; @@ -32,8 +30,7 @@ const select = (state, props) => ({ playingUri: selectPlayingUri(state), contentType: makeSelectContentTypeForUri(props.uri)(state), volume: selectVolume(state), - mediaPaused: selectMediaPaused(state), - mediaPosition: makeSelectMediaPositionForUri(props.uri)(state), + position: makeSelectContentPositionForUri(props.uri)(state), autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state), searchBarFocused: selectSearchBarFocused(state), fileInfoErrors: selectFileInfoErrors(state), @@ -43,10 +40,9 @@ const perform = dispatch => ({ play: uri => dispatch(doPlayUri(uri)), cancelPlay: () => dispatch(doSetPlayingUri(null)), changeVolume: volume => dispatch(doChangeVolume(volume)), - doPlay: () => dispatch(doPlay()), - doPause: () => dispatch(doPause()), - savePosition: (claimId, position) => dispatch(savePosition(claimId, position)), claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()), + savePosition: (claimId, outpoint, position) => + dispatch(savePosition(claimId, outpoint, position)), }); export default connect( diff --git a/src/renderer/component/fileViewer/internal/player.jsx b/src/renderer/component/fileViewer/internal/player.jsx index d76d0bcaf..10b47fd0e 100644 --- a/src/renderer/component/fileViewer/internal/player.jsx +++ b/src/renderer/component/fileViewer/internal/player.jsx @@ -27,9 +27,11 @@ class MediaPlayer extends React.PureComponent { this.toggleFullScreenVideo = this.toggleFullScreen.bind(this); } - componentWillReceiveProps(nextProps) { + componentDidUpdate(nextProps) { const el = this.refs.media.children[0]; - if (!this.props.paused && nextProps.paused && !el.paused) el.pause(); + if (this.props.playingUri && !nextProps.playingUri && !el.paused) { + el.pause(); + } } componentDidMount() { @@ -86,11 +88,15 @@ class MediaPlayer extends React.PureComponent { document.addEventListener('keydown', this.togglePlayListener); const mediaElement = this.media.children[0]; if (mediaElement) { - mediaElement.currentTime = position || 0; - mediaElement.addEventListener('play', () => this.props.doPlay()); - mediaElement.addEventListener('pause', () => this.props.doPause()); + if (position) { + mediaElement.currentTime = position; + } mediaElement.addEventListener('timeupdate', () => - this.props.savePosition(claim.claim_id, mediaElement.currentTime) + this.props.savePosition( + claim.claim_id, + `${claim.txid}:${claim.nout}`, + mediaElement.currentTime + ) ); mediaElement.addEventListener('click', this.togglePlayListener); mediaElement.addEventListener('loadedmetadata', loadedMetadata.bind(this), { @@ -136,7 +142,6 @@ class MediaPlayer extends React.PureComponent { if (mediaElement) { mediaElement.removeEventListener('click', this.togglePlayListener); } - this.props.doPause(); } toggleFullScreen(event) { diff --git a/src/renderer/component/fileViewer/view.jsx b/src/renderer/component/fileViewer/view.jsx index 9f28ab0e9..e16a438b7 100644 --- a/src/renderer/component/fileViewer/view.jsx +++ b/src/renderer/component/fileViewer/view.jsx @@ -34,11 +34,8 @@ type Props = { volume: number, claim: Claim, uri: string, - doPlay: () => void, - doPause: () => void, - savePosition: (string, number) => void, - mediaPaused: boolean, - mediaPosition: ?number, + savePosition: (string, string, number) => void, + position: ?number, className: ?string, obscureNsfw: boolean, play: string => void, @@ -202,11 +199,8 @@ class FileViewer extends React.PureComponent { volume, claim, uri, - doPlay, - doPause, savePosition, - mediaPaused, - mediaPosition, + position, className, obscureNsfw, mediaType, @@ -251,14 +245,12 @@ class FileViewer extends React.PureComponent { downloadCompleted={fileInfo.completed} changeVolume={changeVolume} volume={volume} - doPlay={doPlay} - doPause={doPause} savePosition={savePosition} claim={claim} uri={uri} - paused={mediaPaused} - position={mediaPosition} + position={position} startedPlayingCb={this.startedPlayingCb} + playingUri={playingUri} /> )}
diff --git a/src/renderer/component/inviteList/index.js b/src/renderer/component/inviteList/index.js index 7dca63af5..d8df4a4b3 100644 --- a/src/renderer/component/inviteList/index.js +++ b/src/renderer/component/inviteList/index.js @@ -1,6 +1,5 @@ -import React from 'react'; import { connect } from 'react-redux'; -import { selectUserInvitees, selectUserInviteStatusIsPending } from 'redux/selectors/user'; +import { selectUserInvitees, selectUserInviteStatusIsPending } from 'lbryinc'; import InviteList from './view'; const select = state => ({ @@ -8,6 +7,9 @@ const select = state => ({ isPending: selectUserInviteStatusIsPending(state), }); -const perform = dispatch => ({}); +const perform = () => ({}); -export default connect(select, perform)(InviteList); +export default connect( + select, + perform +)(InviteList); diff --git a/src/renderer/component/inviteList/view.jsx b/src/renderer/component/inviteList/view.jsx index 7874e9251..8d98f94d9 100644 --- a/src/renderer/component/inviteList/view.jsx +++ b/src/renderer/component/inviteList/view.jsx @@ -1,10 +1,20 @@ +// @flow import React from 'react'; import Icon from 'component/common/icon'; import RewardLink from 'component/rewardLink'; -import rewards from 'rewards.js'; +import { rewards } from 'lbryinc'; import * as icons from 'constants/icons'; -class InviteList extends React.PureComponent { +type Props = { + invitees: ?Array<{ + email: string, + invite_accepted: boolean, + invite_reward_claimed: boolean, + invite_reward_claimable: boolean, + }>, +}; + +class InviteList extends React.PureComponent { render() { const { invitees } = this.props; @@ -31,8 +41,8 @@ class InviteList extends React.PureComponent { - {invitees.map((invitee, index) => ( - + {invitees.map(invitee => ( + {invitee.email} {invitee.invite_accepted ? ( diff --git a/src/renderer/component/inviteNew/index.js b/src/renderer/component/inviteNew/index.js index bfb65ab39..f8f527b93 100644 --- a/src/renderer/component/inviteNew/index.js +++ b/src/renderer/component/inviteNew/index.js @@ -3,8 +3,8 @@ import { selectUserInvitesRemaining, selectUserInviteNewIsPending, selectUserInviteNewErrorMessage, -} from 'redux/selectors/user'; -import { doUserInviteNew } from 'redux/actions/user'; + doUserInviteNew, +} from 'lbryinc'; import InviteNew from './view'; const select = state => ({ @@ -17,4 +17,7 @@ const perform = dispatch => ({ inviteNew: email => dispatch(doUserInviteNew(email)), }); -export default connect(select, perform)(InviteNew); +export default connect( + select, + perform +)(InviteNew); diff --git a/src/renderer/component/page/index.js b/src/renderer/component/page/index.js index f9111f85e..1cbffa8ce 100644 --- a/src/renderer/component/page/index.js +++ b/src/renderer/component/page/index.js @@ -4,11 +4,10 @@ import { selectPageTitle, selectIsBackDisabled, selectIsForwardDisabled, - selectNavLinks, } from 'lbry-redux'; import { doNavigate, doHistoryBack, doHistoryForward } from 'redux/actions/navigation'; import { doDownloadUpgrade } from 'redux/actions/app'; -import { selectIsUpgradeAvailable } from 'redux/selectors/app'; +import { selectIsUpgradeAvailable, selectNavLinks } from 'redux/selectors/app'; import { formatCredits } from 'util/formatCredits'; import Page from './view'; @@ -28,4 +27,7 @@ const perform = dispatch => ({ downloadUpgrade: () => dispatch(doDownloadUpgrade()), }); -export default connect(select, perform)(Page); +export default connect( + select, + perform +)(Page); diff --git a/src/renderer/component/publishForm/internal/bid-help-text.jsx b/src/renderer/component/publishForm/internal/bid-help-text.jsx index 9230b8e73..f13b78e33 100644 --- a/src/renderer/component/publishForm/internal/bid-help-text.jsx +++ b/src/renderer/component/publishForm/internal/bid-help-text.jsx @@ -1,72 +1,36 @@ // @flow import * as React from 'react'; -import Button from 'component/button'; -import { buildURI } from 'lbry-redux'; -import type { Claim } from 'types/claim'; type Props = { uri: ?string, isResolvingUri: boolean, - winningBidForClaimUri: ?number, - myClaimForUri: ?Claim, - isStillEditing: boolean, - onEditMyClaim: (any, string) => void, + amountNeededForTakeover: ?number, }; class BidHelpText extends React.PureComponent { render() { - const { - uri, - isResolvingUri, - winningBidForClaimUri, - myClaimForUri, - onEditMyClaim, - isStillEditing, - } = this.props; + const { uri, isResolvingUri, amountNeededForTakeover } = this.props; + let bidHelpText; - if (!uri) { - return __('Create a URL for this content.'); + if (uri) { + if (isResolvingUri) { + bidHelpText = __('Checking the winning claim amount...'); + } else if (!amountNeededForTakeover) { + bidHelpText = __('Any amount will give you the winning bid.'); + } else { + bidHelpText = `${__('If you bid more than')} ${amountNeededForTakeover} LBC, ${__( + 'when someone navigates to' + )} ${uri} ${__('it will load your published content')}. ${__( + 'However, you can get a longer version of this URL for any bid' + )}.`; + } } - if (isStillEditing) { - return __( - 'You are currently editing this claim. If you change the URL, you will need to reselect a file.' - ); - } - - if (isResolvingUri) { - return __('Checking the winning claim amount...'); - } - - if (myClaimForUri) { - const editUri = buildURI({ - contentName: myClaimForUri.name, - claimId: myClaimForUri.claim_id, - }); - - return ( - - {__('You already have a claim at')} - {` ${uri} `} -
{(filePath || !!editingURI) && (
@@ -420,12 +428,12 @@ class PublishForm extends React.PureComponent { {uploadThumbnailStatus === THUMBNAIL_STATUSES.API_DOWN ? ( __('Enter a URL for your thumbnail.') ) : ( - - {__('Upload your thumbnail (.png/.jpg/.jpeg/.gif) to')}{' '} -
{ { onChange={event => this.handleNameChange(event.target.value)} error={nameError} helper={ - @@ -534,8 +540,14 @@ class PublishForm extends React.PureComponent { min="0" disabled={!name} onChange={event => this.handleBidChange(parseFloat(event.target.value))} - helper={__('This LBC remains yours and the deposit can be undone at any time.')} placeholder={winningBidForClaimUri ? winningBidForClaimUri + 0.1 : 0.1} + helper={ + + } />
diff --git a/src/renderer/component/rewardLink/index.js b/src/renderer/component/rewardLink/index.js index f7e3561ef..7eea35460 100644 --- a/src/renderer/component/rewardLink/index.js +++ b/src/renderer/component/rewardLink/index.js @@ -3,9 +3,10 @@ import { makeSelectClaimRewardError, makeSelectRewardByType, makeSelectIsRewardClaimPending, -} from 'redux/selectors/rewards'; + doClaimRewardType, + doClaimRewardClearError, +} from 'lbryinc'; import { doNavigate } from 'redux/actions/navigation'; -import { doClaimRewardType, doClaimRewardClearError } from 'redux/actions/rewards'; import RewardLink from './view'; const makeSelect = () => { @@ -23,9 +24,12 @@ const makeSelect = () => { }; const perform = dispatch => ({ - claimReward: reward => dispatch(doClaimRewardType(reward.reward_type, true)), + claimReward: reward => dispatch(doClaimRewardType(reward.reward_type)), clearError: reward => dispatch(doClaimRewardClearError(reward)), navigate: path => dispatch(doNavigate(path)), }); -export default connect(makeSelect, perform)(RewardLink); +export default connect( + makeSelect, + perform +)(RewardLink); diff --git a/src/renderer/component/rewardListClaimed/index.js b/src/renderer/component/rewardListClaimed/index.js index 212341cda..1bab4ea82 100644 --- a/src/renderer/component/rewardListClaimed/index.js +++ b/src/renderer/component/rewardListClaimed/index.js @@ -1,10 +1,12 @@ -import React from 'react'; import { connect } from 'react-redux'; -import { selectClaimedRewards } from 'redux/selectors/rewards'; +import { selectClaimedRewards } from 'lbryinc'; import RewardListClaimed from './view'; const select = state => ({ rewards: selectClaimedRewards(state), }); -export default connect(select, null)(RewardListClaimed); +export default connect( + select, + null +)(RewardListClaimed); diff --git a/src/renderer/component/rewardSummary/index.js b/src/renderer/component/rewardSummary/index.js index 331825385..8aaea1384 100644 --- a/src/renderer/component/rewardSummary/index.js +++ b/src/renderer/component/rewardSummary/index.js @@ -1,6 +1,5 @@ import { connect } from 'react-redux'; -import { selectUnclaimedRewardValue, selectFetchingRewards } from 'redux/selectors/rewards'; -import { doRewardList } from 'redux/actions/rewards'; +import { selectUnclaimedRewardValue, selectFetchingRewards, doRewardList } from 'lbryinc'; import { doFetchRewardedContent } from 'redux/actions/content'; import RewardSummary from './view'; diff --git a/src/renderer/component/rewardTile/index.js b/src/renderer/component/rewardTile/index.js index ade2a1db9..5dc6eedd4 100644 --- a/src/renderer/component/rewardTile/index.js +++ b/src/renderer/component/rewardTile/index.js @@ -1,5 +1,12 @@ -import React from 'react'; import { connect } from 'react-redux'; +import { MODALS, doNotify } from 'lbry-redux'; import RewardTile from './view'; -export default connect(null, null)(RewardTile); +const perform = dispatch => ({ + openRewardCodeModal: () => dispatch(doNotify({ id: MODALS.REWARD_GENERATED_CODE })), +}); + +export default connect( + null, + perform +)(RewardTile); diff --git a/src/renderer/component/rewardTile/view.jsx b/src/renderer/component/rewardTile/view.jsx index 743d5161e..0bacd52ac 100644 --- a/src/renderer/component/rewardTile/view.jsx +++ b/src/renderer/component/rewardTile/view.jsx @@ -3,10 +3,11 @@ import React from 'react'; import Icon from 'component/common/icon'; import RewardLink from 'component/rewardLink'; import Button from 'component/button'; -import rewards from 'rewards'; +import { rewards } from 'lbryinc'; import * as icons from 'constants/icons'; type Props = { + openRewardCodeModal: () => void, reward: { id: string, reward_title: string, @@ -19,7 +20,7 @@ type Props = { }; const RewardTile = (props: Props) => { - const { reward } = props; + const { reward, openRewardCodeModal } = props; const claimed = !!reward.transaction_id; return ( @@ -27,6 +28,9 @@ const RewardTile = (props: Props) => {
{reward.reward_title}
{reward.reward_description}
+ {reward.reward_type === rewards.TYPE_GENERATED_CODE && ( +
+
+
+ + ); + } +} + +export default SocialShare; diff --git a/src/renderer/component/transactionList/index.js b/src/renderer/component/transactionList/index.js index f87371f4d..fde907969 100644 --- a/src/renderer/component/transactionList/index.js +++ b/src/renderer/component/transactionList/index.js @@ -1,8 +1,7 @@ import { connect } from 'react-redux'; -import { selectClaimedRewardsByTransactionId } from 'redux/selectors/rewards'; +import { selectClaimedRewardsByTransactionId } from 'lbryinc'; import { doNavigate } from 'redux/actions/navigation'; -import { doNotify } from 'lbry-redux'; -import { selectAllMyClaimsByOutpoint } from 'lbry-redux'; +import { selectAllMyClaimsByOutpoint, doNotify } from 'lbry-redux'; import TransactionList from './view'; const select = state => ({ @@ -15,4 +14,7 @@ const perform = dispatch => ({ openModal: (modal, props) => dispatch(doNotify(modal, props)), }); -export default connect(select, perform)(TransactionList); +export default connect( + select, + perform +)(TransactionList); diff --git a/src/renderer/component/userEmailNew/index.js b/src/renderer/component/userEmailNew/index.js index 0228a4aaa..b15793b6b 100644 --- a/src/renderer/component/userEmailNew/index.js +++ b/src/renderer/component/userEmailNew/index.js @@ -1,7 +1,5 @@ -import React from 'react'; import { connect } from 'react-redux'; -import { doUserEmailNew, doUserInviteNew } from 'redux/actions/user'; -import { selectEmailNewIsPending, selectEmailNewErrorMessage } from 'redux/selectors/user'; +import { selectEmailNewIsPending, selectEmailNewErrorMessage, doUserEmailNew } from 'lbryinc'; import UserEmailNew from './view'; const select = state => ({ @@ -13,4 +11,7 @@ const perform = dispatch => ({ addUserEmail: email => dispatch(doUserEmailNew(email)), }); -export default connect(select, perform)(UserEmailNew); +export default connect( + select, + perform +)(UserEmailNew); diff --git a/src/renderer/component/userEmailNew/view.jsx b/src/renderer/component/userEmailNew/view.jsx index e6485811f..47553e476 100644 --- a/src/renderer/component/userEmailNew/view.jsx +++ b/src/renderer/component/userEmailNew/view.jsx @@ -47,7 +47,7 @@ class UserEmailNew extends React.PureComponent {

{__("We'll never sell your email, and you can unsubscribe at any time.")}

- + { {cancelButton} +
+ {`${__('Your email may be used to sync usage data across devices.')} `} +
); diff --git a/src/renderer/component/userEmailVerify/index.js b/src/renderer/component/userEmailVerify/index.js index 886a0318a..6ac91cd13 100644 --- a/src/renderer/component/userEmailVerify/index.js +++ b/src/renderer/component/userEmailVerify/index.js @@ -1,14 +1,12 @@ import { connect } from 'react-redux'; -import { - doUserEmailVerify, - doUserEmailVerifyFailure, - doUserResendVerificationEmail, -} from 'redux/actions/user'; import { selectEmailVerifyIsPending, selectEmailToVerify, selectEmailVerifyErrorMessage, -} from 'redux/selectors/user'; + doUserEmailVerify, + doUserEmailVerifyFailure, + doUserResendVerificationEmail, +} from 'lbryinc'; import UserEmailVerify from './view'; const select = state => ({ @@ -23,4 +21,7 @@ const perform = dispatch => ({ resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)), }); -export default connect(select, perform)(UserEmailVerify); +export default connect( + select, + perform +)(UserEmailVerify); diff --git a/src/renderer/component/userEmailVerify/view.jsx b/src/renderer/component/userEmailVerify/view.jsx index 95c9b53e5..07429a2c4 100644 --- a/src/renderer/component/userEmailVerify/view.jsx +++ b/src/renderer/component/userEmailVerify/view.jsx @@ -56,7 +56,7 @@ class UserEmailVerify extends React.PureComponent { return (

Please enter the verification code emailed to {email}.

- + { + const paramPage = Number(makeSelectCurrentParam('page')(state) || 0); + return { + pageCount: selectHistoryPageCount(state), + page: paramPage, + params: selectCurrentParams(state), + history: makeSelectHistoryForPage(paramPage)(state), + }; +}; + +const perform = dispatch => ({ + navigate: (path, params) => dispatch(doNavigate(path, params)), + clearHistoryUri: uri => dispatch(doClearContentHistoryUri(uri)), + +}); + +export default connect( + select, + perform +)(UserHistory); diff --git a/src/renderer/component/userHistory/view.jsx b/src/renderer/component/userHistory/view.jsx new file mode 100644 index 000000000..ae8f4b532 --- /dev/null +++ b/src/renderer/component/userHistory/view.jsx @@ -0,0 +1,167 @@ +// @flow +import * as React from 'react'; +import Button from 'component/button'; +import { FormField, FormRow } from 'component/common/form'; +import ReactPaginate from 'react-paginate'; +import UserHistoryItem from 'component/userHistoryItem'; + +type HistoryItem = { + uri: string, + lastViewed: number, +}; + +type Props = { + history: Array, + page: number, + pageCount: number, + navigate: (string, {}) => void, + clearHistoryUri: string => void, + params: { page: number }, +}; + +type State = { + itemsSelected: {}, +}; + +class UserHistoryPage extends React.PureComponent { + constructor() { + super(); + + this.state = { + itemsSelected: {}, + }; + + (this: any).selectAll = this.selectAll.bind(this); + (this: any).unselectAll = this.unselectAll.bind(this); + (this: any).removeSelected = this.removeSelected.bind(this); + } + + onSelect(uri: string) { + const { itemsSelected } = this.state; + + const newItemsSelected = { ...itemsSelected }; + if (itemsSelected[uri]) { + delete newItemsSelected[uri]; + } else { + newItemsSelected[uri] = true; + } + + this.setState({ + itemsSelected: { ...newItemsSelected }, + }); + } + + changePage(pageNumber: number) { + const { params } = this.props; + const newParams = { ...params, page: pageNumber }; + this.props.navigate('/user_history', newParams); + } + + paginate(e: SyntheticKeyboardEvent<*>) { + const pageFromInput = Number(e.currentTarget.value); + if ( + pageFromInput && + e.keyCode === 13 && + !Number.isNaN(pageFromInput) && + pageFromInput > 0 && + pageFromInput <= this.props.pageCount + ) { + this.changePage(pageFromInput); + } + } + + selectAll() { + const { history } = this.props; + const newSelectedState = {}; + history.forEach(({ uri }) => (newSelectedState[uri] = true)); + this.setState({ itemsSelected: newSelectedState }); + } + + unselectAll() { + this.setState({ + itemsSelected: {}, + }); + } + + removeSelected() { + const { clearHistoryUri } = this.props; + const { itemsSelected } = this.state; + + Object.keys(itemsSelected).forEach(uri => clearHistoryUri(uri)); + this.setState({ + itemsSelected: {}, + }); + } + + render() { + const { history, page, pageCount } = this.props; + const { itemsSelected } = this.state; + + const allSelected = Object.keys(itemsSelected).length === history.length; + const selectHandler = allSelected ? this.unselectAll : this.selectAll; + return ( + +
+ {Object.keys(itemsSelected).length ? ( +
+ {!!history.length && ( + + + {history.map(item => ( + { + this.onSelect(item.uri); + }} + /> + ))} + +
+ )} + {pageCount > 1 && ( + + this.changePage(e.selected)} + forcePage={page} + initialPage={page} + containerClassName="pagination" + /> + + this.paginate(e)} + prefix={__('Go to page:')} + type="text" + /> + + )} +
+ ); + } +} +export default UserHistoryPage; diff --git a/src/renderer/component/userHistoryItem/index.js b/src/renderer/component/userHistoryItem/index.js new file mode 100644 index 000000000..a2a00ac58 --- /dev/null +++ b/src/renderer/component/userHistoryItem/index.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux'; +import { doResolveUri, makeSelectClaimForUri } from 'lbry-redux'; +import UserHistoryItem from './view'; + +const select = (state, props) => ({ + claim: makeSelectClaimForUri(props.uri)(state), +}); + +const perform = dispatch => ({ + resolveUri: uri => dispatch(doResolveUri(uri)), +}); + +export default connect( + select, + perform +)(UserHistoryItem); diff --git a/src/renderer/component/userHistoryItem/view.jsx b/src/renderer/component/userHistoryItem/view.jsx new file mode 100644 index 000000000..de35a919b --- /dev/null +++ b/src/renderer/component/userHistoryItem/view.jsx @@ -0,0 +1,64 @@ +// @flow +import React from 'react'; +import type { Claim } from 'types/claim'; +import moment from 'moment'; +import classnames from 'classnames'; +import Button from 'component/button'; + +type Props = { + lastViewed: number, + uri: string, + claim: ?Claim, + selected: boolean, + onSelect: () => void, + resolveUri: string => void, +}; + +class UserHistoryItem extends React.PureComponent { + componentDidMount() { + const { claim, uri, resolveUri } = this.props; + + if (!claim) { + resolveUri(uri); + } + } + + render() { + const { lastViewed, selected, onSelect, claim } = this.props; + + let name; + let title; + let uri; + if (claim && claim.value && claim.value.stream) { + ({ name } = claim); + ({ title } = claim.value.stream.metadata); + uri = claim.permanent_url; + } + + return ( + + + + + {moment(lastViewed).from(moment())} + {title} + +