From b43ed0b64f6b89d0d2fd289d1d929439e3f72645 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sat, 1 Jul 2017 21:33:23 +0100 Subject: [PATCH 001/152] Issue #311 playback input improvements --- ui/js/component/video/internal/player.jsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index 1a86c3b20..4385278c0 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -33,6 +33,7 @@ class VideoPlayer extends React.PureComponent { renderMediaCallback.bind(this) ); + document.addEventListener("keydown", this.togglePlay.bind(this)); const mediaElement = this.refs.media.children[0]; if (mediaElement) { mediaElement.addEventListener( @@ -42,6 +43,23 @@ class VideoPlayer extends React.PureComponent { once: true, } ); + mediaElement.addEventListener("click", this.togglePlay.bind(this)); + } + } + + togglePlay(event) { + // ignore all events except click and spacebar keydown + if ("keydown" === event.type && event.keyCode !== 32) { + return; + } + event.preventDefault(); + const mediaElement = this.refs.media.children[0]; + if (mediaElement) { + if (!mediaElement.paused) { + mediaElement.pause(); + } else { + mediaElement.play(); + } } } From fb3785feed9552c271442007ba43073676b1b0ec Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sat, 1 Jul 2017 21:48:12 +0100 Subject: [PATCH 002/152] Implemented the ability for the spacebar to initiate video playback --- .../component/video/internal/play-button.jsx | 26 ++++++++++++++----- ui/js/component/video/internal/player.jsx | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/ui/js/component/video/internal/play-button.jsx b/ui/js/component/video/internal/play-button.jsx index fb297904a..f696ab539 100644 --- a/ui/js/component/video/internal/play-button.jsx +++ b/ui/js/component/video/internal/play-button.jsx @@ -4,12 +4,23 @@ import Link from "component/link"; import Modal from "component/modal"; class VideoPlayButton extends React.PureComponent { + componentDidMount() { + document.addEventListener("keydown", this.onKeyDown.bind(this)); + } + onPurchaseConfirmed() { this.props.closeModal(); this.props.startPlaying(); this.props.loadVideo(this.props.uri); } + onKeyDown(event) { + if (event.keyCode === 32) { + event.preventDefault(); + this.onWatchClick(); + } + } + onWatchClick() { this.props.purchaseUri(this.props.uri).then(() => { if (!this.props.modal) { @@ -45,9 +56,10 @@ class VideoPlayButton extends React.PureComponent { isLoading || fileInfo === undefined || (fileInfo === null && (!costInfo || costInfo.cost === undefined)); - const icon = ["audio", "video"].indexOf(mediaType) !== -1 - ? "icon-play" - : "icon-folder-o"; + const icon = + ["audio", "video"].indexOf(mediaType) !== -1 + ? "icon-play" + : "icon-folder-o"; return (
@@ -73,9 +85,11 @@ class VideoPlayButton extends React.PureComponent { onConfirmed={this.onPurchaseConfirmed.bind(this)} onAborted={closeModal} > - {__("This will purchase")} {title} {__("for")} - {" "} - {" "}{__("credits")}. + {__("This will purchase")} {title} {__("for")}{" "} + + + {" "} + {__("credits")}. Date: Sat, 1 Jul 2017 22:57:14 +0100 Subject: [PATCH 003/152] Added requested event handling changes --- ui/js/component/video/internal/play-button.jsx | 16 ++++++++++------ ui/js/component/video/internal/player.jsx | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/ui/js/component/video/internal/play-button.jsx b/ui/js/component/video/internal/play-button.jsx index f696ab539..aa352ddf7 100644 --- a/ui/js/component/video/internal/play-button.jsx +++ b/ui/js/component/video/internal/play-button.jsx @@ -5,7 +5,12 @@ import Modal from "component/modal"; class VideoPlayButton extends React.PureComponent { componentDidMount() { - document.addEventListener("keydown", this.onKeyDown.bind(this)); + this.keyDownListener = this.onKeyDown.bind(this); + document.addEventListener("keydown", this.keyDownListener); + } + + componentWillUnmount() { + document.removeEventListener("keydown", this.keyDownListener); } onPurchaseConfirmed() { @@ -15,7 +20,7 @@ class VideoPlayButton extends React.PureComponent { } onKeyDown(event) { - if (event.keyCode === 32) { + if ("Space" === event.code) { event.preventDefault(); this.onWatchClick(); } @@ -56,10 +61,9 @@ class VideoPlayButton extends React.PureComponent { isLoading || fileInfo === undefined || (fileInfo === null && (!costInfo || costInfo.cost === undefined)); - const icon = - ["audio", "video"].indexOf(mediaType) !== -1 - ? "icon-play" - : "icon-folder-o"; + const icon = ["audio", "video"].indexOf(mediaType) !== -1 + ? "icon-play" + : "icon-folder-o"; return (
diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index 6b7c04eed..bf2a6dcc3 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -13,6 +13,8 @@ class VideoPlayer extends React.PureComponent { startedPlaying: false, unplayable: false, }; + + this.togglePlayListener = this.togglePlay.bind(this); } componentDidMount() { @@ -33,7 +35,7 @@ class VideoPlayer extends React.PureComponent { renderMediaCallback.bind(this) ); - document.addEventListener("keydown", this.togglePlay.bind(this)); + document.addEventListener("keydown", this.togglePlayListener); const mediaElement = this.refs.media.children[0]; if (mediaElement) { mediaElement.addEventListener( @@ -43,13 +45,21 @@ class VideoPlayer extends React.PureComponent { once: true, } ); - mediaElement.addEventListener("click", this.togglePlay.bind(this)); + mediaElement.addEventListener("click", this.togglePlayListener); + } + } + + componentWillUnmount() { + document.removeEventListener("keydown", this.togglePlayListener); + const mediaElement = this.refs.media.children[0]; + if (mediaElement) { + mediaElement.removeEventListener("click", this.togglePlayListener); } } togglePlay(event) { // ignore all events except click and spacebar keydown - if ("keydown" === event.type && event.keyCode !== 32) { + if ("keydown" === event.type && "Space" !== event.code) { return; } event.preventDefault(); From c111af6579d611104e72b792a5e5e7e7f01f5531 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sun, 2 Jul 2017 11:09:26 +0100 Subject: [PATCH 004/152] Issue #253 mp3 audio seek fix --- ui/js/component/video/internal/player.jsx | 29 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index 1a86c3b20..f1c20d0fb 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -16,11 +16,31 @@ class VideoPlayer extends React.PureComponent { } componentDidMount() { + const component = this; const container = this.refs.media; - const { mediaType } = this.props; + const { downloadPath, mediaType } = this.props; const loadedMetadata = e => { - this.setState({ hasMetadata: true, startedPlaying: true }); - this.refs.media.children[0].play(); + const media = this.refs.media.children[0]; + if ("audio" === media.tagName.toLowerCase()) { + // Load the entire audio file so that the length is available before playing + let xhr = new XMLHttpRequest(); + const xhrLoaded = () => { + if (xhr.status === 200) { + media.src = URL.createObjectURL(xhr.response); + + this.setState({ hasMetadata: true, startedPlaying: true }); + media.play(); + } + }; + + xhr.open("GET", downloadPath, true); + xhr.onload = xhrLoaded.bind(this); + xhr.responseType = "blob"; + xhr.send(); + } else { + this.setState({ hasMetadata: true, startedPlaying: true }); + media.play(); + } }; const renderMediaCallback = err => { if (err) this.setState({ unplayable: true }); @@ -66,8 +86,7 @@ class VideoPlayer extends React.PureComponent { }, }; } - - playableType() { + *playableType() { const { mediaType } = this.props; return ["audio", "video"].indexOf(mediaType) !== -1; From 2cc24318c10512d665b66c67d5947f84ab7036f1 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 3 Jul 2017 13:54:03 +0700 Subject: [PATCH 005/152] Stop progress bar code blowing up if window is defocused before download starts --- ui/js/util/setProgressBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/util/setProgressBar.js b/ui/js/util/setProgressBar.js index 304a54f17..b5d869f3d 100644 --- a/ui/js/util/setProgressBar.js +++ b/ui/js/util/setProgressBar.js @@ -1,6 +1,6 @@ const { remote } = require("electron"); const application = remote.app; -const win = remote.BrowserWindow.getFocusedWindow(); +const win = remote.getCurrentWindow(); const setProgressBar = progress => { win.setProgressBar(progress); From 48c10b6c53a937b2aa2a90708acf662efce9ed3d Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 4 Jul 2017 13:06:09 -0400 Subject: [PATCH 006/152] update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c6a2184e..9c1829aa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added - * + * Added option to release claim when deleting a file * ### Changed @@ -16,7 +16,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * ### Fixed - * + * Fixed bug with download notice when switching window focus * ### Deprecated From 52b404b552038da52096268c71e6a82840509126 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 04:14:45 +0100 Subject: [PATCH 007/152] Issue #312 home page scroll position on back navigation --- ui/js/page/discover/view.jsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index d6c506dfd..99387949d 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -3,6 +3,7 @@ import lbryio from "lbryio.js"; import lbryuri from "lbryuri"; import FileCard from "component/fileCard"; import { BusyMessage } from "component/common.js"; +import { setSession, getSession } from "utils"; import ToolTip from "component/tooltip.js"; const FeaturedCategory = props => { @@ -37,10 +38,27 @@ const FeaturedCategory = props => { class DiscoverPage extends React.PureComponent { componentWillMount() { this.props.fetchFeaturedUris(); + this.scrollListener = this.handleScroll.bind(this); + } + + componentDidMount() { + const scrollY = parseInt(getSession("prefs_scrolly")); + if (!isNaN(scrollY)) { + const restoreScrollPosition = () => { + window.scrollTo(0, scrollY); + }; + setTimeout(restoreScrollPosition, 100); + } + window.addEventListener("scroll", this.scrollListener); + } + + handleScroll() { + setSession("prefs_scrolly", window.scrollY); } componentWillUnmount() { this.props.cancelResolvingUris(); + window.removeEventListener("scroll", this.scrollListener); } render() { @@ -51,7 +69,7 @@ class DiscoverPage extends React.PureComponent { (featuredUris !== undefined && Object.keys(featuredUris).length === 0)); return ( -
+
{fetchingFeaturedUris && } {typeof featuredUris === "object" && From 89b58aa588bcf4f9e57ef1066a3999820f3506b3 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 04:20:25 +0100 Subject: [PATCH 008/152] removed unused ref --- ui/js/page/discover/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index 99387949d..b68d026b9 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -69,7 +69,7 @@ class DiscoverPage extends React.PureComponent { (featuredUris !== undefined && Object.keys(featuredUris).length === 0)); return ( -
+
{fetchingFeaturedUris && } {typeof featuredUris === "object" && From 49b1399926b327e9ffba001f3a524bf36114b2b3 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 18:33:44 +0100 Subject: [PATCH 009/152] MP3 audio seek fix #2 --- ui/js/component/video/internal/player.jsx | 67 ++++++++++++----------- ui/js/component/video/view.jsx | 1 + 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index f1c20d0fb..91de346d5 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -5,6 +5,8 @@ import fs from "fs"; import LoadingScreen from "./loading-screen"; class VideoPlayer extends React.PureComponent { + static MP3_CONTENT_TYPES = ["audio/mpeg3", "audio/mpeg"]; + constructor(props) { super(props); @@ -18,40 +20,26 @@ class VideoPlayer extends React.PureComponent { componentDidMount() { const component = this; const container = this.refs.media; - const { downloadPath, mediaType } = this.props; + const { contentType, downloadPath, mediaType } = this.props; const loadedMetadata = e => { - const media = this.refs.media.children[0]; - if ("audio" === media.tagName.toLowerCase()) { - // Load the entire audio file so that the length is available before playing - let xhr = new XMLHttpRequest(); - const xhrLoaded = () => { - if (xhr.status === 200) { - media.src = URL.createObjectURL(xhr.response); - - this.setState({ hasMetadata: true, startedPlaying: true }); - media.play(); - } - }; - - xhr.open("GET", downloadPath, true); - xhr.onload = xhrLoaded.bind(this); - xhr.responseType = "blob"; - xhr.send(); - } else { - this.setState({ hasMetadata: true, startedPlaying: true }); - media.play(); - } + this.setState({ hasMetadata: true, startedPlaying: true }); + this.refs.media.children[0].play(); }; const renderMediaCallback = err => { if (err) this.setState({ unplayable: true }); }; - player.append( - this.file(), - container, - { autoplay: false, controls: true }, - renderMediaCallback.bind(this) - ); + // use renderAudio override for mp3 + if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { + this.renderAudio(container, false); + } else { + player.append( + this.file(), + container, + { autoplay: false, controls: true }, + renderMediaCallback.bind(this) + ); + } const mediaElement = this.refs.media.children[0]; if (mediaElement) { @@ -65,25 +53,42 @@ class VideoPlayer extends React.PureComponent { } } + renderAudio(container, autoplay) { + const { downloadPath } = this.props; + const audio = document.createElement("audio"); + audio.autoplay = autoplay; + audio.controls = true; + audio.src = downloadPath; + container.appendChild(audio); + } + componentDidUpdate() { - const { mediaType, downloadCompleted } = this.props; + const { contentType, downloadCompleted } = this.props; const { startedPlaying } = this.state; if (this.playableType() && !startedPlaying && downloadCompleted) { const container = this.refs.media.children[0]; - player.render(this.file(), container, { autoplay: true, controls: true }); + if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { + this.renderAudio(container, true); + } else { + player.render(this.file(), container, { + autoplay: true, + controls: true, + }); + } } } file() { const { downloadPath, filename } = this.props; - + const stat = fs.statSync(downloadPath); return { name: filename, createReadStream: opts => { return fs.createReadStream(downloadPath, opts); }, + length: stat.size, }; } *playableType() { diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index c3cb499b9..10df6ea9a 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -98,6 +98,7 @@ class Video extends React.PureComponent { poster={poster} downloadPath={fileInfo.download_path} mediaType={mediaType} + contentType={contentType} downloadCompleted={fileInfo.completed} />)} {!isPlaying && From f61a8a4372a12396f4795d42199192fd8d47e8d8 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 18:42:39 +0100 Subject: [PATCH 010/152] removed unused code --- ui/js/component/video/internal/player.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index eee756ccb..ab05daac0 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -137,13 +137,11 @@ class VideoPlayer extends React.PureComponent { file() { const { downloadPath, filename } = this.props; - const stat = fs.statSync(downloadPath); return { name: filename, createReadStream: opts => { return fs.createReadStream(downloadPath, opts); }, - length: stat.size, }; } *playableType() { From 913d642b523b513f14576c82ba78a994233e8bb3 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 18:44:52 +0100 Subject: [PATCH 011/152] removed additional unused code --- ui/js/component/video/internal/player.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index ab05daac0..48fdaa25c 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -137,6 +137,7 @@ class VideoPlayer extends React.PureComponent { file() { const { downloadPath, filename } = this.props; + return { name: filename, createReadStream: opts => { @@ -144,7 +145,8 @@ class VideoPlayer extends React.PureComponent { }, }; } - *playableType() { + + playableType() { const { mediaType } = this.props; return ["audio", "video"].indexOf(mediaType) !== -1; From 95145b57e3da683c68d1accf8708dd5b508eba62 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 20:03:31 +0100 Subject: [PATCH 012/152] additional tweaks to renderAudio --- ui/js/component/video/internal/player.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index 48fdaa25c..28cb554d0 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -22,7 +22,6 @@ class VideoPlayer extends React.PureComponent { } componentDidMount() { - const component = this; const container = this.refs.media; const { contentType, downloadPath, mediaType } = this.props; const loadedMetadata = e => { @@ -44,7 +43,7 @@ class VideoPlayer extends React.PureComponent { // use renderAudio override for mp3 if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { - this.renderAudio(container, false); + this.renderAudio(container, null, false); } else { player.append( this.file(), @@ -85,6 +84,11 @@ class VideoPlayer extends React.PureComponent { } renderAudio(container, autoplay) { + if (container.firstChild) { + container.firstChild.remove(); + } + + // clear the container const { downloadPath } = this.props; const audio = document.createElement("audio"); audio.autoplay = autoplay; @@ -125,7 +129,7 @@ class VideoPlayer extends React.PureComponent { const container = this.refs.media.children[0]; if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { - this.renderAudio(container, true); + this.renderAudio(this.refs.media, true); } else { player.render(this.file(), container, { autoplay: true, From 034860685947d3a8a2d80769bcc2506eaca29de5 Mon Sep 17 00:00:00 2001 From: Josh Finer Date: Thu, 6 Jul 2017 21:14:55 -0400 Subject: [PATCH 013/152] Update view.jsx --- ui/js/page/rewards/view.jsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index 3b6964761..237994667 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -73,11 +73,6 @@ class RewardsPage extends React.PureComponent { content = (

{__("You are not eligible to claim rewards.")}

-

- {__("To become eligible, email")} - {" "}{" "} - {__("with a link to a public social media profile.")} -

); } else if (fetching) { From a7d8d1a7b0701d5c66ecbaa0b956958f50d09efa Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Mon, 10 Jul 2017 13:19:42 -0400 Subject: [PATCH 014/152] fix misnamed modal --- ui/js/component/app/view.jsx | 8 ++++---- ui/js/component/modalWelcome/index.js | 4 ++-- ui/js/component/modalWelcome/view.jsx | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 727f4e39a..bc8264f21 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -3,8 +3,8 @@ import Router from "component/router"; import Header from "component/header"; import ModalError from "component/modalError"; import ModalDownloading from "component/modalDownloading"; -import UpgradeModal from "component/modalUpgrade"; -import WelcomeModal from "component/modalWelcome"; +import ModalUpgrade from "component/modalUpgrade"; +import ModalWelcome from "component/modalWelcome"; import lbry from "lbry"; import { Line } from "rc-progress"; @@ -32,10 +32,10 @@ class App extends React.PureComponent {
- {modal == "upgrade" && } + {modal == "upgrade" && } {modal == "downloading" && } {modal == "error" && } - {modal == "welcome" && } + {modal == "welcome" && }
); } diff --git a/ui/js/component/modalWelcome/index.js b/ui/js/component/modalWelcome/index.js index bbfc5b6a7..12ec5c0be 100644 --- a/ui/js/component/modalWelcome/index.js +++ b/ui/js/component/modalWelcome/index.js @@ -8,7 +8,7 @@ import { makeSelectClaimRewardError, makeSelectRewardByType, } from "selectors/rewards"; -import WelcomeModal from "./view"; +import ModalWelcome from "./view"; const select = (state, props) => { const selectHasClaimed = makeSelectHasClaimedReward(), @@ -25,4 +25,4 @@ const perform = dispatch => ({ closeModal: () => dispatch(doCloseModal()), }); -export default connect(select, perform)(WelcomeModal); +export default connect(select, perform)(ModalWelcome); diff --git a/ui/js/component/modalWelcome/view.jsx b/ui/js/component/modalWelcome/view.jsx index 9b6930bc6..82448c3ae 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -4,7 +4,7 @@ import { CreditAmount } from "component/common"; import Link from "component/link"; import RewardLink from "component/rewardLink"; -class WelcomeModal extends React.PureComponent { +class ModalWelcome extends React.PureComponent { render() { const { closeModal, hasClaimed, isRewardApproved, reward } = this.props; @@ -78,4 +78,4 @@ class WelcomeModal extends React.PureComponent { } } -export default WelcomeModal; +export default ModalWelcome; From afd361bcd7ee1f80cebecdcb75ada6d3c41fc8f9 Mon Sep 17 00:00:00 2001 From: hackrush Date: Mon, 3 Jul 2017 00:07:20 +0530 Subject: [PATCH 015/152] Added max_key_fee setting Enables user to set the max_key_fee via the UI. Fixes #268 --- ui/js/page/settings/view.jsx | 90 ++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/ui/js/page/settings/view.jsx b/ui/js/page/settings/view.jsx index 7704a37e1..3914a83b2 100644 --- a/ui/js/page/settings/view.jsx +++ b/ui/js/page/settings/view.jsx @@ -64,6 +64,32 @@ class SettingsPage extends React.PureComponent { }); } + onKeyFeeChange(event) { + var oldSettings = this.props.daemonSettings.max_key_fee; + var newSettings = { + amount: oldSettings.amount, + currency: oldSettings.currency, + }; + newSettings.amount = Number(event.target.value); + + this.setDaemonSetting("max_key_fee", newSettings); + } + + onFeeCurrencyChange(event) { + var oldSettings = this.props.daemonSettings.max_key_fee; + var newSettings = { + amount: oldSettings.amount, + currency: oldSettings.currency, + }; + newSettings.currency = event.target.value; + + this.setDaemonSetting("max_key_fee", newSettings); + } + + onKeyFeeDisableChange(isDisabled) { + this.setDaemonSetting("disable_max_key_fee", isDisabled); + } + onMaxUploadFieldChange(event) { this.setDaemonSetting("max_upload", Number(event.target.value)); } @@ -165,6 +191,7 @@ class SettingsPage extends React.PureComponent { />
+

{__("Bandwidth Limits")}

@@ -252,6 +279,69 @@ class SettingsPage extends React.PureComponent {
+ +
+
+

{__("Key Fee")}

+
+
+
+
{__("Max Key Fee")}
+
+ { + this.onKeyFeeDisableChange(true); + }} + defaultChecked={daemonSettings.disable_max_key_fee} + label={__("No Limit")} + /> +
+ { + this.onKeyFeeDisableChange(false); + }} + defaultChecked={!daemonSettings.disable_max_key_fee} + label={ + daemonSettings.disable_max_key_fee + ? __("Choose limit") + : __("Limit to") + } + /> + {!daemonSettings.disable_max_key_fee + ? + : ""} + {!daemonSettings.disable_max_key_fee + ? + + + + : ""} +
+
+ {__( + "This will prevent you from purchasing anything over this fee, as a safety measure. (Default: 50 USD)" + )} +
+
+
+

{__("Content")}

From 7c380ce01b7e9d45399f993a4ade39f9a4adce9d Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 11 Jul 2017 15:41:37 -0400 Subject: [PATCH 016/152] add transition to card hovers --- CHANGELOG.md | 2 +- ui/scss/component/_card.scss | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c1829aa4..bcbe49bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added * Added option to release claim when deleting a file - * + * Added transition to card hovers to smooth animation ### Changed * diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index 60b128afd..a112bd232 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -79,6 +79,9 @@ $card-link-scaling: 1.1; .card__link { display: block; } +.card--link { + transition: transform 120ms ease-in-out; +} .card--link:hover { position: relative; z-index: 1; From 877586a00ae43a5f8257ad43e0024d53335b0c71 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Wed, 12 Jul 2017 13:40:36 +0100 Subject: [PATCH 017/152] Issue #333 media switch fix --- ui/js/component/video/view.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index 10df6ea9a..4d52883ab 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -14,6 +14,13 @@ class Video extends React.PureComponent { }; } + componentWillReceiveProps(nextProps) { + // reset playing state upon change path action + if (this.state.isPlaying) { + this.state.isPlaying = false; + } + } + startPlaying() { this.setState({ isPlaying: true, From 0198c991e3b4d6a93f10aaac55a35f6391ffbd6f Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Wed, 12 Jul 2017 21:56:18 +0100 Subject: [PATCH 018/152] check that fileInfo outpoint is different before resetting media play state --- ui/js/component/video/view.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index 4d52883ab..f5cd375bd 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -16,11 +16,19 @@ class Video extends React.PureComponent { componentWillReceiveProps(nextProps) { // reset playing state upon change path action - if (this.state.isPlaying) { + if (!this.isMediaSame(nextProps) && this.state.isPlaying) { this.state.isPlaying = false; } } + isMediaSame(nextProps) { + return ( + this.props.fileInfo && + nextProps.fileInfo && + this.props.fileInfo.outpoint === nextProps.fileInfo.outpoint + ); + } + startPlaying() { this.setState({ isPlaying: true, From 284ab8a01af4bc802e8d9fe5ab405582d2d6f4b7 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 10 Jul 2017 21:49:12 +0700 Subject: [PATCH 019/152] Move fetching my channels into redux --- ui/js/actions/content.js | 17 ++++++++++ ui/js/constants/action_types.js | 4 +++ ui/js/page/publish/index.js | 11 ++++-- ui/js/page/publish/view.jsx | 59 +++++++++++++++++---------------- ui/js/reducers/claims.js | 35 ++++++++++--------- ui/js/selectors/claims.js | 18 ++++++++++ 6 files changed, 99 insertions(+), 45 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index b80f8fc0c..d9566b091 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -339,3 +339,20 @@ export function doFetchClaimListMine() { }); }; } + +export function doFetchChannelListMine() { + return function(dispatch, getState) { + dispatch({ + type: types.FETCH_CHANNEL_LIST_MINE_STARTED, + }); + + const callback = channels => { + dispatch({ + type: types.FETCH_CHANNEL_LIST_MINE_COMPLETED, + data: { claims: channels }, + }); + }; + + lbry.channel_list_mine().then(callback); + }; +} diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 216c84762..e278bac3d 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -64,6 +64,10 @@ export const FETCH_AVAILABILITY_COMPLETED = "FETCH_AVAILABILITY_COMPLETED"; export const FILE_DELETE = "FILE_DELETE"; export const ABANDON_CLAIM_STARTED = "ABANDON_CLAIM_STARTED"; export const ABANDON_CLAIM_COMPLETED = "ABANDON_CLAIM_COMPLETED"; +export const FETCH_CHANNEL_LIST_MINE_STARTED = + "FETCH_CHANNEL_LIST_MINE_STARTED"; +export const FETCH_CHANNEL_LIST_MINE_COMPLETED = + "FETCH_CHANNEL_LIST_MINE_COMPLETED"; // Search export const SEARCH_STARTED = "SEARCH_STARTED"; diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js index fac4419c1..bfe1f06e0 100644 --- a/ui/js/page/publish/index.js +++ b/ui/js/page/publish/index.js @@ -2,13 +2,19 @@ import React from "react"; import { connect } from "react-redux"; import { doNavigate, doHistoryBack } from "actions/app"; import { doClaimRewardType } from "actions/rewards"; -import { selectMyClaims } from "selectors/claims"; -import { doFetchClaimListMine } from "actions/content"; +import { + selectMyClaims, + selectFetchingMyChannels, + selectMyChannelClaims, +} from "selectors/claims"; +import { doFetchClaimListMine, doFetchChannelListMine } from "actions/content"; import rewards from "rewards"; import PublishPage from "./view"; const select = state => ({ myClaims: selectMyClaims(state), + fetchingChannels: selectFetchingMyChannels(state), + channels: selectMyChannelClaims(state), }); const perform = dispatch => ({ @@ -17,6 +23,7 @@ const perform = dispatch => ({ fetchClaimListMine: () => dispatch(doFetchClaimListMine()), claimFirstChannelReward: () => dispatch(doClaimRewardType(rewards.TYPE_FIRST_CHANNEL)), + fetchChannelListMine: () => dispatch(doFetchChannelListMine()), }); export default connect(select, perform)(PublishPage); diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index 0693d61cf..cb9e938c8 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -5,6 +5,7 @@ import { FormField, FormRow } from "component/form.js"; import Link from "component/link"; import rewards from "rewards"; import Modal from "component/modal"; +import { BusyMessage } from "component/common"; class PublishPage extends React.PureComponent { constructor(props) { @@ -13,7 +14,6 @@ class PublishPage extends React.PureComponent { this._requiredFields = ["meta_title", "name", "bid", "tos_agree"]; this.state = { - channels: null, rawName: "", name: "", bid: 10, @@ -41,15 +41,18 @@ class PublishPage extends React.PureComponent { } _updateChannelList(channel) { + const { fetchingChannels, fetchChannelListMine } = this.props; + + if (!fetchingChannels) fetchChannelListMine(); // Calls API to update displayed list of channels. If a channel name is provided, will select // that channel at the same time (used immediately after creating a channel) - lbry.channel_list_mine().then(channels => { - this.props.claimFirstChannelReward(); - this.setState({ - channels: channels, - ...(channel ? { channel } : {}), - }); - }); + // lbry.channel_list_mine().then(channels => { + // this.props.claimFirstChannelReward(); + // this.setState({ + // channels: channels, + // ...(channel ? { channel } : {}), + // }); + // }); } handleSubmit(event) { @@ -465,10 +468,6 @@ class PublishPage extends React.PureComponent { } render() { - if (this.state.channels === null) { - return null; - } - const lbcInputHelp = __( "This LBC remains yours and the deposit can be undone at any time." ); @@ -729,22 +728,26 @@ class PublishPage extends React.PureComponent {
- { - this.handleChannelChange(event); - }} - value={this.state.channel} - > - - {this.state.channels.map(({ name }) => - - )} - - + {this.props.fetchingChannels + ? + : { + this.handleChannelChange(event); + }} + value={this.state.channel} + > + + {this.props.channels.map(({ name }) => + + )} + + }
{this.state.channel == "new" ?
diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 7417bc2fb..f71e2cadd 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -50,21 +50,26 @@ reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { }); }; -// reducers[types.FETCH_CHANNEL_CLAIMS_STARTED] = function(state, action) { -// const { -// uri, -// } = action.data -// -// const newClaims = Object.assign({}, state.claimsByChannel) -// -// if (claims !== undefined) { -// newClaims[uri] = claims -// } -// -// return Object.assign({}, state, { -// claimsByChannel: newClaims -// }) -// } +reducers[types.FETCH_CHANNEL_LIST_MINE_STARTED] = function(state, action) { + return Object.assign({}, state, { fetchingMyChannels: true }); +}; + +reducers[types.FETCH_CHANNEL_LIST_MINE_COMPLETED] = function(state, action) { + const { claims } = action.data; + const myChannelClaims = new Set(state.myChannelClaims); + const byId = Object.assign({}, state.byId); + + claims.forEach(claim => { + myChannelClaims.add(claim.claim_id); + byId[claims.claim_id] = claim; + }); + + return Object.assign({}, state, { + byId, + fetchingMyChannels: false, + myChannelClaims, + }); +}; reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) { const { uri, claims } = action.data; diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index b966375e9..1792db453 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -124,3 +124,21 @@ export const selectMyClaimsOutpoints = createSelector( return outpoints; } ); + +export const selectFetchingMyChannels = createSelector( + _selectState, + state => !!state.fetchingMyChannels +); + +export const selectMyChannelClaims = createSelector( + _selectState, + selectClaimsById, + (state, byId) => { + const ids = state.myChannelClaims || []; + const claims = []; + + ids.forEach(id => claims.push(byId[id])); + + return claims; + } +); From e01868d29b80e8394f29c1acfa4dda0d3b264401 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 12 Jun 2017 15:01:22 +0700 Subject: [PATCH 020/152] Move claim lookup on publish page into redux --- ui/js/page/publish/index.js | 11 ++- ui/js/page/publish/view.jsx | 133 ++++++++++++++++-------------------- ui/js/selectors/claims.js | 26 +++++-- 3 files changed, 88 insertions(+), 82 deletions(-) diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js index bfe1f06e0..d959b04bd 100644 --- a/ui/js/page/publish/index.js +++ b/ui/js/page/publish/index.js @@ -6,8 +6,14 @@ import { selectMyClaims, selectFetchingMyChannels, selectMyChannelClaims, + selectClaimsByUri, } from "selectors/claims"; -import { doFetchClaimListMine, doFetchChannelListMine } from "actions/content"; +import { selectResolvingUris } from "selectors/content"; +import { + doFetchClaimListMine, + doFetchChannelListMine, + doResolveUri, +} from "actions/content"; import rewards from "rewards"; import PublishPage from "./view"; @@ -15,6 +21,8 @@ const select = state => ({ myClaims: selectMyClaims(state), fetchingChannels: selectFetchingMyChannels(state), channels: selectMyChannelClaims(state), + claimsByUri: selectClaimsByUri(state), + resolvingUris: selectResolvingUris(state), }); const perform = dispatch => ({ @@ -24,6 +32,7 @@ const perform = dispatch => ({ claimFirstChannelReward: () => dispatch(doClaimRewardType(rewards.TYPE_FIRST_CHANNEL)), fetchChannelListMine: () => dispatch(doFetchChannelListMine()), + resolveUri: uri => dispatch(doResolveUri(uri)), }); export default connect(select, perform)(PublishPage); diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index cb9e938c8..a76d4eb71 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -23,9 +23,6 @@ class PublishPage extends React.PureComponent { channel: "anonymous", newChannelName: "@", newChannelBid: 10, - nameResolved: null, - myClaimExists: null, - topClaimValue: 0.0, myClaimValue: 0.0, myClaimMetadata: null, copyrightNotice: "", @@ -44,15 +41,6 @@ class PublishPage extends React.PureComponent { const { fetchingChannels, fetchChannelListMine } = this.props; if (!fetchingChannels) fetchChannelListMine(); - // Calls API to update displayed list of channels. If a channel name is provided, will select - // that channel at the same time (used immediately after creating a channel) - // lbry.channel_list_mine().then(channels => { - // this.props.claimFirstChannelReward(); - // this.setState({ - // channels: channels, - // ...(channel ? { channel } : {}), - // }); - // }); } handleSubmit(event) { @@ -65,7 +53,7 @@ class PublishPage extends React.PureComponent { }); let checkFields = this._requiredFields; - if (!this.state.myClaimExists) { + if (!this.myClaimExists()) { checkFields.unshift("file"); } @@ -182,6 +170,49 @@ class PublishPage extends React.PureComponent { }); } + claim() { + const { claimsByUri } = this.props; + const { uri } = this.state; + + return claimsByUri[uri]; + } + + topClaimValue() { + if (!this.claim()) return null; + + return parseFloat(this.claim().amount); + } + + myClaimExists() { + const { myClaims } = this.props; + const { name } = this.state; + + if (!name) return false; + + return !!myClaims.find(claim => claim.name === name); + } + + topClaimIsMine() { + const myClaimInfo = this.myClaimInfo(); + const { claimsByUri } = this.props; + const { uri } = this.state; + + if (!uri) return null; + + const claim = claimsByUri[uri]; + + if (!claim) return true; + if (!myClaimInfo) return false; + + return myClaimInfo.amount >= claimInfo.amount; + } + + myClaimInfo() { + return Object.values(this.props.myClaims).find( + claim => claim.name === name + ); + } + handleNameChange(event) { var rawName = event.target.value; @@ -189,7 +220,7 @@ class PublishPage extends React.PureComponent { this.setState({ rawName: "", name: "", - nameResolved: false, + uri: "", }); return; @@ -203,61 +234,14 @@ class PublishPage extends React.PureComponent { } const name = rawName.toLowerCase(); + const uri = lbryuri.normalize(name); this.setState({ rawName: rawName, name: name, - nameResolved: null, - myClaimExists: null, + uri, }); - const myClaimInfo = Object.values(this.props.myClaims).find( - claim => claim.name === name - ); - - this.setState({ - myClaimExists: !!myClaimInfo, - }); - lbry.resolve({ uri: name }).then( - claimInfo => { - if (name != this.state.name) { - return; - } - - if (!claimInfo) { - this.setState({ - nameResolved: false, - }); - } else { - const topClaimIsMine = - myClaimInfo && myClaimInfo.amount >= claimInfo.amount; - const newState = { - nameResolved: true, - topClaimValue: parseFloat(claimInfo.amount), - myClaimExists: !!myClaimInfo, - myClaimValue: myClaimInfo ? parseFloat(myClaimInfo.amount) : null, - myClaimMetadata: myClaimInfo ? myClaimInfo.value : null, - topClaimIsMine: topClaimIsMine, - }; - - if (topClaimIsMine) { - newState.bid = myClaimInfo.amount; - } else if (this.state.myClaimMetadata) { - // Just changed away from a name we have a claim on, so clear pre-fill - newState.bid = ""; - } - - this.setState(newState); - } - }, - () => { - // Assume an error means the name is available - this.setState({ - name: name, - nameResolved: false, - myClaimExists: false, - }); - } - ); + this.props.resolveUri(uri); } handleBidChange(event) { @@ -427,11 +411,16 @@ class PublishPage extends React.PureComponent { } getNameBidHelpText() { - if (!this.state.name) { + if ( + this.state.uri && + this.props.resolvingUris.indexOf(this.state.uri) !== -1 + ) { + return ; + } else if (!this.state.name) { return __("Select a URL for this publish."); - } else if (this.state.nameResolved === false) { + } else if (!this.claim()) { return __("This URL is unused."); - } else if (this.state.myClaimExists) { + } else if (this.myClaimExists()) { return __( "You have already used this URL. Publishing to it again will update your previous publish." ); @@ -496,7 +485,7 @@ class PublishPage extends React.PureComponent { this.onFileChange(event); }} helper={ - this.state.myClaimExists + this.myClaimExists() ? __( "If you don't choose a file, the file from your existing claim will be used." ) @@ -829,11 +818,7 @@ class PublishPage extends React.PureComponent { this.handleBidChange(event); }} value={this.state.bid} - placeholder={ - this.state.nameResolved - ? this.state.topClaimValue + 10 - : 100 - } + placeholder={this.claim() ? this.topClaimValue() + 10 : 100} helper={lbcInputHelp} />
@@ -898,7 +883,7 @@ class PublishPage extends React.PureComponent { >

{__("Your file has been published to LBRY at the address")} - {" "}lbry://{this.state.name}! + {" "}{this.state.uri}!

{__( diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index 1792db453..3478c9fc4 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -105,21 +105,33 @@ export const selectClaimListMineIsPending = createSelector( state => state.isClaimListMinePending ); -export const selectMyClaims = createSelector( +export const selectMyClaimsRaw = createSelector( _selectState, state => new Set(state.myClaims) ); +export const selectMyClaims = createSelector( + selectMyClaimsRaw, + selectClaimsById, + (myClaimIds, byId) => { + const claims = []; + + myClaimIds.forEach(id => { + const claim = byId[id]; + + if (claim) claims.push(claim); + }); + + return claims; + } +); + export const selectMyClaimsOutpoints = createSelector( selectMyClaims, - selectClaimsById, - (claimIds, byId) => { + myClaims => { const outpoints = []; - claimIds.forEach(claimId => { - const claim = byId[claimId]; - if (claim) outpoints.push(`${claim.txid}:${claim.nout}`); - }); + myClaims.forEach(claim => outpoints.push(`${claim.txid}:${claim.nout}`)); return outpoints; } From df954882bc9484b85eb0fa4033918281669b0371 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 16 Jun 2017 10:06:01 +0700 Subject: [PATCH 021/152] Cache channel claims --- ui/js/store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/js/store.js b/ui/js/store.js index 0ec06c017..1bb5ec058 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -90,6 +90,7 @@ const saveClaimsFilter = createFilter("claims", [ "byId", "claimsByUri", "myClaims", + "myChannelClaims", ]); const persistOptions = { From 8325828f6e18d63fd3cdb04924a196ab983ed066 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sun, 18 Jun 2017 00:59:18 +0700 Subject: [PATCH 022/152] Progress towards working publish --- ui/js/actions/content.js | 90 +++ ui/js/component/common.js | 34 ++ ui/js/constants/action_types.js | 5 + ui/js/lbryuri.js | 2 + ui/js/page/publish/index.js | 4 + ui/js/page/publish/view.jsx | 344 ++++++++---- ui/js/reducers/claims.js | 36 +- ui/js/reducers/file_info.js | 59 +- ui/js/selectors/file_info.js | 19 +- ui/js/store.js | 8 +- ui/yarn.lock | 957 +++++++++++++++++++++++++++++++- 11 files changed, 1399 insertions(+), 159 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index d9566b091..ca6f1850d 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -15,6 +15,7 @@ import { selectBadgeNumber } from "selectors/app"; import { selectTotalDownloadProgress } from "selectors/file_info"; import setBadge from "util/setBadge"; import setProgressBar from "util/setProgressBar"; +import { doFileList } from "actions/file_info"; import batchActions from "util/batchActions"; const { ipcRenderer } = require("electron"); @@ -356,3 +357,92 @@ export function doFetchChannelListMine() { lbry.channel_list_mine().then(callback); }; } + +export function doCreateChannel(name, amount) { + return function(dispatch, getState) { + dispatch({ + type: types.CREATE_CHANNEL_STARTED, + }); + + return new Promise((resolve, reject) => { + lbry + .channel_new({ + channel_name: name, + amount: parseFloat(amount), + }) + .then( + channelClaim => { + channelClaim.name = name; + dispatch({ + type: types.CREATE_CHANNEL_COMPLETED, + data: { channelClaim }, + }); + resolve(channelClaim); + }, + err => { + resolve(err); + } + ); + }); + }; +} + +export function doPublish(params) { + return function(dispatch, getState) { + let uri; + const { name, channel_name } = params; + if (channel_name) { + uri = lbryuri.build({ name: channel_name, path: name }, false); + } else { + uri = lbryuri.build({ name: name }, false); + } + const pendingPublish = { + name, + channel_name, + claim_id: "pending_claim_" + uri, + txid: "pending_" + uri, + nout: 0, + outpoint: "pending_" + uri + ":0", + time: Date.now(), + }; + + dispatch({ + type: types.PUBLISH_STARTED, + data: { + params, + pendingPublish, + }, + }); + + return new Promise((resolve, reject) => { + const success = claim => { + claim.name = params.name; + claim.channel_name = params.channel_name; + dispatch({ + type: types.PUBLISH_COMPLETED, + data: { + claim, + uri, + pendingPublish, + }, + }); + dispatch(doFileList()); + resolve(claim); + }; + const failure = error => { + dispatch({ + type: types.PUBLISH_FAILED, + data: { + error, + params, + uri, + pendingPublish, + }, + }); + reject(error); + }; + + lbry.publish(params).then(success, failure); + }); + }; +} diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 38dbf83fd..1b48bc4df 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -42,6 +42,40 @@ export class TruncatedText extends React.PureComponent { } } +export class TruncatedMarkdown extends React.PureComponent { + static propTypes = { + lines: React.PropTypes.number, + }; + + static defaultProps = { + lines: null, + }; + + transformMarkdown(text) { + // render markdown to html string then trim html tag + let htmlString = ReactDOMServer.renderToStaticMarkup( + + ); + var txt = document.createElement("textarea"); + txt.innerHTML = htmlString; + return txt.value.replace(/<(?:.|\n)*?>/gm, ""); + } + + render() { + let content = this.props.children && typeof this.props.children === "string" + ? this.transformMarkdown(this.props.children) + : this.props.children; + return ( + + {content} + + ); + } +} + export class BusyMessage extends React.PureComponent { static propTypes = { message: React.PropTypes.string, diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index e278bac3d..457761441 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -68,6 +68,11 @@ export const FETCH_CHANNEL_LIST_MINE_STARTED = "FETCH_CHANNEL_LIST_MINE_STARTED"; export const FETCH_CHANNEL_LIST_MINE_COMPLETED = "FETCH_CHANNEL_LIST_MINE_COMPLETED"; +export const CREATE_CHANNEL_STARTED = "CREATE_CHANNEL_STARTED"; +export const CREATE_CHANNEL_COMPLETED = "CREATE_CHANNEL_COMPLETED"; +export const PUBLISH_STARTED = "PUBLISH_STARTED"; +export const PUBLISH_COMPLETED = "PUBLISH_COMPLETED"; +export const PUBLISH_FAILED = "PUBLISH_FAILED"; // Search export const SEARCH_STARTED = "SEARCH_STARTED"; diff --git a/ui/js/lbryuri.js b/ui/js/lbryuri.js index 1fcfdec58..42a825949 100644 --- a/ui/js/lbryuri.js +++ b/ui/js/lbryuri.js @@ -203,6 +203,8 @@ lbryuri.build = function(uriObj, includeProto = true, allowExtraProps = false) { /* Takes a parseable LBRY URI and converts it to standard, canonical format (currently this just * consists of adding the lbry:// prefix if needed) */ lbryuri.normalize = function(uri) { + if (uri.match(/pending_claim/)) return uri; + const { name, path, bidPosition, claimSequence, claimId } = lbryuri.parse( uri ); diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js index d959b04bd..f296f1687 100644 --- a/ui/js/page/publish/index.js +++ b/ui/js/page/publish/index.js @@ -13,6 +13,8 @@ import { doFetchClaimListMine, doFetchChannelListMine, doResolveUri, + doCreateChannel, + doPublish, } from "actions/content"; import rewards from "rewards"; import PublishPage from "./view"; @@ -33,6 +35,8 @@ const perform = dispatch => ({ dispatch(doClaimRewardType(rewards.TYPE_FIRST_CHANNEL)), fetchChannelListMine: () => dispatch(doFetchChannelListMine()), resolveUri: uri => dispatch(doResolveUri(uri)), + createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)), + publish: params => dispatch(doPublish(params)), }); export default connect(select, perform)(PublishPage); diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index a76d4eb71..12b38cbd7 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -125,16 +125,11 @@ class PublishPage extends React.PureComponent { publishArgs.file_path = this.refs.file.getValue(); } - lbry.publishDeprecated( - publishArgs, - message => { - this.handlePublishStarted(); - }, - null, - error => { - this.handlePublishError(error); - } - ); + const success = claim => {}; + const failure = error => this.handlePublishError(error); + + this.handlePublishStarted(); + this.props.publish(publishArgs).then(success, failure); }; if (this.state.isFee) { @@ -216,6 +211,10 @@ class PublishPage extends React.PureComponent { handleNameChange(event) { var rawName = event.target.value; + this.nameChanged(rawName); + } + + nameChanged(rawName) { if (!rawName) { this.setState({ rawName: "", @@ -233,15 +232,26 @@ class PublishPage extends React.PureComponent { return; } + let channel = ""; + if (this.state.channel !== "anonymous") channel = this.state.channel; + const name = rawName.toLowerCase(); - const uri = lbryuri.normalize(name); + const uri = lbryuri.build({ contentName: name, channelName: channel }); this.setState({ rawName: rawName, name: name, uri, }); - this.props.resolveUri(uri); + if (this.resolveUriTimeout) { + clearTimeout(this.resolveUriTimeout); + this.resolveUriTimeout = undefined; + } + const resolve = () => this.props.resolveUri(uri); + + this.resolveUriTimeout = setTimeout(resolve.bind(this), 500, { + once: true, + }); } handleBidChange(event) { @@ -302,40 +312,12 @@ class PublishPage extends React.PureComponent { }); } - handleChannelChange(event) { - const channel = event.target.value; - + handleChannelChange(channelName) { this.setState({ - channel: channel, - }); - } - - handleNewChannelNameChange(event) { - const newChannelName = event.target.value.startsWith("@") - ? event.target.value - : "@" + event.target.value; - - if ( - newChannelName.length > 1 && - !lbryuri.isValidName(newChannelName.substr(1), false) - ) { - this.refs.newChannelName.showError( - __("LBRY channel names must contain only letters, numbers and dashes.") - ); - return; - } else { - this.refs.newChannelName.clearError(); - } - - this.setState({ - newChannelName: newChannelName, - }); - } - - handleNewChannelBidChange(event) { - this.setState({ - newChannelBid: event.target.value, + channel: channelName, }); + const nameChanged = () => this.nameChanged(this.state.rawName); + setTimeout(nameChanged.bind(this), 500, { once: true }); } handleTOSChange(event) { @@ -413,19 +395,26 @@ class PublishPage extends React.PureComponent { getNameBidHelpText() { if ( this.state.uri && - this.props.resolvingUris.indexOf(this.state.uri) !== -1 + this.props.resolvingUris.indexOf(this.state.uri) !== -1 && + this.claim() === undefined ) { return ; } else if (!this.state.name) { return __("Select a URL for this publish."); } else if (!this.claim()) { return __("This URL is unused."); - } else if (this.myClaimExists()) { - return __( - "You have already used this URL. Publishing to it again will update your previous publish." + } else if (this.myClaimExists() && !this.state.prefillDone) { + return ( + + {__("You already have a claim with this name.")}{" "} + this.handlePrefillClicked()} + /> + ); - } else if (this.state.topClaimValue) { - if (this.state.topClaimValue === 1) { + } else if (this.claim()) { + if (this.topClaimValue() === 1) { return ( {__( @@ -439,7 +428,7 @@ class PublishPage extends React.PureComponent { {__( 'A deposit of at least "%s" credits is required to win "%s". However, you can still get a permanent URL for any amount.', - this.state.topClaimValue, + this.topClaimValue(), this.state.name )} @@ -709,77 +698,11 @@ class PublishPage extends React.PureComponent {

-
-
-

{__("Identity")}

-
- {__("Who created this content?")} -
-
-
- {this.props.fetchingChannels - ? - : { - this.handleChannelChange(event); - }} - value={this.state.channel} - > - - {this.props.channels.map(({ name }) => - - )} - - } -
- {this.state.channel == "new" - ?
- { - this.handleNewChannelNameChange(event); - }} - ref={newChannelName => { - this.refs.newChannelName = newChannelName; - }} - value={this.state.newChannelName} - /> - { - this.handleNewChannelBidChange(event); - }} - value={this.state.newChannelBid} - /> -
- { - this.handleCreateChannelClick(event); - }} - disabled={this.state.creatingChannel} - /> -
-
- : null} -
+
@@ -795,7 +718,9 @@ class PublishPage extends React.PureComponent {
1 && + !lbryuri.isValidName(newChannelName.substr(1), false) + ) { + this.refs.newChannelName.showError( + __("LBRY channel names must contain only letters, numbers and dashes.") + ); + return; + } else { + this.refs.newChannelName.clearError(); + } + + this.setState({ + newChannelName, + }); + } + + handleNewChannelBidChange(event) { + this.setState({ + newChannelBid: event.target.value, + }); + } + + handleCreateChannelClick(event) { + if (this.state.newChannelName.length < 5) { + this.refs.newChannelName.showError( + __("LBRY channel names must be at least 4 characters in length.") + ); + return; + } + + this.setState({ + creatingChannel: true, + }); + + const newChannelName = this.state.newChannelName; + const amount = parseFloat(this.state.newChannelBid); + this.setState({ + creatingChannel: true, + }); + const success = (() => { + this.setState({ + creatingChannel: false, + addingChannel: false, + channel: newChannelName, + }); + this.props.handleChannelChange(newChannelName); + }).bind(this); + const failure = (err => { + this.setState({ + creatingChannel: false, + }); + this.refs.newChannelName.showError( + __("Unable to create channel due to an internal error.") + ); + }).bind(this); + this.props.createChannel(newChannelName, amount).then(success, failure); + } + + render() { + const lbcInputHelp = __( + "This LBC remains yours and the deposit can be undone at any time." + ); + + const { fetchingChannels, channels } = this.props; + + let channelContent = []; + if (channels.length > 0) { + channelContent.push( + + + {this.props.channels.map(({ name }) => + + )} + + + ); + if (fetchingChannels) { + channelContent.push( + + ); + } + } else if (fetchingChannels) { + channelContent.push( + + ); + } + + return ( +
+
+

{__("Identity")}

+
+ {__("Who created this content?")} +
+
+
+ {channelContent} +
+ {this.state.addingChannel && +
+ { + this.handleNewChannelNameChange(event); + }} + value={this.state.newChannelName} + /> + +
+ +
+
} +
+ ); + } +} + export default PublishPage; diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index f71e2cadd..75bfc4a52 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -15,7 +15,13 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { byUri[uri] = claim.claim_id; } else if (claim === undefined && certificate !== undefined) { byId[certificate.claim_id] = certificate; - byUri[uri] = certificate.claim_id; + // Don't point URI at the channel certificate unless it actually is + // a channel URI. This is brittle. + if (!uri.split(certificate.name)[1].match(/\//)) { + byUri[uri] = certificate.claim_id; + } else { + byUri[uri] = null; + } } else { byUri[uri] = null; } @@ -108,6 +114,34 @@ reducers[types.ABANDON_CLAIM_COMPLETED] = function(state, action) { }); }; +reducers[types.CREATE_CHANNEL_COMPLETED] = function(state, action) { + const { channelClaim } = action.data; + const byId = Object.assign({}, state.byId); + const myChannelClaims = new Set(state.myChannelClaims); + + byId[channelClaim.claim_id] = channelClaim; + myChannelClaims.add(channelClaim.claim_id); + + return Object.assign({}, state, { + byId, + myChannelClaims, + }); +}; + +reducers[types.PUBLISH_COMPLETED] = function(state, action) { + const { claim } = action.data; + const byId = Object.assign({}, state.byId); + const myClaims = new Set(state.myClaims); + + byId[claim.claim_id] = claim; + myClaims.add(claim.claim_id); + + return Object.assign({}, state, { + byId, + myClaims, + }); +}; + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index 0f6b7a63d..fe6979045 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -12,8 +12,9 @@ reducers[types.FILE_LIST_STARTED] = function(state, action) { reducers[types.FILE_LIST_COMPLETED] = function(state, action) { const { fileInfos } = action.data; - const newByOutpoint = Object.assign({}, state.byOutpoint); + const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); + fileInfos.forEach(fileInfo => { const { outpoint } = fileInfo; @@ -23,6 +24,7 @@ reducers[types.FILE_LIST_COMPLETED] = function(state, action) { return Object.assign({}, state, { isFileListPending: false, byOutpoint: newByOutpoint, + pendingByOutpoint, }); }; @@ -136,6 +138,61 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { }); }; +reducers[types.PUBLISH_STARTED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); + + pendingByOutpoint[pendingPublish.outpoint] = pendingPublish; + + return Object.assign({}, state, { + pendingByOutpoint, + }); +}; + +reducers[types.PUBLISH_COMPLETED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); + + delete pendingByOutpoint[pendingPublish.outpoint]; + + return Object.assign({}, state, { + pendingByOutpoint, + }); +}; + +reducers[types.PUBLISH_FAILED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); + + delete pendingByOutpoint[pendingPublish.outpoint]; + + return Object.assign({}, state, { + pendingByOutpoint, + }); +}; + +// reducers[types.PUBLISH_COMPLETED] = function(state, action) { +// const { claim } = action.data; +// const uri = lbryuri.build({ +// txid: claim.txId +// }) +// const newPendingPublish = { +// name, +// channel_name, +// claim_id: "pending_claim_" + uri, +// txid: "pending_" + uri, +// nout: 0, +// outpoint: "pending_" + uri + ":0", +// time: Date.now(), +// }; +// const fileInfos = Object.assign({}, state.fileInfos) +// fileInfos[newPendingPublish.outpoint] = newPendingPublish + +// return Object.assign({}, state, { +// fileInfos, +// }) +// } + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 552368f7e..5b0e4941b 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -69,6 +69,11 @@ export const makeSelectLoadingForUri = () => { return createSelector(selectLoadingForUri, loading => !!loading); }; +export const selectFileInfosPendingPublish = createSelector( + _selectState, + state => Object.values(state.pendingByOutpoint || {}) +); + export const selectFileInfosDownloaded = createSelector( selectFileInfosByOutpoint, selectMyClaimsOutpoints, @@ -87,24 +92,17 @@ export const selectFileInfosDownloaded = createSelector( } ); -export const selectFileInfosPendingPublish = createSelector( - _selectState, - state => { - return lbry.getPendingPublishes(); - } -); - export const selectFileInfosPublished = createSelector( selectFileInfosByOutpoint, - selectFileInfosPendingPublish, selectMyClaimsOutpoints, - (byOutpoint, pendingFileInfos, outpoints) => { + selectFileInfosPendingPublish, + (byOutpoint, outpoints, pendingPublish) => { const fileInfos = []; outpoints.forEach(outpoint => { const fileInfo = byOutpoint[outpoint]; if (fileInfo) fileInfos.push(fileInfo); }); - return [...fileInfos, ...pendingFileInfos]; + return fileInfos; } ); @@ -133,7 +131,6 @@ export const selectFileInfosByUri = createSelector( if (fileInfo) fileInfos[uri] = fileInfo; } }); - return fileInfos; } ); diff --git a/ui/js/store.js b/ui/js/store.js index 1bb5ec058..8e6c11949 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -92,12 +92,16 @@ const saveClaimsFilter = createFilter("claims", [ "myClaims", "myChannelClaims", ]); +const saveFileInfosFilter = createFilter("fileInfo", [ + "fileInfos", + "pendingByOutpoint", +]); const persistOptions = { - whitelist: ["claims"], + whitelist: ["claims", "fileInfo"], // Order is important. Needs to be compressed last or other transforms can't // read the data - transforms: [saveClaimsFilter, compressor], + transforms: [saveClaimsFilter, saveFileInfosFilter, compressor], debounce: 1000, storage: localForage, }; diff --git a/ui/yarn.lock b/ui/yarn.lock index 78dcb8080..cb2ca85fb 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -52,6 +52,15 @@ ajv@^4.7.0, ajv@^4.9.1: co "^4.6.0" json-stable-stringify "^1.0.1" +ajv@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.0.tgz#c1735024c5da2ef75cc190713073d44f098bf486" + dependencies: + co "^4.6.0" + fast-deep-equal "^0.1.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -60,6 +69,10 @@ align-text@^0.1.1, align-text@^0.1.3: longest "^1.0.1" repeat-string "^1.5.2" +alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -84,6 +97,10 @@ ansicolors@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" +any-promise@^1.0.0, any-promise@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + anymatch@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" @@ -193,6 +210,10 @@ ast-types@0.8.15: version "0.8.15" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.15.tgz#8eef0827f04dff0ec8857ba925abe3fea6194e52" +ast-types@0.9.6: + version "0.9.6" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" + async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -223,6 +244,17 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" +autoprefixer@^6.3.1: + version "6.7.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" + dependencies: + browserslist "^1.7.6" + caniuse-db "^1.0.30000634" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^5.2.16" + postcss-value-parser "^3.2.3" + aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" @@ -252,7 +284,7 @@ babel-cli@^6.24.1: optionalDependencies: chokidar "^1.6.1" -babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: +babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" dependencies: @@ -887,6 +919,10 @@ babylon@^6.11.5, babylon@^6.17.2: version "6.17.4" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" +balanced-match@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -895,6 +931,10 @@ base62@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/base62/-/base62-0.1.1.tgz#7b4174c2f94449753b11c2651c083da841a7b084" +base62@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.0.tgz#31e7e560dc846c9f44c1a531df6514da35474157" + base64-js@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" @@ -1024,6 +1064,13 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" +browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: + version "1.7.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" + dependencies: + caniuse-db "^1.0.30000639" + electron-to-chromium "^1.2.7" + buffer-indexof@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982" @@ -1081,6 +1128,19 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" +caniuse-api@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" + dependencies: + browserslist "^1.3.6" + caniuse-db "^1.0.30000529" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: + version "1.0.30000694" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000694.tgz#02009f4f82d2f0126e4c691b7cd5adb351935c01" + cardinal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-1.0.0.tgz#50e21c1b0aa37729f9377def196b5a9cec932ee9" @@ -1138,16 +1198,32 @@ circular-json@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" +clap@^1.0.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.0.tgz#59c90fe3e137104746ff19469a27a634ff68c857" + dependencies: + chalk "^1.1.3" + cli-cursor@^1.0.1, cli-cursor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" dependencies: restore-cursor "^1.0.1" +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + cli-spinners@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" +cli-spinners@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.0.0.tgz#ef987ed3d48391ac3dab9180b406a742180d6e6a" + cli-table@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" @@ -1196,21 +1272,73 @@ co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" +coa@~1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.3.tgz#1b54a5e1dcf77c990455d4deea98c564416dc893" + dependencies: + q "^1.1.2" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" +codemirror-spell-checker@*: + version "1.1.2" + resolved "https://registry.yarnpkg.com/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz#1c660f9089483ccb5113b9ba9ca19c3f4993371e" + dependencies: + typo-js "*" + +codemirror@*: + version "5.27.2" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.27.2.tgz#a292d42f079d5b98c68c3146fab99844f3d8776c" + +color-convert@^1.3.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" + dependencies: + color-name "^1.1.1" + +color-name@^1.0.0, color-name@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" + +color-string@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" + dependencies: + color-name "^1.0.0" + +color@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" + dependencies: + clone "^1.0.2" + color-convert "^1.3.0" + color-string "^0.3.0" + +colormin@^1.0.5: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" + dependencies: + color "^0.11.0" + css-color-names "0.0.4" + has "^1.0.1" + colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" +colors@^1.1.2, colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" dependencies: delayed-stream "~1.0.0" -commander@^2.8.1, commander@^2.9.0: +commander@^2.5.0, commander@^2.8.1, commander@^2.9.0: version "2.10.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.10.0.tgz#e1f5d3245de246d1a5ca04702fa1ad1bd7e405fe" dependencies: @@ -1220,6 +1348,38 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" +commoner@^0.10.1: + version "0.10.8" + resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" + dependencies: + commander "^2.5.0" + detective "^4.3.1" + glob "^5.0.15" + graceful-fs "^4.1.2" + iconv-lite "^0.4.5" + mkdirp "^0.5.0" + private "^0.1.6" + q "^1.1.2" + recast "^0.11.17" + +commonmark-react-renderer@^4.2.4: + version "4.3.3" + resolved "https://registry.yarnpkg.com/commonmark-react-renderer/-/commonmark-react-renderer-4.3.3.tgz#9c4bca138bc83287bae792ccf133738be9cbc6fa" + dependencies: + in-publish "^2.0.0" + lodash.assign "^4.2.0" + lodash.isplainobject "^4.0.6" + pascalcase "^0.1.1" + xss-filters "^1.2.6" + +commonmark@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.24.0.tgz#b80de0182c546355643aa15db12bfb282368278f" + dependencies: + entities "~ 1.1.1" + mdurl "~ 1.0.1" + string.prototype.repeat "^0.2.0" + compressible@~2.0.8: version "2.0.10" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd" @@ -1396,6 +1556,85 @@ crypto-browserify@^3.11.0: public-encrypt "^4.0.0" randombytes "^2.0.0" +css-color-names@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + +css-loader@^0.28.4: + version "0.28.4" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.4.tgz#6cf3579192ce355e8b38d5f42dd7a1f2ec898d0f" + dependencies: + babel-code-frame "^6.11.0" + css-selector-tokenizer "^0.7.0" + cssnano ">=2.6.1 <4" + icss-utils "^2.1.0" + loader-utils "^1.0.2" + lodash.camelcase "^4.3.0" + object-assign "^4.0.1" + postcss "^5.0.6" + postcss-modules-extract-imports "^1.0.0" + postcss-modules-local-by-default "^1.0.1" + postcss-modules-scope "^1.0.0" + postcss-modules-values "^1.1.0" + postcss-value-parser "^3.3.0" + source-list-map "^0.1.7" + +css-selector-tokenizer@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" + dependencies: + cssesc "^0.1.0" + fastparse "^1.1.1" + regexpu-core "^1.0.0" + +cssesc@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" + +"cssnano@>=2.6.1 <4": + version "3.10.0" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" + dependencies: + autoprefixer "^6.3.1" + decamelize "^1.1.2" + defined "^1.0.0" + has "^1.0.1" + object-assign "^4.0.1" + postcss "^5.0.14" + postcss-calc "^5.2.0" + postcss-colormin "^2.1.8" + postcss-convert-values "^2.3.4" + postcss-discard-comments "^2.0.4" + postcss-discard-duplicates "^2.0.1" + postcss-discard-empty "^2.0.1" + postcss-discard-overridden "^0.1.1" + postcss-discard-unused "^2.2.1" + postcss-filter-plugins "^2.0.0" + postcss-merge-idents "^2.1.5" + postcss-merge-longhand "^2.0.1" + postcss-merge-rules "^2.0.3" + postcss-minify-font-values "^1.0.2" + postcss-minify-gradients "^1.0.1" + postcss-minify-params "^1.0.4" + postcss-minify-selectors "^2.0.4" + postcss-normalize-charset "^1.1.0" + postcss-normalize-url "^3.0.7" + postcss-ordered-values "^2.1.0" + postcss-reduce-idents "^2.2.2" + postcss-reduce-initial "^1.0.0" + postcss-reduce-transforms "^1.0.3" + postcss-svgo "^2.1.1" + postcss-unique-selectors "^2.0.2" + postcss-value-parser "^3.2.3" + postcss-zindex "^2.0.1" + +csso@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" + dependencies: + clap "^1.0.9" + source-map "^0.5.3" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -1432,7 +1671,7 @@ debug@2.6.7: dependencies: ms "2.0.0" -debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.6.8: +debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.5.1, debug@^2.6.3, debug@^2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: @@ -1471,6 +1710,10 @@ define-properties@^1.1.2: foreach "^2.0.5" object-keys "^1.0.8" +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + del@^2.0.2: version "2.2.2" resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" @@ -1527,6 +1770,13 @@ detect-node@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" +detective@^4.3.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-4.5.0.tgz#6e5a8c6b26e6c7a254b1c6b6d7490d98ec91edd1" + dependencies: + acorn "^4.0.3" + defined "^1.0.0" + diffie-hellman@^5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" @@ -1580,6 +1830,24 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" +electron-rebuild@^1.5.11: + version "1.5.11" + resolved "https://registry.yarnpkg.com/electron-rebuild/-/electron-rebuild-1.5.11.tgz#6ea660deb546a516e7efaa81cd5985d5664f245c" + dependencies: + colors "^1.1.2" + debug "^2.6.3" + fs-promise "^2.0.2" + node-abi "^2.0.0" + node-gyp "^3.6.0" + ora "^1.2.0" + rimraf "^2.6.1" + spawn-rx "^2.0.10" + yargs "^7.0.2" + +electron-to-chromium@^1.2.7: + version "1.3.14" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.14.tgz#64af0f9efd3c3c6acd57d71f83b49ca7ee9c4b43" + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -1637,6 +1905,17 @@ enhanced-resolve@~0.9.0: memory-fs "^0.2.0" tapable "^0.1.8" +"entities@~ 1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +envify@^3.0.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/envify/-/envify-3.4.1.tgz#d7122329e8df1688ba771b12501917c9ce5cbce8" + dependencies: + jstransform "^11.0.3" + through "~2.3.4" + errno@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" @@ -1862,6 +2141,10 @@ espree@^3.4.0: acorn "^5.0.1" acorn-jsx "^3.0.0" +esprima-fb@^15001.1.0-dev-harmony-fb: + version "15001.1.0-dev-harmony-fb" + resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" + esprima-fb@~15001.1001.0-dev-harmony-fb: version "15001.1001.0-dev-harmony-fb" resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659" @@ -1870,7 +2153,11 @@ esprima-fb@~3001.0001.0000-dev-harmony-fb, esprima-fb@~3001.1.0-dev-harmony-fb: version "3001.1.0-dev-harmony-fb" resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz#b77d37abcd38ea0b77426bb8bc2922ce6b426411" -esprima@^3.1.1: +esprima@^2.6.0: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^3.1.1, esprima@~3.1.0: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -2018,10 +2305,18 @@ falafel@^1.0.1: isarray "0.0.1" object-keys "^1.0.6" +fast-deep-equal@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-0.1.0.tgz#5c6f4599aba6b333ee3342e2ed978672f1001f8d" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" +fastparse@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" + faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -2034,6 +2329,16 @@ faye-websocket@~0.11.0: dependencies: websocket-driver ">=0.5.1" +fbjs@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.6.1.tgz#9636b7705f5ba9684d44b72f78321254afc860f7" + dependencies: + core-js "^1.0.0" + loose-envify "^1.0.0" + promise "^7.0.3" + ua-parser-js "^0.7.9" + whatwg-fetch "^0.9.0" + fbjs@^0.8.9: version "0.8.12" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" @@ -2120,6 +2425,10 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" +flatten@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -2161,6 +2470,22 @@ from2@^2.3.0: inherits "^2.0.1" readable-stream "^2.0.0" +fs-extra@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-2.1.2.tgz#046c70163cef9aad46b0e4a7fa467fb22d71de35" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + +fs-promise@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/fs-promise/-/fs-promise-2.0.3.tgz#f64e4f854bcf689aa8bddcba268916db3db46854" + dependencies: + any-promise "^1.3.0" + fs-extra "^2.0.0" + mz "^2.6.0" + thenify-all "^1.6.0" + fs-readdir-recursive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560" @@ -2263,6 +2588,16 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" +glob@^5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@~7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -2307,7 +2642,7 @@ globule@^1.0.0: lodash "~4.17.4" minimatch "~3.0.2" -graceful-fs@^4.1.2, graceful-fs@^4.1.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -2344,6 +2679,10 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2412,6 +2751,10 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" +html-comment-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" + html-entities@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" @@ -2475,10 +2818,20 @@ i18n-extract@^0.4.4: gettext-parser "^1.2.0" glob "^7.1.1" -iconv-lite@~0.4.13: +iconv-lite@^0.4.5, iconv-lite@~0.4.13: version "0.4.18" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" +icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + +icss-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962" + dependencies: + postcss "^6.0.1" + ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" @@ -2509,6 +2862,10 @@ indent-string@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.1.0.tgz#08ff4334603388399b329e6b9538dc7a3cf5de7d" +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" @@ -2589,6 +2946,10 @@ ipaddr.js@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -2714,6 +3075,10 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" @@ -2746,6 +3111,12 @@ is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" +is-svg@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" + dependencies: + html-comment-regex "^1.1.0" + is-symbol@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" @@ -2787,7 +3158,7 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" -js-base64@^2.1.8: +js-base64@^2.1.8, js-base64@^2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" @@ -2802,6 +3173,13 @@ js-yaml@^3.4.3, js-yaml@^3.5.1: argparse "^1.0.7" esprima "^3.1.1" +js-yaml@~3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -2822,6 +3200,10 @@ json-loader@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -2844,6 +3226,12 @@ json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -2861,6 +3249,16 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.3.6" +jstransform@^11.0.3: + version "11.0.3" + resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223" + dependencies: + base62 "^1.1.0" + commoner "^0.10.1" + esprima-fb "^15001.1.0-dev-harmony-fb" + object-assign "^2.0.0" + source-map "^0.4.2" + jstransform@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-3.0.0.tgz#a2591ab6cee8d97bf3be830dbfa2313b87cd640b" @@ -3082,6 +3480,10 @@ lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + lodash.chunk@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc" @@ -3121,6 +3523,10 @@ lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -3129,6 +3535,10 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + lodash.mergewith@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" @@ -3141,6 +3551,10 @@ lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + lodash.unset@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed" @@ -3194,6 +3608,10 @@ lz-string@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" +macaddress@^0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" + map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -3208,10 +3626,18 @@ marked-terminal@^1.6.2: lodash.assign "^4.2.0" node-emoji "^1.4.1" -marked@^0.3.6: +marked@*, marked@^0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7" +math-expression-evaluator@^1.2.14: + version "1.2.17" + resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" + +"mdurl@~ 1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -3308,6 +3734,10 @@ mime@^1.3.4: version "1.3.6" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" @@ -3316,7 +3746,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" -minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -3330,7 +3760,7 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -3381,6 +3811,14 @@ mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" +mz@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.6.0.tgz#c8b8521d958df0a4f2768025db69c719ee4ef1ce" + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + nan@^2.3.0, nan@^2.3.2: version "2.5.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" @@ -3397,6 +3835,10 @@ next-event@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/next-event/-/next-event-1.0.0.tgz#e7778acde2e55802e0ad1879c39cf6f75eda61d8" +node-abi@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.0.3.tgz#0ca67e5e667b8e1343549ca17153a815d0bbfdaa" + node-emoji@^1.4.1: version "1.5.1" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.5.1.tgz#fd918e412769bf8c448051238233840b2aff16a1" @@ -3414,7 +3856,7 @@ node-forge@0.6.33: version "0.6.33" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" -node-gyp@^3.3.1: +node-gyp@^3.3.1, node-gyp@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60" dependencies: @@ -3573,6 +4015,19 @@ normalize-path@^2.0.1: dependencies: remove-trailing-separator "^1.0.1" +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + +normalize-url@^1.4.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + npm-path@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.3.tgz#15cff4e1c89a38da77f56f6055b24f975dfb2bbe" @@ -3602,6 +4057,10 @@ npm-which@^3.0.1: gauge "~2.7.3" set-blocking "~2.0.0" +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -3610,6 +4069,10 @@ oauth-sign@~0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +object-assign@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -3661,6 +4124,12 @@ onetime@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + opn@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95" @@ -3695,6 +4164,15 @@ ora@^0.2.3: cli-spinners "^0.1.2" object-assign "^4.0.1" +ora@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-1.3.0.tgz#80078dd2b92a934af66a3ad72a5b910694ede51a" + dependencies: + chalk "^1.1.1" + cli-cursor "^2.1.0" + cli-spinners "^1.0.0" + log-symbols "^1.0.2" + original@>=0.0.5: version "1.0.0" resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b" @@ -3785,6 +4263,10 @@ parseurl@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" @@ -3887,10 +4369,260 @@ portfinder@^1.0.9: debug "^2.2.0" mkdirp "0.5.x" +postcss-calc@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" + dependencies: + postcss "^5.0.2" + postcss-message-helpers "^2.0.0" + reduce-css-calc "^1.2.6" + +postcss-colormin@^2.1.8: + version "2.2.2" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" + dependencies: + colormin "^1.0.5" + postcss "^5.0.13" + postcss-value-parser "^3.2.3" + +postcss-convert-values@^2.3.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" + dependencies: + postcss "^5.0.11" + postcss-value-parser "^3.1.2" + +postcss-discard-comments@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" + dependencies: + postcss "^5.0.14" + +postcss-discard-duplicates@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" + dependencies: + postcss "^5.0.4" + +postcss-discard-empty@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" + dependencies: + postcss "^5.0.14" + +postcss-discard-overridden@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" + dependencies: + postcss "^5.0.16" + +postcss-discard-unused@^2.2.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" + dependencies: + postcss "^5.0.14" + uniqs "^2.0.0" + +postcss-filter-plugins@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c" + dependencies: + postcss "^5.0.4" + uniqid "^4.0.0" + +postcss-merge-idents@^2.1.5: + version "2.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" + dependencies: + has "^1.0.1" + postcss "^5.0.10" + postcss-value-parser "^3.1.1" + +postcss-merge-longhand@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" + dependencies: + postcss "^5.0.4" + +postcss-merge-rules@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" + dependencies: + browserslist "^1.5.2" + caniuse-api "^1.5.2" + postcss "^5.0.4" + postcss-selector-parser "^2.2.2" + vendors "^1.0.0" + +postcss-message-helpers@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" + +postcss-minify-font-values@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" + dependencies: + object-assign "^4.0.1" + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-minify-gradients@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" + dependencies: + postcss "^5.0.12" + postcss-value-parser "^3.3.0" + +postcss-minify-params@^1.0.4: + version "1.2.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.2" + postcss-value-parser "^3.0.2" + uniqs "^2.0.0" + +postcss-minify-selectors@^2.0.4: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" + dependencies: + alphanum-sort "^1.0.2" + has "^1.0.1" + postcss "^5.0.14" + postcss-selector-parser "^2.0.0" + +postcss-modules-extract-imports@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85" + dependencies: + postcss "^6.0.1" + +postcss-modules-local-by-default@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-values@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + +postcss-normalize-charset@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" + dependencies: + postcss "^5.0.5" + +postcss-normalize-url@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^1.4.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + +postcss-ordered-values@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.1" + +postcss-reduce-idents@^2.2.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-reduce-initial@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" + dependencies: + postcss "^5.0.4" + +postcss-reduce-transforms@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" + dependencies: + has "^1.0.1" + postcss "^5.0.8" + postcss-value-parser "^3.0.1" + +postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^2.1.1: + version "2.1.6" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" + dependencies: + is-svg "^2.0.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + svgo "^0.7.0" + +postcss-unique-selectors@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" + +postcss-zindex@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" + dependencies: + has "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: + version "5.2.17" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.17.tgz#cf4f597b864d65c8a492b2eabe9d706c879c388b" + dependencies: + chalk "^1.1.3" + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.1: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.3.tgz#b7f565b3d956fbb8565ca7c1e239d0506e427d8b" + dependencies: + chalk "^1.1.3" + source-map "^0.5.6" + supports-color "^4.0.0" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" @@ -3915,13 +4647,13 @@ progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" -promise@^7.1.1: +promise@^7.0.3, promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8: +prop-types@^15.5.1, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8: version "15.5.10" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" dependencies: @@ -3968,10 +4700,21 @@ punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" +q@^1.1.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" + qs@6.4.0, qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -4041,6 +4784,15 @@ react-dom@^15.4.0: object-assign "^4.1.0" prop-types "^15.5.10" +react-markdown@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-2.5.0.tgz#b1c61904fee5895886803bd9df7db23c3dc3a89e" + dependencies: + commonmark "^0.24.0" + commonmark-react-renderer "^4.2.4" + in-publish "^2.0.0" + prop-types "^15.5.1" + react-modal@^1.5.2: version "1.9.7" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-1.9.7.tgz#07ef56790b953e3b98ef1e2989e347983c72871d" @@ -4064,6 +4816,20 @@ react-redux@^5.0.3: loose-envify "^1.1.0" prop-types "^15.5.10" +react-simplemde-editor@^3.6.11: + version "3.6.11" + resolved "https://registry.yarnpkg.com/react-simplemde-editor/-/react-simplemde-editor-3.6.11.tgz#4b9e136f6d4d00218e8ece3d87949e23b14e21dc" + dependencies: + react "^0.14.2" + simplemde "^1.11.2" + +react@^0.14.2: + version "0.14.9" + resolved "https://registry.yarnpkg.com/react/-/react-0.14.9.tgz#9110a6497c49d44ba1c0edd317aec29c2e0d91d1" + dependencies: + envify "^3.0.0" + fbjs "^0.6.1" + react@^15.4.0: version "15.6.1" resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df" @@ -4151,6 +4917,15 @@ recast@^0.10.1: private "~0.1.5" source-map "~0.5.0" +recast@^0.11.17: + version "0.11.23" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" + dependencies: + ast-types "0.9.6" + esprima "~3.1.0" + private "~0.1.5" + source-map "~0.5.0" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -4170,6 +4945,20 @@ redeyed@~1.0.0: dependencies: esprima "~3.0.0" +reduce-css-calc@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" + dependencies: + balanced-match "^0.4.2" + math-expression-evaluator "^1.2.14" + reduce-function-call "^1.0.1" + +reduce-function-call@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" + dependencies: + balanced-match "^0.4.2" + redux-action-buffer@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/redux-action-buffer/-/redux-action-buffer-1.1.0.tgz#9c692ab6532b042d0d43a9f01a48ada120fc941a" @@ -4242,6 +5031,14 @@ regex-cache@^0.4.2: is-equal-shallow "^0.1.3" is-primitive "^2.0.0" +regexpu-core@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + regexpu-core@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" @@ -4359,6 +5156,13 @@ restore-cursor@^1.0.1: exit-hook "^1.0.0" onetime "^1.0.0" +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -4392,7 +5196,7 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" -rxjs@^5.0.0-beta.11: +rxjs@^5.0.0-beta.11, rxjs@^5.1.1: version "5.4.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.1.tgz#b62f757f279445d265a18a58fb0a70dc90e91626" dependencies: @@ -4411,6 +5215,16 @@ sass-graph@^2.1.1: scss-tokenizer "^0.2.3" yargs "^7.0.0" +sax@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +schema-utils@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" + dependencies: + ajv "^5.0.0" + scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -4519,10 +5333,18 @@ shellwords@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.0.tgz#66afd47b6a12932d9071cbfd98a52e785cd0ba14" -signal-exit@^3.0.0: +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" +simplemde@^1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/simplemde/-/simplemde-1.11.2.tgz#a23a35d978d2c40ef07dec008c92f070d8e080e3" + dependencies: + codemirror "*" + codemirror-spell-checker "*" + marked "*" + slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" @@ -4555,14 +5377,20 @@ sockjs@0.3.18: faye-websocket "^0.10.0" uuid "^2.0.2" +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +source-list-map@^0.1.7, source-list-map@~0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" + source-list-map@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.2.tgz#9889019d1024cce55cdc069498337ef6186a11a1" -source-list-map@~0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" - source-map-support@^0.4.2: version "0.4.15" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" @@ -4585,6 +5413,14 @@ source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, sour version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" +spawn-rx@^2.0.10: + version "2.0.11" + resolved "https://registry.yarnpkg.com/spawn-rx/-/spawn-rx-2.0.11.tgz#65451ad65662801daea75549832a782de0048dbf" + dependencies: + debug "^2.5.1" + lodash.assign "^4.2.0" + rxjs "^5.1.1" + spdx-correct@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" @@ -4687,6 +5523,10 @@ stream-to-observable@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe" +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -4706,6 +5546,10 @@ string.prototype.codepointat@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz#6b26e9bd3afcaa7be3b4269b526de1b82000ac78" +string.prototype.repeat@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz#aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf" + string_decoder@^0.10.25, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -4750,16 +5594,41 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +style-loader@^0.18.2: + version "0.18.2" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.18.2.tgz#cc31459afbcd6d80b7220ee54b291a9fd66ff5eb" + dependencies: + loader-utils "^1.0.2" + schema-utils "^0.3.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.0, supports-color@^3.1.1: +supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: has-flag "^1.0.0" +supports-color@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.0.0.tgz#33a7c680aa512c9d03ef929cacbb974d203d2790" + dependencies: + has-flag "^2.0.0" + +svgo@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" + dependencies: + coa "~1.0.1" + colors "~1.1.2" + csso "~2.3.1" + js-yaml "~3.7.0" + mkdirp "~0.5.1" + sax "~1.2.1" + whet.extend "~0.9.9" + symbol-observable@^1.0.1, symbol-observable@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" @@ -4808,6 +5677,18 @@ text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" +thenify-all@^1.0.0, thenify-all@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.0" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" + dependencies: + any-promise "^1.0.0" + through2@^0.6.2, through2@^0.6.5: version "0.6.5" resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" @@ -4886,6 +5767,10 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +typo-js@*: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.0.3.tgz#54d8ebc7949f1a7810908b6002c6841526c99d5a" + ua-parser-js@^0.7.9: version "0.7.13" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.13.tgz#cd9dd2f86493b3f44dbeeef3780fda74c5ee14be" @@ -4920,6 +5805,20 @@ uint64be@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uint64be/-/uint64be-1.0.1.tgz#1f7154202f2a1b8af353871dda651bf34ce93e95" +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + +uniqid@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1" + dependencies: + macaddress "^0.2.8" + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -5002,6 +5901,10 @@ vary@~1.1.0, vary@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" +vendors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" + verror@1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" @@ -5172,6 +6075,14 @@ whatwg-fetch@>=0.10.0: version "2.0.3" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" +whatwg-fetch@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0" + +whet.extend@~0.9.9: + version "0.9.9" + resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" + which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -5227,6 +6138,10 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" +xss-filters@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/xss-filters/-/xss-filters-1.2.7.tgz#59fa1de201f36f2f3470dcac5f58ccc2830b0a9a" + "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -5269,7 +6184,7 @@ yargs@^6.0.0: y18n "^3.2.1" yargs-parser "^4.2.0" -yargs@^7.0.0: +yargs@^7.0.0, yargs@^7.0.2: version "7.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" dependencies: From b09d71ecffa1c4a43c074f526e667addc7739d3d Mon Sep 17 00:00:00 2001 From: Le Long Date: Thu, 15 Jun 2017 21:30:56 +0200 Subject: [PATCH 023/152] Markdown Support --- .gitignore | 1 + CHANGELOG.md | 7 +- ui/js/component/common.js | 2 + ui/js/component/fileCard/view.jsx | 9 +- ui/js/component/form.js | 24 ++- ui/js/page/filePage/view.jsx | 7 +- ui/js/page/publish/view.jsx | 272 ++++++++++++++++++----------- ui/package.json | 5 + ui/scss/component/_form-field.scss | 7 + 9 files changed, 224 insertions(+), 110 deletions(-) diff --git a/.gitignore b/.gitignore index c212707ad..233924d55 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ build/daemon.zip .vimrc package-lock.json +ui/yarn.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index bcbe49bfb..d8f65e2ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,14 +10,17 @@ Web UI version numbers should always match the corresponding version of LBRY App ### Added * Added option to release claim when deleting a file * Added transition to card hovers to smooth animation + * Support markdown makeup in claim description + * ### Changed - * + * Publishes now uses claims rather than files * ### Fixed * Fixed bug with download notice when switching window focus - * + * Fixed newly published files appearing twice + * Fixed unconfirmed published files missing channel name ### Deprecated * diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 1b48bc4df..57314bdd3 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -1,5 +1,7 @@ import React from "react"; +import ReactDOMServer from "react-dom/server"; import lbry from "../lbry.js"; +import ReactMarkdown from "react-markdown"; //component/icon.js export class Icon extends React.PureComponent { diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 42c0e5b68..256bc9014 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -1,7 +1,12 @@ import React from "react"; import lbryuri from "lbryuri.js"; import Link from "component/link"; -import { TruncatedText, Icon } from "component/common"; +import { + Thumbnail, + TruncatedText, + Icon, + TruncatedMarkdown, +} from "component/common"; import FilePrice from "component/filePrice"; import UriIndicator from "component/uriIndicator"; import NsfwOverlay from "component/nsfwOverlay"; @@ -94,7 +99,7 @@ class FileCard extends React.PureComponent { style={{ backgroundImage: "url('" + metadata.thumbnail + "')" }} />}
- {description} + {description}
diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 7ab78325c..6a65218e4 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -1,8 +1,9 @@ import React from "react"; import FileSelector from "./file-selector.js"; -import { Icon } from "./common.js"; +import SimpleMDE from "react-simplemde-editor"; +import style from "react-simplemde-editor/dist/simplemde.min.css"; -var formFieldCounter = 0, +let formFieldCounter = 0, formFieldFileSelectorTypes = ["file", "directory"], formFieldNestedLabelTypes = ["radio", "checkbox"]; @@ -24,6 +25,7 @@ export class FormField extends React.PureComponent { this._fieldRequiredText = __("This field is required"); this._type = null; this._element = null; + this._extraElementProps = {}; this.state = { isError: null, @@ -38,6 +40,12 @@ export class FormField extends React.PureComponent { } else if (this.props.type == "text-number") { this._element = "input"; this._type = "text"; + } else if (this.props.type == "SimpleMDE") { + this._element = SimpleMDE; + this._type = "textarea"; + this._extraElementProps.options = { + hideIcons: ["guide", "heading", "image", "fullscreen"], + }; } else if (formFieldFileSelectorTypes.includes(this.props.type)) { this._element = "input"; this._type = "hidden"; @@ -81,6 +89,8 @@ export class FormField extends React.PureComponent { getValue() { if (this.props.type == "checkbox") { return this.refs.field.checked; + } else if (this.props.type == "SimpleMDE") { + return this.refs.field.simplemde.value(); } else { return this.refs.field.value; } @@ -90,6 +100,10 @@ export class FormField extends React.PureComponent { return this.refs.field.options[this.refs.field.selectedIndex]; } + getOptions() { + return this.refs.field.options; + } + render() { // Pass all unhandled props to the field element const otherProps = Object.assign({}, this.props), @@ -106,7 +120,6 @@ export class FormField extends React.PureComponent { delete otherProps.className; delete otherProps.postfix; delete otherProps.prefix; - const element = ( {this.props.children} @@ -220,6 +234,10 @@ export class FormRow extends React.PureComponent { return this.refs.field.getSelectedElement(); } + getOptions() { + return this.refs.field.getOptions(); + } + focus() { this.refs.field.focus(); } diff --git a/ui/js/page/filePage/view.jsx b/ui/js/page/filePage/view.jsx index adb478bdc..82ac64c57 100644 --- a/ui/js/page/filePage/view.jsx +++ b/ui/js/page/filePage/view.jsx @@ -1,4 +1,5 @@ import React from "react"; +import ReactMarkdown from "react-markdown"; import lbry from "lbry.js"; import lbryuri from "lbryuri.js"; import Video from "component/video"; @@ -119,7 +120,11 @@ class FilePage extends React.PureComponent {
- {metadata && metadata.description} +
{metadata diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index 12b38cbd7..b76d018a1 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -5,13 +5,16 @@ import { FormField, FormRow } from "component/form.js"; import Link from "component/link"; import rewards from "rewards"; import Modal from "component/modal"; +import Notice from "component/notice"; import { BusyMessage } from "component/common"; class PublishPage extends React.PureComponent { constructor(props) { super(props); - this._requiredFields = ["meta_title", "name", "bid", "tos_agree"]; + this._requiredFields = ["name", "bid", "meta_title", "tosAgree"]; + + this._defaultCopyrightNotice = "All rights reserved."; this.state = { rawName: "", @@ -23,11 +26,17 @@ class PublishPage extends React.PureComponent { channel: "anonymous", newChannelName: "@", newChannelBid: 10, - myClaimValue: 0.0, - myClaimMetadata: null, - copyrightNotice: "", + meta_title: "", + meta_thumbnail: "", + meta_description: "", + meta_language: "en", + meta_nsfw: "0", + licenseType: "", + copyrightNotice: this._defaultCopyrightNotice, otherLicenseDescription: "", otherLicenseUrl: "", + tosAgree: false, + prefillDone: false, uploadProgress: 0.0, uploaded: false, errorMessage: null, @@ -80,36 +89,18 @@ class PublishPage extends React.PureComponent { return; } - if (this.state.nameIsMine) { - // Pre-populate with existing metadata - var metadata = Object.assign({}, this.state.myClaimMetadata); - if (this.refs.file.getValue() !== "") { - delete metadata.sources; - } - } else { - var metadata = {}; - } + let metadata = {}; - for (let metaField of [ - "title", - "description", - "thumbnail", - "license", - "license_url", - "language", - ]) { - var value = this.refs["meta_" + metaField].getValue(); - if (value !== "") { + for (let metaField of ["title", "description", "thumbnail", "language"]) { + const value = this.state["meta_" + metaField]; + if (value) { metadata[metaField] = value; } } - metadata.nsfw = parseInt(this.refs.meta_nsfw.getValue()) === 1; - - const licenseUrl = this.refs.meta_license_url.getValue(); - if (licenseUrl) { - metadata.license_url = licenseUrl; - } + metadata.license = this.getLicense(); + metadata.licenseUrl = this.getLicenseUrl(); + metadata.nsfw = !!parseInt(this.state.meta_nsfw); var doPublish = () => { var publishArgs = { @@ -203,6 +194,8 @@ class PublishPage extends React.PureComponent { } myClaimInfo() { + const { name } = this.state; + return Object.values(this.props.myClaims).find( claim => claim.name === name ); @@ -240,6 +233,7 @@ class PublishPage extends React.PureComponent { this.setState({ rawName: rawName, name: name, + prefillDone: false, uri, }); @@ -254,6 +248,43 @@ class PublishPage extends React.PureComponent { }); } + handlePrefillClicked() { + const {license, licenseUrl, title, thumbnail, description, + language, nsfw} = this.myClaimInfo().value.stream.metadata; + + let newState = { + meta_title: title, + meta_thumbnail: thumbnail, + meta_description: description, + meta_language: language, + meta_nsfw: nsfw, + }; + + if (license == this._defaultCopyrightNotice) { + newState.licenseType = "copyright"; + newState.copyrightNotice = this._defaultCopyrightNotice; + } else { + // If the license URL or description matches one of the drop-down options, use that + let licenseType = "other"; // Will be overridden if we find a match + for (let option of this._meta_license.getOptions()) { + if ( + option.getAttribute("data-url") === licenseUrl || + option.text === license + ) { + licenseType = option.value; + } + } + + if (licenseType == "other") { + newState.otherLicenseDescription = license; + newState.otherLicenseUrl = licenseUrl; + } + newState.licenseType = licenseType; + } + + this.setState(newState); + } + handleBidChange(event) { this.setState({ bid: event.target.value, @@ -278,20 +309,21 @@ class PublishPage extends React.PureComponent { }); } - handleLicenseChange(event) { - var licenseType = event.target.options[ - event.target.selectedIndex - ].getAttribute("data-license-type"); - var newState = { - copyrightChosen: licenseType == "copyright", - otherLicenseChosen: licenseType == "other", - }; + handleMetadataChange(event) { + /** + * This function is used for all metadata inputs that store the final value directly into state. + * The only exceptions are inputs related to license description and license URL, which require + * more complex logic and the final value is determined at submit time. + */ + this.setState({ + ["meta_" + event.target.name]: event.target.value, + }); + } - if (licenseType == "copyright") { - newState.copyrightNotice = __("All rights reserved."); - } - - this.setState(newState); + handleLicenseTypeChange(event) { + this.setState({ + licenseType: event.target.value, + }); } handleCopyrightNoticeChange(event) { @@ -322,7 +354,7 @@ class PublishPage extends React.PureComponent { handleTOSChange(event) { this.setState({ - TOSAgreed: event.target.checked, + tosAgree: event.target.checked, }); } @@ -366,16 +398,25 @@ class PublishPage extends React.PureComponent { ); } + getLicense() { + switch (this.state.licenseType) { + case "copyright": + return this.state.copyrightNotice; + case "other": + return this.state.otherLicenseDescription; + default: + return this._meta_license.getSelectedElement().text; + } + } + getLicenseUrl() { - if (!this.refs.meta_license) { - return ""; - } else if (this.state.otherLicenseChosen) { - return this.state.otherLicenseUrl; - } else { - return ( - this.refs.meta_license.getSelectedElement().getAttribute("data-url") || - "" - ); + switch (this.state.licenseType) { + case "copyright": + return ""; + case "other": + return this.state.otherLicenseUrl; + default: + return this._meta_license.getSelectedElement().getAttribute("data-url"); } } @@ -398,7 +439,7 @@ class PublishPage extends React.PureComponent { this.props.resolvingUris.indexOf(this.state.uri) !== -1 && this.claim() === undefined ) { - return ; + return __("Checking..."); } else if (!this.state.name) { return __("Select a URL for this publish."); } else if (!this.claim()) { @@ -482,43 +523,55 @@ class PublishPage extends React.PureComponent { } /> - {!this.state.hasFile - ? "" + {!this.state.hasFile && !this.myClaimExists() + ? null :
{ + this.handleMetadataChange(event); + }} />
{ + this.handleMetadataChange(event); + }} />
{ + this.handleMetadataChange(event); + }} />
{ + this.handleMetadataChange(event); + }} > @@ -533,9 +586,11 @@ class PublishPage extends React.PureComponent { { + this.handleMetadataChange(event); + }} > {/* */} @@ -583,8 +638,7 @@ class PublishPage extends React.PureComponent { placeholder="1.00" min="0.01" onChange={event => this.handleFeeAmountChange(event)} - /> - {" "} + />{" "} { @@ -605,66 +659,71 @@ class PublishPage extends React.PureComponent { { + this._meta_license = row; + }} onChange={event => { - this.handleLicenseChange(event); + this.handleLicenseTypeChange(event); }} > - + - - - - - - - - - {this.state.copyrightChosen + + {this.state.licenseType == "copyright" ? : null} - {this.state.otherLicenseChosen + + {this.state.licenseType == "other" ? { - this.handleOtherLicenseDescriptionChange(); + this.handleOtherLicenseDescriptionChange(event); }} /> : null} - {this.state.otherLicenseChosen + + {this.state.licenseType == "other" ? { this.handleOtherLicenseUrlChange(event); }} @@ -730,6 +793,15 @@ class PublishPage extends React.PureComponent { }} helper={this.getNameBidHelpText()} /> + {this.myClaimExists() && !this.state.prefillDone + ? + {__("You already have a claim with this name.")}{" "} + this.handlePrefillClicked()} + /> + + : null}
{this.state.rawName ?
@@ -763,15 +835,11 @@ class PublishPage extends React.PureComponent { } type="checkbox" - name="tos_agree" - ref={field => { - this.refs.tos_agree = field; - }} + checked={this.state.tosAgree} onChange={event => { this.handleTOSChange(event); }} diff --git a/ui/package.json b/ui/package.json index d7aa3535a..4c4986ec2 100644 --- a/ui/package.json +++ b/ui/package.json @@ -29,8 +29,10 @@ "rc-progress": "^2.0.6", "react": "^15.4.0", "react-dom": "^15.4.0", + "react-markdown": "^2.5.0", "react-modal": "^1.5.2", "react-redux": "^5.0.3", + "react-simplemde-editor": "^3.6.11", "redux": "^3.6.0", "redux-action-buffer": "^1.1.0", "redux-logger": "^3.0.1", @@ -52,6 +54,8 @@ "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.18.0", + "electron-rebuild": "^1.5.11", + "css-loader": "^0.28.4", "eslint": "^3.10.2", "eslint-config-airbnb": "^13.0.0", "eslint-loader": "^1.6.1", @@ -64,6 +68,7 @@ "lint-staged": "^3.6.0", "node-loader": "^0.6.0", "prettier": "^1.4.2", + "style-loader": "^0.18.2", "webpack": "^2.6.1", "webpack-dev-server": "^2.4.4", "webpack-notifier": "^1.5.0", diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index 8fd86efef..f701ebe06 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -117,6 +117,9 @@ input[type="text"].input-copyable { border: $width-input-border solid $color-form-border; } } +.form-field--SimpleMDE { + display: block; +} .form-field__label { &[for] { cursor: pointer; } @@ -163,4 +166,8 @@ input[type="text"].input-copyable { } .form-field__helper { color: $color-help; +} + +.form-field__input.form-field__input-SimpleMDE .CodeMirror-scroll { + height: auto; } \ No newline at end of file From 7c5187c4e4d8e1cef4d6a478f12a475fcf47adcf Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 30 Jun 2017 15:45:54 +0700 Subject: [PATCH 024/152] Create publish form component, fix markdown editor, fix prefill --- ui/js/component/publishForm/index.js | 5 + .../publishForm/internal/channelSection.jsx | 179 +++ ui/js/component/publishForm/view.jsx | 920 ++++++++++++++ ui/js/page/publish/view.jsx | 1076 +---------------- 4 files changed, 1108 insertions(+), 1072 deletions(-) create mode 100644 ui/js/component/publishForm/index.js create mode 100644 ui/js/component/publishForm/internal/channelSection.jsx create mode 100644 ui/js/component/publishForm/view.jsx diff --git a/ui/js/component/publishForm/index.js b/ui/js/component/publishForm/index.js new file mode 100644 index 000000000..3e2d02b42 --- /dev/null +++ b/ui/js/component/publishForm/index.js @@ -0,0 +1,5 @@ +import React from "react"; +import { connect } from "react-redux"; +import PublishForm from "./view"; + +export default connect()(PublishForm); diff --git a/ui/js/component/publishForm/internal/channelSection.jsx b/ui/js/component/publishForm/internal/channelSection.jsx new file mode 100644 index 000000000..c0c4bf473 --- /dev/null +++ b/ui/js/component/publishForm/internal/channelSection.jsx @@ -0,0 +1,179 @@ +import React from "react"; +import lbryuri from "lbryuri"; +import { FormField, FormRow } from "component/form.js"; +import { BusyMessage } from "component/common"; + +class ChannelSection extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + newChannelName: "@", + newChannelBid: 10, + addingChannel: false, + }; + } + + handleChannelChange(event) { + const channel = event.target.value; + if (channel === "new") this.setState({ addingChannel: true }); + else { + this.setState({ addingChannel: false }); + this.props.handleChannelChange(event.target.value); + } + } + + handleNewChannelNameChange(event) { + const newChannelName = event.target.value.startsWith("@") + ? event.target.value + : "@" + event.target.value; + + if ( + newChannelName.length > 1 && + !lbryuri.isValidName(newChannelName.substr(1), false) + ) { + this.refs.newChannelName.showError( + __("LBRY channel names must contain only letters, numbers and dashes.") + ); + return; + } else { + this.refs.newChannelName.clearError(); + } + + this.setState({ + newChannelName, + }); + } + + handleNewChannelBidChange(event) { + this.setState({ + newChannelBid: event.target.value, + }); + } + + handleCreateChannelClick(event) { + if (this.state.newChannelName.length < 5) { + this.refs.newChannelName.showError( + __("LBRY channel names must be at least 4 characters in length.") + ); + return; + } + + this.setState({ + creatingChannel: true, + }); + + const newChannelName = this.state.newChannelName; + const amount = parseFloat(this.state.newChannelBid); + this.setState({ + creatingChannel: true, + }); + const success = (() => { + this.setState({ + creatingChannel: false, + addingChannel: false, + channel: newChannelName, + }); + this.props.handleChannelChange(newChannelName); + }).bind(this); + const failure = (err => { + this.setState({ + creatingChannel: false, + }); + this.refs.newChannelName.showError( + __("Unable to create channel due to an internal error.") + ); + }).bind(this); + this.props.createChannel(newChannelName, amount).then(success, failure); + } + + render() { + const lbcInputHelp = __( + "This LBC remains yours and the deposit can be undone at any time." + ); + + const { fetchingChannels, channels } = this.props; + + let channelContent = []; + if (channels.length > 0) { + channelContent.push( + + + {this.props.channels.map(({ name }) => + + )} + + + ); + if (fetchingChannels) { + channelContent.push( + + ); + } + } else if (fetchingChannels) { + channelContent.push( + + ); + } + + return ( +
+
+

{__("Identity")}

+
+ {__("Who created this content?")} +
+
+
+ {channelContent} +
+ {this.state.addingChannel && +
+ { + this.handleNewChannelNameChange(event); + }} + value={this.state.newChannelName} + /> + +
+ +
+
} +
+ ); + } +} + +export default ChannelSection; diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx new file mode 100644 index 000000000..91fd5eb09 --- /dev/null +++ b/ui/js/component/publishForm/view.jsx @@ -0,0 +1,920 @@ +import React from "react"; +import lbry from "lbry"; +import lbryuri from "lbryuri"; +import { FormField, FormRow } from "component/form.js"; +import Link from "component/link"; +import Modal from "component/modal"; +import Notice from "component/notice"; +import { BusyMessage } from "component/common"; +import ChannelSection from "./internal/ChannelSection"; + +class PublishForm extends React.PureComponent { + constructor(props) { + super(props); + + this._requiredFields = ["name", "bid", "meta_title", "tosAgree"]; + + this._defaultCopyrightNotice = "All rights reserved."; + + this.state = { + rawName: "", + name: "", + bid: 10, + hasFile: false, + feeAmount: "", + feeCurrency: "USD", + channel: "anonymous", + newChannelName: "@", + newChannelBid: 10, + meta_title: "", + meta_thumbnail: "", + meta_description: "", + meta_language: "en", + meta_nsfw: "0", + licenseType: "", + copyrightNotice: this._defaultCopyrightNotice, + otherLicenseDescription: "", + otherLicenseUrl: "", + tosAgree: false, + prefillDone: false, + uploadProgress: 0.0, + uploaded: false, + errorMessage: null, + submitting: false, + creatingChannel: false, + modal: null, + }; + } + + _updateChannelList(channel) { + const { fetchingChannels, fetchChannelListMine } = this.props; + + if (!fetchingChannels) fetchChannelListMine(); + } + + handleSubmit(event) { + if (typeof event !== "undefined") { + event.preventDefault(); + } + + this.setState({ + submitting: true, + }); + + let checkFields = this._requiredFields; + if (!this.myClaimExists()) { + checkFields.unshift("file"); + } + + let missingFieldFound = false; + for (let fieldName of checkFields) { + const field = this.refs[fieldName]; + if (field) { + if (field.getValue() === "" || field.getValue() === false) { + field.showRequiredError(); + if (!missingFieldFound) { + field.focus(); + missingFieldFound = true; + } + } else { + field.clearError(); + } + } + } + + if (missingFieldFound) { + this.setState({ + submitting: false, + }); + return; + } + + let metadata = {}; + + for (let metaField of ["title", "description", "thumbnail", "language"]) { + const value = this.state["meta_" + metaField]; + if (value) { + metadata[metaField] = value; + } + } + + metadata.license = this.getLicense(); + metadata.licenseUrl = this.getLicenseUrl(); + metadata.nsfw = !!parseInt(this.state.meta_nsfw); + + var doPublish = () => { + var publishArgs = { + name: this.state.name, + bid: parseFloat(this.state.bid), + metadata: metadata, + ...(this.state.channel != "new" && this.state.channel != "anonymous" + ? { channel_name: this.state.channel } + : {}), + }; + + if (this.refs.file.getValue() !== "") { + publishArgs.file_path = this.refs.file.getValue(); + } + + const success = claim => {}; + const failure = error => this.handlePublishError(error); + + this.handlePublishStarted(); + this.props.publish(publishArgs).then(success, failure); + }; + + if (this.state.isFee) { + lbry.wallet_unused_address().then(address => { + metadata.fee = { + currency: this.state.feeCurrency, + amount: parseFloat(this.state.feeAmount), + address: address, + }; + + doPublish(); + }); + } else { + doPublish(); + } + } + + handlePublishStarted() { + this.setState({ + modal: "publishStarted", + }); + } + + handlePublishStartedConfirmed() { + this.props.navigate("/published"); + } + + handlePublishError(error) { + this.setState({ + submitting: false, + modal: "error", + errorMessage: error.message, + }); + } + + claim() { + const { claimsByUri } = this.props; + const { uri } = this.state; + + return claimsByUri[uri]; + } + + topClaimValue() { + if (!this.claim()) return null; + + return parseFloat(this.claim().amount); + } + + myClaimExists() { + const { myClaims } = this.props; + const { name } = this.state; + + if (!name) return false; + + return !!myClaims.find(claim => claim.name === name); + } + + topClaimIsMine() { + const myClaimInfo = this.myClaimInfo(); + const { claimsByUri } = this.props; + const { uri } = this.state; + + if (!uri) return null; + + const claim = claimsByUri[uri]; + + if (!claim) return true; + if (!myClaimInfo) return false; + + return myClaimInfo.amount >= claimInfo.amount; + } + + myClaimInfo() { + const { name } = this.state; + + return Object.values(this.props.myClaims).find( + claim => claim.name === name + ); + } + + handleNameChange(event) { + var rawName = event.target.value; + + this.nameChanged(rawName); + } + + nameChanged(rawName) { + if (!rawName) { + this.setState({ + rawName: "", + name: "", + uri: "", + prefillDone: false, + }); + + return; + } + + if (!lbryuri.isValidName(rawName, false)) { + this.refs.name.showError( + __("LBRY names must contain only letters, numbers and dashes.") + ); + return; + } + + let channel = ""; + if (this.state.channel !== "anonymous") channel = this.state.channel; + + const name = rawName.toLowerCase(); + const uri = lbryuri.build({ contentName: name, channelName: channel }); + this.setState({ + rawName: rawName, + name: name, + prefillDone: false, + uri, + }); + + if (this.resolveUriTimeout) { + clearTimeout(this.resolveUriTimeout); + this.resolveUriTimeout = undefined; + } + const resolve = () => this.props.resolveUri(uri); + + this.resolveUriTimeout = setTimeout(resolve.bind(this), 500, { + once: true, + }); + } + + handlePrefillClicked() { + const claimInfo = this.myClaimInfo(); + const { + license, + licenseUrl, + title, + thumbnail, + description, + language, + nsfw, + } = claimInfo.value.stream.metadata; + + let newState = { + meta_title: title, + meta_thumbnail: thumbnail, + meta_description: description, + meta_language: language, + meta_nsfw: nsfw, + prefillDone: true, + bid: claimInfo.amount, + }; + + if (license == this._defaultCopyrightNotice) { + newState.licenseType = "copyright"; + newState.copyrightNotice = this._defaultCopyrightNotice; + } else { + // If the license URL or description matches one of the drop-down options, use that + let licenseType = "other"; // Will be overridden if we find a match + for (let option of this._meta_license.getOptions()) { + if ( + option.getAttribute("data-url") === licenseUrl || + option.text === license + ) { + licenseType = option.value; + } + } + + if (licenseType == "other") { + newState.otherLicenseDescription = license; + newState.otherLicenseUrl = licenseUrl; + } + newState.licenseType = licenseType; + } + + console.log(newState); + this.setState(newState); + } + + handleBidChange(event) { + this.setState({ + bid: event.target.value, + }); + } + + handleFeeAmountChange(event) { + this.setState({ + feeAmount: event.target.value, + }); + } + + handleFeeCurrencyChange(event) { + this.setState({ + feeCurrency: event.target.value, + }); + } + + handleFeePrefChange(feeEnabled) { + this.setState({ + isFee: feeEnabled, + }); + } + + handleMetadataChange(event) { + /** + * This function is used for all metadata inputs that store the final value directly into state. + * The only exceptions are inputs related to license description and license URL, which require + * more complex logic and the final value is determined at submit time. + */ + this.setState({ + ["meta_" + event.target.name]: event.target.value, + }); + } + + handleDescriptionChanged(text) { + this.setState({ + meta_description: text, + }); + } + + handleLicenseTypeChange(event) { + this.setState({ + licenseType: event.target.value, + }); + } + + handleCopyrightNoticeChange(event) { + this.setState({ + copyrightNotice: event.target.value, + }); + } + + handleOtherLicenseDescriptionChange(event) { + this.setState({ + otherLicenseDescription: event.target.value, + }); + } + + handleOtherLicenseUrlChange(event) { + this.setState({ + otherLicenseUrl: event.target.value, + }); + } + + handleChannelChange(channelName) { + this.setState({ + channel: channelName, + }); + const nameChanged = () => this.nameChanged(this.state.rawName); + setTimeout(nameChanged.bind(this), 500, { once: true }); + } + + handleTOSChange(event) { + this.setState({ + tosAgree: event.target.checked, + }); + } + + handleCreateChannelClick(event) { + if (this.state.newChannelName.length < 5) { + this.refs.newChannelName.showError( + __("LBRY channel names must be at least 4 characters in length.") + ); + return; + } + + this.setState({ + creatingChannel: true, + }); + + const newChannelName = this.state.newChannelName; + lbry + .channel_new({ + channel_name: newChannelName, + amount: parseFloat(this.state.newChannelBid), + }) + .then( + () => { + setTimeout(() => { + this.setState({ + creatingChannel: false, + }); + + this._updateChannelList(newChannelName); + }, 10000); + }, + error => { + // TODO: better error handling + this.refs.newChannelName.showError( + __("Unable to create channel due to an internal error.") + ); + this.setState({ + creatingChannel: false, + }); + } + ); + } + + getLicense() { + switch (this.state.licenseType) { + case "copyright": + return this.state.copyrightNotice; + case "other": + return this.state.otherLicenseDescription; + default: + return this._meta_license.getSelectedElement().text; + } + } + + getLicenseUrl() { + switch (this.state.licenseType) { + case "copyright": + return ""; + case "other": + return this.state.otherLicenseUrl; + default: + return this._meta_license.getSelectedElement().getAttribute("data-url"); + } + } + + componentWillMount() { + this.props.fetchClaimListMine(); + this._updateChannelList(); + } + + onFileChange() { + if (this.refs.file.getValue()) { + this.setState({ hasFile: true }); + } else { + this.setState({ hasFile: false }); + } + } + + getNameBidHelpText() { + if (this.state.prefillDone) { + return ( + + {__("Existing claim data was prefilled")} + + ); + } + + if ( + this.state.uri && + this.props.resolvingUris.indexOf(this.state.uri) !== -1 && + this.claim() === undefined + ) { + return __("Checking..."); + } else if (!this.state.name) { + return __("Select a URL for this publish."); + } else if (!this.claim()) { + return __("This URL is unused."); + } else if (this.myClaimExists() && !this.state.prefillDone) { + return ( + + {__("You already have a claim with this name.")}{" "} + this.handlePrefillClicked()} + /> + + ); + } else if (this.claim()) { + if (this.topClaimValue() === 1) { + return ( + + {__( + 'A deposit of at least one credit is required to win "%s". However, you can still get a permanent URL for any amount.', + this.state.name + )} + + ); + } else { + return ( + + {__( + 'A deposit of at least "%s" credits is required to win "%s". However, you can still get a permanent URL for any amount.', + this.topClaimValue(), + this.state.name + )} + + ); + } + } else { + return ""; + } + } + + closeModal() { + this.setState({ + modal: null, + }); + } + + render() { + const lbcInputHelp = __( + "This LBC remains yours and the deposit can be undone at any time." + ); + + return ( +
+
{ + this.handleSubmit(event); + }} + > +
+
+

{__("Content")}

+
+ {__("What are you publishing?")} +
+
+
+ { + this.onFileChange(event); + }} + helper={ + this.myClaimExists() + ? __( + "If you don't choose a file, the file from your existing claim will be used." + ) + : null + } + /> +
+ {!this.state.hasFile && !this.myClaimExists() + ? null + :
+
+ { + this.handleMetadataChange(event); + }} + /> +
+
+ { + this.handleMetadataChange(event); + }} + /> +
+
+ { + this.handleDescriptionChanged(text); + }} + /> +
+
+ { + this.handleMetadataChange(event); + }} + > + + + + + + + + +
+
+ { + this.handleMetadataChange(event); + }} + > + {/* */} + + + +
+
} +
+ +
+
+

{__("Access")}

+
+ {__("How much does this content cost?")} +
+
+
+
+ +
+ { + this.handleFeePrefChange(false); + }} + defaultChecked={!this.state.isFee} + /> + { + this.handleFeePrefChange(true); + }} + defaultChecked={this.state.isFee} + /> + + this.handleFeeAmountChange(event)} + />{" "} + { + this.handleFeeCurrencyChange(event); + }} + > + + + + + {this.state.isFee + ?
+ {__( + "If you choose to price this content in dollars, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase." + )} +
+ : ""} + { + this._meta_license = row; + }} + onChange={event => { + this.handleLicenseTypeChange(event); + }} + > + + + + + + + + + + + + {this.state.licenseType == "copyright" + ? { + this.handleCopyrightNoticeChange(event); + }} + /> + : null} + + {this.state.licenseType == "other" + ? { + this.handleOtherLicenseDescriptionChange(event); + }} + /> + : null} + + {this.state.licenseType == "other" + ? { + this.handleOtherLicenseUrlChange(event); + }} + /> + : null} +
+
+ + + +
+
+

{__("Address")}

+
+ {__("Where should this content permanently reside?")} + {" "} + . +
+
+
+ { + this.handleNameChange(event); + }} + helper={this.getNameBidHelpText()} + /> +
+ {this.state.rawName + ?
+ { + this.handleBidChange(event); + }} + value={this.state.bid} + placeholder={this.claim() ? this.topClaimValue() + 10 : 100} + helper={lbcInputHelp} + /> +
+ : ""} +
+ +
+
+

{__("Terms of Service")}

+
+
+ + {__("I agree to the")} + {" "} + + + } + type="checkbox" + checked={this.state.tosAgree} + onChange={event => { + this.handleTOSChange(event); + }} + /> +
+
+ +
+ { + this.handleSubmit(event); + }} + disabled={this.state.submitting} + /> + + +
+ + + { + this.handlePublishStartedConfirmed(event); + }} + > +

+ {__("Your file has been published to LBRY at the address")} + {" "}{this.state.uri}! +

+

+ {__( + 'The file will take a few minutes to appear for other LBRY users. Until then it will be listed as "pending" under your published files.' + )} +

+
+ { + this.closeModal(event); + }} + > + {__( + "The following error occurred when attempting to publish your file" + )}: {this.state.errorMessage} + +
+ ); + } +} + +export default PublishForm; diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index b76d018a1..ab39fcec8 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -1,1076 +1,8 @@ import React from "react"; -import lbry from "lbry"; -import lbryuri from "lbryuri"; -import { FormField, FormRow } from "component/form.js"; -import Link from "component/link"; -import rewards from "rewards"; -import Modal from "component/modal"; -import Notice from "component/notice"; -import { BusyMessage } from "component/common"; +import PublishForm from "component/publishForm"; -class PublishPage extends React.PureComponent { - constructor(props) { - super(props); - - this._requiredFields = ["name", "bid", "meta_title", "tosAgree"]; - - this._defaultCopyrightNotice = "All rights reserved."; - - this.state = { - rawName: "", - name: "", - bid: 10, - hasFile: false, - feeAmount: "", - feeCurrency: "USD", - channel: "anonymous", - newChannelName: "@", - newChannelBid: 10, - meta_title: "", - meta_thumbnail: "", - meta_description: "", - meta_language: "en", - meta_nsfw: "0", - licenseType: "", - copyrightNotice: this._defaultCopyrightNotice, - otherLicenseDescription: "", - otherLicenseUrl: "", - tosAgree: false, - prefillDone: false, - uploadProgress: 0.0, - uploaded: false, - errorMessage: null, - submitting: false, - creatingChannel: false, - modal: null, - }; - } - - _updateChannelList(channel) { - const { fetchingChannels, fetchChannelListMine } = this.props; - - if (!fetchingChannels) fetchChannelListMine(); - } - - handleSubmit(event) { - if (typeof event !== "undefined") { - event.preventDefault(); - } - - this.setState({ - submitting: true, - }); - - let checkFields = this._requiredFields; - if (!this.myClaimExists()) { - checkFields.unshift("file"); - } - - let missingFieldFound = false; - for (let fieldName of checkFields) { - const field = this.refs[fieldName]; - if (field) { - if (field.getValue() === "" || field.getValue() === false) { - field.showRequiredError(); - if (!missingFieldFound) { - field.focus(); - missingFieldFound = true; - } - } else { - field.clearError(); - } - } - } - - if (missingFieldFound) { - this.setState({ - submitting: false, - }); - return; - } - - let metadata = {}; - - for (let metaField of ["title", "description", "thumbnail", "language"]) { - const value = this.state["meta_" + metaField]; - if (value) { - metadata[metaField] = value; - } - } - - metadata.license = this.getLicense(); - metadata.licenseUrl = this.getLicenseUrl(); - metadata.nsfw = !!parseInt(this.state.meta_nsfw); - - var doPublish = () => { - var publishArgs = { - name: this.state.name, - bid: parseFloat(this.state.bid), - metadata: metadata, - ...(this.state.channel != "new" && this.state.channel != "anonymous" - ? { channel_name: this.state.channel } - : {}), - }; - - if (this.refs.file.getValue() !== "") { - publishArgs.file_path = this.refs.file.getValue(); - } - - const success = claim => {}; - const failure = error => this.handlePublishError(error); - - this.handlePublishStarted(); - this.props.publish(publishArgs).then(success, failure); - }; - - if (this.state.isFee) { - lbry.wallet_unused_address().then(address => { - metadata.fee = { - currency: this.state.feeCurrency, - amount: parseFloat(this.state.feeAmount), - address: address, - }; - - doPublish(); - }); - } else { - doPublish(); - } - } - - handlePublishStarted() { - this.setState({ - modal: "publishStarted", - }); - } - - handlePublishStartedConfirmed() { - this.props.navigate("/published"); - } - - handlePublishError(error) { - this.setState({ - submitting: false, - modal: "error", - errorMessage: error.message, - }); - } - - claim() { - const { claimsByUri } = this.props; - const { uri } = this.state; - - return claimsByUri[uri]; - } - - topClaimValue() { - if (!this.claim()) return null; - - return parseFloat(this.claim().amount); - } - - myClaimExists() { - const { myClaims } = this.props; - const { name } = this.state; - - if (!name) return false; - - return !!myClaims.find(claim => claim.name === name); - } - - topClaimIsMine() { - const myClaimInfo = this.myClaimInfo(); - const { claimsByUri } = this.props; - const { uri } = this.state; - - if (!uri) return null; - - const claim = claimsByUri[uri]; - - if (!claim) return true; - if (!myClaimInfo) return false; - - return myClaimInfo.amount >= claimInfo.amount; - } - - myClaimInfo() { - const { name } = this.state; - - return Object.values(this.props.myClaims).find( - claim => claim.name === name - ); - } - - handleNameChange(event) { - var rawName = event.target.value; - - this.nameChanged(rawName); - } - - nameChanged(rawName) { - if (!rawName) { - this.setState({ - rawName: "", - name: "", - uri: "", - }); - - return; - } - - if (!lbryuri.isValidName(rawName, false)) { - this.refs.name.showError( - __("LBRY names must contain only letters, numbers and dashes.") - ); - return; - } - - let channel = ""; - if (this.state.channel !== "anonymous") channel = this.state.channel; - - const name = rawName.toLowerCase(); - const uri = lbryuri.build({ contentName: name, channelName: channel }); - this.setState({ - rawName: rawName, - name: name, - prefillDone: false, - uri, - }); - - if (this.resolveUriTimeout) { - clearTimeout(this.resolveUriTimeout); - this.resolveUriTimeout = undefined; - } - const resolve = () => this.props.resolveUri(uri); - - this.resolveUriTimeout = setTimeout(resolve.bind(this), 500, { - once: true, - }); - } - - handlePrefillClicked() { - const {license, licenseUrl, title, thumbnail, description, - language, nsfw} = this.myClaimInfo().value.stream.metadata; - - let newState = { - meta_title: title, - meta_thumbnail: thumbnail, - meta_description: description, - meta_language: language, - meta_nsfw: nsfw, - }; - - if (license == this._defaultCopyrightNotice) { - newState.licenseType = "copyright"; - newState.copyrightNotice = this._defaultCopyrightNotice; - } else { - // If the license URL or description matches one of the drop-down options, use that - let licenseType = "other"; // Will be overridden if we find a match - for (let option of this._meta_license.getOptions()) { - if ( - option.getAttribute("data-url") === licenseUrl || - option.text === license - ) { - licenseType = option.value; - } - } - - if (licenseType == "other") { - newState.otherLicenseDescription = license; - newState.otherLicenseUrl = licenseUrl; - } - newState.licenseType = licenseType; - } - - this.setState(newState); - } - - handleBidChange(event) { - this.setState({ - bid: event.target.value, - }); - } - - handleFeeAmountChange(event) { - this.setState({ - feeAmount: event.target.value, - }); - } - - handleFeeCurrencyChange(event) { - this.setState({ - feeCurrency: event.target.value, - }); - } - - handleFeePrefChange(feeEnabled) { - this.setState({ - isFee: feeEnabled, - }); - } - - handleMetadataChange(event) { - /** - * This function is used for all metadata inputs that store the final value directly into state. - * The only exceptions are inputs related to license description and license URL, which require - * more complex logic and the final value is determined at submit time. - */ - this.setState({ - ["meta_" + event.target.name]: event.target.value, - }); - } - - handleLicenseTypeChange(event) { - this.setState({ - licenseType: event.target.value, - }); - } - - handleCopyrightNoticeChange(event) { - this.setState({ - copyrightNotice: event.target.value, - }); - } - - handleOtherLicenseDescriptionChange(event) { - this.setState({ - otherLicenseDescription: event.target.value, - }); - } - - handleOtherLicenseUrlChange(event) { - this.setState({ - otherLicenseUrl: event.target.value, - }); - } - - handleChannelChange(channelName) { - this.setState({ - channel: channelName, - }); - const nameChanged = () => this.nameChanged(this.state.rawName); - setTimeout(nameChanged.bind(this), 500, { once: true }); - } - - handleTOSChange(event) { - this.setState({ - tosAgree: event.target.checked, - }); - } - - handleCreateChannelClick(event) { - if (this.state.newChannelName.length < 5) { - this.refs.newChannelName.showError( - __("LBRY channel names must be at least 4 characters in length.") - ); - return; - } - - this.setState({ - creatingChannel: true, - }); - - const newChannelName = this.state.newChannelName; - lbry - .channel_new({ - channel_name: newChannelName, - amount: parseFloat(this.state.newChannelBid), - }) - .then( - () => { - setTimeout(() => { - this.setState({ - creatingChannel: false, - }); - - this._updateChannelList(newChannelName); - }, 10000); - }, - error => { - // TODO: better error handling - this.refs.newChannelName.showError( - __("Unable to create channel due to an internal error.") - ); - this.setState({ - creatingChannel: false, - }); - } - ); - } - - getLicense() { - switch (this.state.licenseType) { - case "copyright": - return this.state.copyrightNotice; - case "other": - return this.state.otherLicenseDescription; - default: - return this._meta_license.getSelectedElement().text; - } - } - - getLicenseUrl() { - switch (this.state.licenseType) { - case "copyright": - return ""; - case "other": - return this.state.otherLicenseUrl; - default: - return this._meta_license.getSelectedElement().getAttribute("data-url"); - } - } - - componentWillMount() { - this.props.fetchClaimListMine(); - this._updateChannelList(); - } - - onFileChange() { - if (this.refs.file.getValue()) { - this.setState({ hasFile: true }); - } else { - this.setState({ hasFile: false }); - } - } - - getNameBidHelpText() { - if ( - this.state.uri && - this.props.resolvingUris.indexOf(this.state.uri) !== -1 && - this.claim() === undefined - ) { - return __("Checking..."); - } else if (!this.state.name) { - return __("Select a URL for this publish."); - } else if (!this.claim()) { - return __("This URL is unused."); - } else if (this.myClaimExists() && !this.state.prefillDone) { - return ( - - {__("You already have a claim with this name.")}{" "} - this.handlePrefillClicked()} - /> - - ); - } else if (this.claim()) { - if (this.topClaimValue() === 1) { - return ( - - {__( - 'A deposit of at least one credit is required to win "%s". However, you can still get a permanent URL for any amount.', - this.state.name - )} - - ); - } else { - return ( - - {__( - 'A deposit of at least "%s" credits is required to win "%s". However, you can still get a permanent URL for any amount.', - this.topClaimValue(), - this.state.name - )} - - ); - } - } else { - return ""; - } - } - - closeModal() { - this.setState({ - modal: null, - }); - } - - render() { - const lbcInputHelp = __( - "This LBC remains yours and the deposit can be undone at any time." - ); - - return ( -
-
{ - this.handleSubmit(event); - }} - > -
-
-

{__("Content")}

-
- {__("What are you publishing?")} -
-
-
- { - this.onFileChange(event); - }} - helper={ - this.myClaimExists() - ? __( - "If you don't choose a file, the file from your existing claim will be used." - ) - : null - } - /> -
- {!this.state.hasFile && !this.myClaimExists() - ? null - :
-
- { - this.handleMetadataChange(event); - }} - /> -
-
- { - this.handleMetadataChange(event); - }} - /> -
-
- { - this.handleMetadataChange(event); - }} - /> -
-
- { - this.handleMetadataChange(event); - }} - > - - - - - - - - -
-
- { - this.handleMetadataChange(event); - }} - > - {/* */} - - - -
-
} -
- -
-
-

{__("Access")}

-
- {__("How much does this content cost?")} -
-
-
-
- -
- { - this.handleFeePrefChange(false); - }} - defaultChecked={!this.state.isFee} - /> - { - this.handleFeePrefChange(true); - }} - defaultChecked={this.state.isFee} - /> - - this.handleFeeAmountChange(event)} - />{" "} - { - this.handleFeeCurrencyChange(event); - }} - > - - - - - {this.state.isFee - ?
- {__( - "If you choose to price this content in dollars, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase." - )} -
- : ""} - { - this._meta_license = row; - }} - onChange={event => { - this.handleLicenseTypeChange(event); - }} - > - - - - - - - - - - - - {this.state.licenseType == "copyright" - ? { - this.handleCopyrightNoticeChange(event); - }} - /> - : null} - - {this.state.licenseType == "other" - ? { - this.handleOtherLicenseDescriptionChange(event); - }} - /> - : null} - - {this.state.licenseType == "other" - ? { - this.handleOtherLicenseUrlChange(event); - }} - /> - : null} -
-
- - - -
-
-

{__("Address")}

-
- {__("Where should this content permanently reside?")} - {" "} - . -
-
-
- { - this.handleNameChange(event); - }} - helper={this.getNameBidHelpText()} - /> - {this.myClaimExists() && !this.state.prefillDone - ? - {__("You already have a claim with this name.")}{" "} - this.handlePrefillClicked()} - /> - - : null} -
- {this.state.rawName - ?
- { - this.handleBidChange(event); - }} - value={this.state.bid} - placeholder={this.claim() ? this.topClaimValue() + 10 : 100} - helper={lbcInputHelp} - /> -
- : ""} -
- -
-
-

{__("Terms of Service")}

-
-
- - {__("I agree to the")} - {" "} - - - } - type="checkbox" - checked={this.state.tosAgree} - onChange={event => { - this.handleTOSChange(event); - }} - /> -
-
- -
- { - this.handleSubmit(event); - }} - disabled={this.state.submitting} - /> - - -
- - - { - this.handlePublishStartedConfirmed(event); - }} - > -

- {__("Your file has been published to LBRY at the address")} - {" "}{this.state.uri}! -

-

- {__( - 'The file will take a few minutes to appear for other LBRY users. Until then it will be listed as "pending" under your published files.' - )} -

-
- { - this.closeModal(event); - }} - > - {__( - "The following error occurred when attempting to publish your file" - )}: {this.state.errorMessage} - -
- ); - } -} - -class ChannelSection extends React.PureComponent { - constructor(props) { - super(props); - - this.state = { - newChannelName: "@", - newChannelBid: 10, - addingChannel: false, - }; - } - - handleChannelChange(event) { - const channel = event.target.value; - if (channel === "new") this.setState({ addingChannel: true }); - else { - this.setState({ addingChannel: false }); - this.props.handleChannelChange(event.target.value); - } - } - - handleNewChannelNameChange(event) { - const newChannelName = event.target.value.startsWith("@") - ? event.target.value - : "@" + event.target.value; - - if ( - newChannelName.length > 1 && - !lbryuri.isValidName(newChannelName.substr(1), false) - ) { - this.refs.newChannelName.showError( - __("LBRY channel names must contain only letters, numbers and dashes.") - ); - return; - } else { - this.refs.newChannelName.clearError(); - } - - this.setState({ - newChannelName, - }); - } - - handleNewChannelBidChange(event) { - this.setState({ - newChannelBid: event.target.value, - }); - } - - handleCreateChannelClick(event) { - if (this.state.newChannelName.length < 5) { - this.refs.newChannelName.showError( - __("LBRY channel names must be at least 4 characters in length.") - ); - return; - } - - this.setState({ - creatingChannel: true, - }); - - const newChannelName = this.state.newChannelName; - const amount = parseFloat(this.state.newChannelBid); - this.setState({ - creatingChannel: true, - }); - const success = (() => { - this.setState({ - creatingChannel: false, - addingChannel: false, - channel: newChannelName, - }); - this.props.handleChannelChange(newChannelName); - }).bind(this); - const failure = (err => { - this.setState({ - creatingChannel: false, - }); - this.refs.newChannelName.showError( - __("Unable to create channel due to an internal error.") - ); - }).bind(this); - this.props.createChannel(newChannelName, amount).then(success, failure); - } - - render() { - const lbcInputHelp = __( - "This LBC remains yours and the deposit can be undone at any time." - ); - - const { fetchingChannels, channels } = this.props; - - let channelContent = []; - if (channels.length > 0) { - channelContent.push( - - - {this.props.channels.map(({ name }) => - - )} - - - ); - if (fetchingChannels) { - channelContent.push( - - ); - } - } else if (fetchingChannels) { - channelContent.push( - - ); - } - - return ( -
-
-

{__("Identity")}

-
- {__("Who created this content?")} -
-
-
- {channelContent} -
- {this.state.addingChannel && -
- { - this.handleNewChannelNameChange(event); - }} - value={this.state.newChannelName} - /> - -
- -
-
} -
- ); - } -} +const PublishPage = props => { + return ; +}; export default PublishPage; From 7c3953ac517803c2ac348f420aca42dc99abe662 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 3 Jul 2017 14:53:56 +0700 Subject: [PATCH 025/152] Fix selectClaimForUriIsMine --- ui/js/selectors/claims.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index 3478c9fc4..aa19a6e80 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -51,7 +51,7 @@ export const makeSelectClaimForUri = () => { const selectClaimForUriIsMine = (state, props) => { const uri = lbryuri.normalize(props.uri); const claim = selectClaimsByUri(state)[uri]; - const myClaims = selectMyClaims(state); + const myClaims = selectMyClaimsRaw(state); return myClaims.has(claim.claim_id); }; From 35a5cb0918e813f67857fe5626621996cef04bab Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 4 Jul 2017 13:48:52 +0700 Subject: [PATCH 026/152] Fix file info selector --- ui/js/selectors/file_info.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 5b0e4941b..4010b95c8 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -102,7 +102,7 @@ export const selectFileInfosPublished = createSelector( const fileInfo = byOutpoint[outpoint]; if (fileInfo) fileInfos.push(fileInfo); }); - return fileInfos; + return [...fileInfos, ...pendingPublish]; } ); From 6c6f1beb19b02faa03180c12f4d57295e7080b3e Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 1 Jul 2017 18:03:51 +0700 Subject: [PATCH 027/152] Change redux-persist debounce to 10 seconds --- ui/js/store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/store.js b/ui/js/store.js index 8e6c11949..35f7ab2ec 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -102,7 +102,7 @@ const persistOptions = { // Order is important. Needs to be compressed last or other transforms can't // read the data transforms: [saveClaimsFilter, saveFileInfosFilter, compressor], - debounce: 1000, + debounce: 10000, storage: localForage, }; window.cacheStore = persistStore(reduxStore, persistOptions); From f3fdf5e84177cc894904c1146075dcdd24787cac Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 4 Jul 2017 18:05:35 +0700 Subject: [PATCH 028/152] Rename pending to fetching in selectors to avoid confusion --- ui/js/actions/file_info.js | 16 ++++++++-------- ui/js/page/fileListDownloaded/index.js | 4 ++-- ui/js/page/fileListDownloaded/view.jsx | 8 ++++---- ui/js/page/fileListPublished/index.js | 4 ++-- ui/js/page/fileListPublished/view.jsx | 8 ++++---- ui/js/reducers/claims.js | 4 ++-- ui/js/reducers/file_info.js | 26 ++------------------------ ui/js/selectors/claims.js | 4 ++-- ui/js/selectors/file_info.js | 16 ++++++++-------- 9 files changed, 34 insertions(+), 56 deletions(-) diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index 9d3861fb7..70db7244d 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -3,11 +3,11 @@ import lbry from "lbry"; import { doFetchClaimListMine } from "actions/content"; import { selectClaimsByUri, - selectClaimListMineIsPending, + selectIsFetchingClaimListMine, selectMyClaimsOutpoints, } from "selectors/claims"; import { - selectFileListIsPending, + selectIsFetchingFileList, selectFileInfosByOutpoint, selectUrisLoading, } from "selectors/file_info"; @@ -48,9 +48,9 @@ export function doFetchFileInfo(uri) { export function doFileList() { return function(dispatch, getState) { const state = getState(); - const isPending = selectFileListIsPending(state); + const isFetching = selectIsFetchingFileList(state); - if (!isPending) { + if (!isFetching) { dispatch({ type: types.FILE_LIST_STARTED, }); @@ -128,10 +128,10 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { export function doFetchFileInfosAndPublishedClaims() { return function(dispatch, getState) { const state = getState(), - isClaimListMinePending = selectClaimListMineIsPending(state), - isFileInfoListPending = selectFileListIsPending(state); + isFetchingClaimListMine = selectIsFetchingClaimListMine(state), + isFetchingFileInfo = selectIsFetchingFileList(state); - dispatch(doFetchClaimListMine()); - dispatch(doFileList()); + if (!isFetchingClaimListMine) dispatch(doFetchClaimListMine()); + if (!isFetchingFileInfo) dispatch(doFileList()); }; } diff --git a/ui/js/page/fileListDownloaded/index.js b/ui/js/page/fileListDownloaded/index.js index 86d26d851..e51d5389f 100644 --- a/ui/js/page/fileListDownloaded/index.js +++ b/ui/js/page/fileListDownloaded/index.js @@ -3,7 +3,7 @@ import { connect } from "react-redux"; import { doFetchFileInfosAndPublishedClaims } from "actions/file_info"; import { selectFileInfosDownloaded, - selectFileListDownloadedOrPublishedIsPending, + selectIsFetchingFileListDownloadedOrPublished, } from "selectors/file_info"; import { doNavigate } from "actions/app"; import { doCancelAllResolvingUris } from "actions/content"; @@ -11,7 +11,7 @@ import FileListDownloaded from "./view"; const select = state => ({ fileInfos: selectFileInfosDownloaded(state), - isPending: selectFileListDownloadedOrPublishedIsPending(state), + isFetching: selectIsFetchingFileListDownloadedOrPublished(state), }); const perform = dispatch => ({ diff --git a/ui/js/page/fileListDownloaded/view.jsx b/ui/js/page/fileListDownloaded/view.jsx index 03665847c..c1501ec78 100644 --- a/ui/js/page/fileListDownloaded/view.jsx +++ b/ui/js/page/fileListDownloaded/view.jsx @@ -12,7 +12,7 @@ import SubHeader from "component/subHeader"; class FileListDownloaded extends React.PureComponent { componentWillMount() { - if (!this.props.isPending) this.props.fetchFileInfosDownloaded(); + if (!this.props.isFetching) this.props.fetchFileInfosDownloaded(); } componentWillUnmount() { @@ -20,13 +20,13 @@ class FileListDownloaded extends React.PureComponent { } render() { - const { fileInfos, isPending, navigate } = this.props; + const { fileInfos, isFetching, navigate } = this.props; let content; if (fileInfos && fileInfos.length > 0) { - content = ; + content = ; } else { - if (isPending) { + if (isFetching) { content = ; } else { content = ( diff --git a/ui/js/page/fileListPublished/index.js b/ui/js/page/fileListPublished/index.js index 7e5e349c3..9b8b2b80b 100644 --- a/ui/js/page/fileListPublished/index.js +++ b/ui/js/page/fileListPublished/index.js @@ -4,7 +4,7 @@ import { connect } from "react-redux"; import { doFetchFileInfosAndPublishedClaims } from "actions/file_info"; import { selectFileInfosPublished, - selectFileListDownloadedOrPublishedIsPending, + selectIsFetchingFileListDownloadedOrPublished, } from "selectors/file_info"; import { doClaimRewardType } from "actions/rewards"; import { doNavigate } from "actions/app"; @@ -13,7 +13,7 @@ import FileListPublished from "./view"; const select = state => ({ fileInfos: selectFileInfosPublished(state), - isPending: selectFileListDownloadedOrPublishedIsPending(state), + isFetching: selectIsFetchingFileListDownloadedOrPublished(state), }); const perform = dispatch => ({ diff --git a/ui/js/page/fileListPublished/view.jsx b/ui/js/page/fileListPublished/view.jsx index 822cfeb7d..fdcbe97cd 100644 --- a/ui/js/page/fileListPublished/view.jsx +++ b/ui/js/page/fileListPublished/view.jsx @@ -12,7 +12,7 @@ import SubHeader from "component/subHeader"; class FileListPublished extends React.PureComponent { componentWillMount() { - if (!this.props.isPending) this.props.fetchFileListPublished(); + if (!this.props.isFetching) this.props.fetchFileListPublished(); } componentDidUpdate() { @@ -24,7 +24,7 @@ class FileListPublished extends React.PureComponent { } render() { - const { fileInfos, isPending, navigate } = this.props; + const { fileInfos, isFetching, navigate } = this.props; let content; @@ -32,12 +32,12 @@ class FileListPublished extends React.PureComponent { content = ( ); } else { - if (isPending) { + if (isFetching) { content = ; } else { content = ( diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 75bfc4a52..3ee992f94 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -34,7 +34,7 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { reducers[types.FETCH_CLAIM_LIST_MINE_STARTED] = function(state, action) { return Object.assign({}, state, { - isClaimListMinePending: true, + isFetchingClaimListMine: true, }); }; @@ -50,7 +50,7 @@ reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { }); return Object.assign({}, state, { - isClaimListMinePending: false, + isFetchingClaimListMine: false, myClaims: myClaims, byId, }); diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index fe6979045..e462f3cf6 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -6,7 +6,7 @@ const defaultState = {}; reducers[types.FILE_LIST_STARTED] = function(state, action) { return Object.assign({}, state, { - isFileListPending: true, + isFetchingFileList: true, }); }; @@ -22,7 +22,7 @@ reducers[types.FILE_LIST_COMPLETED] = function(state, action) { }); return Object.assign({}, state, { - isFileListPending: false, + isFetchingFileList: false, byOutpoint: newByOutpoint, pendingByOutpoint, }); @@ -171,28 +171,6 @@ reducers[types.PUBLISH_FAILED] = function(state, action) { }); }; -// reducers[types.PUBLISH_COMPLETED] = function(state, action) { -// const { claim } = action.data; -// const uri = lbryuri.build({ -// txid: claim.txId -// }) -// const newPendingPublish = { -// name, -// channel_name, -// claim_id: "pending_claim_" + uri, -// txid: "pending_" + uri, -// nout: 0, -// outpoint: "pending_" + uri + ":0", -// time: Date.now(), -// }; -// const fileInfos = Object.assign({}, state.fileInfos) -// fileInfos[newPendingPublish.outpoint] = newPendingPublish - -// return Object.assign({}, state, { -// fileInfos, -// }) -// } - export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index aa19a6e80..f0708cf86 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -100,9 +100,9 @@ export const makeSelectContentTypeForUri = () => { ); }; -export const selectClaimListMineIsPending = createSelector( +export const selectIsFetchingClaimListMine = createSelector( _selectState, - state => state.isClaimListMinePending + state => !!state.isFetchingClaimListMine ); export const selectMyClaimsRaw = createSelector( diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 4010b95c8..7b8a10769 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -2,7 +2,7 @@ import lbry from "lbry"; import { createSelector } from "reselect"; import { selectClaimsByUri, - selectClaimListMineIsPending, + selectIsFetchingClaimListMine, selectMyClaimsOutpoints, } from "selectors/claims"; @@ -13,16 +13,16 @@ export const selectFileInfosByOutpoint = createSelector( state => state.byOutpoint || {} ); -export const selectFileListIsPending = createSelector( +export const selectIsFetchingFileList = createSelector( _selectState, - state => state.isFileListPending + state => !!state.isFetchingFileList ); -export const selectFileListDownloadedOrPublishedIsPending = createSelector( - selectFileListIsPending, - selectClaimListMineIsPending, - (isFileListPending, isClaimListMinePending) => - isFileListPending || isClaimListMinePending +export const selectIsFetchingFileListDownloadedOrPublished = createSelector( + selectIsFetchingFileList, + selectIsFetchingClaimListMine, + (isFetchingFileList, isFetchingClaimListMine) => + isFetchingFileList || isFetchingClaimListMine ); export const selectFileInfoForUri = (state, props) => { From 253932113f11e55694490f11c1f5b07299946328 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 4 Jul 2017 19:51:05 +0700 Subject: [PATCH 029/152] Fix adding new identities --- .../publishForm/internal/channelSection.jsx | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/ui/js/component/publishForm/internal/channelSection.jsx b/ui/js/component/publishForm/internal/channelSection.jsx index c0c4bf473..274f30bdf 100644 --- a/ui/js/component/publishForm/internal/channelSection.jsx +++ b/ui/js/component/publishForm/internal/channelSection.jsx @@ -2,6 +2,7 @@ import React from "react"; import lbryuri from "lbryuri"; import { FormField, FormRow } from "component/form.js"; import { BusyMessage } from "component/common"; +import Link from "component/link"; class ChannelSection extends React.PureComponent { constructor(props) { @@ -92,37 +93,31 @@ class ChannelSection extends React.PureComponent { "This LBC remains yours and the deposit can be undone at any time." ); - const { fetchingChannels, channels } = this.props; + const { fetchingChannels, channels = [] } = this.props; let channelContent = []; - if (channels.length > 0) { + channelContent.push( + + + {this.props.channels.map(({ name }) => + + )} + + + ); + if (fetchingChannels) { channelContent.push( - - - {this.props.channels.map(({ name }) => - - )} - - - ); - if (fetchingChannels) { - channelContent.push( - - ); - } - } else if (fetchingChannels) { - channelContent.push( - + ); } From 5012d44384e8da43cbba400f1d06caf468b72b93 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 5 Jul 2017 13:38:17 +0700 Subject: [PATCH 030/152] Start using claims instead of file info for published files --- ui/js/component/fileList/view.jsx | 4 +++- ui/js/page/fileListPublished/index.js | 14 +++++++------- ui/js/page/fileListPublished/view.jsx | 10 +++++----- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index 20631b59e..8785972d9 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -67,7 +67,9 @@ class FileList extends React.PureComponent { const content = []; this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => { - let uriParams = {}; + let uriParams = { + claimId: fileInfo.claim_id, + }; if (fileInfo.channel_name) { uriParams.channelName = fileInfo.channel_name; uriParams.contentName = fileInfo.name; diff --git a/ui/js/page/fileListPublished/index.js b/ui/js/page/fileListPublished/index.js index 9b8b2b80b..bd00db2ed 100644 --- a/ui/js/page/fileListPublished/index.js +++ b/ui/js/page/fileListPublished/index.js @@ -1,24 +1,24 @@ import React from "react"; import rewards from "rewards"; import { connect } from "react-redux"; -import { doFetchFileInfosAndPublishedClaims } from "actions/file_info"; +import { doFetchClaimListMine } from "actions/content"; import { - selectFileInfosPublished, - selectIsFetchingFileListDownloadedOrPublished, -} from "selectors/file_info"; + selectMyClaims, + selectIsFetchingClaimListMine, +} from "selectors/claims"; import { doClaimRewardType } from "actions/rewards"; import { doNavigate } from "actions/app"; import { doCancelAllResolvingUris } from "actions/content"; import FileListPublished from "./view"; const select = state => ({ - fileInfos: selectFileInfosPublished(state), - isFetching: selectIsFetchingFileListDownloadedOrPublished(state), + claims: selectMyClaims(state), + isFetching: selectIsFetchingClaimListMine(state), }); const perform = dispatch => ({ navigate: path => dispatch(doNavigate(path)), - fetchFileListPublished: () => dispatch(doFetchFileInfosAndPublishedClaims()), + fetchClaims: () => dispatch(doFetchClaimListMine()), claimFirstPublishReward: () => dispatch(doClaimRewardType(rewards.TYPE_FIRST_PUBLISH)), cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()), diff --git a/ui/js/page/fileListPublished/view.jsx b/ui/js/page/fileListPublished/view.jsx index fdcbe97cd..a7b500b29 100644 --- a/ui/js/page/fileListPublished/view.jsx +++ b/ui/js/page/fileListPublished/view.jsx @@ -12,11 +12,11 @@ import SubHeader from "component/subHeader"; class FileListPublished extends React.PureComponent { componentWillMount() { - if (!this.props.isFetching) this.props.fetchFileListPublished(); + if (!this.props.isFetching) this.props.fetchClaims(); } componentDidUpdate() { - if (this.props.fileInfos.length > 0) this.props.claimFirstPublishReward(); + // if (this.props.claims.length > 0) this.props.fetchClaims(); } componentWillUnmount() { @@ -24,14 +24,14 @@ class FileListPublished extends React.PureComponent { } render() { - const { fileInfos, isFetching, navigate } = this.props; + const { claims, isFetching, navigate } = this.props; let content; - if (fileInfos && fileInfos.length > 0) { + if (claims && claims.length > 0) { content = ( From f9b9221471239fb541663cfb315accdb75e0eee5 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Thu, 6 Jul 2017 14:47:02 +0700 Subject: [PATCH 031/152] Fix show page being blank for unconfirmed claims --- ui/js/page/showPage/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx index 05b8b0b91..687cb5498 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/showPage/view.jsx @@ -24,7 +24,7 @@ class ShowPage extends React.PureComponent { let innerContent = ""; - if (isResolvingUri && !claim) { + if ((isResolvingUri && !claim) || !claim) { innerContent = (
From 470f61da9d0eace14caac61d56bec590b8d7ac15 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 8 Jul 2017 15:03:12 +0700 Subject: [PATCH 032/152] commit little and often fail --- ui/js/actions/content.js | 6 ++- ui/js/actions/file_info.js | 22 ++++++---- ui/js/component/fileList/view.jsx | 1 - ui/js/component/fileTile/view.jsx | 11 ++++- ui/js/component/publishForm/view.jsx | 6 ++- ui/js/page/fileListPublished/index.js | 4 +- ui/js/reducers/claims.js | 62 +++++++++++++++++++++++++-- ui/js/reducers/file_info.js | 33 -------------- ui/js/selectors/claims.js | 21 +++++++-- ui/js/store.js | 9 ++-- 10 files changed, 114 insertions(+), 61 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index ca6f1850d..291dc4d81 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -396,14 +396,16 @@ export function doPublish(params) { } else { uri = lbryuri.build({ name: name }, false); } + const fakeId = "pending"; const pendingPublish = { name, channel_name, - claim_id: "pending_claim_" + uri, + claim_id: fakeId, txid: "pending_" + uri, nout: 0, - outpoint: "pending_" + uri + ":0", + outpoint: fakeId + ":0", time: Date.now(), + pending: true, }; dispatch({ diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index 70db7244d..eb4a6753f 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -102,14 +102,20 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { }, }); - const success = () => { - dispatch({ - type: types.ABANDON_CLAIM_COMPLETED, - data: { - claimId: fileInfo.claim_id, - }, - }); - }; + // We need to run this after a few seconds or the claim gets added back + // to the store again by an already running fetch claims query. + const success = setTimeout( + () => { + dispatch({ + type: types.ABANDON_CLAIM_COMPLETED, + data: { + claimId: fileInfo.claim_id, + }, + }); + }, + 10000, + { once: true } + ); lbry.claim_abandon({ claim_id: fileInfo.claim_id }).then(success); } } diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index 8785972d9..edd64b993 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -96,7 +96,6 @@ class FileList extends React.PureComponent { - {content} diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index c0c25a202..eb865d1ac 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -64,9 +64,16 @@ class FileTile extends React.PureComponent { const isClaimable = lbryuri.isClaimable(uri); const title = isClaimed && metadata && metadata.title ? metadata.title - : uri; + : lbryuri.parse(uri).contentName; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - let onClick = () => navigate("/show", { uri }); + let onClick; + if (isClaimed) { + onClick = () => navigate("/show", { uri }); + } else { + onClick = () => { + return false; + }; + } let description = ""; if (isClaimed) { diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 91fd5eb09..446081f6d 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -873,7 +873,11 @@ class PublishForm extends React.PureComponent { onClick={event => { this.handleSubmit(event); }} - disabled={this.state.submitting} + disabled={ + this.state.submitting || + (this.state.uri && + this.props.resolvingUris.indexOf(this.state.uri) !== -1) + } /> ({ - claims: selectMyClaims(state), + claims: selectMyClaimsWithoutChannels(state), isFetching: selectIsFetchingClaimListMine(state), }); diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 3ee992f94..8f60bf9a1 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -40,19 +40,39 @@ reducers[types.FETCH_CLAIM_LIST_MINE_STARTED] = function(state, action) { reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { const { claims } = action.data; - const myClaims = new Set(state.myClaims); const byUri = Object.assign({}, state.claimsByUri); const byId = Object.assign({}, state.byId); + const pendingById = Object.assign({}, state.pendingById); + + const myClaims = new Set(claims.map(claim => claim.claim_id)); claims.forEach(claim => { - myClaims.add(claim.claim_id); byId[claim.claim_id] = claim; + + const pending = Object.values(pendingById).find(pendingClaim => { + return ( + pendingClaim.name == claim.name && + pendingClaim.channel_name == claim.channel_name + ); + }); + + if (pending) { + delete pendingById[pending.claim_id]; + } }); + // Remove old timed out pending publishes + const old = Object.values(pendingById) + .filter(pendingClaim => Date.now() - pendingClaim.time >= 20 * 60 * 1000) + .forEach(pendingClaim => { + delete pendingById[pendingClaim.claim_id]; + }); + return Object.assign({}, state, { isFetchingClaimListMine: false, myClaims: myClaims, byId, + pendingById, }); }; @@ -91,6 +111,17 @@ reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) { }); }; +reducers[types.ABANDON_CLAIM_STARTED] = function(state, action) { + const { claimId } = action.data; + const abandoningById = Object.assign({}, state.abandoningById); + + abandoningById[claimId] = true; + + return Object.assign({}, state, { + abandoningById, + }); +}; + reducers[types.ABANDON_CLAIM_COMPLETED] = function(state, action) { const { claimId } = action.data; const myClaims = new Set(state.myClaims); @@ -128,17 +159,42 @@ reducers[types.CREATE_CHANNEL_COMPLETED] = function(state, action) { }); }; +reducers[types.PUBLISH_STARTED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingById = Object.assign({}, state.pendingById); + + pendingById[pendingPublish.claim_id] = pendingPublish; + + return Object.assign({}, state, { + pendingById, + }); +}; + reducers[types.PUBLISH_COMPLETED] = function(state, action) { - const { claim } = action.data; + const { claim, pendingPublish } = action.data; const byId = Object.assign({}, state.byId); const myClaims = new Set(state.myClaims); + const pendingById = Object.assign({}, state.pendingById); byId[claim.claim_id] = claim; myClaims.add(claim.claim_id); + delete pendingById[pendingPublish.claim_id]; return Object.assign({}, state, { byId, myClaims, + pendingById, + }); +}; + +reducers[types.PUBLISH_FAILED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingById = Object.assign({}, state.pendingById); + + delete pendingById[pendingPublish.claim_id]; + + return Object.assign({}, state, { + pendingById, }); }; diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index e462f3cf6..c9e2817b3 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -138,39 +138,6 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { }); }; -reducers[types.PUBLISH_STARTED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); - - pendingByOutpoint[pendingPublish.outpoint] = pendingPublish; - - return Object.assign({}, state, { - pendingByOutpoint, - }); -}; - -reducers[types.PUBLISH_COMPLETED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); - - delete pendingByOutpoint[pendingPublish.outpoint]; - - return Object.assign({}, state, { - pendingByOutpoint, - }); -}; - -reducers[types.PUBLISH_FAILED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); - - delete pendingByOutpoint[pendingPublish.outpoint]; - - return Object.assign({}, state, { - pendingByOutpoint, - }); -}; - export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index f0708cf86..3c10ec931 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -110,22 +110,37 @@ export const selectMyClaimsRaw = createSelector( state => new Set(state.myClaims) ); +export const selectAbandoningIds = createSelector(_selectState, state => + Object.keys(state.abandoningById || {}) +); + +export const selectPendingClaims = createSelector(_selectState, state => + Object.values(state.pendingById || {}) +); + export const selectMyClaims = createSelector( selectMyClaimsRaw, selectClaimsById, - (myClaimIds, byId) => { + selectAbandoningIds, + selectPendingClaims, + (myClaimIds, byId, abandoningIds, pendingClaims) => { const claims = []; myClaimIds.forEach(id => { const claim = byId[id]; - if (claim) claims.push(claim); + if (claim && abandoningIds.indexOf(id) == -1) claims.push(claim); }); - return claims; + return [...claims, ...pendingClaims]; } ); +export const selectMyClaimsWithoutChannels = createSelector( + selectMyClaims, + myClaims => myClaims.filter(claim => !claim.name.match(/^@/)) +); + export const selectMyClaimsOutpoints = createSelector( selectMyClaims, myClaims => { diff --git a/ui/js/store.js b/ui/js/store.js index 35f7ab2ec..124174bc1 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -91,17 +91,14 @@ const saveClaimsFilter = createFilter("claims", [ "claimsByUri", "myClaims", "myChannelClaims", -]); -const saveFileInfosFilter = createFilter("fileInfo", [ - "fileInfos", - "pendingByOutpoint", + "pendingById", ]); const persistOptions = { - whitelist: ["claims", "fileInfo"], + whitelist: ["claims"], // Order is important. Needs to be compressed last or other transforms can't // read the data - transforms: [saveClaimsFilter, saveFileInfosFilter, compressor], + transforms: [saveClaimsFilter, compressor], debounce: 10000, storage: localForage, }; From 76fe44e519635e30cf0d34a2da15a5eb6d1d86da Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 10 Jul 2017 21:44:49 +0700 Subject: [PATCH 033/152] Refactor back to lbry.js localStorage for pending publishes --- ui/js/actions/content.js | 61 +++++-------------------------- ui/js/component/fileTile/view.jsx | 9 +---- ui/js/lbry.js | 53 ++++++++++++--------------- ui/js/reducers/claims.js | 39 -------------------- ui/js/store.js | 8 +--- 5 files changed, 34 insertions(+), 136 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 291dc4d81..fb58d0a74 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -389,62 +389,19 @@ export function doCreateChannel(name, amount) { export function doPublish(params) { return function(dispatch, getState) { - let uri; - const { name, channel_name } = params; - if (channel_name) { - uri = lbryuri.build({ name: channel_name, path: name }, false); - } else { - uri = lbryuri.build({ name: name }, false); - } - const fakeId = "pending"; - const pendingPublish = { - name, - channel_name, - claim_id: fakeId, - txid: "pending_" + uri, - nout: 0, - outpoint: fakeId + ":0", - time: Date.now(), - pending: true, - }; - - dispatch({ - type: types.PUBLISH_STARTED, - data: { - params, - pendingPublish, - }, - }); - return new Promise((resolve, reject) => { const success = claim => { - claim.name = params.name; - claim.channel_name = params.channel_name; - dispatch({ - type: types.PUBLISH_COMPLETED, - data: { - claim, - uri, - pendingPublish, - }, - }); - dispatch(doFileList()); resolve(claim); - }; - const failure = error => { - dispatch({ - type: types.PUBLISH_FAILED, - data: { - error, - params, - uri, - pendingPublish, - }, - }); - reject(error); - }; - lbry.publish(params).then(success, failure); + if (claim === true) dispatch(doFetchClaimListMine()); + else + setTimeout(() => dispatch(doFetchClaimListMine()), 20000, { + once: true, + }); + }; + const failure = err => reject(err); + + lbry.publishDeprecated(params, null, success, failure); }); }; } diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index eb865d1ac..ef4654ce8 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -66,14 +66,7 @@ class FileTile extends React.PureComponent { ? metadata.title : lbryuri.parse(uri).contentName; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - let onClick; - if (isClaimed) { - onClick = () => navigate("/show", { uri }); - } else { - onClick = () => { - return false; - }; - } + let onClick = () => navigate("/show", { uri }); let description = ""; if (isClaimed) { diff --git a/ui/js/lbry.js b/ui/js/lbry.js index f13665e09..7409bdde4 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -223,45 +223,38 @@ lbry.publishDeprecated = function( ) { lbry.publish(params).then( result => { - if (returnedPending) { - return; - } - - clearTimeout(returnPendingTimeout); + if (returnPendingTimeout) clearTimeout(returnPendingTimeout); publishedCallback(result); }, err => { - if (returnedPending) { - return; - } - - clearTimeout(returnPendingTimeout); + if (returnPendingTimeout) clearTimeout(returnPendingTimeout); errorCallback(err); } ); - let returnedPending = false; // Give a short grace period in case publish() returns right away or (more likely) gives an error - const returnPendingTimeout = setTimeout(() => { - returnedPending = true; + const returnPendingTimeout = setTimeout( + () => { + if (publishedCallback) { + savePendingPublish({ + name: params.name, + channel_name: params.channel_name, + }); + publishedCallback(true); + } - if (publishedCallback) { - savePendingPublish({ - name: params.name, - channel_name: params.channel_name, - }); - publishedCallback(true); - } - - if (fileListedCallback) { - const { name, channel_name } = params; - savePendingPublish({ - name: params.name, - channel_name: params.channel_name, - }); - fileListedCallback(true); - } - }, 2000); + if (fileListedCallback) { + const { name, channel_name } = params; + savePendingPublish({ + name: params.name, + channel_name: params.channel_name, + }); + fileListedCallback(true); + } + }, + 2000, + { once: true } + ); }; lbry.getClientSettings = function() { diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 8f60bf9a1..2470e5f9c 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -159,45 +159,6 @@ reducers[types.CREATE_CHANNEL_COMPLETED] = function(state, action) { }); }; -reducers[types.PUBLISH_STARTED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingById = Object.assign({}, state.pendingById); - - pendingById[pendingPublish.claim_id] = pendingPublish; - - return Object.assign({}, state, { - pendingById, - }); -}; - -reducers[types.PUBLISH_COMPLETED] = function(state, action) { - const { claim, pendingPublish } = action.data; - const byId = Object.assign({}, state.byId); - const myClaims = new Set(state.myClaims); - const pendingById = Object.assign({}, state.pendingById); - - byId[claim.claim_id] = claim; - myClaims.add(claim.claim_id); - delete pendingById[pendingPublish.claim_id]; - - return Object.assign({}, state, { - byId, - myClaims, - pendingById, - }); -}; - -reducers[types.PUBLISH_FAILED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingById = Object.assign({}, state.pendingById); - - delete pendingById[pendingPublish.claim_id]; - - return Object.assign({}, state, { - pendingById, - }); -}; - export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/store.js b/ui/js/store.js index 124174bc1..5eb84d4cf 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -86,13 +86,7 @@ const createStoreWithMiddleware = redux.compose( const reduxStore = createStoreWithMiddleware(enableBatching(reducers)); const compressor = createCompressor(); -const saveClaimsFilter = createFilter("claims", [ - "byId", - "claimsByUri", - "myClaims", - "myChannelClaims", - "pendingById", -]); +const saveClaimsFilter = createFilter("claims", ["byId", "claimsByUri"]); const persistOptions = { whitelist: ["claims"], From 443caa27406311fc8063e062a2f53d2fac61624e Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 11 Jul 2017 13:01:44 +0700 Subject: [PATCH 034/152] Fix promise reject when creating a channel --- ui/js/actions/content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index fb58d0a74..f46a1fa33 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -380,7 +380,7 @@ export function doCreateChannel(name, amount) { resolve(channelClaim); }, err => { - resolve(err); + reject(err); } ); }); From 6f336c96c5a89e82d42f886964ab3fab49567272 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 11 Jul 2017 13:15:51 +0700 Subject: [PATCH 035/152] Remove unnecessary binds --- ui/js/component/publishForm/internal/channelSection.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/js/component/publishForm/internal/channelSection.jsx b/ui/js/component/publishForm/internal/channelSection.jsx index 274f30bdf..6c7802625 100644 --- a/ui/js/component/publishForm/internal/channelSection.jsx +++ b/ui/js/component/publishForm/internal/channelSection.jsx @@ -69,22 +69,22 @@ class ChannelSection extends React.PureComponent { this.setState({ creatingChannel: true, }); - const success = (() => { + const success = () => { this.setState({ creatingChannel: false, addingChannel: false, channel: newChannelName, }); this.props.handleChannelChange(newChannelName); - }).bind(this); - const failure = (err => { + }; + const failure = err => { this.setState({ creatingChannel: false, }); this.refs.newChannelName.showError( __("Unable to create channel due to an internal error.") ); - }).bind(this); + }; this.props.createChannel(newChannelName, amount).then(success, failure); } From d3c621ed1265645cf18cbea7bb10ee8801a71fe1 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 11 Jul 2017 14:44:45 +0700 Subject: [PATCH 036/152] use _SUCCEEDED for abandom claim and file list action types --- ui/js/actions/file_info.js | 4 ++-- ui/js/constants/action_types.js | 4 ++-- ui/js/reducers/claims.js | 2 +- ui/js/reducers/file_info.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index eb4a6753f..b36cb784e 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -57,7 +57,7 @@ export function doFileList() { lbry.file_list().then(fileInfos => { dispatch({ - type: types.FILE_LIST_COMPLETED, + type: types.FILE_LIST_SUCCEEDED, data: { fileInfos, }, @@ -107,7 +107,7 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { const success = setTimeout( () => { dispatch({ - type: types.ABANDON_CLAIM_COMPLETED, + type: types.ABANDON_CLAIM_SUCCEEDED, data: { claimId: fileInfo.claim_id, }, diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 457761441..7d38568f3 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -47,7 +47,7 @@ export const FETCH_CLAIM_LIST_MINE_STARTED = "FETCH_CLAIM_LIST_MINE_STARTED"; export const FETCH_CLAIM_LIST_MINE_COMPLETED = "FETCH_CLAIM_LIST_MINE_COMPLETED"; export const FILE_LIST_STARTED = "FILE_LIST_STARTED"; -export const FILE_LIST_COMPLETED = "FILE_LIST_COMPLETED"; +export const FILE_LIST_SUCCEEDED = "FILE_LIST_SUCCEEDED"; export const FETCH_FILE_INFO_STARTED = "FETCH_FILE_INFO_STARTED"; export const FETCH_FILE_INFO_COMPLETED = "FETCH_FILE_INFO_COMPLETED"; export const FETCH_COST_INFO_STARTED = "FETCH_COST_INFO_STARTED"; @@ -63,7 +63,7 @@ export const FETCH_AVAILABILITY_STARTED = "FETCH_AVAILABILITY_STARTED"; export const FETCH_AVAILABILITY_COMPLETED = "FETCH_AVAILABILITY_COMPLETED"; export const FILE_DELETE = "FILE_DELETE"; export const ABANDON_CLAIM_STARTED = "ABANDON_CLAIM_STARTED"; -export const ABANDON_CLAIM_COMPLETED = "ABANDON_CLAIM_COMPLETED"; +export const ABANDON_CLAIM_SUCCEEDED = "ABANDON_CLAIM_SUCCEEDED"; export const FETCH_CHANNEL_LIST_MINE_STARTED = "FETCH_CHANNEL_LIST_MINE_STARTED"; export const FETCH_CHANNEL_LIST_MINE_COMPLETED = diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 2470e5f9c..adc0eb2ed 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -122,7 +122,7 @@ reducers[types.ABANDON_CLAIM_STARTED] = function(state, action) { }); }; -reducers[types.ABANDON_CLAIM_COMPLETED] = function(state, action) { +reducers[types.ABANDON_CLAIM_SUCCEEDED] = function(state, action) { const { claimId } = action.data; const myClaims = new Set(state.myClaims); const byId = Object.assign({}, state.byId); diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index c9e2817b3..500fbdf82 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -10,7 +10,7 @@ reducers[types.FILE_LIST_STARTED] = function(state, action) { }); }; -reducers[types.FILE_LIST_COMPLETED] = function(state, action) { +reducers[types.FILE_LIST_SUCCEEDED] = function(state, action) { const { fileInfos } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); From d0e3dd8f990a438e3177f6b770d58da017b0d11f Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 11 Jul 2017 14:57:56 +0700 Subject: [PATCH 037/152] Extract TruncatedMarkdown component --- ui/js/component/common.js | 36 -------------------- ui/js/component/fileCard/view.jsx | 8 ++--- ui/js/component/truncatedMarkdown/index.js | 5 +++ ui/js/component/truncatedMarkdown/view.jsx | 39 ++++++++++++++++++++++ 4 files changed, 46 insertions(+), 42 deletions(-) create mode 100644 ui/js/component/truncatedMarkdown/index.js create mode 100644 ui/js/component/truncatedMarkdown/view.jsx diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 57314bdd3..38dbf83fd 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -1,7 +1,5 @@ import React from "react"; -import ReactDOMServer from "react-dom/server"; import lbry from "../lbry.js"; -import ReactMarkdown from "react-markdown"; //component/icon.js export class Icon extends React.PureComponent { @@ -44,40 +42,6 @@ export class TruncatedText extends React.PureComponent { } } -export class TruncatedMarkdown extends React.PureComponent { - static propTypes = { - lines: React.PropTypes.number, - }; - - static defaultProps = { - lines: null, - }; - - transformMarkdown(text) { - // render markdown to html string then trim html tag - let htmlString = ReactDOMServer.renderToStaticMarkup( - - ); - var txt = document.createElement("textarea"); - txt.innerHTML = htmlString; - return txt.value.replace(/<(?:.|\n)*?>/gm, ""); - } - - render() { - let content = this.props.children && typeof this.props.children === "string" - ? this.transformMarkdown(this.props.children) - : this.props.children; - return ( - - {content} - - ); - } -} - export class BusyMessage extends React.PureComponent { static propTypes = { message: React.PropTypes.string, diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 256bc9014..c6e179af7 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -1,15 +1,11 @@ import React from "react"; import lbryuri from "lbryuri.js"; import Link from "component/link"; -import { - Thumbnail, - TruncatedText, - Icon, - TruncatedMarkdown, -} from "component/common"; +import { Thumbnail, TruncatedText, Icon } from "component/common"; import FilePrice from "component/filePrice"; import UriIndicator from "component/uriIndicator"; import NsfwOverlay from "component/nsfwOverlay"; +import TruncatedMarkdown from "component/truncatedMarkdown"; class FileCard extends React.PureComponent { constructor(props) { diff --git a/ui/js/component/truncatedMarkdown/index.js b/ui/js/component/truncatedMarkdown/index.js new file mode 100644 index 000000000..7cec6defe --- /dev/null +++ b/ui/js/component/truncatedMarkdown/index.js @@ -0,0 +1,5 @@ +import React from "react"; +import { connect } from "react-redux"; +import TruncatedMarkdown from "./view"; + +export default connect()(TruncatedMarkdown); diff --git a/ui/js/component/truncatedMarkdown/view.jsx b/ui/js/component/truncatedMarkdown/view.jsx new file mode 100644 index 000000000..59e42d6af --- /dev/null +++ b/ui/js/component/truncatedMarkdown/view.jsx @@ -0,0 +1,39 @@ +import React from "react"; +import ReactMarkdown from "react-markdown"; +import ReactDOMServer from "react-dom/server"; + +class TruncatedMarkdown extends React.PureComponent { + static propTypes = { + lines: React.PropTypes.number, + }; + + static defaultProps = { + lines: null, + }; + + transformMarkdown(text) { + // render markdown to html string then trim html tag + let htmlString = ReactDOMServer.renderToStaticMarkup( + + ); + var txt = document.createElement("textarea"); + txt.innerHTML = htmlString; + return txt.value.replace(/<(?:.|\n)*?>/gm, ""); + } + + render() { + let content = this.props.children && typeof this.props.children === "string" + ? this.transformMarkdown(this.props.children) + : this.props.children; + return ( + + {content} + + ); + } +} + +export default TruncatedMarkdown; From f1c45775ab33b48f88eed0b41bd872035c7e7ecf Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 11 Jul 2017 15:30:28 +0700 Subject: [PATCH 038/152] Cleaner way of filtering published claims while abandoning --- ui/js/actions/file_info.js | 18 +++++------------- ui/js/reducers/claims.js | 8 ++++++-- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index b36cb784e..d21df8d2a 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -102,20 +102,12 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { }, }); - // We need to run this after a few seconds or the claim gets added back - // to the store again by an already running fetch claims query. - const success = setTimeout( - () => { - dispatch({ - type: types.ABANDON_CLAIM_SUCCEEDED, - data: { - claimId: fileInfo.claim_id, - }, - }); + const success = dispatch({ + type: types.ABANDON_CLAIM_SUCCEEDED, + data: { + claimId: fileInfo.claim_id, }, - 10000, - { once: true } - ); + }); lbry.claim_abandon({ claim_id: fileInfo.claim_id }).then(success); } } diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index adc0eb2ed..9e072f1ce 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -43,8 +43,12 @@ reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { const byUri = Object.assign({}, state.claimsByUri); const byId = Object.assign({}, state.byId); const pendingById = Object.assign({}, state.pendingById); - - const myClaims = new Set(claims.map(claim => claim.claim_id)); + const abandoningById = Object.assign({}, state.abandoningById); + const myClaims = new Set( + claims + .map(claim => claim.claim_id) + .filter(claimId => Object.keys(abandoningById).indexOf(claimId) === -1) + ); claims.forEach(claim => { byId[claim.claim_id] = claim; From 103e4302d46c5a05dbc905378c51605f9a1b162e Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 12 Jul 2017 13:36:08 +0700 Subject: [PATCH 039/152] Disable publish button if bid is too low --- ui/js/component/publishForm/view.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 446081f6d..95abbaa96 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -876,7 +876,10 @@ class PublishForm extends React.PureComponent { disabled={ this.state.submitting || (this.state.uri && - this.props.resolvingUris.indexOf(this.state.uri) !== -1) + this.props.resolvingUris.indexOf(this.state.uri) !== -1) || + (this.claim() && + !this.topClaimIsMine() && + this.state.bid <= this.topClaimValue()) } /> Date: Wed, 12 Jul 2017 14:05:10 +0700 Subject: [PATCH 040/152] Drop notice component --- ui/js/component/notice.js | 27 --------------------------- ui/js/component/publishForm/view.jsx | 13 ++++--------- 2 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 ui/js/component/notice.js diff --git a/ui/js/component/notice.js b/ui/js/component/notice.js deleted file mode 100644 index 623ed51ec..000000000 --- a/ui/js/component/notice.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; - -export class Notice extends React.PureComponent { - static propTypes = { - isError: React.PropTypes.bool, - }; - - static defaultProps = { - isError: false, - }; - - render() { - return ( -
- {this.props.children} -
- ); - } -} - -export default Notice; diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 95abbaa96..26eadf74b 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -4,7 +4,6 @@ import lbryuri from "lbryuri"; import { FormField, FormRow } from "component/form.js"; import Link from "component/link"; import Modal from "component/modal"; -import Notice from "component/notice"; import { BusyMessage } from "component/common"; import ChannelSection from "./internal/ChannelSection"; @@ -190,7 +189,7 @@ class PublishForm extends React.PureComponent { if (!claim) return true; if (!myClaimInfo) return false; - return myClaimInfo.amount >= claimInfo.amount; + return myClaimInfo.amount >= claim.amount; } myClaimInfo() { @@ -453,11 +452,7 @@ class PublishForm extends React.PureComponent { getNameBidHelpText() { if (this.state.prefillDone) { - return ( - - {__("Existing claim data was prefilled")} - - ); + return __("Existing claim data was prefilled"); } if ( @@ -472,13 +467,13 @@ class PublishForm extends React.PureComponent { return __("This URL is unused."); } else if (this.myClaimExists() && !this.state.prefillDone) { return ( - + {__("You already have a claim with this name.")}{" "} this.handlePrefillClicked()} /> - + ); } else if (this.claim()) { if (this.topClaimValue() === 1) { From cd9fcf550aaf7642e66ee49ed1b8cc0680bbba88 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 12 Jul 2017 14:05:44 +0700 Subject: [PATCH 041/152] Remove console.log --- ui/js/component/publishForm/view.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 26eadf74b..61ef71b1c 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -292,7 +292,6 @@ class PublishForm extends React.PureComponent { newState.licenseType = licenseType; } - console.log(newState); this.setState(newState); } From 0419399decd3a8b19e5dc47ba39af985629c42a0 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 12 Jul 2017 14:28:28 +0700 Subject: [PATCH 042/152] Disable markdown editor side-by-side mode --- ui/js/component/form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 6a65218e4..805afac70 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -44,7 +44,7 @@ export class FormField extends React.PureComponent { this._element = SimpleMDE; this._type = "textarea"; this._extraElementProps.options = { - hideIcons: ["guide", "heading", "image", "fullscreen"], + hideIcons: ["guide", "heading", "image", "fullscreen", "side-by-side"], }; } else if (formFieldFileSelectorTypes.includes(this.props.type)) { this._element = "input"; From d1eb8f5de375d91e03f77ef5afaf694a782dc2ec Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 12 Jul 2017 15:08:59 +0700 Subject: [PATCH 043/152] Stop old file infos from updated claims appearing in downloaded file list --- CHANGELOG.md | 1 + ui/js/component/fileList/view.jsx | 2 +- ui/js/page/fileListDownloaded/index.js | 8 ++++++++ ui/js/page/fileListDownloaded/view.jsx | 1 + ui/js/selectors/file_info.js | 19 +++++++++---------- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8f65e2ee..0dd35a53c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * Fixed bug with download notice when switching window focus * Fixed newly published files appearing twice * Fixed unconfirmed published files missing channel name + * Fixed old files from updated published claims appearing in downloaded list ### Deprecated * diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index edd64b993..f910e9500 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -81,7 +81,7 @@ class FileList extends React.PureComponent { content.push( ({ fileInfos: selectFileInfosDownloaded(state), isFetching: selectIsFetchingFileListDownloadedOrPublished(state), + claims: selectMyClaimsWithoutChannels(state), + isFetchingClaims: selectIsFetchingClaimListMine(state), }); const perform = dispatch => ({ @@ -19,6 +26,7 @@ const perform = dispatch => ({ fetchFileInfosDownloaded: () => dispatch(doFetchFileInfosAndPublishedClaims()), cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()), + fetchClaims: () => dispatch(doFetchClaimListMine()), }); export default connect(select, perform)(FileListDownloaded); diff --git a/ui/js/page/fileListDownloaded/view.jsx b/ui/js/page/fileListDownloaded/view.jsx index c1501ec78..8eb18e9d5 100644 --- a/ui/js/page/fileListDownloaded/view.jsx +++ b/ui/js/page/fileListDownloaded/view.jsx @@ -12,6 +12,7 @@ import SubHeader from "component/subHeader"; class FileListDownloaded extends React.PureComponent { componentWillMount() { + if (!this.props.isFetchingClaims) this.props.fetchClaims(); if (!this.props.isFetching) this.props.fetchFileInfosDownloaded(); } diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 7b8a10769..ef469ab02 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -3,6 +3,7 @@ import { createSelector } from "reselect"; import { selectClaimsByUri, selectIsFetchingClaimListMine, + selectMyClaims, selectMyClaimsOutpoints, } from "selectors/claims"; @@ -76,19 +77,17 @@ export const selectFileInfosPendingPublish = createSelector( export const selectFileInfosDownloaded = createSelector( selectFileInfosByOutpoint, - selectMyClaimsOutpoints, - (byOutpoint, myClaimOutpoints) => { - const fileInfoList = []; - Object.values(byOutpoint).forEach(fileInfo => { - if ( + selectMyClaims, + (byOutpoint, myClaims) => { + return Object.values(byOutpoint).filter(fileInfo => { + const myClaimIds = myClaims.map(claim => claim.claim_id); + + return ( fileInfo && - myClaimOutpoints.indexOf(fileInfo.outpoint) === -1 && + myClaimIds.indexOf(fileInfo.claim_id) === -1 && (fileInfo.completed || fileInfo.written_bytes) - ) { - fileInfoList.push(fileInfo); - } + ); }); - return fileInfoList; } ); From 3950f7e9b24d3eae66862665ad1d679f4bd241f9 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 13 Jul 2017 10:19:27 -0400 Subject: [PATCH 044/152] fix import case --- ui/js/component/publishForm/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 61ef71b1c..da6e6eb39 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -5,7 +5,7 @@ import { FormField, FormRow } from "component/form.js"; import Link from "component/link"; import Modal from "component/modal"; import { BusyMessage } from "component/common"; -import ChannelSection from "./internal/ChannelSection"; +import ChannelSection from "./internal/channelSection"; class PublishForm extends React.PureComponent { constructor(props) { From 187ac405fe44aa7551b91fb54fda4db0a2199cd3 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 13 Jul 2017 16:32:32 +0100 Subject: [PATCH 045/152] Modified to use redux store and return to previous scroll position only upon back navigation --- ui/js/actions/app.js | 11 +++++++++++ ui/js/constants/action_types.js | 1 + ui/js/page/discover/index.js | 4 ++++ ui/js/page/discover/view.jsx | 20 ++++++++++++-------- ui/js/reducers/app.js | 12 ++++++++++++ ui/js/selectors/app.js | 5 +++++ 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 35049d435..5eba212f1 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -64,6 +64,17 @@ export function doHistoryBack() { if (!history.state) return; history.back(); + dispatch({ + type: types.HISTORY_BACK, + }); + }; +} + +export function doHistoryBackCompleted() { + return function(dispatch, getState) { + dispatch({ + type: types.HISTORY_BACK_COMPLETED, + }); }; } diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 216c84762..eca5dc928 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -2,6 +2,7 @@ export const CHANGE_PATH = "CHANGE_PATH"; export const OPEN_MODAL = "OPEN_MODAL"; export const CLOSE_MODAL = "CLOSE_MODAL"; export const HISTORY_BACK = "HISTORY_BACK"; +export const HISTORY_BACK_COMPLETED = "HISTORY_BACK_COMPLETED"; export const SHOW_SNACKBAR = "SHOW_SNACKBAR"; export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK"; export const WINDOW_FOCUSED = "WINDOW_FOCUSED"; diff --git a/ui/js/page/discover/index.js b/ui/js/page/discover/index.js index be49b892d..eb8d4d0bc 100644 --- a/ui/js/page/discover/index.js +++ b/ui/js/page/discover/index.js @@ -1,20 +1,24 @@ import React from "react"; import { connect } from "react-redux"; +import { doHistoryBackCompleted } from "actions/app"; import { doFetchFeaturedUris, doCancelAllResolvingUris } from "actions/content"; import { selectFeaturedUris, selectFetchingFeaturedUris, } from "selectors/content"; +import { selectNavigatingBack } from "selectors/app"; import DiscoverPage from "./view"; const select = state => ({ featuredUris: selectFeaturedUris(state), fetchingFeaturedUris: selectFetchingFeaturedUris(state), + isNavigatingBack: selectNavigatingBack(state), }); const perform = dispatch => ({ fetchFeaturedUris: () => dispatch(doFetchFeaturedUris()), cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()), + finishedNavigatingBack: () => dispatch(doHistoryBackCompleted()), }); export default connect(select, perform)(DiscoverPage); diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index b68d026b9..ea3e7b858 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -1,9 +1,9 @@ import React from "react"; +import lbry from "lbry.js"; import lbryio from "lbryio.js"; import lbryuri from "lbryuri"; import FileCard from "component/fileCard"; import { BusyMessage } from "component/common.js"; -import { setSession, getSession } from "utils"; import ToolTip from "component/tooltip.js"; const FeaturedCategory = props => { @@ -42,18 +42,22 @@ class DiscoverPage extends React.PureComponent { } componentDidMount() { - const scrollY = parseInt(getSession("prefs_scrolly")); - if (!isNaN(scrollY)) { - const restoreScrollPosition = () => { - window.scrollTo(0, scrollY); - }; - setTimeout(restoreScrollPosition, 100); + if (this.props.isNavigatingBack) { + const scrollY = parseInt(lbry.getClientSetting("prefs_scrolly")); + if (!isNaN(scrollY)) { + const restoreScrollPosition = () => { + window.scrollTo(0, scrollY); + }; + setTimeout(restoreScrollPosition, 100); + } + + this.props.finishedNavigatingBack(); } window.addEventListener("scroll", this.scrollListener); } handleScroll() { - setSession("prefs_scrolly", window.scrollY); + lbry.setClientSetting("prefs_scrolly", window.scrollY); } componentWillUnmount() { diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index fe8c9adae..f2c50d2ec 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -141,6 +141,18 @@ reducers[types.WINDOW_FOCUSED] = function(state, action) { }); }; +reducers[types.HISTORY_BACK] = function(state, action) { + return Object.assign({}, state, { + navigatingBack: true, + }); +}; + +reducers[types.HISTORY_BACK_COMPLETED] = function(state, action) { + return Object.assign({}, state, { + navigatingBack: false, + }); +}; + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index f6acd6d07..c36ccc18c 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -191,3 +191,8 @@ export const selectBadgeNumber = createSelector( _selectState, state => state.badgeNumber ); + +export const selectNavigatingBack = createSelector( + _selectState, + state => state.navigatingBack +); From f1b0c272cb4467ced737453324988738198657f6 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Fri, 14 Jul 2017 00:18:28 +0100 Subject: [PATCH 046/152] CardMedia component --- ui/js/component/cardMedia/index.js | 8 +++++ ui/js/component/cardMedia/view.jsx | 54 ++++++++++++++++++++++++++++++ ui/js/component/fileCard/view.jsx | 11 +++--- ui/js/component/fileTile/view.jsx | 16 +++------ ui/scss/component/_card.scss | 46 +++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 ui/js/component/cardMedia/index.js create mode 100644 ui/js/component/cardMedia/view.jsx diff --git a/ui/js/component/cardMedia/index.js b/ui/js/component/cardMedia/index.js new file mode 100644 index 000000000..3616b0331 --- /dev/null +++ b/ui/js/component/cardMedia/index.js @@ -0,0 +1,8 @@ +import React from "react"; +import { connect } from "react-redux"; +import CardMedia from "./view"; + +const select = state => ({}); +const perform = dispatch => ({}); + +export default connect(select, perform)(CardMedia); diff --git a/ui/js/component/cardMedia/view.jsx b/ui/js/component/cardMedia/view.jsx new file mode 100644 index 000000000..d0f45f4ac --- /dev/null +++ b/ui/js/component/cardMedia/view.jsx @@ -0,0 +1,54 @@ +import React from "react"; + +class CardMedia extends React.PureComponent { + static AUTO_THUMB_CLASSES = [ + "purple", + "red", + "pink", + "indigo", + "blue", + "light-blue", + "cyan", + "teal", + "green", + "yellow", + "orange", + ]; + + componentWillMount() { + this.setState({ + autoThumbClass: + CardMedia.AUTO_THUMB_CLASSES[ + Math.floor(Math.random() * CardMedia.AUTO_THUMB_CLASSES.length) + ], + }); + } + + render() { + const { title, thumbnail } = this.props; + const atClass = this.state.autoThumbClass; + + if (thumbnail) { + return ( +
+ ); + } + + return ( +
+
+ {title && + title + .replace(/\s+/g, "") + .substring(0, Math.min(title.replace(" ", "").length, 5)) + .toUpperCase()} +
+
+ ); + } +} + +export default CardMedia; diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index c6e179af7..5a35d740d 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -1,5 +1,6 @@ import React from "react"; import lbryuri from "lbryuri.js"; +import CardMedia from "component/cardMedia"; import Link from "component/link"; import { Thumbnail, TruncatedText, Icon } from "component/common"; import FilePrice from "component/filePrice"; @@ -49,6 +50,9 @@ class FileCard extends React.PureComponent { const uri = lbryuri.normalize(this.props.uri); const title = metadata && metadata.title ? metadata.title : uri; + const thumbnail = metadata && metadata.thumbnail + ? metadata.thumbnail + : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; let description = ""; @@ -88,12 +92,7 @@ class FileCard extends React.PureComponent {
- {metadata && - metadata.thumbnail && -
} +
{description}
diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index ef4654ce8..0bdcce64c 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -1,6 +1,7 @@ import React from "react"; import lbry from "lbry.js"; import lbryuri from "lbryuri.js"; +import CardMedia from "component/cardMedia"; import Link from "component/link"; import { TruncatedText } from "component/common.js"; import FilePrice from "component/filePrice"; @@ -65,6 +66,9 @@ class FileTile extends React.PureComponent { const title = isClaimed && metadata && metadata.title ? metadata.title : lbryuri.parse(uri).contentName; + const thumbnail = metadata && metadata.thumbnail + ? metadata.thumbnail + : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; let onClick = () => navigate("/show", { uri }); @@ -98,17 +102,7 @@ class FileTile extends React.PureComponent { >
-
+
{!hidePrice ? : null} diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index a112bd232..da73b21bd 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -98,6 +98,52 @@ $card-link-scaling: 1.1; background-position: 50% 50%; } +.card__media--autothumb { + position: relative +} +.card__media--autothumb.purple { + background-color: #9c27b0 +} +.card__media--autothumb.red { + background-color: #e53935 +} +.card__media--autothumb.pink { + background-color: #e91e63 +} +.card__media--autothumb.indigo { + background-color: #3f51b5 +} +.card__media--autothumb.blue { + background-color: #2196f3 +} +.card__media--autothumb.light-blue { + background-color: #039be5 +} +.card__media--autothumb.cyan { + background-color: #00acc1 +} +.card__media--autothumb.teal { + background-color: #009688 +} +.card__media--autothumb.green { + background-color: #43a047 +} +.card__media--autothumb.yellow { + background-color: #ffeb3b +} +.card__media--autothumb.orange { + background-color: #ffa726 +} + +.card__media--autothumb .card__autothumb__text { + font-size: 2.0em; + width: 100%; + color: #ffffff; + text-align: center; + position: absolute; + top: 36% +} + $width-card-small: $spacing-vertical * 12; $height-card-small: $spacing-vertical * 15; From e918e9b17d57fd847f212bd779a3d82233afe161 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 14 Jul 2017 15:34:53 -0400 Subject: [PATCH 047/152] fix search bug and update changelog --- CHANGELOG.md | 6 ++++-- ui/js/component/fileListSearch/view.jsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dd35a53c..8b08dd887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,17 +11,19 @@ Web UI version numbers should always match the corresponding version of LBRY App * Added option to release claim when deleting a file * Added transition to card hovers to smooth animation * Support markdown makeup in claim description - * + * Replaced free speech flag (used when image is missing) with labeled color tiles ### Changed * Publishes now uses claims rather than files - * + * Publishing revamped. Editing claims is much easier. ### Fixed * Fixed bug with download notice when switching window focus * Fixed newly published files appearing twice * Fixed unconfirmed published files missing channel name * Fixed old files from updated published claims appearing in downloaded list + * Fixed inappropriate text showing on searches + * Restored feedback on claim amounts ### Deprecated * diff --git a/ui/js/component/fileListSearch/view.jsx b/ui/js/component/fileListSearch/view.jsx index bd8efeb00..682d07e7c 100644 --- a/ui/js/component/fileListSearch/view.jsx +++ b/ui/js/component/fileListSearch/view.jsx @@ -67,7 +67,7 @@ class FileListSearch extends React.PureComponent { {results && !!results.length ? - : } + : !isSearching && }
); } From 25098bfab85af061615043bed432c2d6c5326125 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 14 Jul 2017 17:41:49 -0400 Subject: [PATCH 048/152] upgrade daemon --- build/DAEMON_URL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/DAEMON_URL b/build/DAEMON_URL index b3b581882..e7d65abfa 100644 --- a/build/DAEMON_URL +++ b/build/DAEMON_URL @@ -1 +1 @@ -https://github.com/lbryio/lbry/releases/download/v0.13.1/lbrynet-daemon-v0.13.1-OSNAME.zip +https://github.com/lbryio/lbry/releases/download/v0.14.1/lbrynet-daemon-v0.14.1-OSNAME.zip From a7195d12e1d2f217a49143971bde1fc29c771cd0 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 15 Jul 2017 15:15:17 -0400 Subject: [PATCH 049/152] discover page vertical jumping fix --- CHANGELOG.md | 1 + ui/js/page/discover/view.jsx | 17 ++++++++++++----- ui/scss/_gui.scss | 13 +++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b08dd887..9a6a82780 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * Fixed unconfirmed published files missing channel name * Fixed old files from updated published claims appearing in downloaded list * Fixed inappropriate text showing on searches + * Stop discover page from pushing jumping vertically while loading * Restored feedback on claim amounts ### Deprecated diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index d6c506dfd..c347a971f 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -48,13 +48,20 @@ class DiscoverPage extends React.PureComponent { const failedToLoad = !fetchingFeaturedUris && (featuredUris === undefined || - (featuredUris !== undefined && Object.keys(featuredUris).length === 0)); + (featuredUris !== undefined && Object.keys(featuredUris).length === 0)), + hasContent = + typeof featuredUris === "object" && Object.keys(featuredUris).length; return ( -
- {fetchingFeaturedUris && - } - {typeof featuredUris === "object" && +
+ {!hasContent && + fetchingFeaturedUris && + } + {hasContent && Object.keys(featuredUris).map( category => featuredUris[category].length diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index a902fe2da..057027771 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -47,6 +47,19 @@ body width: $width-page-constrained; } } +main.main--refreshing { + &:before { + $width: 30px; + position: absolute; + background: url('../img/busy.gif') no-repeat center center; + width: $width; + height: $spacing-vertical; + content: ""; + left: 50%; + margin-left: -1 / 2 * $width; + display: inline-block; + } +} .icon-fixed-width { /* This borrowed is from a component of Font Awesome we're not using, maybe add it? */ From 902b7f203c3a8260c788b0694d130079b584e073 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 15 Jul 2017 15:24:57 -0400 Subject: [PATCH 050/152] cleanup previous commit --- ui/js/page/discover/view.jsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index c347a971f..fc57a5ba8 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -45,12 +45,9 @@ class DiscoverPage extends React.PureComponent { render() { const { featuredUris, fetchingFeaturedUris } = this.props; - const failedToLoad = - !fetchingFeaturedUris && - (featuredUris === undefined || - (featuredUris !== undefined && Object.keys(featuredUris).length === 0)), - hasContent = - typeof featuredUris === "object" && Object.keys(featuredUris).length; + const hasContent = + typeof featuredUris === "object" && Object.keys(featuredUris).length, + failedToLoad = !fetchingFeaturedUris && !hasContent; return (
{!hasContent && fetchingFeaturedUris && - } + } {hasContent && Object.keys(featuredUris).map( category => From b3de4ceee9d2100175758af1ad91e56de574f2d8 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 15 Jul 2017 14:44:50 -0400 Subject: [PATCH 051/152] beginnings of new flow --- ui/js/actions/app.js | 6 ++++ ui/js/actions/content.js | 4 +-- ui/js/component/app/view.jsx | 12 ++++---- ui/js/component/fileActions/view.jsx | 7 ----- .../modalInsufficientCredits/index.js | 16 ++++++++++ .../modalInsufficientCredits/view.jsx | 24 +++++++++++++++ ui/js/component/modalUpgrade/view.jsx | 1 - ui/js/component/modalWelcome/index.js | 21 ++++++++++---- ui/js/component/modalWelcome/view.jsx | 29 ++++++++++++++----- .../component/video/internal/play-button.jsx | 7 ----- ui/js/constants/modal_types.js | 6 +++- ui/js/main.js | 16 +--------- ui/js/selectors/app.js | 5 ++++ ui/package.json | 3 +- 14 files changed, 104 insertions(+), 53 deletions(-) create mode 100644 ui/js/component/modalInsufficientCredits/index.js create mode 100644 ui/js/component/modalInsufficientCredits/view.jsx diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 35049d435..d812172b6 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -8,11 +8,13 @@ import { selectPageTitle, selectCurrentPage, selectCurrentParams, + selectWelcomeModalAcknowledged, } from "selectors/app"; import { doSearch } from "actions/search"; import { doFetchDaemonSettings } from "actions/settings"; import { doAuthenticate } from "actions/user"; import { doFileList } from "actions/file_info"; +import * as modals from "constants/modal_types"; const { remote, ipcRenderer, shell } = require("electron"); const path = require("path"); @@ -219,12 +221,16 @@ export function doAlertError(errorList) { export function doDaemonReady() { return function(dispatch, getState) { + const showWelcome = !selectWelcomeModalAcknowledged(getState()); dispatch(doAuthenticate()); dispatch({ type: types.DAEMON_READY, }); dispatch(doFetchDaemonSettings()); dispatch(doFileList()); + if (showWelcome) { + dispatch(doOpenModal(modals.WELCOME)); + } }; } diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index f46a1fa33..9d6f55408 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -15,8 +15,8 @@ import { selectBadgeNumber } from "selectors/app"; import { selectTotalDownloadProgress } from "selectors/file_info"; import setBadge from "util/setBadge"; import setProgressBar from "util/setProgressBar"; -import { doFileList } from "actions/file_info"; import batchActions from "util/batchActions"; +import * as modals from "constants/modal_types"; const { ipcRenderer } = require("electron"); @@ -293,7 +293,7 @@ export function doPurchaseUri(uri, purchaseModalName) { } if (cost > balance) { - dispatch(doOpenModal("notEnoughCredits")); + dispatch(doOpenModal(modals.INSUFFICIENT_CREDITS)); } else { dispatch(doOpenModal(purchaseModalName)); } diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index bc8264f21..8f4c54cb0 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -3,10 +3,11 @@ import Router from "component/router"; import Header from "component/header"; import ModalError from "component/modalError"; import ModalDownloading from "component/modalDownloading"; +import ModalInsufficientCredits from "component/modalInsufficientCredits"; import ModalUpgrade from "component/modalUpgrade"; import ModalWelcome from "component/modalWelcome"; import lbry from "lbry"; -import { Line } from "rc-progress"; +import * as modals from "constants/modal_types"; class App extends React.PureComponent { componentWillMount() { @@ -32,10 +33,11 @@ class App extends React.PureComponent {
- {modal == "upgrade" && } - {modal == "downloading" && } - {modal == "error" && } - {modal == "welcome" && } + {modal == modals.UPGRADE && } + {modal == modals.DOWNLOADING && } + {modal == modals.ERROR && } + {modal == modals.INSUFFICIENT_CREDITS && } + {modal == modals.WELCOME && }
); } diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index 53188a1f5..0d4dbbd6b 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -176,13 +176,6 @@ class FileActions extends React.PureComponent { {" "} {__("credits")}. - - {__("You don't have enough LBRY credits to pay for this stream.")} - ({}); + +const perform = dispatch => ({ + addFunds: () => { + dispatch(doNavigate("/rewards")); + dispatch(doCloseModal()); + }, + closeModal: () => dispatch(doCloseModal()), +}); + +export default connect(select, perform)(ModalInsufficientCredits); diff --git a/ui/js/component/modalInsufficientCredits/view.jsx b/ui/js/component/modalInsufficientCredits/view.jsx new file mode 100644 index 000000000..fd214cd11 --- /dev/null +++ b/ui/js/component/modalInsufficientCredits/view.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Modal } from "component/modal"; + +class ModalInsufficientCredits extends React.PureComponent { + render() { + const { addFunds, closeModal } = this.props; + + return ( + + {__("More LBRY credits are required to purchase this.")} + + ); + } +} + +export default ModalInsufficientCredits; diff --git a/ui/js/component/modalUpgrade/view.jsx b/ui/js/component/modalUpgrade/view.jsx index 544fd96b7..2d364bd31 100644 --- a/ui/js/component/modalUpgrade/view.jsx +++ b/ui/js/component/modalUpgrade/view.jsx @@ -1,6 +1,5 @@ import React from "react"; import { Modal } from "component/modal"; -import { downloadUpgrade, skipUpgrade } from "actions/app"; class ModalUpgrade extends React.PureComponent { render() { diff --git a/ui/js/component/modalWelcome/index.js b/ui/js/component/modalWelcome/index.js index 12ec5c0be..5ad4b0bc9 100644 --- a/ui/js/component/modalWelcome/index.js +++ b/ui/js/component/modalWelcome/index.js @@ -1,11 +1,11 @@ import React from "react"; import rewards from "rewards"; import { connect } from "react-redux"; -import { doCloseModal } from "actions/app"; +import { doCloseModal, doNavigate } from "actions/app"; +import { doSetClientSetting } from "actions/settings"; import { selectUserIsRewardApproved } from "selectors/user"; import { makeSelectHasClaimedReward, - makeSelectClaimRewardError, makeSelectRewardByType, } from "selectors/rewards"; import ModalWelcome from "./view"; @@ -21,8 +21,19 @@ const select = (state, props) => { }; }; -const perform = dispatch => ({ - closeModal: () => dispatch(doCloseModal()), -}); +const perform = dispatch => () => { + const closeModal = () => { + dispatch(doSetClientSetting("welcome_acknowledged", true)); + dispatch(doCloseModal()); + }; + + return { + verifyAccount: () => { + closeModal(); + dispatch(doNavigate("/rewards")); + }, + closeModal: closeModal, + }; +}; export default connect(select, perform)(ModalWelcome); diff --git a/ui/js/component/modalWelcome/view.jsx b/ui/js/component/modalWelcome/view.jsx index 82448c3ae..cfd3d2c67 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -6,7 +6,13 @@ import RewardLink from "component/rewardLink"; class ModalWelcome extends React.PureComponent { render() { - const { closeModal, hasClaimed, isRewardApproved, reward } = this.props; + const { + closeModal, + hasClaimed, + isRewardApproved, + reward, + verifyAccount, + } = this.props; return !hasClaimed ? @@ -29,13 +35,20 @@ class ModalWelcome extends React.PureComponent { {" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""}

- {isRewardApproved - ? - : } + {isRewardApproved && + } + {!isRewardApproved && + } + {!isRewardApproved && + }
diff --git a/ui/js/component/video/internal/play-button.jsx b/ui/js/component/video/internal/play-button.jsx index d104fcdeb..f5223231c 100644 --- a/ui/js/component/video/internal/play-button.jsx +++ b/ui/js/component/video/internal/play-button.jsx @@ -78,13 +78,6 @@ class VideoPlayButton extends React.PureComponent { icon={icon} onClick={this.onWatchClick.bind(this)} /> - - {__("You don't have enough LBRY credits to pay for this stream.")} - { const initialState = app.store.getState(); -// import whyDidYouUpdate from "why-did-you-update"; -// if (env === "development") { -// /* -// https://github.com/garbles/why-did-you-update -// "A function that monkey patches React and notifies you in the console when -// potentially unnecessary re-renders occur." -// -// Just checks if props change between updates. Can be fixed by manually -// adding a check in shouldComponentUpdate or using React.PureComponent -// */ -// whyDidYouUpdate(React); -// } - var init = function() { function onDaemonReady() { window.sessionStorage.setItem("loaded", "y"); //once we've made it here once per session, we don't need to show splash again @@ -117,7 +103,7 @@ var init = function() { ReactDOM.render( -
+
, canvas ); diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index f6acd6d07..209b428c1 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -187,6 +187,11 @@ export const selectSnackBarSnacks = createSelector( snackBar => snackBar.snacks || [] ); +export const selectWelcomeModalAcknowledged = createSelector( + _selectState, + state => lbry.getClientSetting("welcome_acknowledged") +); + export const selectBadgeNumber = createSelector( _selectState, state => state.badgeNumber diff --git a/ui/package.json b/ui/package.json index 4c4986ec2..7e7491ed9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -72,8 +72,7 @@ "webpack": "^2.6.1", "webpack-dev-server": "^2.4.4", "webpack-notifier": "^1.5.0", - "webpack-target-electron-renderer": "^0.4.0", - "why-did-you-update": "0.0.8" + "webpack-target-electron-renderer": "^0.4.0" }, "lint-staged": { "gitDir": "../", From d07a0bd8ad974c1c3f1cae1e5f45cb12ce8afc5f Mon Sep 17 00:00:00 2001 From: hackrush Date: Sun, 16 Jul 2017 15:35:57 +0530 Subject: [PATCH 052/152] A quick fix for oscuring cards for nsfw content. The download cards in page were not obscured when opening a link directly(e.g. lbry://jacki2). --- ui/js/page/filePage/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/page/filePage/index.js b/ui/js/page/filePage/index.js index f6629214c..ec151ab50 100644 --- a/ui/js/page/filePage/index.js +++ b/ui/js/page/filePage/index.js @@ -25,7 +25,7 @@ const makeSelect = () => { contentType: selectContentType(state, props), costInfo: selectCostInfo(state, props), metadata: selectMetadata(state, props), - showNsfw: !selectShowNsfw(state), + obscureNsfw: !selectShowNsfw(state), fileInfo: selectFileInfo(state, props), }); From 16abedbf3a27c039ea6b5642a4d10e05aea2ec60 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 16 Jul 2017 12:29:46 -0400 Subject: [PATCH 053/152] moderate progress --- ui/js/actions/app.js | 8 +-- ui/js/component/app/index.js | 32 +++++++-- ui/js/component/app/view.jsx | 32 ++++++++- ui/js/component/auth/index.js | 2 + ui/js/component/auth/view.jsx | 9 ++- ui/js/component/authOverlay/view.jsx | 2 +- ui/js/component/common.js | 5 +- ui/js/component/header/index.js | 4 +- ui/js/component/modalWelcome/view.jsx | 17 +++-- .../userEmailNew/{index.jsx => index.js} | 0 ui/js/component/userEmailNew/view.jsx | 1 - .../userEmailVerify/{index.jsx => index.js} | 0 ui/js/component/userEmailVerify/view.jsx | 1 - ui/js/component/userVerify/index.js | 21 ++++++ ui/js/component/userVerify/view.jsx | 69 +++++++++++++++++++ ui/js/lbry.js | 5 -- ui/js/page/rewards/index.js | 27 +++++--- ui/js/page/rewards/view.jsx | 56 +++++++++------ ui/js/selectors/user.js | 7 +- ui/js/utils.js | 4 ++ ui/scss/component/_form-field.scss | 4 -- 21 files changed, 228 insertions(+), 78 deletions(-) rename ui/js/component/userEmailNew/{index.jsx => index.js} (100%) rename ui/js/component/userEmailVerify/{index.jsx => index.js} (100%) create mode 100644 ui/js/component/userVerify/index.js create mode 100644 ui/js/component/userVerify/view.jsx diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index d812172b6..309e21b01 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -8,13 +8,11 @@ import { selectPageTitle, selectCurrentPage, selectCurrentParams, - selectWelcomeModalAcknowledged, } from "selectors/app"; import { doSearch } from "actions/search"; import { doFetchDaemonSettings } from "actions/settings"; import { doAuthenticate } from "actions/user"; import { doFileList } from "actions/file_info"; -import * as modals from "constants/modal_types"; const { remote, ipcRenderer, shell } = require("electron"); const path = require("path"); @@ -220,17 +218,13 @@ export function doAlertError(errorList) { } export function doDaemonReady() { - return function(dispatch, getState) { - const showWelcome = !selectWelcomeModalAcknowledged(getState()); + return function(dispatch) { dispatch(doAuthenticate()); dispatch({ type: types.DAEMON_READY, }); dispatch(doFetchDaemonSettings()); dispatch(doFileList()); - if (showWelcome) { - dispatch(doOpenModal(modals.WELCOME)); - } }; } diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index 4c966cb56..0d4e1c093 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -1,18 +1,40 @@ import React from "react"; import { connect } from "react-redux"; - import { selectCurrentModal } from "selectors/app"; -import { doCheckUpgradeAvailable, doAlertError } from "actions/app"; +import { + doCheckUpgradeAvailable, + doOpenModal, + doAlertError, +} from "actions/app"; import { doUpdateBalance } from "actions/wallet"; +import { selectWelcomeModalAcknowledged } from "selectors/app"; +import rewards from "rewards"; +import { + selectFetchingRewards, + makeSelectHasClaimedReward, +} from "selectors/rewards"; +import { selectUser } from "selectors/user"; import App from "./view"; +import * as modals from "constants/modal_types"; -const select = state => ({ - modal: selectCurrentModal(state), -}); +const select = (state, props) => { + const selectHasClaimed = makeSelectHasClaimedReward(); + + return { + modal: selectCurrentModal(state), + isWelcomeAcknowledged: selectWelcomeModalAcknowledged(state), + isFetchingRewards: selectFetchingRewards(state), + isWelcomeRewardClaimed: selectHasClaimed(state, { + reward_type: rewards.TYPE_NEW_USER, + }), + user: selectUser(state), + }; +}; const perform = dispatch => ({ alertError: errorList => dispatch(doAlertError(errorList)), checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), + openWelcomeModal: () => dispatch(doOpenModal(modals.WELCOME)), updateBalance: balance => dispatch(doUpdateBalance(balance)), }); diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 8f4c54cb0..7c6b17eca 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -11,17 +11,43 @@ import * as modals from "constants/modal_types"; class App extends React.PureComponent { componentWillMount() { + const { alertError, checkUpgradeAvailable, updateBalance } = this.props; + document.addEventListener("unhandledError", event => { - this.props.alertError(event.detail); + alertError(event.detail); }); if (!this.props.upgradeSkipped) { - this.props.checkUpgradeAvailable(); + checkUpgradeAvailable(); } lbry.balanceSubscribe(balance => { - this.props.updateBalance(balance); + updateBalance(balance); }); + + this.showWelcome(this.props); + } + + componentWillReceiveProps(nextProps) { + this.showWelcome(nextProps); + } + + showWelcome(props) { + const { + isFetchingRewards, + isWelcomeAcknowledged, + isWelcomeRewardClaimed, + openWelcomeModal, + user, + } = props; + + if ( + !isWelcomeAcknowledged && + user && + (!isFetchingRewards || !isWelcomeRewardClaimed) + ) { + openWelcomeModal(); + } } render() { diff --git a/ui/js/component/auth/index.js b/ui/js/component/auth/index.js index 37af9f90f..f448d6e82 100644 --- a/ui/js/component/auth/index.js +++ b/ui/js/component/auth/index.js @@ -4,12 +4,14 @@ import { selectAuthenticationIsPending, selectEmailToVerify, selectUserIsVerificationCandidate, + selectUser, } from "selectors/user"; import Auth from "./view"; const select = state => ({ isPending: selectAuthenticationIsPending(state), email: selectEmailToVerify(state), + user: selectUser(state), isVerificationCandidate: selectUserIsVerificationCandidate(state), }); diff --git a/ui/js/component/auth/view.jsx b/ui/js/component/auth/view.jsx index 551113ffa..1c49c513f 100644 --- a/ui/js/component/auth/view.jsx +++ b/ui/js/component/auth/view.jsx @@ -2,17 +2,20 @@ import React from "react"; import { BusyMessage } from "component/common"; import UserEmailNew from "component/userEmailNew"; import UserEmailVerify from "component/userEmailVerify"; +import UserVerify from "component/userVerify"; export class Auth extends React.PureComponent { render() { - const { isPending, email, isVerificationCandidate } = this.props; + const { email, isPending, isVerificationCandidate, user } = this.props; if (isPending) { return ; - } else if (!email) { + } else if (user && !user.has_verified_email && !email) { return ; - } else if (isVerificationCandidate) { + } else if (user && !user.has_verified_email) { return ; + } else if (user && !user.is_identity_verified) { + return ; } else { return {__("No further steps.")}; } diff --git a/ui/js/component/authOverlay/view.jsx b/ui/js/component/authOverlay/view.jsx index 753fe2fe4..fe8b9151d 100644 --- a/ui/js/component/authOverlay/view.jsx +++ b/ui/js/component/authOverlay/view.jsx @@ -54,7 +54,7 @@ export class AuthOverlay extends React.PureComponent { ? "" :
{!hasEmail && this.state.showNoEmailConfirm - ?
+ ?

{__( "If you continue without an email, you will be ineligible to earn free LBC rewards, as well as unable to receive security related communications." diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 38dbf83fd..8e7279248 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -1,4 +1,5 @@ import React from "react"; +import { formatCredits } from "utils"; import lbry from "../lbry.js"; //component/icon.js @@ -78,7 +79,7 @@ export class CreditAmount extends React.PureComponent { }; render() { - const formattedAmount = lbry.formatCredits( + const formattedAmount = formatCredits( this.props.amount, this.props.precision ); @@ -140,7 +141,7 @@ export class Address extends React.PureComponent { }} style={addressStyle} readOnly="readonly" - value={this.props.address} + value={this.props.address || ""} /> ); } diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js index d05e7800b..eda8923d3 100644 --- a/ui/js/component/header/index.js +++ b/ui/js/component/header/index.js @@ -1,12 +1,12 @@ import React from "react"; -import lbry from "lbry"; +import { formatCredits } from "utils"; import { connect } from "react-redux"; import { selectBalance } from "selectors/wallet"; import { doNavigate, doHistoryBack } from "actions/app"; import Header from "./view"; const select = state => ({ - balance: lbry.formatCredits(selectBalance(state), 1), + balance: formatCredits(selectBalance(state), 1), publish: __("Publish"), }); diff --git a/ui/js/component/modalWelcome/view.jsx b/ui/js/component/modalWelcome/view.jsx index cfd3d2c67..3dec74cfa 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -31,8 +31,11 @@ class ModalWelcome extends React.PureComponent { )}

- {__("Thank you for making content freedom possible!")} - {" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""} + {__("Please have")} {" "} + {reward && + } + {!reward && {__("??")}} + {" "} {__("as a thank you for building content freedom.")}

{isRewardApproved && @@ -40,15 +43,11 @@ class ModalWelcome extends React.PureComponent { {!isRewardApproved && } {!isRewardApproved && - } + }
diff --git a/ui/js/component/userEmailNew/index.jsx b/ui/js/component/userEmailNew/index.js similarity index 100% rename from ui/js/component/userEmailNew/index.jsx rename to ui/js/component/userEmailNew/index.js diff --git a/ui/js/component/userEmailNew/view.jsx b/ui/js/component/userEmailNew/view.jsx index 5391bdb3f..cc553f63e 100644 --- a/ui/js/component/userEmailNew/view.jsx +++ b/ui/js/component/userEmailNew/view.jsx @@ -27,7 +27,6 @@ class UserEmailNew extends React.PureComponent { return (
{ this.handleSubmit(event); }} diff --git a/ui/js/component/userEmailVerify/index.jsx b/ui/js/component/userEmailVerify/index.js similarity index 100% rename from ui/js/component/userEmailVerify/index.jsx rename to ui/js/component/userEmailVerify/index.js diff --git a/ui/js/component/userEmailVerify/view.jsx b/ui/js/component/userEmailVerify/view.jsx index 5aa656a61..de9cda5c3 100644 --- a/ui/js/component/userEmailVerify/view.jsx +++ b/ui/js/component/userEmailVerify/view.jsx @@ -27,7 +27,6 @@ class UserEmailVerify extends React.PureComponent { return ( { this.handleSubmit(event); }} diff --git a/ui/js/component/userVerify/index.js b/ui/js/component/userVerify/index.js new file mode 100644 index 000000000..b24dbdb8f --- /dev/null +++ b/ui/js/component/userVerify/index.js @@ -0,0 +1,21 @@ +import React from "react"; +import { connect } from "react-redux"; +import { doUserEmailVerify } from "actions/user"; +import { + selectEmailVerifyIsPending, + selectEmailToVerify, + selectEmailVerifyErrorMessage, +} from "selectors/user"; +import UserVerify from "./view"; + +const select = state => ({ + isPending: selectEmailVerifyIsPending(state), + email: selectEmailToVerify(state), + errorMessage: selectEmailVerifyErrorMessage(state), +}); + +const perform = dispatch => ({ + verifyUserEmail: code => dispatch(doUserEmailVerify(code)), +}); + +export default connect(select, perform)(UserVerify); diff --git a/ui/js/component/userVerify/view.jsx b/ui/js/component/userVerify/view.jsx new file mode 100644 index 000000000..4ff9e1192 --- /dev/null +++ b/ui/js/component/userVerify/view.jsx @@ -0,0 +1,69 @@ +import React from "react"; +import Link from "component/link"; +import { FormRow } from "component/form.js"; + +class UserVerify extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + code: "", + }; + } + + handleCodeChanged(event) { + this.setState({ + code: event.target.value, + }); + } + + handleSubmit(event) { + event.preventDefault(); + this.props.verifyUserEmail(this.state.code); + } + + render() { + const { errorMessage, isPending } = this.props; + return

VERIFY

; + return ( + { + this.handleSubmit(event); + }} + > + zzzzzzzzzzzzzzzzzzzzzzzzzzzzz + { + this.handleCodeChanged(event); + }} + errorMessage={errorMessage} + /> + {/* render help separately so it always shows */} +
+

+ {__("Email")}{" "} + {" "} + {__("if you did not receive or are having trouble with your code.")} +

+
+
+ { + this.handleSubmit(event); + }} + /> +
+ + ); + } +} + +export default UserVerify; diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 7409bdde4..6b4730762 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -288,11 +288,6 @@ lbry.setClientSetting = function(setting, value) { return localStorage.setItem("setting_" + setting, JSON.stringify(value)); }; -//utilities -lbry.formatCredits = function(amount, precision) { - return amount.toFixed(precision || 1).replace(/\.?0+$/, ""); -}; - lbry.formatName = function(name) { // Converts LBRY name to standard format (all lower case, no special characters, spaces replaced by dashes) name = name.replace("/s+/g", "-"); diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js index 8e7abb1fc..14029ff26 100644 --- a/ui/js/page/rewards/index.js +++ b/ui/js/page/rewards/index.js @@ -1,22 +1,31 @@ import React from "react"; import { connect } from "react-redux"; -import { doNavigate } from "actions/app"; -import { selectFetchingRewards, selectRewards } from "selectors/rewards"; +import { + makeSelectRewardByType, + selectFetchingRewards, + selectRewards, +} from "selectors/rewards"; import { selectUserIsRewardEligible, selectUserHasEmail, selectUserIsVerificationCandidate, } from "selectors/user"; import { doRewardList } from "actions/rewards"; +import rewards from "rewards"; import RewardsPage from "./view"; -const select = state => ({ - fetching: selectFetchingRewards(state), - rewards: selectRewards(state), - hasEmail: selectUserHasEmail(state), - isEligible: selectUserIsRewardEligible(state), - isVerificationCandidate: selectUserIsVerificationCandidate(state), -}); +const select = (state, props) => { + const selectReward = makeSelectRewardByType(); + + return { + fetching: selectFetchingRewards(state), + rewards: selectRewards(state), + hasEmail: selectUserHasEmail(state), + isEligible: selectUserIsRewardEligible(state), + isVerificationCandidate: selectUserIsVerificationCandidate(state), + newUserReward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + }; +}; const perform = dispatch => ({ fetchRewards: () => dispatch(doRewardList()), diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index 237994667..67c8b1a27 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -1,9 +1,7 @@ import React from "react"; -import lbryio from "lbryio"; import { BusyMessage, CreditAmount, Icon } from "component/common"; import SubHeader from "component/subHeader"; import Auth from "component/auth"; -import Link from "component/link"; import RewardLink from "component/rewardLink"; const RewardTile = props => { @@ -41,7 +39,9 @@ class RewardsPage extends React.PureComponent { fetchRewards(props) { const { fetching, rewards, fetchRewards } = props; - if (!fetching && Object.keys(rewards).length < 1) fetchRewards(); + if (!fetching && (!rewards || !rewards.length)) { + fetchRewards(); + } } render() { @@ -51,6 +51,7 @@ class RewardsPage extends React.PureComponent { isVerificationCandidate, hasEmail, rewards, + newUserReward, } = this.props; let content, @@ -59,42 +60,55 @@ class RewardsPage extends React.PureComponent { if (!hasEmail || isVerificationCandidate) { content = (
-

- {__( - "Additional information is required to be eligible for the rewards program." - )} -

- +
+ {newUserReward && + } +

Welcome to LBRY

+
+
+

+ {" "}{__( + "Claim your welcome credits to be able to publish content, pay creators, and have a say over the LBRY network." + )} +

+
+
); isCard = true; } else if (!isEligible) { isCard = true; content = ( -
+

{__("You are not eligible to claim rewards.")}

); } else if (fetching) { - content = ; + content = ( +
+ +
+ ); } else if (rewards.length > 0) { - content = rewards.map(reward => - + content = ( +
+ {rewards.map(reward => + + )} +
); } else { - content =
{__("Failed to load rewards.")}
; + content = ( +
+ {__("Failed to load rewards.")} +
+ ); } return (
- {isCard - ?
-
- {content} -
-
- : content} + {isCard ?
{content}
: content}
); } diff --git a/ui/js/selectors/user.js b/ui/js/selectors/user.js index d17485aa6..a7ff1dc9f 100644 --- a/ui/js/selectors/user.js +++ b/ui/js/selectors/user.js @@ -12,10 +12,7 @@ export const selectUserIsPending = createSelector( state => state.userIsPending ); -export const selectUser = createSelector( - _selectState, - state => state.user || {} -); +export const selectUser = createSelector(_selectState, state => state.user); export const selectEmailToVerify = createSelector( _selectState, @@ -65,7 +62,7 @@ export const selectEmailVerifyErrorMessage = createSelector( export const selectUserIsVerificationCandidate = createSelector( selectUser, - user => user && !user.has_verified_email + user => user && (!user.has_verified_email || !user.is_identity_verified) ); export const selectUserIsAuthRequested = createSelector( diff --git a/ui/js/utils.js b/ui/js/utils.js index 783f85113..b41a2e0d4 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -29,3 +29,7 @@ export function getSession(key, fallback = undefined) { export function setSession(key, value) { sessionStorage.setItem(key, JSON.stringify(value)); } + +export function formatCredits(amount, precision) { + return amount.toFixed(precision || 1).replace(/\.?0+$/, ""); +} diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index f701ebe06..8f918cc7d 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -3,10 +3,6 @@ $width-input-border: 2px; $width-input-text: 330px; -.form-input-width { - width: $width-input-text -} - .form-row-submit { margin-top: $spacing-vertical; From 0854fb70045e00d235b385d745e90fd72fc1469a Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Mon, 17 Jul 2017 15:50:07 +0100 Subject: [PATCH 054/152] Discover page UI tweaks --- ui/js/component/fileCard/view.jsx | 4 ++-- ui/scss/component/_card.scss | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 5a35d740d..12f0218e2 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -78,8 +78,9 @@ class FileCard extends React.PureComponent { onClick={() => navigate("/show", { uri })} className="card__link" > +
-
+
{title}
@@ -92,7 +93,6 @@ class FileCard extends React.PureComponent {
-
{description}
diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index da73b21bd..72032fbb4 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -28,6 +28,9 @@ $padding-card-horizontal: $spacing-vertical * 2/3; margin-top: $spacing-vertical * 1/3; margin-bottom: $spacing-vertical * 1/3; } +.card__title-identity .card__title { + font-size: 0.95em +} .card__actions { padding: 0 $padding-card-horizontal; } @@ -51,8 +54,8 @@ $padding-card-horizontal: $spacing-vertical * 2/3; color: #444; margin-top: 12px; font-size: 0.9em; - margin-top: $spacing-vertical * 2/3; - margin-bottom: $spacing-vertical * 2/3; + margin-top: $spacing-vertical * 1/3; + margin-bottom: $spacing-vertical * 1/3; padding: 0 $padding-card-horizontal; } .card__subtext--allow-newlines { @@ -60,6 +63,8 @@ $padding-card-horizontal: $spacing-vertical * 2/3; } .card__subtext--two-lines { height: $font-size * 0.9 * $font-line-height * 2; + font-size: 0.82em; + color: #515151 } .card-overlay { position: absolute; @@ -144,7 +149,7 @@ $card-link-scaling: 1.1; top: 36% } -$width-card-small: $spacing-vertical * 12; +$width-card-small: $spacing-vertical * 10; $height-card-small: $spacing-vertical * 15; .card--small { From 95c5ddbfda155d0e87678418622a51429f974ed5 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 18 Jul 2017 13:45:00 +0700 Subject: [PATCH 055/152] Fix hiding price input when free is checked on publish form --- ui/js/component/publishForm/view.jsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index da6e6eb39..4d765e6d6 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -42,6 +42,7 @@ class PublishForm extends React.PureComponent { submitting: false, creatingChannel: false, modal: null, + isFee: false, }; } @@ -635,11 +636,8 @@ class PublishForm extends React.PureComponent { label={__("Free")} type="radio" name="isFree" - value="1" - onChange={() => { - this.handleFeePrefChange(false); - }} - defaultChecked={!this.state.isFee} + onChange={() => this.handleFeePrefChange(false)} + checked={!this.state.isFee} /> { this.handleFeePrefChange(true); }} - defaultChecked={this.state.isFee} + checked={this.state.isFee} /> Date: Tue, 18 Jul 2017 13:53:45 +0700 Subject: [PATCH 056/152] Fix hiding new channel fields on publish form --- ui/js/component/publishForm/internal/channelSection.jsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ui/js/component/publishForm/internal/channelSection.jsx b/ui/js/component/publishForm/internal/channelSection.jsx index 6c7802625..76f442af1 100644 --- a/ui/js/component/publishForm/internal/channelSection.jsx +++ b/ui/js/component/publishForm/internal/channelSection.jsx @@ -93,6 +93,7 @@ class ChannelSection extends React.PureComponent { "This LBC remains yours and the deposit can be undone at any time." ); + const channel = this.state.addingChannel ? "new" : this.props.channel; const { fetchingChannels, channels = [] } = this.props; let channelContent = []; @@ -102,7 +103,7 @@ class ChannelSection extends React.PureComponent { type="select" tabIndex="1" onChange={this.handleChannelChange.bind(this)} - value={this.props.channel} + value={channel} >
-
{__("Max Key Fee")}
+
{__("Max Purchase Price")}
{__( - "This will prevent you from purchasing anything over this fee, as a safety measure. (Default: 50 USD)" + "This will prevent you from purchasing any content over this cost, as a safety measure." )}
From c0afe22d7ffeef2b2ce7c1deca4a24f4e82e47b7 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 25 Jul 2017 13:09:22 -0400 Subject: [PATCH 088/152] update daemon and changelog --- CHANGELOG.md | 4 +++- app/package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 151700ce2..49d594679 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,16 +8,18 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added + * Identity verification for new reward participants * Added option to release claim when deleting a file * Added transition to card hovers to smooth animation * Support markdown makeup in claim description - * Replaced free speech flag (used when image is missing) with labeled color tiles + * Use randomly colored tiles when image is missing from metadata * Added a loading message to file actions * URL is auto suggested in Publish Page ### Changed * Publishes now uses claims rather than files * Publishing revamped. Editing claims is much easier. + * Daemon updated to [v0.14.2](https://github.com/lbryio/lbry/releases/tag/v0.14.2) ### Fixed * Fixed bug with download notice when switching window focus diff --git a/app/package.json b/app/package.json index ced806d22..5494a55cd 100644 --- a/app/package.json +++ b/app/package.json @@ -20,6 +20,6 @@ "electron-rebuild": "^1.5.11" }, "lbrySettings": { - "lbrynetDaemonVersion": "0.14.1" + "lbrynetDaemonVersion": "0.14.2" } } From 6e08fd6888dc8ddb4a1673a63ad11ecc5cd6b5c1 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 25 Jul 2017 14:06:36 -0400 Subject: [PATCH 089/152] I think DAEMON_URL can't die yet --- build/DAEMON_URL | 1 + 1 file changed, 1 insertion(+) create mode 100644 build/DAEMON_URL diff --git a/build/DAEMON_URL b/build/DAEMON_URL new file mode 100644 index 000000000..10aa02a3a --- /dev/null +++ b/build/DAEMON_URL @@ -0,0 +1 @@ +https://github.com/lbryio/lbry/releases/download/v0.14.2/lbrynet-daemon-v0.14.2-OSNAME.zip \ No newline at end of file From 85468c350c47313aed88c51c0f83699f44503a2e Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 25 Jul 2017 14:34:28 -0400 Subject: [PATCH 090/152] minor flow tweak for grandfathered users --- ui/js/page/auth/view.jsx | 4 +- ui/js/page/rewards/view.jsx | 89 ++++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/ui/js/page/auth/view.jsx b/ui/js/page/auth/view.jsx index fac6e13a0..1ad8d9f9d 100644 --- a/ui/js/page/auth/view.jsx +++ b/ui/js/page/auth/view.jsx @@ -19,7 +19,7 @@ export class AuthPage extends React.PureComponent { !isPending && user && user.has_verified_email && - user.is_identity_verified + (user.is_reward_approved || user.is_identity_verified) ) { props.navigate(props.pathAfterAuth); } @@ -32,7 +32,7 @@ export class AuthPage extends React.PureComponent { return __("Welcome to LBRY"); } else if (user && !user.has_verified_email) { return __("Confirm Email"); - } else if (user && !user.is_identity_verified) { + } else if (user && !user.is_identity_verified && !user.is_reward_approved) { return __("Confirm Identity"); } else { return __("Welcome to LBRY"); diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index f69f3c61b..deb6fa00d 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -71,52 +71,57 @@ class RewardsPage extends React.PureComponent { ); } - if ( - user && - (!user.primary_email || + if (user && !user.is_reward_approved) { + if ( + !user.primary_email || !user.has_verified_email || - !user.is_identity_verified) - ) { - cardHeader = ( -
-
-

{__("Only verified accounts are eligible to earn rewards.")}

+ !user.is_identity_verified + ) { + cardHeader = ( +
+
+

+ {__("Only verified accounts are eligible to earn rewards.")} +

+
+
+ +
+ ); + } else { + cardHeader = (
- -
-
- ); - } else if (user && !user.is_reward_approved) { - cardHeader = ( -
-

- {__( - "This account must undergo review before you can participate in the rewards program." - )} - {" "} - {__("This can take anywhere from several minutes to several days.")} -

+

+ {__( + "This account must undergo review before you can participate in the rewards program." + )} + {" "} + {__( + "This can take anywhere from several minutes to several days." + )} +

-

- {__( - "We apologize for this inconvenience, but have added this additional step to prevent fraud." - )} -

-

- {__("You will receive an email when this process is complete.") + - " " + - __("Please enjoy free content in the meantime!")} -

-

- navigate("/discover")} - button="primary" - label="Return Home" - /> -

-
- ); +

+ {__( + "We apologize for this inconvenience, but have added this additional step to prevent fraud." + )} +

+

+ {__("You will receive an email when this process is complete.") + + " " + + __("Please enjoy free content in the meantime!")} +

+

+ navigate("/discover")} + button="primary" + label="Return Home" + /> +

+
+ ); + } } return ( From edb9ad3f01e2dd55aba9fabae112f5a26f874232 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 25 Jul 2017 14:59:47 -0400 Subject: [PATCH 091/152] simplify config --- ui/js/component/cardVerify/view.jsx | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/ui/js/component/cardVerify/view.jsx b/ui/js/component/cardVerify/view.jsx index 79d4370a4..7f4c1a1a6 100644 --- a/ui/js/component/cardVerify/view.jsx +++ b/ui/js/component/cardVerify/view.jsx @@ -314,25 +314,6 @@ class CardVerify extends React.Component { this.setState({ open: false }); }; - getConfig = () => - ["token", "name", "description"].reduce( - (config, key) => - Object.assign( - {}, - config, - this.props.hasOwnProperty(key) && { - [key]: this.props[key], - } - ), - { - allowRememberMe: false, - closed: this.onClosed, - description: __("Confirm Identity"), - email: this.props.email, - panelLabel: "Verify", - } - ); - updateStripeHandler() { if (!CardVerify.stripeHandler) { CardVerify.stripeHandler = StripeCheckout.configure({ @@ -343,7 +324,14 @@ class CardVerify extends React.Component { showStripeDialog() { this.setState({ open: true }); - CardVerify.stripeHandler.open(this.getConfig()); + CardVerify.stripeHandler.open({ + allowRememberMe: false, + closed: this.onClosed, + description: __("Confirm Identity"), + email: this.props.email, + panelLabel: "Verify", + token: this.props.token, + }); } onClick = () => { From a3ce714af162916e6c19d22a1c1813a49457447e Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 25 Jul 2017 15:20:47 -0400 Subject: [PATCH 092/152] =?UTF-8?q?Bump=20version:=200.13.0=20=E2=86=92=20?= =?UTF-8?q?0.14.0rc1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- app/package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 235981e26..2ecb7189e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.13.0 +current_version = 0.14.0rc1 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/app/package.json b/app/package.json index 5494a55cd..e33ddb0b3 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.13.0", + "version": "0.14.0rc1", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { diff --git a/ui/package.json b/ui/package.json index 7e7491ed9..e12e5052e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.13.0", + "version": "0.14.0rc1", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From 1ccd03ea5984565225ec3d3631776196774f0bf4 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 25 Jul 2017 16:41:21 -0400 Subject: [PATCH 093/152] fix typo in file_info reducer --- ui/js/reducers/file_info.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index cb5221c98..b54a61f31 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -58,7 +58,7 @@ reducers[types.DOWNLOADING_STARTED] = function(state, action) { const { uri, outpoint, fileInfo } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); - const newDownloading = Object.assign({}, state.downloadingByOutpoin); + const newDownloading = Object.assign({}, state.downloadingByOutpoint); const newLoading = Object.assign({}, state.urisLoading); newDownloading[outpoint] = true; From 4f068afa98611a5227768ef8f80c3d06c96c3f5e Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 26 Jul 2017 15:17:39 +0700 Subject: [PATCH 094/152] Remove old commented code in settings/view --- ui/js/page/settings/view.jsx | 143 ++--------------------------------- 1 file changed, 5 insertions(+), 138 deletions(-) diff --git a/ui/js/page/settings/view.jsx b/ui/js/page/settings/view.jsx index fbe5ebb94..e664c52d7 100644 --- a/ui/js/page/settings/view.jsx +++ b/ui/js/page/settings/view.jsx @@ -10,7 +10,7 @@ class SettingsPage extends React.PureComponent { constructor(props) { super(props); - const { daemonSettings } = this.props; + const { daemonSettings } = this.props || {}; this.state = { // isMaxUpload: daemonSettings && daemonSettings.max_upload != 0, @@ -80,7 +80,7 @@ class SettingsPage extends React.PureComponent { onKeyFeeDisableChange(isDisabled) { this.setDaemonSetting("disable_max_key_fee", isDisabled); } - + // onMaxUploadPrefChange(isLimited) { // if (!isLimited) { // this.setDaemonSetting("max_upload", 0.0); @@ -129,141 +129,6 @@ class SettingsPage extends React.PureComponent { ); } - /* -
-
-

Run on Startup

-
-
- -
-
- */ - /* - -
-
-

{__("Language")}

-
-
-
- { - this.onLanguageChange("en"); - }} - defaultChecked={this.state.language == "en"} - /> -
-
- { - this.onLanguageChange("rs"); - }} - defaultChecked={this.state.language == "rs"} - /> -
-
-
- */ - - /* -
-
-

{__("Bandwidth Limits")}

-
-
-
-
{__("Max Upload")}
-
- { - this.onMaxUploadPrefChange(false); - }} - defaultChecked={!this.state.isMaxUpload} - label={__("Unlimited")} - /> -
- { - this.onMaxUploadPrefChange(true); - }} - defaultChecked={this.state.isMaxUpload} - label={ - this.state.isMaxUpload ? __("Up to") : __("Choose limit...") - } - /> - {this.state.isMaxUpload - ? - : ""} - {this.state.isMaxUpload - ? MB/s - : ""} -
-
-
-
-
{__("Max Download")}
-
- { - this.onMaxDownloadPrefChange(false); - }} - defaultChecked={!this.state.isMaxDownload} - /> -
- { - this.onMaxDownloadPrefChange(true); - }} - defaultChecked={this.state.isMaxDownload} - label={ - this.state.isMaxDownload ? __("Up to") : __("Choose limit...") - } - /> - {this.state.isMaxDownload - ? - : ""} - {this.state.isMaxDownload - ? MB/s - : ""} -
-
-
- */ return (
@@ -287,7 +152,9 @@ class SettingsPage extends React.PureComponent {
-
{__("Max Purchase Price")}
+
+ {__("Max Purchase Price")} +
Date: Wed, 26 Jul 2017 15:20:18 +0700 Subject: [PATCH 095/152] Stop app blowing up on refresh on settings page --- ui/js/page/settings/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/page/settings/view.jsx b/ui/js/page/settings/view.jsx index e664c52d7..d7646c476 100644 --- a/ui/js/page/settings/view.jsx +++ b/ui/js/page/settings/view.jsx @@ -122,7 +122,7 @@ class SettingsPage extends React.PureComponent { render() { const { daemonSettings } = this.props; - if (!daemonSettings) { + if (!daemonSettings || Object.keys(daemonSettings).length === 0) { return (
{__("Failed to load settings.")} From 12e0d99a959d02a661995b6ce0946bcda7a0b4b8 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 26 Jul 2017 15:44:05 +0700 Subject: [PATCH 096/152] Quick fix to stop an infinite loop --- ui/js/component/fileActions/view.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index 4eae24a6f..df624a345 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -31,7 +31,8 @@ class FileActions extends React.PureComponent { if ( !downloading && fileInfo && - !fileInfo.written_bytes && + !fileInfo.completed && + fileInfo.written_bytes !== false && fileInfo.written_bytes < fileInfo.total_bytes ) { restartDownload(uri, fileInfo.outpoint); From 658e88a46720bd9147f7bd7d49739d992ee4994f Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 26 Jul 2017 16:46:46 -0400 Subject: [PATCH 097/152] =?UTF-8?q?Bump=20version:=200.14.0rc1=20=E2=86=92?= =?UTF-8?q?=200.14.0rc2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- app/package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 2ecb7189e..8e3940bd9 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.0rc1 +current_version = 0.14.0rc2 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/app/package.json b/app/package.json index e33ddb0b3..367ed0c8d 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.0rc1", + "version": "0.14.0rc2", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { diff --git a/ui/package.json b/ui/package.json index e12e5052e..7494b1027 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.0rc1", + "version": "0.14.0rc2", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From 2a07aa4916248bf57c70aff5cbef2ef7a71f855b Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 26 Jul 2017 16:49:45 -0400 Subject: [PATCH 098/152] more changelog cleanup --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49d594679..00e74d16d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,17 +9,17 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added * Identity verification for new reward participants - * Added option to release claim when deleting a file + * Support rich markup in publishing descriptions and show pages. + * Release past publishing claims (and recover LBC) via the UI * Added transition to card hovers to smooth animation - * Support markdown makeup in claim description * Use randomly colored tiles when image is missing from metadata * Added a loading message to file actions * URL is auto suggested in Publish Page ### Changed - * Publishes now uses claims rather than files * Publishing revamped. Editing claims is much easier. - * Daemon updated to [v0.14.2](https://github.com/lbryio/lbry/releases/tag/v0.14.2) + * Daemon updated from v0.13.1 to [v0.14.2](https://github.com/lbryio/lbry/releases/tag/v0.14.2) + * Publish page now use `claim_list` rather than `file_list` ### Fixed * Fixed bug with download notice when switching window focus From 603217f554e5aefe91b6229b071583fca6e6ddeb Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Thu, 27 Jul 2017 11:45:34 +0700 Subject: [PATCH 099/152] Fix a bug where refreshing strips the claim_id from the uri --- ui/js/reducers/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index 6ee007b86..497915e89 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -4,7 +4,7 @@ import lbry from "lbry"; const currentPath = () => { const hash = document.location.hash; - if (hash !== "") return hash.split("#")[1]; + if (hash !== "") return hash.replace(/^#/, ""); else return "/discover"; }; From ad73e6f1ae43d4355796494796c5d79d474e40b9 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 27 Jul 2017 14:42:43 -0400 Subject: [PATCH 100/152] add zip code --- ui/js/component/cardVerify/view.jsx | 194 +--------------------------- 1 file changed, 4 insertions(+), 190 deletions(-) diff --git a/ui/js/component/cardVerify/view.jsx b/ui/js/component/cardVerify/view.jsx index 7f4c1a1a6..0bc17507b 100644 --- a/ui/js/component/cardVerify/view.jsx +++ b/ui/js/component/cardVerify/view.jsx @@ -7,15 +7,9 @@ let scriptLoaded = false; let scriptDidError = false; class CardVerify extends React.Component { - static defaultProps = { - label: "Verify", - locale: "auto", - }; - static propTypes = { - // If included, will render the default blue button with label text. - // (Requires including stripe-checkout.css or adding the .styl file - // to your pipeline) + disabled: PropTypes.bool, + label: PropTypes.string, // ===================================================== @@ -34,188 +28,6 @@ class CardVerify extends React.Component { // token.id can be used to create a charge or customer. // token.email contains the email address entered by the user. token: PropTypes.func.isRequired, - - // ========================== - // Highly Recommended Options - // ========================== - - // Name of the company or website. - name: PropTypes.string, - - // A description of the product or service being purchased. - description: PropTypes.string, - - // Specify auto to display Checkout in the user's preferred language, if - // available. English will be used by default. - // - // https://stripe.com/docs/checkout#supported-languages - // for more info. - locale: PropTypes.oneOf([ - "auto", // (Default) Automatically chosen by checkout - "zh", // Simplified Chinese - "da", // Danish - "nl", // Dutch - "en", // English - "fr", // French - "de", // German - "it", // Italian - "ja", // Japanease - "no", // Norwegian - "es", // Spanish - "sv", // Swedish - ]), - - // ============== - // Optional Props - // ============== - - // The currency of the amount (3-letter ISO code). The default is USD. - currency: PropTypes.oneOf([ - "AED", - "AFN", - "ALL", - "AMD", - "ANG", - "AOA", - "ARS", - "AUD", - "AWG", - "AZN", - "BAM", - "BBD", - "BDT", - "BGN", - "BIF", - "BMD", - "BND", - "BOB", - "BRL", - "BSD", - "BWP", - "BZD", - "CAD", - "CDF", - "CHF", - "CLP", - "CNY", - "COP", - "CRC", - "CVE", - "CZK", - "DJF", - "DKK", - "DOP", - "DZD", - "EEK", - "EGP", - "ETB", - "EUR", - "FJD", - "FKP", - "GBP", - "GEL", - "GIP", - "GMD", - "GNF", - "GTQ", - "GYD", - "HKD", - "HNL", - "HRK", - "HTG", - "HUF", - "IDR", - "ILS", - "INR", - "ISK", - "JMD", - "JPY", - "KES", - "KGS", - "KHR", - "KMF", - "KRW", - "KYD", - "KZT", - "LAK", - "LBP", - "LKR", - "LRD", - "LSL", - "LTL", - "LVL", - "MAD", - "MDL", - "MGA", - "MKD", - "MNT", - "MOP", - "MRO", - "MUR", - "MVR", - "MWK", - "MXN", - "MYR", - "MZN", - "NAD", - "NGN", - "NIO", - "NOK", - "NPR", - "NZD", - "PAB", - "PEN", - "PGK", - "PHP", - "PKR", - "PLN", - "PYG", - "QAR", - "RON", - "RSD", - "RUB", - "RWF", - "SAR", - "SBD", - "SCR", - "SEK", - "SGD", - "SHP", - "SLL", - "SOS", - "SRD", - "STD", - "SVC", - "SZL", - "THB", - "TJS", - "TOP", - "TRY", - "TTD", - "TWD", - "TZS", - "UAH", - "UGX", - "USD", - "UYU", - "UZS", - "VND", - "VUV", - "WST", - "XAF", - "XCD", - "XOF", - "XPF", - "YER", - "ZAR", - "ZMW", - ]), - - // The label of the payment button in the Checkout form (e.g. “Subscribe”, - // “Pay {{amount}}”, etc.). If you include {{amount}}, it will be replaced - // by the provided amount. Otherwise, the amount will be appended to the - // end of your label. - panelLabel: PropTypes.string, }; constructor(props) { @@ -329,8 +141,10 @@ class CardVerify extends React.Component { closed: this.onClosed, description: __("Confirm Identity"), email: this.props.email, + locale: "auto", panelLabel: "Verify", token: this.props.token, + zipCode: true, }); } From 6b1bd038b4847066831343ff654363cc6c4c20fe Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 27 Jul 2017 14:43:04 -0400 Subject: [PATCH 101/152] =?UTF-8?q?Bump=20version:=200.14.0rc2=20=E2=86=92?= =?UTF-8?q?=200.14.0rc3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- app/package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 8e3940bd9..21315ab72 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.0rc2 +current_version = 0.14.0rc3 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/app/package.json b/app/package.json index 367ed0c8d..35b00d087 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.0rc2", + "version": "0.14.0rc3", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { diff --git a/ui/package.json b/ui/package.json index 7494b1027..97aa4bc83 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.0rc2", + "version": "0.14.0rc3", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From 687321d91b1e64103364e29ed0d87b72d5657cd8 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 27 Jul 2017 21:13:12 -0400 Subject: [PATCH 102/152] rework welcome flow and copy again --- ui/js/component/app/index.js | 23 ++---- ui/js/component/app/view.jsx | 11 +-- ui/js/component/cardVerify/view.jsx | 1 + ui/js/component/modalFirstReward/view.jsx | 13 ++-- ui/js/component/modalWelcome/index.js | 4 +- ui/js/component/modalWelcome/view.jsx | 95 +++++++++++++++-------- ui/js/component/userEmailVerify/view.jsx | 4 +- ui/js/component/userVerify/view.jsx | 18 +++-- ui/js/page/auth/view.jsx | 2 +- ui/js/selectors/rewards.js | 6 ++ ui/scss/component/_modal.scss | 4 +- 11 files changed, 103 insertions(+), 78 deletions(-) diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index ff4940f88..41f79393e 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -9,28 +9,15 @@ import { } from "actions/app"; import { doUpdateBalance } from "actions/wallet"; import { selectWelcomeModalAcknowledged } from "selectors/app"; -import rewards from "rewards"; -import { - selectFetchingRewards, - makeSelectHasClaimedReward, -} from "selectors/rewards"; import { selectUser } from "selectors/user"; import App from "./view"; import * as modals from "constants/modal_types"; -const select = (state, props) => { - const selectHasClaimed = makeSelectHasClaimedReward(); - - return { - modal: selectCurrentModal(state), - isWelcomeAcknowledged: selectWelcomeModalAcknowledged(state), - isFetchingRewards: selectFetchingRewards(state), - isWelcomeRewardClaimed: selectHasClaimed(state, { - reward_type: rewards.TYPE_NEW_USER, - }), - user: selectUser(state), - }; -}; +const select = (state, props) => ({ + modal: selectCurrentModal(state), + isWelcomeAcknowledged: selectWelcomeModalAcknowledged(state), + user: selectUser(state), +}); const perform = dispatch => ({ alertError: errorList => dispatch(doAlertError(errorList)), diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index e364c0db0..4d875dd54 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -38,18 +38,13 @@ class App extends React.PureComponent { } showWelcome(props) { - const { - isFetchingRewards, - isWelcomeAcknowledged, - isWelcomeRewardClaimed, - openWelcomeModal, - user, - } = props; + const { isWelcomeAcknowledged, openWelcomeModal, user } = props; if ( !isWelcomeAcknowledged && user && - (isFetchingRewards === false && isWelcomeRewardClaimed === false) + !user.is_reward_approved && + !user.is_identity_verified ) { openWelcomeModal(); } diff --git a/ui/js/component/cardVerify/view.jsx b/ui/js/component/cardVerify/view.jsx index 0bc17507b..0432a69e1 100644 --- a/ui/js/component/cardVerify/view.jsx +++ b/ui/js/component/cardVerify/view.jsx @@ -167,6 +167,7 @@ class CardVerify extends React.Component {
-

{__("About Your Reward")}

+

{__("Your First Reward")}

- {__("You earned a reward of")} - {" "} - {" "}{__("LBRY credits, or")} {__("LBC")}. + {__("You just earned your first reward of")} + {" "}.

{__( - "This reward will show in your Wallet momentarily, shown in the top right, probably while you are reading this message." + "This reward will show in your Wallet in the top right momentarily (if it hasn't already)." )}

{__( - "LBC is used to compensate creators, to publish, and to have say in how the network works." + "These credits are used to compensate creators, to publish your own content, and to have say in how the network works." )}

@@ -38,7 +37,7 @@ class ModalFirstReward extends React.PureComponent {

{__( - "Finally, pleaseh know that LBRY is an early beta and that it earns the name." + "Finally, please know that LBRY is an early beta and that it earns the name." )}

diff --git a/ui/js/component/modalWelcome/index.js b/ui/js/component/modalWelcome/index.js index 4d8962a70..3cb4a44d6 100644 --- a/ui/js/component/modalWelcome/index.js +++ b/ui/js/component/modalWelcome/index.js @@ -7,6 +7,7 @@ import { selectUserIsRewardApproved } from "selectors/user"; import { makeSelectHasClaimedReward, makeSelectRewardByType, + selectTotalRewardValue, } from "selectors/rewards"; import ModalWelcome from "./view"; @@ -17,6 +18,7 @@ const select = (state, props) => { return { isRewardApproved: selectUserIsRewardApproved(state), reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + totalRewardValue: selectTotalRewardValue(state), }; }; @@ -29,7 +31,7 @@ const perform = dispatch => () => { return { verifyAccount: () => { closeModal(); - dispatch(doAuthNavigate("/rewards")); + dispatch(doAuthNavigate("/discover")); }, closeModal: closeModal, }; diff --git a/ui/js/component/modalWelcome/view.jsx b/ui/js/component/modalWelcome/view.jsx index c87df9da7..42ed50a15 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -5,44 +5,77 @@ import Link from "component/link"; import RewardLink from "component/rewardLink"; class ModalWelcome extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + isFirstScreen: true, + }; + } + render() { - const { closeModal, isRewardApproved, reward, verifyAccount } = this.props; + const { closeModal, totalRewardValue, verifyAccount } = this.props; + + const totalRewardRounded = Math.round(totalRewardValue / 10) * 10; return ( -
-

{__("Welcome to LBRY.")}

-

- {__( - "Using LBRY is like dating a centaur. Totally normal up top, and" - )} - {" "}{__("way different")} {__("underneath.")} -

-

{__("Up top, LBRY is similar to popular media sites.")}

-

- {__( - "Below, LBRY is controlled by users -- you -- via blockchain and decentralization." - )} -

-

- {__("Please have")} {" "} - {reward && - } - {!reward && {__("??")}} - {" "} {__("as a thank you for building content freedom.")} -

-
- {isRewardApproved && - } - {!isRewardApproved && + {this.state.isFirstScreen && +
+

{__("Welcome to LBRY")}

+

+ {__( + "Using LBRY is like dating a centaur. Totally normal up top, and" + )} + {" "}{__("way different")} {__("underneath.")} +

+

{__("Up top, LBRY is similar to popular media sites.")}

+

+ {__( + "Below, LBRY is controlled by users -- you -- via blockchain and decentralization." + )} +

+
+ { + this.setState({ isFirstScreen: false }); + }} + label={__("Continue")} + /> +
+
} + {!this.state.isFirstScreen && +
+

{__("Claim Your Credits")}

+

+ The LBRY network is controlled and powered by credits called{" "} + LBC, a blockchain asset. +

+

+ {__("New patrons receive ")} {" "} + {totalRewardValue + ? + : {__("credits")}} + {" "} {__("in rewards for usage and influence of the network.")} +

+

+ {__( + "You'll also earn weekly bonuses for checking out the greatest new stuff." + )} +

+
} - -
-
+ label={__("You Had Me At Free LBC")} + /> + +
+
}
); } diff --git a/ui/js/component/userEmailVerify/view.jsx b/ui/js/component/userEmailVerify/view.jsx index e1bcfdfd1..47ed10bc9 100644 --- a/ui/js/component/userEmailVerify/view.jsx +++ b/ui/js/component/userEmailVerify/view.jsx @@ -44,9 +44,9 @@ class UserEmailVerify extends React.PureComponent { {/* render help separately so it always shows */}

- {__("Check your email for a verification code. Email")}{" "} + {__("Email")}{" "} {" "} - {__("if you did not receive or are having trouble with your code.")} + {__("if you encounter any trouble with your code.")}

diff --git a/ui/js/component/userVerify/view.jsx b/ui/js/component/userVerify/view.jsx index d09a97d34..7c9012aef 100644 --- a/ui/js/component/userVerify/view.jsx +++ b/ui/js/component/userVerify/view.jsx @@ -25,14 +25,16 @@ class UserVerify extends React.PureComponent { const { errorMessage, isPending, reward } = this.props; return (
- {(!reward || !reward.transaction_id) && -

- Please link a credit card to confirm your identity and receive{" "} - {reward - ? - : your reward} -

} -

{__("This is to prevent abuse. You will not be charged.")}

+

+ {__( + "To ensure you are a real and unique person, you must link a valid credit or debit card." + )} +

+

+ {__( + "A small authorization, but no actual charge, will be run on this card." + )} +

{errorMessage &&

{errorMessage}

}
{__( - "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is optional and only collected to provide communication and prevent abuse. You may use LBRY without providing this information." + "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards." )}
diff --git a/ui/js/selectors/rewards.js b/ui/js/selectors/rewards.js index 7a6a20429..bddb7714e 100644 --- a/ui/js/selectors/rewards.js +++ b/ui/js/selectors/rewards.js @@ -23,6 +23,12 @@ export const selectFetchingRewards = createSelector( state => !!state.fetching ); +export const selectTotalRewardValue = createSelector(selectRewards, rewards => + rewards.reduce((sum, reward) => { + return sum + reward.reward_amount; + }, 0) +); + export const selectHasClaimedReward = (state, props) => { const reward = selectRewardsByType(state)[props.reward_type]; return reward && reward.transaction_id !== ""; diff --git a/ui/scss/component/_modal.scss b/ui/scss/component/_modal.scss index 5b637fcde..74597dc8f 100644 --- a/ui/scss/component/_modal.scss +++ b/ui/scss/component/_modal.scss @@ -34,7 +34,7 @@ } .modal__header { - margin-bottom: 5px; + margin-bottom: $spacing-vertical * 2/3; text-align: center; } @@ -42,7 +42,7 @@ display: flex; flex-direction: row; justify-content: center; - margin-top: 15px; + margin-top: $spacing-vertical * 2/3; } .modal__button { From fcf8ba97accdc3dbf35cf45483eb5a0ee387858f Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 27 Jul 2017 21:13:47 -0400 Subject: [PATCH 103/152] =?UTF-8?q?Bump=20version:=200.14.0rc3=20=E2=86=92?= =?UTF-8?q?=200.14.0rc4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- app/package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 21315ab72..7e977c586 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.0rc3 +current_version = 0.14.0rc4 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/app/package.json b/app/package.json index 35b00d087..03c9c38fa 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.0rc3", + "version": "0.14.0rc4", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { diff --git a/ui/package.json b/ui/package.json index 97aa4bc83..23d49e5ff 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.0rc3", + "version": "0.14.0rc4", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From a8ca574eb36894992e78940cec7f5359fc60a6ca Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Fri, 28 Jul 2017 21:36:39 +0100 Subject: [PATCH 104/152] Issue #255 modal dialog css fix --- ui/scss/component/_modal.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/scss/component/_modal.scss b/ui/scss/component/_modal.scss index 74597dc8f..f5249341d 100644 --- a/ui/scss/component/_modal.scss +++ b/ui/scss/component/_modal.scss @@ -31,6 +31,8 @@ padding: $spacing-vertical; box-shadow: $box-shadow-layer; max-width: 400px; + + word-break: break-word } .modal__header { From aa2e3459999c8004c38e8e7dbd3c037272de2496 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 28 Jul 2017 17:31:14 -0400 Subject: [PATCH 105/152] =?UTF-8?q?Bump=20version:=200.14.0rc4=20=E2=86=92?= =?UTF-8?q?=200.14.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGELOG.md | 72 +++++++++++++++++++++++++++++++----------------- app/package.json | 2 +- ui/package.json | 2 +- 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 7e977c586..7a888d581 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.0rc4 +current_version = 0.14.0 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/CHANGELOG.md b/CHANGELOG.md index 00e74d16d..4fdd8d5f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,42 +8,64 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added - * Identity verification for new reward participants - * Support rich markup in publishing descriptions and show pages. - * Release past publishing claims (and recover LBC) via the UI - * Added transition to card hovers to smooth animation - * Use randomly colored tiles when image is missing from metadata - * Added a loading message to file actions - * URL is auto suggested in Publish Page + * + * ### Changed - * Publishing revamped. Editing claims is much easier. - * Daemon updated from v0.13.1 to [v0.14.2](https://github.com/lbryio/lbry/releases/tag/v0.14.2) - * Publish page now use `claim_list` rather than `file_list` + * + * ### Fixed - * Fixed bug with download notice when switching window focus - * Fixed newly published files appearing twice - * Fixed unconfirmed published files missing channel name - * Fixed old files from updated published claims appearing in downloaded list - * Fixed inappropriate text showing on searches - * Stop discover page from pushing jumping vertically while loading - * Restored feedback on claim amounts - * Fixed hiding price input when Free is checked on publish form - * Fixed hiding new identity fields on publish form - * Fixed files on downloaded tab not showing download progress - * Fixed downloading files that are deleted not being removed from the downloading list - * Fixed download progress bar not being cleared when a downloading file is deleted - * Fixed refresh regression after adding scroll position to history state - * Fixed app not monitoring download progress on files in progress between restarts + * + * ### Deprecated * * ### Removed - * Removed bandwidth caps from settings, because the daemon was not respecting them anyway. * + * + +## [0.14.0] - 2017-07-28 + +### Added + * Identity verification for new reward participants + * Support rich markup in publishing descriptions and show pages. + * Release past publishing claims (and recover LBC) via the UI + * Added transition to card hovers to smooth animation + * Use randomly colored tiles when image is missing from metadata + * Added a loading message to file actions + * URL is auto suggested in Publish Page + + +### Changed + * Publishing revamped. Editing claims is much easier. + * Daemon updated from v0.13.1 to [v0.14.2](https://github.com/lbryio/lbry/releases/tag/v0.14.2) + * Publish page now use `claim_list` rather than `file_list` + + +### Removed + * Removed bandwidth caps from settings, because the daemon was not respecting them anyway. + + +### Fixed + * Fixed bug with download notice when switching window focus + * Fixed newly published files appearing twice + * Fixed unconfirmed published files missing channel name + * Fixed old files from updated published claims appearing in downloaded list + * Fixed inappropriate text showing on searches + * Stop discover page from pushing jumping vertically while loading + * Restored feedback on claim amounts + * Fixed hiding price input when Free is checked on publish form + * Fixed hiding new identity fields on publish form + * Fixed files on downloaded tab not showing download progress + * Fixed downloading files that are deleted not being removed from the downloading list + * Fixed download progress bar not being cleared when a downloading file is deleted + * Fixed refresh regression after adding scroll position to history state + * Fixed app not monitoring download progress on files in progress between restarts + + ## [0.13.0] - 2017-06-30 diff --git a/app/package.json b/app/package.json index 03c9c38fa..d602eda0c 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.0rc4", + "version": "0.14.0", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { diff --git a/ui/package.json b/ui/package.json index 23d49e5ff..77adc1fed 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.0rc4", + "version": "0.14.0", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From 022e7f808b8a63c911cffc8d23ad08e330302058 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 28 Jul 2017 19:31:10 -0400 Subject: [PATCH 106/152] fix upgrade file path missing name --- CHANGELOG.md | 2 +- ui/js/actions/app.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fdd8d5f2..6dfef9307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * ### Fixed - * + * Fixed upgrade file path missing file name * ### Deprecated diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index e6d5879f8..4eae4f1a7 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -4,6 +4,7 @@ import { selectUpdateUrl, selectUpgradeDownloadPath, selectUpgradeDownloadItem, + selectUpgradeFilename, selectPageTitle, selectCurrentPage, selectCurrentParams, @@ -151,7 +152,8 @@ export function doDownloadUpgrade() { // Make a new directory within temp directory so the filename is guaranteed to be available const dir = fs.mkdtempSync( remote.app.getPath("temp") + require("path").sep - ); + ), + upgradeFilename = selectUpgradeFilename(state); let options = { onProgress: p => dispatch(doUpdateDownloadProgress(Math.round(p * 100))), From ddb2c534cd99bf63c3767448bea5df93391e2682 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 28 Jul 2017 19:31:31 -0400 Subject: [PATCH 107/152] =?UTF-8?q?Bump=20version:=200.14.0=20=E2=86=92=20?= =?UTF-8?q?0.14.1rc1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- app/package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 7a888d581..8ef8ebaff 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.0 +current_version = 0.14.1rc1 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/app/package.json b/app/package.json index d602eda0c..bea2effe7 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.0", + "version": "0.14.1rc1", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { diff --git a/ui/package.json b/ui/package.json index 77adc1fed..b654b71af 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.0", + "version": "0.14.1rc1", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From 2a0d9a0d08ae03394b469bc93435760e869dce3d Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 28 Jul 2017 19:31:48 -0400 Subject: [PATCH 108/152] =?UTF-8?q?Bump=20version:=200.14.1rc1=20=E2=86=92?= =?UTF-8?q?=200.14.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGELOG.md | 9 ++++++++- app/package.json | 2 +- ui/package.json | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 8ef8ebaff..e251b6297 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.1rc1 +current_version = 0.14.1 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dfef9307..695df1aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * ### Fixed - * Fixed upgrade file path missing file name + * * ### Deprecated @@ -27,6 +27,13 @@ Web UI version numbers should always match the corresponding version of LBRY App * * +## [0.14.1] - 2017-07-28 + +### Fixed + * Fixed upgrade file path missing file name + + + ## [0.14.0] - 2017-07-28 ### Added diff --git a/app/package.json b/app/package.json index bea2effe7..de76ab03e 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.1rc1", + "version": "0.14.1", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { diff --git a/ui/package.json b/ui/package.json index b654b71af..fd9137ba5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.1rc1", + "version": "0.14.1", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From 778c73cf33ce170b376c5bff72693570583f38e6 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 29 Jul 2017 12:58:31 -0400 Subject: [PATCH 109/152] add link to auth flow to exit --- ui/js/page/auth/view.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/js/page/auth/view.jsx b/ui/js/page/auth/view.jsx index 47371e362..16a52efa6 100644 --- a/ui/js/page/auth/view.jsx +++ b/ui/js/page/auth/view.jsx @@ -1,5 +1,6 @@ import React from "react"; import { BusyMessage } from "component/common"; +import Link from "component/link"; import UserEmailNew from "component/userEmailNew"; import UserEmailVerify from "component/userEmailVerify"; import UserVerify from "component/userVerify"; @@ -56,7 +57,7 @@ export class AuthPage extends React.PureComponent { } render() { - const { email, user, isPending } = this.props; + const { email, user, isPending, navigate } = this.props; return (
@@ -77,7 +78,11 @@ export class AuthPage extends React.PureComponent {
{__( "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards." - )} + ) + " "} + navigate("/discover")} + label={__("Return home")} + />.
From 04519678831e1a126771d6cdf25b3a0ad214aef5 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 29 Jul 2017 12:59:47 -0400 Subject: [PATCH 110/152] Add missing semi-colon --- ui/scss/component/_modal.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/scss/component/_modal.scss b/ui/scss/component/_modal.scss index f5249341d..c28ba313e 100644 --- a/ui/scss/component/_modal.scss +++ b/ui/scss/component/_modal.scss @@ -32,7 +32,7 @@ box-shadow: $box-shadow-layer; max-width: 400px; - word-break: break-word + word-break: break-word; } .modal__header { @@ -80,4 +80,4 @@ max-height: 400px; max-width: 400px; overflow-y: hidden; -} \ No newline at end of file +} From a76afb025363ab7ba759266d24f06f9d9b1f343e Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 27 Jul 2017 01:55:00 +0100 Subject: [PATCH 111/152] discover page horizontal scroll initial progress added translate3d transform for card hover and sibling cards implemented simple horizontal scroll animation updated header z-index switched z-index of cards on hover with horizontal left/right nav buttons removed unnecessary code added transition and states for horizontal scroll arrows, and some css tweaks and fixes --- ui/js/page/discover/view.jsx | 222 +++++++++++++++++++++++++++++---- ui/scss/component/_card.scss | 73 +++++++++-- ui/scss/component/_header.scss | 2 +- 3 files changed, 258 insertions(+), 39 deletions(-) diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index fc57a5ba8..2817fb946 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -1,38 +1,206 @@ import React from "react"; +import ReactDOM from "react-dom"; import lbryio from "lbryio.js"; import lbryuri from "lbryuri"; import FileCard from "component/fileCard"; -import { BusyMessage } from "component/common.js"; +import { Icon, BusyMessage } from "component/common.js"; import ToolTip from "component/tooltip.js"; -const FeaturedCategory = props => { - const { category, names } = props; +class FeaturedCategory extends React.PureComponent { + componentWillMount() { + this.setState({ + numItems: this.props.names.length, + canScrollPrevious: false, + canScrollNext: true, + }); + } - return ( -
-

- {category} - {category && - category.match(/^community/i) && - 0) { + // check the visible cards + const cards = cardRow.getElementsByTagName("section"); + let firstVisibleCard = null; + let firstVisibleIdx = -1; + for (var i = 0; i < cards.length; i++) { + if (this.isCardVisible(cards[i], cardRow, false)) { + firstVisibleCard = cards[i]; + firstVisibleIdx = i; + break; + } + } + + const numDisplayed = this.numDisplayedCards(cardRow); + const scrollToIdx = firstVisibleIdx - numDisplayed; + const animationCallback = () => { + this.setState({ + canScrollPrevious: cardRow.scrollLeft !== 0, + canScrollNext: true, + }); + }; + this.scrollCardItemsLeftAnimated( + cardRow, + scrollToIdx < 0 ? 0 : cards[scrollToIdx].offsetLeft, + 100, + animationCallback + ); + } + } + + handleScrollNext() { + const cardRow = ReactDOM.findDOMNode(this.refs.rowitems); + + // check the visible cards + const cards = cardRow.getElementsByTagName("section"); + let lastVisibleCard = null; + let lastVisibleIdx = -1; + for (var i = 0; i < cards.length; i++) { + if (this.isCardVisible(cards[i], cardRow, true)) { + lastVisibleCard = cards[i]; + lastVisibleIdx = i; + } + } + + if (lastVisibleCard) { + const numDisplayed = this.numDisplayedCards(cardRow); + const animationCallback = () => { + // update last visible index after scroll + for (var i = 0; i < cards.length; i++) { + if (this.isCardVisible(cards[i], cardRow, true)) { + lastVisibleIdx = i; + } + } + + this.setState({ canScrollPrevious: true }); + if (lastVisibleIdx === cards.length - 1) { + this.setState({ canScrollNext: false }); + } + }; + + this.scrollCardItemsLeftAnimated( + cardRow, + Math.min( + lastVisibleCard.offsetLeft, + cardRow.scrollWidth - cardRow.clientWidth + ), + 100, + animationCallback + ); + } + } + + scrollCardItemsLeftAnimated(cardRow, target, duration, callback) { + if (!duration || duration <= diff) { + cardRow.scrollLeft = target; + if (callback) { + callback(); + } + return; + } + + const component = this; + const diff = target - cardRow.scrollLeft; + const tick = diff / duration * 10; + setTimeout(() => { + cardRow.scrollLeft = cardRow.scrollLeft + tick; + if (cardRow.scrollLeft === target) { + if (callback) { + callback(); + } + return; + } + component.scrollCardItemsLeftAnimated( + cardRow, + target, + duration - 10, + callback + ); + }, 10); + } + + isCardVisible(section, cardRow, partialVisibility) { + // check if a card is fully or partialy visible in its parent + const cardRowWidth = cardRow.offsetWidth; + const cardRowLeft = cardRow.scrollLeft; + const cardRowEnd = cardRowLeft + cardRow.offsetWidth; + const sectionLeft = section.offsetLeft - cardRowLeft; + const sectionEnd = sectionLeft + section.offsetWidth; + + return ( + (sectionLeft >= 0 && sectionEnd <= cardRowWidth) || + (((sectionLeft < 0 && sectionEnd > 0) || + (sectionLeft > 0 && sectionLeft <= cardRowWidth)) && + partialVisibility) + ); + } + + numDisplayedCards(cardRow) { + const cards = cardRow.getElementsByTagName("section"); + const cardRowWidth = cardRow.offsetWidth; + // get the width of the first card and then calculate + const cardWidth = cards.length > 0 ? cards[0].offsetWidth : 0; + + if (cardWidth > 0) { + return Math.ceil(cardRowWidth / cardWidth); + } + + // return a default value of 1 card displayed if the card width couldn't be determined + return 1; + } + + render() { + const { category, names } = this.props; + const leftNavClassName = + "card-row__nav left-nav" + + (this.state.canScrollPrevious ? " can-scroll" : ""); + const rightNavClassName = + "card-row__nav right-nav" + + (this.state.canScrollNext ? " can-scroll" : ""); + + return ( +
+
+ + + +
+
+ + + +
+

+ {category} + {category && + category.match(/^community/i) && + } +

+
+ {names && + names.map(name => + )} - className="tooltip--header" - />} -

- {names && - names.map(name => - - )} -
- ); -}; +
+ + ); + } +} class DiscoverPage extends React.PureComponent { componentWillMount() { diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index d8416d226..a81470bfb 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -1,6 +1,7 @@ @import "../global"; $padding-card-horizontal: $spacing-vertical * 2/3; +$translate-card-hover: 10px; .card { margin-left: auto; @@ -94,10 +95,13 @@ $card-link-scaling: 1.1; position: relative; z-index: 1; box-shadow: $box-shadow-focus; - transform: scale($card-link-scaling); + transform: scale($card-link-scaling) translate3d($translate-card-hover, 0, 0); transform-origin: 50% 50%; overflow-x: visible; - overflow-y: visible; + overflow-y: visible +} +.card--link:hover ~ .card--link { + transform: translate3d($translate-card-hover * 2, 0, 0); } .card__media { @@ -183,26 +187,73 @@ $height-card-small: $spacing-vertical * 15; } .card-row { + + .card-row { + margin-top: $spacing-vertical * 1/3; + } +} +.card-row__items { > .card { vertical-align: top; display: inline-block; margin-right: $spacing-vertical / 3; } - + .card-row { - margin-top: $spacing-vertical * 1/3; + > .card:last-child { + margin-right: 0 } } .card-row--small { - overflow-x: auto; - overflow-y: hidden; + overflow: hidden; white-space: nowrap; + position: relative; /*hacky way to give space for hover */ - padding-left: 20px; - margin-left: -20px; - padding-right: 20px; - margin-right: -20px; + padding-right: 30px; +} +.card-row__items { + width: 100%; + overflow: hidden; + + /*hacky way to give space for hover */ + padding-top: 20px; + margin-top: -20px; + padding-right: 30px; + margin-right: -30px; } .card-row__header { margin-bottom: $spacing-vertical / 3; -} \ No newline at end of file +} + +.card-row__nav { + display: none; + position: absolute; + padding: 0 $spacing-vertical / 1.5; + top: ($font-size * 1.4 * $font-line-height) + ($spacing-vertical / 3); + height: ($width-card-small * 9 / 16) + ($font-size * 0.82 * $font-line-height * 4) + ($spacing-vertical * 4/3) +} +.card-row__nav .card-row__scroll-button { + background: $color-bg; + color: $color-help; + box-shadow: $box-shadow-layer; + padding: $spacing-vertical $spacing-vertical / 2; + position: absolute; + cursor: pointer; + left: 0; + top: 36%; + z-index: 2; + opacity: 0.5; + transition: transform 60ms ease-in-out; + + &:hover { + opacity: 1.0; + transform: scale($card-link-scaling * 1.1) + } +} +.card-row__nav.left-nav { + left: 0; +} +.card-row__nav.right-nav { + right: 0; +} +.card-row__nav.can-scroll { + display: block +} diff --git a/ui/scss/component/_header.scss b/ui/scss/component/_header.scss index 1343ddf86..5cc51541e 100644 --- a/ui/scss/component/_header.scss +++ b/ui/scss/component/_header.scss @@ -13,7 +13,7 @@ $color-header-active: darken($color-header, 20%); top: 0; left: 0; width: 100%; - z-index: 2; + z-index: 3; padding: $spacing-vertical / 2; box-sizing: border-box; } From 00f2413597dc7ba713f17d2b381588fe4b943cee Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 29 Jul 2017 14:12:13 -0400 Subject: [PATCH 112/152] some css cleanup --- ui/js/page/discover/view.jsx | 61 +++++++++++++++++----------------- ui/scss/_global.scss | 2 +- ui/scss/component/_card.scss | 63 +++++++++++++++++------------------- 3 files changed, 59 insertions(+), 67 deletions(-) diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index 2817fb946..5f4138cbf 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -1,6 +1,5 @@ import React from "react"; import ReactDOM from "react-dom"; -import lbryio from "lbryio.js"; import lbryuri from "lbryuri"; import FileCard from "component/fileCard"; import { Icon, BusyMessage } from "component/common.js"; @@ -150,31 +149,9 @@ class FeaturedCategory extends React.PureComponent { render() { const { category, names } = this.props; - const leftNavClassName = - "card-row__nav left-nav" + - (this.state.canScrollPrevious ? " can-scroll" : ""); - const rightNavClassName = - "card-row__nav right-nav" + - (this.state.canScrollNext ? " can-scroll" : ""); return (
-
- - - -
-
- - - -

{category} {category && @@ -187,15 +164,35 @@ class FeaturedCategory extends React.PureComponent { className="tooltip--header" />}

-
- {names && - names.map(name => - - )} +
+ {this.state.canScrollPrevious && +
+ + + +
} + {this.state.canScrollNext && +
+ + + +
} +
+ {names && + names.map(name => + + )} +
); diff --git a/ui/scss/_global.scss b/ui/scss/_global.scss index d30ae6dc5..b554b77a1 100644 --- a/ui/scss/_global.scss +++ b/ui/scss/_global.scss @@ -31,8 +31,8 @@ $max-text-width: 660px; $width-page-constrained: 800px; $width-input-text: 330px; -$height-header: $spacing-vertical * 2.5; $height-button: $spacing-vertical * 1.5; +$height-header: $spacing-vertical * 2.5; $height-video-embedded: $width-page-constrained * 9 / 16; $box-shadow-layer: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index a81470bfb..3c66cc439 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -2,6 +2,7 @@ $padding-card-horizontal: $spacing-vertical * 2/3; $translate-card-hover: 10px; +$width-card-small: $spacing-vertical * 10; .card { margin-left: auto; @@ -58,9 +59,10 @@ $translate-card-hover: 10px; margin-top: $spacing-vertical * 2/3; margin-bottom: $spacing-vertical * 2/3; } +$font-size-subtext-multiple: 0.82; .card__subtext { color: $color-meta-light; - font-size: 0.82em; + font-size: $font-size-subtext-multiple * 1.0em; margin-top: $spacing-vertical * 1/3; margin-bottom: $spacing-vertical * 1/3; } @@ -68,7 +70,7 @@ $translate-card-hover: 10px; white-space: pre-wrap; } .card__subtext--two-lines { - height: $font-size * 0.82 * $font-line-height * 2; /*this is so one line text still has the proper height*/ + height: $font-size * $font-size-subtext-multiple * $font-line-height * 2; /*this is so one line text still has the proper height*/ } .card-overlay { position: absolute; @@ -156,8 +158,7 @@ $card-link-scaling: 1.1; top: 36% } -$width-card-small: $spacing-vertical * 10; -$height-card-small: $spacing-vertical * 15; + .card--small { width: $width-card-small; @@ -191,44 +192,41 @@ $height-card-small: $spacing-vertical * 15; margin-top: $spacing-vertical * 1/3; } } -.card-row__items { - > .card { - vertical-align: top; - display: inline-block; - margin-right: $spacing-vertical / 3; - } - > .card:last-child { - margin-right: 0 - } -} -.card-row--small { - overflow: hidden; - white-space: nowrap; - position: relative; - - /*hacky way to give space for hover */ - padding-right: 30px; -} +$padding-top-card-hover-hack: 20px; .card-row__items { width: 100%; overflow: hidden; /*hacky way to give space for hover */ - padding-top: 20px; - margin-top: -20px; + padding-top: $padding-top-card-hover-hack; + margin-top: -1 * $padding-top-card-hover-hack; padding-right: 30px; margin-right: -30px; + > .card { + vertical-align: top; + display: inline-block; + } + > .card + .card { + margin-left: $spacing-vertical / 3; + } +} +.card-row--small { + overflow: hidden; + white-space: nowrap; } .card-row__header { margin-bottom: $spacing-vertical / 3; } +.card-row__scrollhouse { + position: relative; +} + .card-row__nav { - display: none; position: absolute; - padding: 0 $spacing-vertical / 1.5; - top: ($font-size * 1.4 * $font-line-height) + ($spacing-vertical / 3); - height: ($width-card-small * 9 / 16) + ($font-size * 0.82 * $font-line-height * 4) + ($spacing-vertical * 4/3) + padding: 0 $spacing-vertical * 2 / 3; + height: 100%; + top: $padding-top-card-hover-hack - $spacing-vertical * 2 / 3; } .card-row__nav .card-row__scroll-button { background: $color-bg; @@ -240,7 +238,7 @@ $height-card-small: $spacing-vertical * 15; left: 0; top: 36%; z-index: 2; - opacity: 0.5; + opacity: 0.8; transition: transform 60ms ease-in-out; &:hover { @@ -248,12 +246,9 @@ $height-card-small: $spacing-vertical * 15; transform: scale($card-link-scaling * 1.1) } } -.card-row__nav.left-nav { +.card-row__nav--left { left: 0; } -.card-row__nav.right-nav { +.card-row__nav--right { right: 0; } -.card-row__nav.can-scroll { - display: block -} From e3b17ef32f46824d9766144a3f97a75042ba8791 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 29 Jul 2017 14:34:02 -0400 Subject: [PATCH 113/152] fix hovering of rightmost card --- ui/scss/component/_card.scss | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index 3c66cc439..aac13e3f8 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -193,6 +193,7 @@ $card-link-scaling: 1.1; } } $padding-top-card-hover-hack: 20px; +$padding-right-card-hover-hack: 30px; .card-row__items { width: 100%; overflow: hidden; @@ -200,8 +201,8 @@ $padding-top-card-hover-hack: 20px; /*hacky way to give space for hover */ padding-top: $padding-top-card-hover-hack; margin-top: -1 * $padding-top-card-hover-hack; - padding-right: 30px; - margin-right: -30px; + padding-right: $padding-right-card-hover-hack; + margin-right: -1 * $padding-right-card-hover-hack; > .card { vertical-align: top; display: inline-block; @@ -210,9 +211,12 @@ $padding-top-card-hover-hack: 20px; margin-left: $spacing-vertical / 3; } } + .card-row--small { overflow: hidden; white-space: nowrap; + /*hacky way to give space for hover */ + padding-right: $padding-right-card-hover-hack; } .card-row__header { margin-bottom: $spacing-vertical / 3; @@ -250,5 +254,5 @@ $padding-top-card-hover-hack: 20px; left: 0; } .card-row__nav--right { - right: 0; + right: -1 * $padding-right-card-hover-hack; } From d7ceeb15cf3e104893a7463bbef5061eed0fb503 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 17 Jul 2017 13:06:04 +0700 Subject: [PATCH 114/152] Add pagination Some bad pagination CSS --- CHANGELOG.md | 16 +++++++--- ui/js/actions/app.js | 2 +- ui/js/actions/content.js | 15 ++++++--- ui/js/component/router/view.jsx | 34 ++++++++++---------- ui/js/component/wunderbar/index.js | 6 ++-- ui/js/component/wunderbar/view.jsx | 10 ++++-- ui/js/page/channel/index.js | 16 ++++++++-- ui/js/page/channel/view.jsx | 51 ++++++++++++++++++++++++------ ui/js/page/showPage/index.js | 4 +-- ui/js/page/showPage/view.jsx | 9 ++++-- ui/js/reducers/claims.js | 43 ++++++++++++++++++++----- ui/js/reducers/content.js | 11 +++++++ ui/js/selectors/app.js | 12 +++++-- ui/js/selectors/claims.js | 48 +++++++++++++++++++++++++++- ui/js/selectors/content.js | 13 ++++++++ ui/package.json | 3 +- ui/scss/all.scss | 1 + ui/scss/component/_pagination.scss | 18 +++++++++++ ui/yarn.lock | 49 ++++++++++++++++++++++++++-- 19 files changed, 296 insertions(+), 65 deletions(-) create mode 100644 ui/scss/component/_pagination.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 695df1aa6..10c3f05b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,20 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added - * - * + * Identity verification for new reward participants + * Support rich markup in publishing descriptions and show pages. + * Release past publishing claims (and recover LBC) via the UI + * Added transition to card hovers to smooth animation + * Use randomly colored tiles when image is missing from metadata + * Added a loading message to file actions + * URL is auto suggested in Publish Page + * Added infinite scroll to channel pages ### Changed - * - * + * Publishing revamped. Editing claims is much easier. + * Publish page now use `claim_list` rather than `file_list` + * Daemon updated to [v0.14.2](https://github.com/lbryio/lbry/releases/tag/v0.14.2) + * Made channel claim storage more efficient ### Fixed * diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 4eae4f1a7..c8586de86 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -22,7 +22,7 @@ const { download } = remote.require("electron-dl"); const fs = remote.require("fs"); const { lbrySettings: config } = require("../../../app/package.json"); -export function doNavigate(path, params = {}) { +export function doNavigate(path, params = {}, options = {}) { return function(dispatch, getState) { let url = path; if (params) url = `${url}?${toQueryString(params)}`; diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 249bb6838..695cbd6ee 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -262,6 +262,7 @@ export function doLoadVideo(uri) { type: types.LOADING_VIDEO_FAILED, data: { uri }, }); + dispatch(doOpenModal("timedOut")); } else { dispatch(doDownloadFile(uri, streamInfo)); @@ -313,22 +314,28 @@ export function doPurchaseUri(uri, purchaseModalName) { }; } -export function doFetchClaimsByChannel(uri, page = 1) { +export function doFetchClaimsByChannel(uri, page) { return function(dispatch, getState) { dispatch({ type: types.FETCH_CHANNEL_CLAIMS_STARTED, - data: { uri }, + data: { uri, page }, }); lbry.claim_list_by_channel({ uri, page }).then(result => { const claimResult = result[uri], - claims = claimResult ? claimResult.claims_in_channel : []; + claims = claimResult ? claimResult.claims_in_channel : [], + totalPages = claimResult + ? claimResult.claims_in_channel_pages + : undefined, + currentPage = claimResult ? claimResult.returned_page : undefined; dispatch({ type: types.FETCH_CHANNEL_CLAIMS_COMPLETED, data: { uri, - claims: claims, + claims, + totalPages, + page: currentPage, }, }); }); diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index ac3f29921..74ffc836b 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -25,23 +25,23 @@ const Router = props => { const { currentPage, params } = props; return route(currentPage, { - auth: , - channel: , - developer: , - discover: , - downloaded: , - help: , - publish: , - published: , - receive: , - report: , - rewards: , - search: , - send: , - settings: , - show: , - start: , - wallet: , + auth: , + channel: , + developer: , + discover: , + downloaded: , + help: , + publish: , + published: , + receive: , + report: , + rewards: , + search: , + send: , + settings: , + show: , + start: , + wallet: , }); }; diff --git a/ui/js/component/wunderbar/index.js b/ui/js/component/wunderbar/index.js index a6b8860c1..9d23f4d26 100644 --- a/ui/js/component/wunderbar/index.js +++ b/ui/js/component/wunderbar/index.js @@ -12,8 +12,10 @@ const select = state => ({ const perform = dispatch => ({ onSearch: query => dispatch(doNavigate("/search", { query })), - onSubmit: query => - dispatch(doNavigate("/show", { uri: lbryuri.normalize(query) })), + onSubmit: (query, extraParams) => + dispatch( + doNavigate("/show", { uri: lbryuri.normalize(query), ...extraParams }) + ), }); export default connect(select, perform)(Wunderbar); diff --git a/ui/js/component/wunderbar/view.jsx b/ui/js/component/wunderbar/view.jsx index 673b015a5..04ba1a030 100644 --- a/ui/js/component/wunderbar/view.jsx +++ b/ui/js/component/wunderbar/view.jsx @@ -1,6 +1,7 @@ import React from "react"; import lbryuri from "lbryuri.js"; import { Icon } from "component/common.js"; +import { parseQueryParams } from "util/query_params"; class WunderBar extends React.PureComponent { static TYPING_TIMEOUT = 800; @@ -120,12 +121,15 @@ class WunderBar extends React.PureComponent { onKeyPress(event) { if (event.charCode == 13 && this._input.value) { let uri = null, - method = "onSubmit"; + method = "onSubmit", + extraParams = {}; this._resetOnNextBlur = false; clearTimeout(this._userTypingTimer); - const value = this._input.value.trim(); + const parts = this._input.value.trim().split("?"); + const value = parts.shift(); + if (parts.length > 0) extraParams = parseQueryParams(parts.join("")); try { uri = lbryuri.normalize(value); @@ -136,7 +140,7 @@ class WunderBar extends React.PureComponent { method = "onSearch"; } - this.props[method](uri); + this.props[method](uri, extraParams); this._input.blur(); } } diff --git a/ui/js/page/channel/index.js b/ui/js/page/channel/index.js index 5e2be42ea..bce739e1e 100644 --- a/ui/js/page/channel/index.js +++ b/ui/js/page/channel/index.js @@ -3,24 +3,34 @@ import { connect } from "react-redux"; import { doFetchClaimsByChannel } from "actions/content"; import { makeSelectClaimForUri, - makeSelectClaimsInChannelForUri, + makeSelectClaimsInChannelForCurrentPage, + makeSelectFetchingChannelClaims, } from "selectors/claims"; +import { selectCurrentParams } from "selectors/app"; +import { doNavigate } from "actions/app"; +import { makeSelectTotalPagesForChannel } from "selectors/content"; import ChannelPage from "./view"; const makeSelect = () => { const selectClaim = makeSelectClaimForUri(), - selectClaimsInChannel = makeSelectClaimsInChannelForUri(); + selectClaimsInChannel = makeSelectClaimsInChannelForCurrentPage(), + selectFetchingChannelClaims = makeSelectFetchingChannelClaims(), + selectTotalPagesForChannel = makeSelectTotalPagesForChannel(); const select = (state, props) => ({ claim: selectClaim(state, props), claimsInChannel: selectClaimsInChannel(state, props), + fetching: selectFetchingChannelClaims(state, props), + totalPages: selectTotalPagesForChannel(state, props), + params: selectCurrentParams(state), }); return select; }; const perform = dispatch => ({ - fetchClaims: uri => dispatch(doFetchClaimsByChannel(uri)), + fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)), + navigate: (path, params) => dispatch(doNavigate(path, params)), }); export default connect(makeSelect, perform)(ChannelPage); diff --git a/ui/js/page/channel/view.jsx b/ui/js/page/channel/view.jsx index 377069330..172e431f9 100644 --- a/ui/js/page/channel/view.jsx +++ b/ui/js/page/channel/view.jsx @@ -2,24 +2,41 @@ import React from "react"; import lbryuri from "lbryuri"; import { BusyMessage } from "component/common"; import FileTile from "component/fileTile"; +import Link from "component/link"; +import ReactPaginate from "react-paginate"; class ChannelPage extends React.PureComponent { componentDidMount() { - this.fetchClaims(this.props); + const { uri, params, fetchClaims } = this.props; + + fetchClaims(uri, params.page || 1); } componentWillReceiveProps(nextProps) { - this.fetchClaims(nextProps); + const { params, fetching, fetchClaims } = this.props; + const nextParams = nextProps.params; + + if (fetching !== nextParams.page && params.page !== nextParams.page) + fetchClaims(nextProps.uri, nextParams.page); } - fetchClaims(props) { - if (props.claimsInChannel === undefined) { - props.fetchClaims(props.uri); - } + changePage(pageNumber) { + const { params, currentPage } = this.props; + const newParams = Object.assign({}, params, { page: pageNumber }); + + this.props.navigate("/show", newParams); } render() { - const { claimsInChannel, claim, uri } = this.props; + const { + fetching, + claimsInChannel, + claim, + uri, + params, + totalPages, + } = this.props; + const { page } = params; let contentList; if (claimsInChannel === undefined) { @@ -29,14 +46,17 @@ class ChannelPage extends React.PureComponent { ? claimsInChannel.map(claim => ) : {__("No content found.")}; } return ( -
+

{uri}

@@ -51,7 +71,18 @@ class ChannelPage extends React.PureComponent {

{__("Published Content")}

{contentList} -
+
+ {(!fetching || (claimsInChannel && claimsInChannel.length)) && + totalPages > 1 && + this.changePage(e.selected + 1)} + initialPage={parseInt(page - 1)} + containerClassName="pagination" + />} +
); } } diff --git a/ui/js/page/showPage/index.js b/ui/js/page/showPage/index.js index cd9677169..6306e5e33 100644 --- a/ui/js/page/showPage/index.js +++ b/ui/js/page/showPage/index.js @@ -10,8 +10,8 @@ const makeSelect = () => { selectIsResolving = makeSelectIsResolvingForUri(); const select = (state, props) => ({ - claim: selectClaim(state, props), - isResolvingUri: selectIsResolving(state, props), + claim: selectClaim(state, props.params), + isResolvingUri: selectIsResolving(state, props.params), }); return select; diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx index 687cb5498..67bbf6178 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/showPage/view.jsx @@ -6,13 +6,15 @@ import FilePage from "page/filePage"; class ShowPage extends React.PureComponent { componentWillMount() { - const { isResolvingUri, resolveUri, uri } = this.props; + const { isResolvingUri, resolveUri, params } = this.props; + const { uri } = params; if (!isResolvingUri) resolveUri(uri); } componentWillReceiveProps(nextProps) { - const { isResolvingUri, resolveUri, claim, uri } = nextProps; + const { isResolvingUri, resolveUri, claim, params } = nextProps; + const { uri } = params; if (!isResolvingUri && claim === undefined && uri) { resolveUri(uri); @@ -20,7 +22,8 @@ class ShowPage extends React.PureComponent { } render() { - const { claim, uri, isResolvingUri } = this.props; + const { claim, params, isResolvingUri } = this.props; + const { uri } = params; let innerContent = ""; diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 9e072f1ce..c70fd45e0 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -101,17 +101,44 @@ reducers[types.FETCH_CHANNEL_LIST_MINE_COMPLETED] = function(state, action) { }); }; -reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) { - const { uri, claims } = action.data; +reducers[types.FETCH_CHANNEL_CLAIMS_STARTED] = function(state, action) { + const { uri, page } = action.data; + const fetchingChannelClaims = Object.assign({}, state.fetchingChannelClaims); - const newClaims = Object.assign({}, state.claimsByChannel); - - if (claims !== undefined) { - newClaims[uri] = claims; - } + fetchingChannelClaims[uri] = page; return Object.assign({}, state, { - claimsByChannel: newClaims, + fetchingChannelClaims, + }); +}; + +reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) { + const { uri, claims, page } = action.data; + + const claimsByChannel = Object.assign({}, state.claimsByChannel); + const byChannel = Object.assign({}, claimsByChannel[uri]); + const allClaimIds = new Set(byChannel["all"]); + const currentPageClaimIds = []; + const byId = Object.assign({}, state.byId); + const fetchingChannelClaims = Object.assign({}, state.fetchingChannelClaims); + + if (claims !== undefined) { + claims.forEach(claim => { + allClaimIds.add(claim.claim_id); + currentPageClaimIds.push(claim.claim_id); + byId[claim.claim_id] = claim; + }); + } + + byChannel["all"] = allClaimIds; + byChannel[page] = currentPageClaimIds; + claimsByChannel[uri] = byChannel; + delete fetchingChannelClaims[uri]; + + return Object.assign({}, state, { + claimsByChannel, + byId, + fetchingChannelClaims, }); }; diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index f346e2d27..0e1d71fed 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -47,6 +47,17 @@ reducers[types.RESOLVE_URI_CANCELED] = reducers[ }); }; +reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) { + const channelPages = Object.assign({}, state.channelPages); + const { uri, totalPages } = action.data; + + channelPages[uri] = totalPages; + + return Object.assign({}, state, { + channelPages, + }); +}; + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index 3951a8216..a67dc9f37 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -1,5 +1,5 @@ import { createSelector } from "reselect"; -import { parseQueryParams } from "util/query_params"; +import { parseQueryParams, toQueryString } from "util/query_params"; import lbry from "lbry"; import lbryuri from "lbryuri"; @@ -55,8 +55,14 @@ export const selectPageTitle = createSelector( return params.query ? __("Search results for %s", params.query) : __("Search"); - case "show": - return lbryuri.normalize(params.uri); + case "show": { + const parts = [lbryuri.normalize(params.uri)]; + // If the params has any keys other than "uri" + if (Object.keys(params).length > 1) { + parts.push(toQueryString(Object.assign({}, params, { uri: null }))); + } + return parts.join("?"); + } case "downloaded": return __("Downloads & Purchases"); case "published": diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index 3c10ec931..2c4b4b511 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -1,4 +1,5 @@ import { createSelector } from "reselect"; +import { selectCurrentParams } from "selectors/app"; import lbryuri from "lbryuri"; const _selectState = state => state.claims || {}; @@ -60,14 +61,59 @@ export const makeSelectClaimForUriIsMine = () => { return createSelector(selectClaimForUriIsMine, isMine => isMine); }; +export const selectAllFetchingChannelClaims = createSelector( + _selectState, + state => state.fetchingChannelClaims || {} +); + +const selectFetchingChannelClaims = (state, props) => { + const allFetchingChannelClaims = selectAllFetchingChannelClaims(state); + + return allFetchingChannelClaims[props.uri]; +}; + +export const makeSelectFetchingChannelClaims = (state, props) => { + return createSelector(selectFetchingChannelClaims, fetching => fetching); +}; + export const selectClaimsInChannelForUri = (state, props) => { - return selectAllClaimsByChannel(state)[props.uri]; + const byId = selectClaimsById(state); + const byChannel = selectAllClaimsByChannel(state)[props.uri] || {}; + const claimIds = byChannel["all"]; + + if (!claimIds) return claimIds; + + const claims = []; + + claimIds.forEach(claimId => claims.push(byId[claimId])); + + return claims; }; export const makeSelectClaimsInChannelForUri = () => { return createSelector(selectClaimsInChannelForUri, claims => claims); }; +export const selectClaimsInChannelForCurrentPage = (state, props) => { + const byId = selectClaimsById(state); + const byChannel = selectAllClaimsByChannel(state)[props.uri] || {}; + const params = selectCurrentParams(state); + const page = params && params.page ? params.page : 1; + const claimIds = byChannel[page]; + + if (!claimIds) return claimIds; + + const claims = []; + + claimIds.forEach(claimId => claims.push(byId[claimId])); + + return claims; +}; + +export const makeSelectClaimsInChannelForCurrentPage = () => { + return createSelector(selectClaimsInChannelForCurrentPage, claims => claims); +}; + const selectMetadataForUri = (state, props) => { const claim = selectClaimForUri(state, props); const metadata = diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index 75162a454..663aab145 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -24,3 +24,16 @@ const selectResolvingUri = (state, props) => { export const makeSelectIsResolvingForUri = () => { return createSelector(selectResolvingUri, resolving => resolving); }; + +export const selectChannelPages = createSelector( + _selectState, + state => state.channelPages || {} +); + +const selectTotalPagesForChannel = (state, props) => { + return selectChannelPages(state)[props.uri]; +}; + +export const makeSelectTotalPagesForChannel = () => { + return createSelector(selectTotalPagesForChannel, totalPages => totalPages); +}; diff --git a/ui/package.json b/ui/package.json index fd9137ba5..5d447d6a4 100644 --- a/ui/package.json +++ b/ui/package.json @@ -31,6 +31,7 @@ "react-dom": "^15.4.0", "react-markdown": "^2.5.0", "react-modal": "^1.5.2", + "react-paginate": "^4.4.3", "react-redux": "^5.0.3", "react-simplemde-editor": "^3.6.11", "redux": "^3.6.0", @@ -54,8 +55,8 @@ "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.18.0", - "electron-rebuild": "^1.5.11", "css-loader": "^0.28.4", + "electron-rebuild": "^1.5.11", "eslint": "^3.10.2", "eslint-config-airbnb": "^13.0.0", "eslint-loader": "^1.6.1", diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 61612d7df..d899566ab 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -17,6 +17,7 @@ @import "component/_modal.scss"; @import "component/_snack-bar.scss"; @import "component/_video.scss"; +@import "component/_pagination.scss"; @import "page/_developer.scss"; @import "page/_reward.scss"; @import "page/_show.scss"; diff --git a/ui/scss/component/_pagination.scss b/ui/scss/component/_pagination.scss new file mode 100644 index 000000000..b16076f76 --- /dev/null +++ b/ui/scss/component/_pagination.scss @@ -0,0 +1,18 @@ +ul.pagination { + display: inline-block; + padding: 0; + margin: 0; +} + +ul.pagination li { + display: inline; + float: left; + padding: 8px 16px; +} + +ul.pagination li:not(.selected) { + background: white; + a { + cursor: hand; + } +} diff --git a/ui/yarn.lock b/ui/yarn.lock index cb2ca85fb..5d5848976 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1204,6 +1204,10 @@ clap@^1.0.9: dependencies: chalk "^1.1.3" +classnames@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" + cli-cursor@^1.0.1, cli-cursor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" @@ -2329,6 +2333,14 @@ faye-websocket@~0.11.0: dependencies: websocket-driver ">=0.5.1" +fbjs@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.2.1.tgz#622061630a43e11f845017b9044aaa648ed3f731" + dependencies: + core-js "^1.0.0" + promise "^7.0.3" + whatwg-fetch "^0.9.0" + fbjs@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.6.1.tgz#9636b7705f5ba9684d44b72f78321254afc860f7" @@ -2339,7 +2351,7 @@ fbjs@^0.6.1: ua-parser-js "^0.7.9" whatwg-fetch "^0.9.0" -fbjs@^0.8.9: +fbjs@^0.8.4, fbjs@^0.8.9: version "0.8.12" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" dependencies: @@ -4771,10 +4783,26 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-addons-create-fragment@^0.14.7: + version "0.14.8" + resolved "https://registry.yarnpkg.com/react-addons-create-fragment/-/react-addons-create-fragment-0.14.8.tgz#e83240d1cba49249690fcc6f148710baa11d2b7a" + +react-addons-create-fragment@^15.0.0: + version "15.6.0" + resolved "https://registry.yarnpkg.com/react-addons-create-fragment/-/react-addons-create-fragment-15.6.0.tgz#af91a22b1fb095dd01f1afba43bfd0ef589d8b20" + dependencies: + fbjs "^0.8.4" + loose-envify "^1.3.1" + object-assign "^4.1.0" + react-dom-factories@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/react-dom-factories/-/react-dom-factories-1.0.0.tgz#f43c05e5051b304f33251618d5bc859b29e46b6d" +react-dom@^0.14.7: + version "0.14.9" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.14.9.tgz#05064a3dcf0fb1880a3b2bfc9d58c55d8d9f6293" + react-dom@^15.4.0: version "15.6.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.1.tgz#2cb0ed4191038e53c209eb3a79a23e2a4cf99470" @@ -4804,6 +4832,15 @@ react-modal@^1.5.2: prop-types "^15.5.7" react-dom-factories "^1.0.0" +react-paginate@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/react-paginate/-/react-paginate-4.4.3.tgz#11817ece628fa59c54a2df7968c854ed64b99077" + dependencies: + classnames "^2.2.5" + prop-types "^15.5.7" + react "^15.0.0" + react-addons-create-fragment "^15.0.0" + react-redux@^5.0.3: version "5.0.5" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.5.tgz#f8e8c7b239422576e52d6b7db06439469be9846a" @@ -4823,14 +4860,20 @@ react-simplemde-editor@^3.6.11: react "^0.14.2" simplemde "^1.11.2" -react@^0.14.2: +react-tap-event-plugin@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/react-tap-event-plugin/-/react-tap-event-plugin-0.2.2.tgz#4f6f257851654f6c2b1c213a1d3ff21b353ae4e1" + dependencies: + fbjs "^0.2.1" + +react@^0.14.2, react@^0.14.7: version "0.14.9" resolved "https://registry.yarnpkg.com/react/-/react-0.14.9.tgz#9110a6497c49d44ba1c0edd317aec29c2e0d91d1" dependencies: envify "^3.0.0" fbjs "^0.6.1" -react@^15.4.0: +react@^15.0.0, react@^15.4.0: version "15.6.1" resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df" dependencies: From 16c2eb94791043e406fa1863d79eff74c1d2fd5c Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 29 Jul 2017 15:34:22 -0400 Subject: [PATCH 115/152] pagination styles --- ui/js/page/channel/view.jsx | 6 +++++ ui/scss/component/_pagination.scss | 40 ++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/ui/js/page/channel/view.jsx b/ui/js/page/channel/view.jsx index 172e431f9..d694c3ba8 100644 --- a/ui/js/page/channel/view.jsx +++ b/ui/js/page/channel/view.jsx @@ -77,6 +77,12 @@ class ChannelPage extends React.PureComponent { this.changePage(e.selected + 1)} initialPage={parseInt(page - 1)} diff --git a/ui/scss/component/_pagination.scss b/ui/scss/component/_pagination.scss index b16076f76..940dd11bd 100644 --- a/ui/scss/component/_pagination.scss +++ b/ui/scss/component/_pagination.scss @@ -1,18 +1,32 @@ -ul.pagination { - display: inline-block; +@import "../global"; + +.pagination { + display: block; padding: 0; - margin: 0; + margin: 0 auto; + text-align: center; } -ul.pagination li { - display: inline; - float: left; - padding: 8px 16px; -} - -ul.pagination li:not(.selected) { - background: white; - a { - cursor: hand; +.pagination__item { + display: inline-block; + line-height: $spacing-vertical * 1.5; + height: $spacing-vertical * 1.5; + border-radius: 2px; + &:not(.pagination__item--selected):hover { + background: rgba(0, 0, 0, 0.2); + > a { cursor: hand } + } + > a { + display: inline-block; + padding: 0 $spacing-vertical * 2 / 3; } } + +.pagination__item--previous, .pagination__item--next { + font-size: 1.2em; +} + +.pagination__item--selected { + color: white; + background: $color-primary; +} From 1f588ff923b97901ae3f2cc9479c629c20d1b987 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 29 Jul 2017 17:52:32 -0400 Subject: [PATCH 116/152] improve first run feedback --- ui/js/component/splash/view.jsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ui/js/component/splash/view.jsx b/ui/js/component/splash/view.jsx index 7cccd5bf0..a962d2f90 100644 --- a/ui/js/component/splash/view.jsx +++ b/ui/js/component/splash/view.jsx @@ -30,6 +30,7 @@ export class SplashScreen extends React.PureComponent { _updateStatusCallback(status) { const startupStatus = status.startup_status; + console.log(status); if (startupStatus.code == "started") { // Wait until we are able to resolve a name before declaring // that we are done. @@ -52,10 +53,19 @@ export class SplashScreen extends React.PureComponent { }); return; } - this.setState({ - details: startupStatus.message + (startupStatus.is_lagging ? "" : "..."), - isLagging: startupStatus.is_lagging, - }); + if (status.blockchain_status && status.blockchain_status.blocks_behind > 0) { + this.setState({ + message: __("Blockchain Sync"), + details: __("%s blocks behind", status.blockchain_status.blocks_behind), + isLagging: startupStatus.is_lagging, + }); + } else { + this.setState({ + message: __("Network Loading"), + details: startupStatus.message + (startupStatus.is_lagging ? "" : "..."), + isLagging: startupStatus.is_lagging, + }); + } setTimeout(() => { this.updateStatus(); }, 500); From 230d2a451c574be038e2028ed0b848f3a6ced265 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 25 Jul 2017 03:07:54 -0400 Subject: [PATCH 117/152] Indicate content eligible for Hot Right Now reward --- ui/js/actions/content.js | 28 ++++++++++++++++++++++++++++ ui/js/component/app/index.js | 3 +++ ui/js/component/app/view.jsx | 9 ++++++++- ui/js/component/fileCard/index.js | 6 +++++- ui/js/component/fileCard/view.jsx | 11 ++++++++++- ui/js/component/fileTile/index.js | 6 +++++- ui/js/component/fileTile/view.jsx | 4 ++++ ui/js/constants/action_types.js | 2 ++ ui/js/page/filePage/index.js | 2 ++ ui/js/page/filePage/view.jsx | 11 ++++++++++- ui/js/reducers/content.js | 16 +++++++++++++++- ui/js/selectors/content.js | 5 +++++ 12 files changed, 97 insertions(+), 6 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 695cbd6ee..9735434be 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -133,6 +133,34 @@ export function doFetchFeaturedUris() { }; } +export function doFetchHotRightNowContent() { + return function(dispatch, getState) { + const state = getState(); + + const success = nameToClaimId => { + dispatch({ + type: types.FETCH_HOT_RIGHT_NOW_CONTENT_COMPLETED, + data: { + claimIds: Object.values(nameToClaimId), + success: true, + }, + }); + }; + + const failure = () => { + dispatch({ + type: types.FETCH_HOT_RIGHT_NOW_CONTENT_COMPLETED, + data: { + claimIds: [], + success: false, + }, + }); + }; + + lbryio.call("reward", "list_featured").then(success, failure); + }; +} + export function doUpdateLoadStatus(uri, outpoint) { return function(dispatch, getState) { const state = getState(); diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index 41f79393e..bdc7420a1 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -7,6 +7,8 @@ import { doAlertError, doRecordScroll, } from "actions/app"; +import { doFetchHotRightNowContent } from "actions/content"; + import { doUpdateBalance } from "actions/wallet"; import { selectWelcomeModalAcknowledged } from "selectors/app"; import { selectUser } from "selectors/user"; @@ -24,6 +26,7 @@ const perform = dispatch => ({ checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), openWelcomeModal: () => dispatch(doOpenModal(modals.WELCOME)), updateBalance: balance => dispatch(doUpdateBalance(balance)), + fetchHotRightNowContent: () => dispatch(doFetchHotRightNowContent()), recordScroll: scrollPosition => dispatch(doRecordScroll(scrollPosition)), }); diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 4d875dd54..24b898122 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -12,7 +12,12 @@ import * as modals from "constants/modal_types"; class App extends React.PureComponent { componentWillMount() { - const { alertError, checkUpgradeAvailable, updateBalance } = this.props; + const { + alertError, + checkUpgradeAvailable, + updateBalance, + fetchHotRightNowContent, + } = this.props; document.addEventListener("unhandledError", event => { alertError(event.detail); @@ -26,6 +31,8 @@ class App extends React.PureComponent { updateBalance(balance); }); + fetchHotRightNowContent(); + this.showWelcome(this.props); this.scrollListener = () => this.props.recordScroll(window.scrollY); diff --git a/ui/js/component/fileCard/index.js b/ui/js/component/fileCard/index.js index 65d162009..d47964175 100644 --- a/ui/js/component/fileCard/index.js +++ b/ui/js/component/fileCard/index.js @@ -8,7 +8,10 @@ import { makeSelectMetadataForUri, } from "selectors/claims"; import { makeSelectFileInfoForUri } from "selectors/file_info"; -import { makeSelectIsResolvingForUri } from "selectors/content"; +import { + makeSelectIsResolvingForUri, + selectHotRightNowClaimIds, +} from "selectors/content"; import FileCard from "./view"; const makeSelect = () => { @@ -22,6 +25,7 @@ const makeSelect = () => { fileInfo: selectFileInfoForUri(state, props), obscureNsfw: !selectShowNsfw(state), metadata: selectMetadataForUri(state, props), + hotRightNowClaimIds: selectHotRightNowClaimIds(state, props), isResolvingUri: selectResolvingUri(state, props), }); diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index cb57a4308..37e0be51c 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -46,7 +46,14 @@ class FileCard extends React.PureComponent { } render() { - const { claim, fileInfo, metadata, isResolvingUri, navigate } = this.props; + const { + claim, + fileInfo, + metadata, + isResolvingUri, + navigate, + hotRightNowClaimIds, + } = this.props; const uri = lbryuri.normalize(this.props.uri); const title = metadata && metadata.title ? metadata.title : uri; @@ -54,6 +61,7 @@ class FileCard extends React.PureComponent { ? metadata.thumbnail : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; + const isHotRightNow = claim && hotRightNowClaimIds.includes(claim.claim_id); let description = ""; if (isResolvingUri && !claim) { @@ -83,6 +91,7 @@ class FileCard extends React.PureComponent {
{title}
+ {isHotRightNow && 🔥 Hot Right Now}
diff --git a/ui/js/component/fileTile/index.js b/ui/js/component/fileTile/index.js index 6e9936fe1..21bc0d926 100644 --- a/ui/js/component/fileTile/index.js +++ b/ui/js/component/fileTile/index.js @@ -8,7 +8,10 @@ import { } from "selectors/claims"; import { makeSelectFileInfoForUri } from "selectors/file_info"; import { selectShowNsfw } from "selectors/settings"; -import { makeSelectIsResolvingForUri } from "selectors/content"; +import { + makeSelectIsResolvingForUri, + selectHotRightNowClaimIds, +} from "selectors/content"; import FileTile from "./view"; const makeSelect = () => { @@ -23,6 +26,7 @@ const makeSelect = () => { obscureNsfw: !selectShowNsfw(state), metadata: selectMetadataForUri(state, props), isResolvingUri: selectResolvingUri(state, props), + hotRightNowClaimIds: selectHotRightNowClaimIds(state, props), }); return select; diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index 0bdcce64c..f388ea103 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -58,6 +58,7 @@ class FileTile extends React.PureComponent { showEmpty, navigate, hidePrice, + hotRightNowClaimIds, } = this.props; const uri = lbryuri.normalize(this.props.uri); @@ -70,6 +71,8 @@ class FileTile extends React.PureComponent { ? metadata.thumbnail : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; + const isHotRightNow = claim && hotRightNowClaimIds.includes(claim.claim_id); + let onClick = () => navigate("/show", { uri }); let description = ""; @@ -108,6 +111,7 @@ class FileTile extends React.PureComponent { {!hidePrice ? : null}
{uri}

{title}

+ {isHotRightNow && 🔥 Hot Right Now}
diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 904ea9a5f..6f6625056 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -112,3 +112,5 @@ export const CLAIM_REWARD_STARTED = "CLAIM_REWARD_STARTED"; export const CLAIM_REWARD_SUCCESS = "CLAIM_REWARD_SUCCESS"; export const CLAIM_REWARD_FAILURE = "CLAIM_REWARD_FAILURE"; export const CLAIM_REWARD_CLEAR_ERROR = "CLAIM_REWARD_CLEAR_ERROR"; +export const FETCH_HOT_RIGHT_NOW_CONTENT_COMPLETED = + "FETCH_HOT_RIGHT_NOW_CONTENT_COMPLETED"; diff --git a/ui/js/page/filePage/index.js b/ui/js/page/filePage/index.js index ec151ab50..20f513d77 100644 --- a/ui/js/page/filePage/index.js +++ b/ui/js/page/filePage/index.js @@ -3,6 +3,7 @@ import { connect } from "react-redux"; import { doNavigate } from "actions/app"; import { doFetchFileInfo } from "actions/file_info"; import { makeSelectFileInfoForUri } from "selectors/file_info"; +import { selectHotRightNowClaimIds } from "selectors/content"; import { doFetchCostInfoForUri } from "actions/cost_info"; import { makeSelectClaimForUri, @@ -27,6 +28,7 @@ const makeSelect = () => { metadata: selectMetadata(state, props), obscureNsfw: !selectShowNsfw(state), fileInfo: selectFileInfo(state, props), + hotRightNowClaimIds: selectHotRightNowClaimIds(state, props), }); return select; diff --git a/ui/js/page/filePage/view.jsx b/ui/js/page/filePage/view.jsx index 82ac64c57..f6bdff83b 100644 --- a/ui/js/page/filePage/view.jsx +++ b/ui/js/page/filePage/view.jsx @@ -54,7 +54,14 @@ class FilePage extends React.PureComponent { } render() { - const { claim, fileInfo, metadata, contentType, uri } = this.props; + const { + claim, + fileInfo, + metadata, + contentType, + uri, + hotRightNowClaimIds, + } = this.props; if (!claim || !metadata) { return ( @@ -80,6 +87,7 @@ class FilePage extends React.PureComponent { ? lbryuri.build({ channelName, claimId: channelClaimId }, false) : null; const uriIndicator = ; + const isHotRightNow = hotRightNowClaimIds.includes(claim.claim_id); const mediaType = lbry.getMediaType(contentType); const player = require("render-media"); const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; @@ -105,6 +113,7 @@ class FilePage extends React.PureComponent { : null}

{title}

+ {isHotRightNow && 🔥 Hot Right Now}
{channelUri ? { export const makeSelectTotalPagesForChannel = () => { return createSelector(selectTotalPagesForChannel, totalPages => totalPages); }; + +export const selectHotRightNowClaimIds = createSelector( + _selectState, + state => state.hotRightNowClaimIds +); From b6d3a52f5d05d107b0477e388bbb5654c2838388 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 29 Jul 2017 18:56:08 -0400 Subject: [PATCH 118/152] featured icon for reward content --- ui/js/actions/content.js | 6 +++--- ui/js/component/app/index.js | 4 ++-- ui/js/component/app/view.jsx | 4 ++-- ui/js/component/fileCard/index.js | 4 ++-- ui/js/component/fileCard/view.jsx | 13 ++++++------- ui/js/component/fileTile/index.js | 4 ++-- ui/js/component/fileTile/view.jsx | 12 +++++++----- ui/js/component/iconFeatured/index.js | 5 +++++ ui/js/component/iconFeatured/view.jsx | 14 ++++++++++++++ ui/js/constants/action_types.js | 4 ++-- ui/js/page/filePage/index.js | 4 ++-- ui/js/page/filePage/view.jsx | 7 ++++--- ui/js/reducers/content.js | 7 +++---- ui/js/selectors/content.js | 4 ++-- ui/scss/_gui.scss | 4 ++++ ui/scss/component/_card.scss | 4 ++++ ui/scss/component/_file-tile.scss | 3 +++ 17 files changed, 67 insertions(+), 36 deletions(-) create mode 100644 ui/js/component/iconFeatured/index.js create mode 100644 ui/js/component/iconFeatured/view.jsx diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 9735434be..9fbbcf698 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -133,13 +133,13 @@ export function doFetchFeaturedUris() { }; } -export function doFetchHotRightNowContent() { +export function doFetchRewardedContent() { return function(dispatch, getState) { const state = getState(); const success = nameToClaimId => { dispatch({ - type: types.FETCH_HOT_RIGHT_NOW_CONTENT_COMPLETED, + type: types.FETCH_REWARD_CONTENT_COMPLETED, data: { claimIds: Object.values(nameToClaimId), success: true, @@ -149,7 +149,7 @@ export function doFetchHotRightNowContent() { const failure = () => { dispatch({ - type: types.FETCH_HOT_RIGHT_NOW_CONTENT_COMPLETED, + type: types.FETCH_REWARD_CONTENT_COMPLETED, data: { claimIds: [], success: false, diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index bdc7420a1..252fdf783 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -7,7 +7,7 @@ import { doAlertError, doRecordScroll, } from "actions/app"; -import { doFetchHotRightNowContent } from "actions/content"; +import { doFetchRewardedContent } from "actions/content"; import { doUpdateBalance } from "actions/wallet"; import { selectWelcomeModalAcknowledged } from "selectors/app"; @@ -26,7 +26,7 @@ const perform = dispatch => ({ checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), openWelcomeModal: () => dispatch(doOpenModal(modals.WELCOME)), updateBalance: balance => dispatch(doUpdateBalance(balance)), - fetchHotRightNowContent: () => dispatch(doFetchHotRightNowContent()), + fetchRewardedContent: () => dispatch(doFetchRewardedContent()), recordScroll: scrollPosition => dispatch(doRecordScroll(scrollPosition)), }); diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 24b898122..1252d0942 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -16,7 +16,7 @@ class App extends React.PureComponent { alertError, checkUpgradeAvailable, updateBalance, - fetchHotRightNowContent, + fetchRewardedContent, } = this.props; document.addEventListener("unhandledError", event => { @@ -31,7 +31,7 @@ class App extends React.PureComponent { updateBalance(balance); }); - fetchHotRightNowContent(); + fetchRewardedContent(); this.showWelcome(this.props); diff --git a/ui/js/component/fileCard/index.js b/ui/js/component/fileCard/index.js index d47964175..68ede91e1 100644 --- a/ui/js/component/fileCard/index.js +++ b/ui/js/component/fileCard/index.js @@ -10,7 +10,7 @@ import { import { makeSelectFileInfoForUri } from "selectors/file_info"; import { makeSelectIsResolvingForUri, - selectHotRightNowClaimIds, + selectRewardContentClaimIds, } from "selectors/content"; import FileCard from "./view"; @@ -25,7 +25,7 @@ const makeSelect = () => { fileInfo: selectFileInfoForUri(state, props), obscureNsfw: !selectShowNsfw(state), metadata: selectMetadataForUri(state, props), - hotRightNowClaimIds: selectHotRightNowClaimIds(state, props), + rewardedContentClaimIds: selectRewardContentClaimIds(state, props), isResolvingUri: selectResolvingUri(state, props), }); diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 37e0be51c..f26eccd1d 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -2,7 +2,8 @@ import React from "react"; import lbryuri from "lbryuri.js"; import CardMedia from "component/cardMedia"; import Link from "component/link"; -import { Thumbnail, TruncatedText, Icon } from "component/common"; +import { TruncatedText, Icon } from "component/common"; +import IconFeatured from "component/iconFeatured"; import FilePrice from "component/filePrice"; import UriIndicator from "component/uriIndicator"; import NsfwOverlay from "component/nsfwOverlay"; @@ -52,7 +53,7 @@ class FileCard extends React.PureComponent { metadata, isResolvingUri, navigate, - hotRightNowClaimIds, + rewardedContentClaimIds, } = this.props; const uri = lbryuri.normalize(this.props.uri); @@ -61,7 +62,7 @@ class FileCard extends React.PureComponent { ? metadata.thumbnail : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - const isHotRightNow = claim && hotRightNowClaimIds.includes(claim.claim_id); + const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); let description = ""; if (isResolvingUri && !claim) { @@ -91,13 +92,11 @@ class FileCard extends React.PureComponent {
{title}
- {isHotRightNow && 🔥 Hot Right Now}
- {fileInfo - ? {" "} - : ""} + {isRewardContent && {" "} } + {fileInfo && {" "} }
diff --git a/ui/js/component/fileTile/index.js b/ui/js/component/fileTile/index.js index 21bc0d926..0631ae849 100644 --- a/ui/js/component/fileTile/index.js +++ b/ui/js/component/fileTile/index.js @@ -10,7 +10,7 @@ import { makeSelectFileInfoForUri } from "selectors/file_info"; import { selectShowNsfw } from "selectors/settings"; import { makeSelectIsResolvingForUri, - selectHotRightNowClaimIds, + selectRewardContentClaimIds, } from "selectors/content"; import FileTile from "./view"; @@ -26,7 +26,7 @@ const makeSelect = () => { obscureNsfw: !selectShowNsfw(state), metadata: selectMetadataForUri(state, props), isResolvingUri: selectResolvingUri(state, props), - hotRightNowClaimIds: selectHotRightNowClaimIds(state, props), + rewardedContentClaimIds: selectRewardContentClaimIds(state, props), }); return select; diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index f388ea103..3c58d706a 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -1,11 +1,11 @@ import React from "react"; -import lbry from "lbry.js"; import lbryuri from "lbryuri.js"; import CardMedia from "component/cardMedia"; import Link from "component/link"; import { TruncatedText } from "component/common.js"; import FilePrice from "component/filePrice"; import NsfwOverlay from "component/nsfwOverlay"; +import IconFeatured from "component/iconFeatured"; class FileTile extends React.PureComponent { static SHOW_EMPTY_PUBLISH = "publish"; @@ -58,7 +58,7 @@ class FileTile extends React.PureComponent { showEmpty, navigate, hidePrice, - hotRightNowClaimIds, + rewardedContentClaimIds, } = this.props; const uri = lbryuri.normalize(this.props.uri); @@ -71,7 +71,7 @@ class FileTile extends React.PureComponent { ? metadata.thumbnail : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - const isHotRightNow = claim && hotRightNowClaimIds.includes(claim.claim_id); + const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); let onClick = () => navigate("/show", { uri }); @@ -109,9 +109,11 @@ class FileTile extends React.PureComponent {
{!hidePrice ? : null} + {isRewardContent && }
{uri}
-

{title}

- {isHotRightNow && 🔥 Hot Right Now} +

+ {title} +

diff --git a/ui/js/component/iconFeatured/index.js b/ui/js/component/iconFeatured/index.js new file mode 100644 index 000000000..02ce8fc38 --- /dev/null +++ b/ui/js/component/iconFeatured/index.js @@ -0,0 +1,5 @@ +import React from "react"; +import { connect } from "react-redux"; +import IconFeatured from "./view"; + +export default connect(null, null)(IconFeatured); diff --git a/ui/js/component/iconFeatured/view.jsx b/ui/js/component/iconFeatured/view.jsx new file mode 100644 index 000000000..e876224f0 --- /dev/null +++ b/ui/js/component/iconFeatured/view.jsx @@ -0,0 +1,14 @@ +import React from "react"; +import { Icon } from "component/common.js"; + +const IconFeatured = props => { + return ( + + + + ); +}; + +export default IconFeatured; diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 6f6625056..71d6d8072 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -112,5 +112,5 @@ export const CLAIM_REWARD_STARTED = "CLAIM_REWARD_STARTED"; export const CLAIM_REWARD_SUCCESS = "CLAIM_REWARD_SUCCESS"; export const CLAIM_REWARD_FAILURE = "CLAIM_REWARD_FAILURE"; export const CLAIM_REWARD_CLEAR_ERROR = "CLAIM_REWARD_CLEAR_ERROR"; -export const FETCH_HOT_RIGHT_NOW_CONTENT_COMPLETED = - "FETCH_HOT_RIGHT_NOW_CONTENT_COMPLETED"; +export const FETCH_REWARD_CONTENT_COMPLETED = + "FETCH_REWARD_CONTENT_COMPLETED"; diff --git a/ui/js/page/filePage/index.js b/ui/js/page/filePage/index.js index 20f513d77..26b98c93a 100644 --- a/ui/js/page/filePage/index.js +++ b/ui/js/page/filePage/index.js @@ -3,7 +3,7 @@ import { connect } from "react-redux"; import { doNavigate } from "actions/app"; import { doFetchFileInfo } from "actions/file_info"; import { makeSelectFileInfoForUri } from "selectors/file_info"; -import { selectHotRightNowClaimIds } from "selectors/content"; +import { selectRewardContentClaimIds } from "selectors/content"; import { doFetchCostInfoForUri } from "actions/cost_info"; import { makeSelectClaimForUri, @@ -28,7 +28,7 @@ const makeSelect = () => { metadata: selectMetadata(state, props), obscureNsfw: !selectShowNsfw(state), fileInfo: selectFileInfo(state, props), - hotRightNowClaimIds: selectHotRightNowClaimIds(state, props), + rewardedContentClaimIds: selectRewardContentClaimIds(state, props), }); return select; diff --git a/ui/js/page/filePage/view.jsx b/ui/js/page/filePage/view.jsx index f6bdff83b..3ca23aadc 100644 --- a/ui/js/page/filePage/view.jsx +++ b/ui/js/page/filePage/view.jsx @@ -8,6 +8,7 @@ import FilePrice from "component/filePrice"; import FileActions from "component/fileActions"; import Link from "component/link"; import UriIndicator from "component/uriIndicator"; +import IconFeatured from "component/iconFeatured"; const FormatItem = props => { const { contentType, metadata: { language, license } } = props; @@ -60,7 +61,7 @@ class FilePage extends React.PureComponent { metadata, contentType, uri, - hotRightNowClaimIds, + rewardedContentClaimIds, } = this.props; if (!claim || !metadata) { @@ -87,7 +88,7 @@ class FilePage extends React.PureComponent { ? lbryuri.build({ channelName, claimId: channelClaimId }, false) : null; const uriIndicator = ; - const isHotRightNow = hotRightNowClaimIds.includes(claim.claim_id); + const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id); const mediaType = lbry.getMediaType(contentType); const player = require("render-media"); const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; @@ -110,10 +111,10 @@ class FilePage extends React.PureComponent { {!fileInfo || fileInfo.written_bytes <= 0 ? + {isRewardContent && {" "} } : null}

{title}

- {isHotRightNow && 🔥 Hot Right Now}
{channelUri ? { return createSelector(selectTotalPagesForChannel, totalPages => totalPages); }; -export const selectHotRightNowClaimIds = createSelector( +export const selectRewardContentClaimIds = createSelector( _selectState, - state => state.hotRightNowClaimIds + state => state.rewardedContentClaimIds ); diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index 057027771..af2e7ebad 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -134,10 +134,14 @@ p } } +/*should this be here or work this way? had to hack additional rule below*/ .icon:only-child { position: relative; top: 0.16em; } +.icon-featured > .icon { + top: 0; +} .help { font-size: .85em; diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index aac13e3f8..20c72b3de 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -256,3 +256,7 @@ $padding-right-card-hover-hack: 30px; .card-row__nav--right { right: -1 * $padding-right-card-hover-hack; } + +.card__icon-featured-content { + color: orangered; +} \ No newline at end of file diff --git a/ui/scss/component/_file-tile.scss b/ui/scss/component/_file-tile.scss index 433abc746..7630f5517 100644 --- a/ui/scss/component/_file-tile.scss +++ b/ui/scss/component/_file-tile.scss @@ -7,6 +7,9 @@ $height-file-tile: $spacing-vertical * 6; .credit-amount { float: right; } + .icon-featured { + float: right; + } //also a hack .card__media { height: $height-file-tile; From 7aa69d4897fbcf520f637a7e4c6fbd2fc02b9807 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sun, 30 Jul 2017 16:27:50 +0100 Subject: [PATCH 119/152] simple fix for cases where the status is 1 block behind --- ui/js/component/splash/view.jsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ui/js/component/splash/view.jsx b/ui/js/component/splash/view.jsx index a962d2f90..841f6f464 100644 --- a/ui/js/component/splash/view.jsx +++ b/ui/js/component/splash/view.jsx @@ -53,16 +53,23 @@ export class SplashScreen extends React.PureComponent { }); return; } - if (status.blockchain_status && status.blockchain_status.blocks_behind > 0) { + if ( + status.blockchain_status && + status.blockchain_status.blocks_behind > 0 + ) { + const format = status.blockchain_status.blocks_behind == 1 + ? "%s block behind" + : "%s blocks behind"; this.setState({ message: __("Blockchain Sync"), - details: __("%s blocks behind", status.blockchain_status.blocks_behind), + details: __(format, status.blockchain_status.blocks_behind), isLagging: startupStatus.is_lagging, }); } else { this.setState({ message: __("Network Loading"), - details: startupStatus.message + (startupStatus.is_lagging ? "" : "..."), + details: + startupStatus.message + (startupStatus.is_lagging ? "" : "..."), isLagging: startupStatus.is_lagging, }); } From f4bdabd5d81571c38f6332cf8734d434b1111700 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Jul 2017 13:57:21 -0400 Subject: [PATCH 120/152] fix clicking play twice required on most videos --- ui/js/component/video/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index f5cd375bd..3157c535d 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -16,7 +16,7 @@ class Video extends React.PureComponent { componentWillReceiveProps(nextProps) { // reset playing state upon change path action - if (!this.isMediaSame(nextProps) && this.state.isPlaying) { + if (!this.isMediaSame(nextProps) && this.props.fileInfo && this.state.isPlaying) { this.state.isPlaying = false; } } From d58990f2406613abc78c56decc5a7edd0ac155cf Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Jul 2017 15:20:36 -0400 Subject: [PATCH 121/152] fix incorrect file_list responses --- ui/js/actions/content.js | 4 +++- ui/js/component/splash/view.jsx | 1 - ui/js/lbry.js | 14 ++++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 9fbbcf698..8e6d12e55 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -231,6 +231,8 @@ export function doStartDownload(uri, outpoint) { return function(dispatch, getState) { const state = getState(); + if (!outpoint) { throw new Error("outpoint is required to begin a download"); } + const { downloadingByOutpoint = {} } = state.fileInfo; if (downloadingByOutpoint[outpoint]) return; @@ -450,4 +452,4 @@ export function doPublish(params) { lbry.publishDeprecated(params, null, success, failure); }); }; -} +} \ No newline at end of file diff --git a/ui/js/component/splash/view.jsx b/ui/js/component/splash/view.jsx index a962d2f90..4d56584b6 100644 --- a/ui/js/component/splash/view.jsx +++ b/ui/js/component/splash/view.jsx @@ -30,7 +30,6 @@ export class SplashScreen extends React.PureComponent { _updateStatusCallback(status) { const startupStatus = status.startup_status; - console.log(status); if (startupStatus.code == "started") { // Wait until we are able to resolve a name before declaring // that we are done. diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 07dec7e5e..e43a20b55 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -416,10 +416,16 @@ lbry.file_list = function(params = {}) { fileInfos => { removePendingPublishIfNeeded({ name, channel_name, outpoint }); - const dummyFileInfos = lbry - .getPendingPublishes() - .map(pendingPublishToDummyFileInfo); - resolve([...fileInfos, ...dummyFileInfos]); + //if a naked file_list call, append the pending file infos + if (!name && !channel_name && !outpoint) { + const dummyFileInfos = lbry + .getPendingPublishes() + .map(pendingPublishToDummyFileInfo); + + resolve([...fileInfos, ...dummyFileInfos]); + } else { + resolve(fileInfos); + } }, reject ); From 0a32ff1735dbfacbcc275a7acc08b8cd1b72fb3e Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Jul 2017 15:20:54 -0400 Subject: [PATCH 122/152] fix error signature not detected by jsonrpc --- ui/js/jsonrpc.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/js/jsonrpc.js b/ui/js/jsonrpc.js index d401d2eaf..42c16fa1a 100644 --- a/ui/js/jsonrpc.js +++ b/ui/js/jsonrpc.js @@ -27,18 +27,19 @@ jsonrpc.call = function( xhr.addEventListener("load", function() { var response = JSON.parse(xhr.responseText); - if (response.error) { + let error = response.error || response.result && response.result.error + if (error) { if (errorCallback) { - errorCallback(response.error); + errorCallback(error); } else { var errorEvent = new CustomEvent("unhandledError", { detail: { connectionString: connectionString, method: method, params: params, - code: response.error.code, - message: response.error.message, - data: response.error.data, + code: error.code, + message: error.message || error, + data: error.data, }, }); document.dispatchEvent(errorEvent); From 8acccafe19dc44677ca3de3aa3b7114fedd49d7d Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Jul 2017 15:37:44 -0400 Subject: [PATCH 123/152] fix handling and display of errors on get API call --- ui/js/actions/content.js | 7 +++++-- ui/js/component/modalError/view.jsx | 10 +++++----- ui/js/jsonrpc.js | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 8e6d12e55..fa8bde671 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -9,7 +9,7 @@ import { } from "selectors/file_info"; import { selectResolvingUris } from "selectors/content"; import { selectCostInfoForUri } from "selectors/cost_info"; -import { doOpenModal } from "actions/app"; +import { doAlertError, doOpenModal } from "actions/app"; import { doClaimEligiblePurchaseRewards } from "actions/rewards"; import { selectBadgeNumber } from "selectors/app"; import { selectTotalDownloadProgress } from "selectors/file_info"; @@ -238,6 +238,7 @@ export function doStartDownload(uri, outpoint) { if (downloadingByOutpoint[outpoint]) return; lbry.file_list({ outpoint, full_status: true }).then(([fileInfo]) => { + dispatch({ type: types.DOWNLOADING_STARTED, data: { @@ -297,6 +298,8 @@ export function doLoadVideo(uri) { } else { dispatch(doDownloadFile(uri, streamInfo)); } + }).catch(error => { + dispatch(doAlertError(error)); }); }; } @@ -452,4 +455,4 @@ export function doPublish(params) { lbry.publishDeprecated(params, null, success, failure); }); }; -} \ No newline at end of file +} diff --git a/ui/js/component/modalError/view.jsx b/ui/js/component/modalError/view.jsx index 8090c4282..49b4e51c7 100644 --- a/ui/js/component/modalError/view.jsx +++ b/ui/js/component/modalError/view.jsx @@ -6,7 +6,7 @@ class ModalError extends React.PureComponent { render() { const { modal, closeModal, error } = this.props; - const errorObj = typeof error === "string" ? { error: error } : error; + const errorObj = typeof error === "string" ? { message: error } : error; const error_key_labels = { connectionString: __("API connection string"), @@ -18,10 +18,10 @@ class ModalError extends React.PureComponent { }; const errorInfoList = []; - for (let key of Object.keys(error)) { - let val = typeof error[key] == "string" - ? error[key] - : JSON.stringify(error[key]); + for (let key of Object.keys(errorObj)) { + let val = typeof errorObj[key] == "string" + ? errorObj[key] + : JSON.stringify(errorObj[key]); let label = error_key_labels[key]; errorInfoList.push(
  • {label}: {val}
  • diff --git a/ui/js/jsonrpc.js b/ui/js/jsonrpc.js index 42c16fa1a..e9807bea7 100644 --- a/ui/js/jsonrpc.js +++ b/ui/js/jsonrpc.js @@ -27,7 +27,7 @@ jsonrpc.call = function( xhr.addEventListener("load", function() { var response = JSON.parse(xhr.responseText); - let error = response.error || response.result && response.result.error + let error = response.error || response.result && response.result.error; if (error) { if (errorCallback) { errorCallback(error); From 64bebd9321457321ff0eb3cad0ff87c029147586 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Jul 2017 15:57:22 -0400 Subject: [PATCH 124/152] do not block play button on cost info --- ui/js/component/video/internal/play-button.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ui/js/component/video/internal/play-button.jsx b/ui/js/component/video/internal/play-button.jsx index f5223231c..266d3b617 100644 --- a/ui/js/component/video/internal/play-button.jsx +++ b/ui/js/component/video/internal/play-button.jsx @@ -47,7 +47,6 @@ class VideoPlayButton extends React.PureComponent { modal, closeModal, isLoading, - costInfo, fileInfo, mediaType, } = this.props; @@ -60,10 +59,7 @@ class VideoPlayButton extends React.PureComponent { } */ - const disabled = - isLoading || - fileInfo === undefined || - (fileInfo === null && (!costInfo || costInfo.cost === undefined)); + const disabled = isLoading || fileInfo === undefined; const icon = ["audio", "video"].indexOf(mediaType) !== -1 ? "icon-play" : "icon-folder-o"; From 99c13f6444626cd04bbb2b66cbde9312928216f5 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Jul 2017 15:57:42 -0400 Subject: [PATCH 125/152] reset downloading state on get fail --- ui/js/actions/content.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index fa8bde671..7e41ccd52 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -299,6 +299,10 @@ export function doLoadVideo(uri) { dispatch(doDownloadFile(uri, streamInfo)); } }).catch(error => { + dispatch({ + type: types.LOADING_VIDEO_FAILED, + data: { uri }, + }); dispatch(doAlertError(error)); }); }; From ae296b31110ff60b7ea334c920ad02418d01e621 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Jul 2017 16:05:41 -0400 Subject: [PATCH 126/152] update changelog --- CHANGELOG.md | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10c3f05b7..1683d599e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,24 +8,16 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added - * Identity verification for new reward participants - * Support rich markup in publishing descriptions and show pages. - * Release past publishing claims (and recover LBC) via the UI - * Added transition to card hovers to smooth animation - * Use randomly colored tiles when image is missing from metadata - * Added a loading message to file actions - * URL is auto suggested in Publish Page - * Added infinite scroll to channel pages + * Replaced horizontal scrollbars with scroll arrows + * Featured weekly reward content shows with an orange star + * Added pagination to channel pages ### Changed - * Publishing revamped. Editing claims is much easier. - * Publish page now use `claim_list` rather than `file_list` - * Daemon updated to [v0.14.2](https://github.com/lbryio/lbry/releases/tag/v0.14.2) - * Made channel claim storage more efficient ### Fixed - * - * + * Fixed requirement to double click play button on many videos + * Fixed errors from calls to `get` not bubbling correctly + * Fixed some corner-case flows that could break file pages ### Deprecated * From a99657328fb7eb62b89299eb78ace785e82e58be Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Jul 2017 16:06:29 -0400 Subject: [PATCH 127/152] =?UTF-8?q?Bump=20version:=200.14.1=20=E2=86=92=20?= =?UTF-8?q?0.14.2rc1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- app/package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e251b6297..e6197afaa 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.1 +current_version = 0.14.2rc1 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/app/package.json b/app/package.json index de76ab03e..6b25f527b 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.1", + "version": "0.14.2rc1", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { diff --git a/ui/package.json b/ui/package.json index 5d447d6a4..c059a37f5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.1", + "version": "0.14.2rc1", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From fde0956959decd08c646d40e9e7f3a98cd9d4b35 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Jul 2017 16:06:55 -0400 Subject: [PATCH 128/152] =?UTF-8?q?Bump=20version:=200.14.2rc1=20=E2=86=92?= =?UTF-8?q?=200.14.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGELOG.md | 27 +++++++++++++++++++++------ app/package.json | 2 +- ui/package.json | 2 +- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e6197afaa..7917e0cc8 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.2rc1 +current_version = 0.14.2 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/CHANGELOG.md b/CHANGELOG.md index 1683d599e..02610e5fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,16 +8,16 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added - * Replaced horizontal scrollbars with scroll arrows - * Featured weekly reward content shows with an orange star - * Added pagination to channel pages + * + * ### Changed + * + * ### Fixed - * Fixed requirement to double click play button on many videos - * Fixed errors from calls to `get` not bubbling correctly - * Fixed some corner-case flows that could break file pages + * + * ### Deprecated * @@ -27,6 +27,21 @@ Web UI version numbers should always match the corresponding version of LBRY App * * +## [0.14.2] - 2017-07-30 + +### Added + * Replaced horizontal scrollbars with scroll arrows + * Featured weekly reward content shows with an orange star + * Added pagination to channel pages + + +### Fixed + * Fixed requirement to double click play button on many videos + * Fixed errors from calls to `get` not bubbling correctly + * Fixed some corner-case flows that could break file pages + + + ## [0.14.1] - 2017-07-28 ### Fixed diff --git a/app/package.json b/app/package.json index 6b25f527b..409537567 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.2rc1", + "version": "0.14.2", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { diff --git a/ui/package.json b/ui/package.json index c059a37f5..5e1a8ccf5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.2rc1", + "version": "0.14.2", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From af34bdfff15ea455f78b7b045f5709f39e91dd64 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Jul 2017 17:24:40 -0400 Subject: [PATCH 129/152] Add tooltips to header buttons, fixes #401 --- CHANGELOG.md | 2 +- ui/js/component/header/view.jsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02610e5fa..699832c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added - * + * Add tooltips to controls in header * ### Changed diff --git a/ui/js/component/header/view.jsx b/ui/js/component/header/view.jsx index 79bd6758d..76758b210 100644 --- a/ui/js/component/header/view.jsx +++ b/ui/js/component/header/view.jsx @@ -8,13 +8,14 @@ export const Header = props => { return ( From 50d7b3ab709b6e69d6059856480d51191d11f1da Mon Sep 17 00:00:00 2001 From: hackrush Date: Sun, 30 Jul 2017 00:52:17 +0530 Subject: [PATCH 130/152] (quickfix) Now displays quit button in IncompatibleDaemonModal. Also display a Learn More link pointing to the appropriate faq. --- ui/js/actions/app.js | 3 +-- .../modalIncompatibleDaemon/{index.jsx => index.js} | 3 +-- ui/js/component/modalIncompatibleDaemon/view.jsx | 13 +++++++++---- 3 files changed, 11 insertions(+), 8 deletions(-) rename ui/js/component/modalIncompatibleDaemon/{index.jsx => index.js} (70%) diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index c8586de86..44964b0ba 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -297,9 +297,8 @@ export function doClearCache() { }; } -export function doQuitAndLaunchDaemonHelp() { +export function doQuit() { return function(dispatch, getState) { - shell.openExternal("https://lbry.io/faq/incompatible-protocol-version"); remote.app.quit(); }; } diff --git a/ui/js/component/modalIncompatibleDaemon/index.jsx b/ui/js/component/modalIncompatibleDaemon/index.js similarity index 70% rename from ui/js/component/modalIncompatibleDaemon/index.jsx rename to ui/js/component/modalIncompatibleDaemon/index.js index 27ddecd8d..669e67316 100644 --- a/ui/js/component/modalIncompatibleDaemon/index.jsx +++ b/ui/js/component/modalIncompatibleDaemon/index.js @@ -1,13 +1,12 @@ import React from "react"; import { connect } from "react-redux"; import { doQuit, doSkipWrongDaemonNotice } from "actions/app"; -import { doQuitAndLaunchDaemonHelp } from "actions/app"; import ModalIncompatibleDaemon from "./view"; const select = state => ({}); const perform = dispatch => ({ - quitAndLaunchDaemonHelp: () => dispatch(doQuitAndLaunchDaemonHelp()), + quit: () => dispatch(doQuit()), }); export default connect(select, perform)(ModalIncompatibleDaemon); diff --git a/ui/js/component/modalIncompatibleDaemon/view.jsx b/ui/js/component/modalIncompatibleDaemon/view.jsx index 851a2ec88..bf5c3a84d 100644 --- a/ui/js/component/modalIncompatibleDaemon/view.jsx +++ b/ui/js/component/modalIncompatibleDaemon/view.jsx @@ -1,21 +1,26 @@ import React from "react"; import { Modal } from "component/modal"; +import Link from "component/link"; class ModalIncompatibleDaemon extends React.PureComponent { render() { - const { quitAndLaunchDaemonHelp } = this.props; + const { quit } = this.props; return ( {__( - "This browser is running with an incompatible version of the LBRY protocol and your install must be repaired." + "This browser is running with an incompatible version of the LBRY protocol and your install must be repaired. " )} + ); } From b1920cfaf2910d1de110de58ce8206f5a1a64578 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Mon, 31 Jul 2017 20:45:15 +0100 Subject: [PATCH 131/152] Fix for blurry text and image on hover --- ui/scss/component/_card.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index 20c72b3de..d44762e75 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -97,7 +97,7 @@ $card-link-scaling: 1.1; position: relative; z-index: 1; box-shadow: $box-shadow-focus; - transform: scale($card-link-scaling) translate3d($translate-card-hover, 0, 0); + transform: scale($card-link-scaling) translateX($translate-card-hover); transform-origin: 50% 50%; overflow-x: visible; overflow-y: visible From cec789b983594b01b7bab1cfdbc7a73b7ae965ba Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 2 Aug 2017 13:43:58 -0400 Subject: [PATCH 132/152] make featured content icon a rocket ship --- CHANGELOG.md | 2 +- ui/js/component/iconFeatured/view.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 699832c9a..ebef2483c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * ### Changed - * + * Reward-eligible content icon is now a rocket ship :D * ### Fixed diff --git a/ui/js/component/iconFeatured/view.jsx b/ui/js/component/iconFeatured/view.jsx index e876224f0..2a0b2b54f 100644 --- a/ui/js/component/iconFeatured/view.jsx +++ b/ui/js/component/iconFeatured/view.jsx @@ -4,7 +4,7 @@ import { Icon } from "component/common.js"; const IconFeatured = props => { return ( - From 7b5f43eb773a8e933edba103497eab961acf0621 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 2 Aug 2017 13:46:17 -0400 Subject: [PATCH 133/152] add missing changelog entries --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebef2483c..07de7127b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,11 @@ Web UI version numbers should always match the corresponding version of LBRY App ### Changed * Reward-eligible content icon is now a rocket ship :D + * Improved flow for when app is run with incompatible daemon * ### Fixed - * + * Corrected improper pluralization on loading screen * ### Deprecated From a83aee1452ea76dd54b0b780945b67230250eb4d Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 2 Aug 2017 18:55:58 -0400 Subject: [PATCH 134/152] flow for failed authentication --- CHANGELOG.md | 2 +- ui/js/actions/user.js | 3 +++ ui/js/component/app/view.jsx | 2 ++ ui/js/component/modalAuthFailure/index.js | 12 +++++++++++ ui/js/component/modalAuthFailure/view.jsx | 25 +++++++++++++++++++++++ ui/js/constants/modal_types.js | 1 + ui/js/page/rewards/view.jsx | 10 +++++++++ 7 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 ui/js/component/modalAuthFailure/index.js create mode 100644 ui/js/component/modalAuthFailure/view.jsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 07de7127b..760787fae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added * Add tooltips to controls in header - * + * New flow for rewards authentication failure ### Changed * Reward-eligible content icon is now a rocket ship :D diff --git a/ui/js/actions/user.js b/ui/js/actions/user.js index e2fa9b917..1e5957c65 100644 --- a/ui/js/actions/user.js +++ b/ui/js/actions/user.js @@ -1,6 +1,8 @@ import * as types from "constants/action_types"; +import * as modals from "constants/modal_types"; import lbryio from "lbryio"; import { setLocal } from "utils"; +import { doOpenModal } from "actions/app"; import { doRewardList, doClaimRewardType } from "actions/rewards"; import { selectEmailToVerify, selectUser } from "selectors/user"; import rewards from "rewards"; @@ -20,6 +22,7 @@ export function doAuthenticate() { dispatch(doRewardList()); }) .catch(error => { + dispatch(doOpenModal(modals.AUTHENTICATION_FAILURE)); dispatch({ type: types.AUTHENTICATION_FAILURE, data: { error }, diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 1252d0942..e8ebbcd09 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -2,6 +2,7 @@ import React from "react"; import Router from "component/router"; import Header from "component/header"; import ModalError from "component/modalError"; +import ModalAuthFailure from "component/modalAuthFailure"; import ModalDownloading from "component/modalDownloading"; import ModalInsufficientCredits from "component/modalInsufficientCredits"; import ModalUpgrade from "component/modalUpgrade"; @@ -76,6 +77,7 @@ class App extends React.PureComponent { {modal == modals.INSUFFICIENT_CREDITS && } {modal == modals.WELCOME && } {modal == modals.FIRST_REWARD && } + {modal == modals.AUTHENTICATION_FAILURE && }
    ); } diff --git a/ui/js/component/modalAuthFailure/index.js b/ui/js/component/modalAuthFailure/index.js new file mode 100644 index 000000000..08acf62f8 --- /dev/null +++ b/ui/js/component/modalAuthFailure/index.js @@ -0,0 +1,12 @@ +import React from "react"; +import { connect } from "react-redux"; +import { doCloseModal } from "actions/app"; +import ModalAuthFailure from "./view"; + +const select = state => ({}); + +const perform = dispatch => ({ + close: () => dispatch(doCloseModal()), +}); + +export default connect(select, perform)(ModalAuthFailure); diff --git a/ui/js/component/modalAuthFailure/view.jsx b/ui/js/component/modalAuthFailure/view.jsx new file mode 100644 index 000000000..d3abe8112 --- /dev/null +++ b/ui/js/component/modalAuthFailure/view.jsx @@ -0,0 +1,25 @@ +import React from "react"; +import { Modal } from "component/modal"; + +class ModalAuthFailure extends React.PureComponent { + render() { + const { close } = this.props; + + return ( + { window.location.reload() }} + onAborted={close} + > +

    {__("Authentication Failure")}

    +

    {__("If reloading does not fix this, or you see this at every start up, please email help@lbry.io.")}

    +
    + ); + } +} + +export default ModalAuthFailure; diff --git a/ui/js/constants/modal_types.js b/ui/js/constants/modal_types.js index 13db30f3d..153996e00 100644 --- a/ui/js/constants/modal_types.js +++ b/ui/js/constants/modal_types.js @@ -6,3 +6,4 @@ export const INSUFFICIENT_CREDITS = "insufficient_credits"; export const UPGRADE = "upgrade"; export const WELCOME = "welcome"; export const FIRST_REWARD = "first_reward"; +export const AUTHENTICATION_FAILURE = "auth_failure"; \ No newline at end of file diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index deb6fa00d..8d97f33f6 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -122,6 +122,16 @@ class RewardsPage extends React.PureComponent {
    ); } + } else if (user === null) { + cardHeader = ( +
    +
    +

    + {__("This application is unable to earn rewards due to an authentication failure.")} +

    +
    +
    + ); } return ( From e39afb3a593b0768db038a4710013a29b2ca271b Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 2 Aug 2017 19:17:28 -0400 Subject: [PATCH 135/152] fix case of null user in auth --- ui/js/page/auth/view.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/js/page/auth/view.jsx b/ui/js/page/auth/view.jsx index 16a52efa6..55bef96a8 100644 --- a/ui/js/page/auth/view.jsx +++ b/ui/js/page/auth/view.jsx @@ -68,6 +68,7 @@ export class AuthPage extends React.PureComponent {
    {!isPending && !email && + user && !user.has_verified_email &&

    {__("Create a verified identity and receive LBC rewards.")} From 6304d6b4b9b7346e36943fb9d870dd6975424b07 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 2 Aug 2017 19:37:38 -0400 Subject: [PATCH 136/152] improve confirm identity flow --- CHANGELOG.md | 3 ++- ui/js/component/userVerify/index.js | 2 ++ ui/js/component/userVerify/view.jsx | 21 +++++++++++---------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 760787fae..637507ac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ Web UI version numbers should always match the corresponding version of LBRY App * New flow for rewards authentication failure ### Changed - * Reward-eligible content icon is now a rocket ship :D + * Make it clearer how to skip identity verification and add link to FAQ + * Reward-eligible content icon is now a rocket ship :D :D :D * Improved flow for when app is run with incompatible daemon * diff --git a/ui/js/component/userVerify/index.js b/ui/js/component/userVerify/index.js index 18d29c34f..1d48bfbbe 100644 --- a/ui/js/component/userVerify/index.js +++ b/ui/js/component/userVerify/index.js @@ -1,5 +1,6 @@ import React from "react"; import { connect } from "react-redux"; +import { doNavigate } from "actions/app"; import { doUserIdentityVerify } from "actions/user"; import rewards from "rewards"; import { makeSelectRewardByType } from "selectors/rewards"; @@ -20,6 +21,7 @@ const select = (state, props) => { }; const perform = dispatch => ({ + navigate: (uri) => dispatch(doNavigate(uri)), verifyUserIdentity: token => dispatch(doUserIdentityVerify(token)), }); diff --git a/ui/js/component/userVerify/view.jsx b/ui/js/component/userVerify/view.jsx index 7c9012aef..ddfc18278 100644 --- a/ui/js/component/userVerify/view.jsx +++ b/ui/js/component/userVerify/view.jsx @@ -1,5 +1,6 @@ import React from "react"; import { CreditAmount } from "component/common"; +import Link from "component/link"; import CardVerify from "component/cardVerify"; class UserVerify extends React.PureComponent { @@ -22,26 +23,26 @@ class UserVerify extends React.PureComponent { } render() { - const { errorMessage, isPending, reward } = this.props; + const { errorMessage, isPending, navigate } = this.props; return (

    {__( - "To ensure you are a real and unique person, you must link a valid credit or debit card." - )} -

    -

    - {__( - "A small authorization, but no actual charge, will be run on this card." - )} + "To ensure you are a real person, we require a valid credit or debit card." + ) + " " + __("There is no charge at all, now or in the future.") + " " } +

    {errorMessage &&

    {errorMessage}

    } - + />

    +

    + {__("You can continue without this step, but you will not be eligible to earn rewards.")} +

    + navigate("/discover")} button="alt" label={__("Skip Rewards")} />
    ); } From 7dbcb4de88a3cc2102555dacca661b06e2e611d7 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 2 Aug 2017 19:38:27 -0400 Subject: [PATCH 137/152] =?UTF-8?q?Bump=20version:=200.14.2=20=E2=86=92=20?= =?UTF-8?q?0.14.3rc1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- app/package.json | 4 ++-- ui/package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 7917e0cc8..3d4da27b3 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.2 +current_version = 0.14.3rc1 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/app/package.json b/app/package.json index 409537567..6658734c7 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.2", + "version": "0.14.3rc1", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { @@ -20,6 +20,6 @@ "electron-rebuild": "^1.5.11" }, "lbrySettings": { - "lbrynetDaemonVersion": "0.14.2" + "lbrynetDaemonVersion": "0.14.3rc1" } } diff --git a/ui/package.json b/ui/package.json index 5e1a8ccf5..474079faa 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.2", + "version": "0.14.3rc1", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From 32a640b6ddd76e1c8ea85ca16b45a7cb067d2331 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 3 Aug 2017 08:37:46 -0400 Subject: [PATCH 138/152] fix daemon version --- app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index 6658734c7..ee182cd61 100644 --- a/app/package.json +++ b/app/package.json @@ -20,6 +20,6 @@ "electron-rebuild": "^1.5.11" }, "lbrySettings": { - "lbrynetDaemonVersion": "0.14.3rc1" + "lbrynetDaemonVersion": "0.14.2" } } From ef0a0883cb0095f3b2a1cff413185ec6658ecc40 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 3 Aug 2017 08:38:11 -0400 Subject: [PATCH 139/152] =?UTF-8?q?Bump=20version:=200.14.3rc1=20=E2=86=92?= =?UTF-8?q?=200.14.3rc2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- app/package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 3d4da27b3..95f2606b7 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.3rc1 +current_version = 0.14.3rc2 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/app/package.json b/app/package.json index ee182cd61..14952d30b 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.3rc1", + "version": "0.14.3rc2", "main": "main.js", "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", "author": { diff --git a/ui/package.json b/ui/package.json index 474079faa..4c3bf52e7 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.3rc1", + "version": "0.14.3rc2", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From cae8e3701f81408a56aff2f5bc507b44939b249c Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 3 Aug 2017 11:30:46 -0400 Subject: [PATCH 140/152] update app description --- CHANGELOG.md | 1 + app/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 637507ac3..e4c345ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ### Changed * Make it clearer how to skip identity verification and add link to FAQ * Reward-eligible content icon is now a rocket ship :D :D :D + * Change install description shown by operating systems * Improved flow for when app is run with incompatible daemon * diff --git a/app/package.json b/app/package.json index 14952d30b..fad8346c5 100644 --- a/app/package.json +++ b/app/package.json @@ -2,7 +2,7 @@ "name": "LBRY", "version": "0.14.3rc2", "main": "main.js", - "description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.", + "description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "author": { "name": "LBRY Inc.", "email": "hello@lbry.io" From da8ed48fa79eacf67777fe6176760b04244fbe91 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 3 Aug 2017 12:15:52 -0400 Subject: [PATCH 141/152] cache auth token to prevent race condition on set --- app/package.json | 2 +- ui/js/lbryio.js | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/package.json b/app/package.json index fad8346c5..bec0c8eef 100644 --- a/app/package.json +++ b/app/package.json @@ -22,4 +22,4 @@ "lbrySettings": { "lbrynetDaemonVersion": "0.14.2" } -} +} \ No newline at end of file diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 8d785ee2a..0ec973a82 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -113,16 +113,24 @@ lbryio.call = function(resource, action, params = {}, method = "get") { }); }; +lbryio._authToken = null; + lbryio.getAuthToken = () => { return new Promise((resolve, reject) => { - ipcRenderer.once("auth-token-response", (event, token) => { - return resolve(token); - }); - ipcRenderer.send("get-auth-token"); + if (lbryio._authToken) { + resolve(lbryio._authToken); + } else { + ipcRenderer.once("auth-token-response", (event, token) => { + lbryio._authToken = token; + return resolve(token); + }); + ipcRenderer.send("get-auth-token"); + } }); }; lbryio.setAuthToken = token => { + lbryio._authToken = token ? token.toString().trim() : null; ipcRenderer.send("set-auth-token", token); }; From 7e6bce1ab2f21e5206d0e0c86ef8ce705167205b Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 3 Aug 2017 12:19:36 -0400 Subject: [PATCH 142/152] =?UTF-8?q?Bump=20version:=200.14.3rc2=20=E2=86=92?= =?UTF-8?q?=200.14.3rc3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- app/package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 95f2606b7..294f37f37 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.3rc2 +current_version = 0.14.3rc3 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/app/package.json b/app/package.json index bec0c8eef..2e70dbe72 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.3rc2", + "version": "0.14.3rc3", "main": "main.js", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "author": { diff --git a/ui/package.json b/ui/package.json index 4c3bf52e7..adace19d9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.3rc2", + "version": "0.14.3rc3", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From 443ab0b804ad565b12bf00f3d0af3f7078a481b2 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 3 Aug 2017 15:08:46 -0400 Subject: [PATCH 143/152] =?UTF-8?q?Bump=20version:=200.14.3rc3=20=E2=86=92?= =?UTF-8?q?=200.14.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGELOG.md | 30 +++++++++++++++++++++++------- app/package.json | 2 +- ui/package.json | 2 +- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 294f37f37..6d0330007 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.14.3rc3 +current_version = 0.14.3 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? diff --git a/CHANGELOG.md b/CHANGELOG.md index e4c345ac7..e4925312d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,18 +8,15 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added - * Add tooltips to controls in header - * New flow for rewards authentication failure + * + * ### Changed - * Make it clearer how to skip identity verification and add link to FAQ - * Reward-eligible content icon is now a rocket ship :D :D :D - * Change install description shown by operating systems - * Improved flow for when app is run with incompatible daemon + * * ### Fixed - * Corrected improper pluralization on loading screen + * * ### Deprecated @@ -30,6 +27,25 @@ Web UI version numbers should always match the corresponding version of LBRY App * * +## [0.14.3] - 2017-08-03 + +### Added + * Add tooltips to controls in header + * New flow for rewards authentication failure + + +### Changed + * Make it clearer how to skip identity verification and add link to FAQ + * Reward-eligible content icon is now a rocket ship :D :D :D + * Change install description shown by operating systems + * Improved flow for when app is run with incompatible daemon + + +### Fixed + * Corrected improper pluralization on loading screen + + + ## [0.14.2] - 2017-07-30 ### Added diff --git a/app/package.json b/app/package.json index 2e70dbe72..81027f7a8 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.14.3rc3", + "version": "0.14.3", "main": "main.js", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "author": { diff --git a/ui/package.json b/ui/package.json index adace19d9..00ea1b844 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "lbry-web-ui", - "version": "0.14.3rc3", + "version": "0.14.3", "description": "LBRY UI", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From fb07adb73c14554f154f95150b4ed940f26d49e8 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sat, 5 Aug 2017 06:02:49 +0100 Subject: [PATCH 144/152] additional css tweaks to fix blurry text --- ui/scss/component/_card.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index d44762e75..17ae70232 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -92,6 +92,7 @@ $card-link-scaling: 1.1; } .card--link { transition: transform 120ms ease-in-out; + backface-visibility: visible } .card--link:hover { position: relative; @@ -103,7 +104,7 @@ $card-link-scaling: 1.1; overflow-y: visible } .card--link:hover ~ .card--link { - transform: translate3d($translate-card-hover * 2, 0, 0); + transform: translateX($translate-card-hover * 2); } .card__media { From 60e0f24b58d8d62cafa301397e3bc1a2c861931a Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sat, 5 Aug 2017 06:21:36 +0100 Subject: [PATCH 145/152] removed unused code --- ui/scss/component/_card.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index 17ae70232..712eb9ea7 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -92,7 +92,6 @@ $card-link-scaling: 1.1; } .card--link { transition: transform 120ms ease-in-out; - backface-visibility: visible } .card--link:hover { position: relative; From 06979f1a1947edd50cd9bdcc986bfd60f9cd582a Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 6 Aug 2017 10:52:27 -0400 Subject: [PATCH 146/152] add changelog for blur fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4925312d..a7089af95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * ### Fixed - * + * Tiles will no longer be blurry on hover (Windows only bug) * ### Deprecated From 9c3d63353da7944e44322161aef8037208ed1658 Mon Sep 17 00:00:00 2001 From: hackrush Date: Fri, 4 Aug 2017 09:01:53 +0530 Subject: [PATCH 147/152] Created a more generic PriceForm component - [ ] Fixes #426 Modifying form to use single onChange event. ONLY MEANT FOR TESTING, NOT MERGING. --- CHANGELOG.md | 6 +-- ui/js/component/priceForm/index.js | 5 ++ ui/js/component/priceForm/view.jsx | 73 ++++++++++++++++++++++++++++ ui/js/component/publishForm/view.jsx | 44 +++++++---------- ui/js/page/settings/view.jsx | 46 +++++------------- 5 files changed, 112 insertions(+), 62 deletions(-) create mode 100644 ui/js/component/priceForm/index.js create mode 100644 ui/js/component/priceForm/view.jsx diff --git a/CHANGELOG.md b/CHANGELOG.md index a7089af95..b292a3b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added - * + * Added a new component, "PriceForm" which is now used in Publish and Settings * ### Changed @@ -16,7 +16,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * ### Fixed - * Tiles will no longer be blurry on hover (Windows only bug) + * Tiles will no longer be blurry on hover (Windows only bug) * ### Deprecated @@ -24,7 +24,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * ### Removed - * + * Removed one instance of string "Max Purchase Price" from settings page, it's redudant. * ## [0.14.3] - 2017-08-03 diff --git a/ui/js/component/priceForm/index.js b/ui/js/component/priceForm/index.js new file mode 100644 index 000000000..f56521afe --- /dev/null +++ b/ui/js/component/priceForm/index.js @@ -0,0 +1,5 @@ +import React from "react"; +import { connect } from "react-redux"; +import FormFieldPrice from "./view"; + +export default connect(null, null)(FormFieldPrice); diff --git a/ui/js/component/priceForm/view.jsx b/ui/js/component/priceForm/view.jsx new file mode 100644 index 000000000..be325dcd0 --- /dev/null +++ b/ui/js/component/priceForm/view.jsx @@ -0,0 +1,73 @@ +import React from "react"; +import { FormField } from "component/form"; + +class FormFieldPrice extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + price: { + feeAmount: "", + feeCurrency: "LBC", + }, + }; + } + + handleFeeAmountChange(event) { + this.setState({ + price: { + ...this.state.price, + feeAmount: event.target.value, + }, + }); + this.props.onChange(event.target.name, this.state.price); + console.log(this.state.price); + } + + handleFeeCurrencyChange(event) { + this.setState({ + price: { + ...this.state.price, + feeCurrency: event.target.value, + }, + }); + this.props.onChange(event.target.name, this.state.price); + console.log(this.state.price); + } + + render() { + const { + defaultFeeValue, + defaultCurrencyValue, + placeholder, + min, + step, + } = this.props; + + return ( + + this.handleFeeAmountChange(event)} + defaultValue={defaultFeeValue} + className="form-field__input--inline" + /> + this.handleFeeCurrencyChange(event)} + defaultValue={defaultCurrencyValue} + className="form-field__input--inline" + > + + + + + ); + } +} + +export default FormFieldPrice; diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 970787205..ebf6def97 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -3,6 +3,7 @@ import lbry from "lbry"; import lbryuri from "lbryuri"; import { FormField, FormRow } from "component/form.js"; import Link from "component/link"; +import FormFieldPrice from "component/priceForm"; import Modal from "component/modal"; import { BusyMessage } from "component/common"; import ChannelSection from "./internal/channelSection"; @@ -21,7 +22,7 @@ class PublishForm extends React.PureComponent { bid: 10, hasFile: false, feeAmount: "", - feeCurrency: "USD", + feeCurrency: "LBC", channel: "anonymous", newChannelName: "@", newChannelBid: 10, @@ -306,21 +307,21 @@ class PublishForm extends React.PureComponent { }); } - handleFeeAmountChange(event) { - this.setState({ - feeAmount: event.target.value, - }); - } + handleFeeAmtAndCurrChange(event) { + name = event.target.name; + let targetValue = { [name]: event.target.value }; - handleFeeCurrencyChange(event) { - this.setState({ - feeCurrency: event.target.value, - }); + if ([name] == "amount") { + this.setState({ feeAmount: targetValue.amount }); + } else { + this.setState({ feeCurrency: targetValue.currency }); + } } handleFeePrefChange(feeEnabled) { this.setState({ isFee: feeEnabled, + feeAmount: this.state.feeAmount == "" ? "5.00" : this.state.feeAmount, }); } @@ -666,23 +667,14 @@ class PublishForm extends React.PureComponent { checked={this.state.isFee} /> - this.handleFeeAmountChange(event)} - />{" "} - { - this.handleFeeCurrencyChange(event); - }} - > - - - + placeholder="5.00" + step="0.1" + onChange={event => this.handleFeeAmtAndCurrChange(event)} + defaultFeeValue="5.00" + defaultCurrencyValue="LBC" + /> {this.state.isFee ?
    diff --git a/ui/js/page/settings/view.jsx b/ui/js/page/settings/view.jsx index d7646c476..e32f22738 100644 --- a/ui/js/page/settings/view.jsx +++ b/ui/js/page/settings/view.jsx @@ -3,6 +3,7 @@ import { FormField, FormRow } from "component/form.js"; import SubHeader from "component/subHeader"; import lbry from "lbry.js"; import Link from "component/link"; +import FormFieldPrice from "component/priceForm"; const { remote } = require("electron"); @@ -55,24 +56,20 @@ class SettingsPage extends React.PureComponent { this.setDaemonSetting("download_directory", event.target.value); } - onKeyFeeChange(event) { + onKeyFeeChange(name, price) { var oldSettings = this.props.daemonSettings.max_key_fee; var newSettings = { amount: oldSettings.amount, currency: oldSettings.currency, }; - newSettings.amount = Number(event.target.value); - this.setDaemonSetting("max_key_fee", newSettings); - } - - onFeeCurrencyChange(event) { - var oldSettings = this.props.daemonSettings.max_key_fee; - var newSettings = { - amount: oldSettings.amount, - currency: oldSettings.currency, - }; - newSettings.currency = event.target.value; + if (name == "amount") { + newSettings.amount = Number(price.feeAmount); + console.log(newSettings.amount, price.feeAmount); + } else { + newSettings.currency = price.feeCurrency; + console.log(newSettings.amount, price.feeAmount); + } this.setDaemonSetting("max_key_fee", newSettings); } @@ -151,11 +148,6 @@ class SettingsPage extends React.PureComponent {

    {__("Max Purchase Price")}

    -
    -
    - {__("Max Purchase Price")} -
    -
    {!daemonSettings.disable_max_key_fee - ? : ""} - {!daemonSettings.disable_max_key_fee - ? - - - - : ""}
    {__( From a299ca2dddffe73bab11f3136d3cc8ee50f427fd Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 6 Aug 2017 18:24:55 -0400 Subject: [PATCH 148/152] significant additional form changes and cleanup --- .gitignore | 1 + CHANGELOG.md | 7 +- package.json | 4 +- ui/js/component/fileList/view.jsx | 2 +- ui/js/component/form.js | 202 ++---------------- ui/js/component/formField/index.js | 5 + ui/js/component/formField/view.jsx | 178 +++++++++++++++ ui/js/component/formFieldPrice/index.js | 5 + ui/js/component/formFieldPrice/view.jsx | 68 ++++++ ui/js/component/modalRemoveFile/view.jsx | 2 +- ui/js/component/priceForm/view.jsx | 12 +- ui/js/component/publishForm/index.js | 2 +- .../publishForm/internal/channelSection.jsx | 7 +- ui/js/component/publishForm/view.jsx | 37 ++-- ui/js/page/developer.js | 2 +- ui/js/page/fileListDownloaded/view.jsx | 5 - ui/js/page/fileListPublished/view.jsx | 5 - ui/js/page/settings/view.jsx | 44 ++-- ui/js/selectors/settings.js | 2 +- yarn.lock | 107 ++++++++-- 20 files changed, 428 insertions(+), 269 deletions(-) create mode 100644 ui/js/component/formField/index.js create mode 100644 ui/js/component/formField/view.jsx create mode 100644 ui/js/component/formFieldPrice/index.js create mode 100644 ui/js/component/formFieldPrice/view.jsx diff --git a/.gitignore b/.gitignore index 233924d55..dc61fadfe 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ /app/node_modules /build/venv /lbry-app-venv +/lbry-app /lbry-venv /daemon/build /daemon/venv diff --git a/CHANGELOG.md b/CHANGELOG.md index b292a3b65..62b54dca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,15 +8,16 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added - * Added a new component, "PriceForm" which is now used in Publish and Settings + * Added a new component, `FormFieldPrice` which is now used in Publish and Settings * ### Changed - * + * Some form field refactoring as we progress towards form sanity. * ### Fixed * Tiles will no longer be blurry on hover (Windows only bug) + * Removed placeholder values from price selection form fields, which was causing confusion that these were real values (#426) * ### Deprecated @@ -24,7 +25,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * ### Removed - * Removed one instance of string "Max Purchase Price" from settings page, it's redudant. + * Removed the label "Max Purchase Price" from settings page. It was redundant. * ## [0.14.3] - 2017-08-03 diff --git a/package.json b/package.json index e0f83e824..50eae487d 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ }, "devDependencies": { "devtron": "^1.4.0", - "electron": "^1.4.15", + "electron": "^1.7.5", "electron-builder": "^11.7.0", - "electron-debug": "^1.1.0" + "electron-debug": "^1.4.0" } } diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index f910e9500..cc397ddbe 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -2,7 +2,7 @@ import React from "react"; import lbry from "lbry.js"; import lbryuri from "lbryuri.js"; import Link from "component/link"; -import { FormField } from "component/form.js"; +import FormField from "component/formField"; import FileTile from "component/fileTile"; import rewards from "rewards.js"; import lbryio from "lbryio.js"; diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 59b9bce50..d47d7445a 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -1,183 +1,14 @@ import React from "react"; -import FileSelector from "./file-selector.js"; -import SimpleMDE from "react-simplemde-editor"; -import style from "react-simplemde-editor/dist/simplemde.min.css"; +import FormField from "component/formField"; -let formFieldCounter = 0, - formFieldFileSelectorTypes = ["file", "directory"], - formFieldNestedLabelTypes = ["radio", "checkbox"]; +let formFieldCounter = 0; -function formFieldId() { +export const formFieldNestedLabelTypes = ["radio", "checkbox"]; + +export function formFieldId() { return "form-field-" + ++formFieldCounter; } -export class FormField extends React.PureComponent { - static propTypes = { - type: React.PropTypes.string.isRequired, - prefix: React.PropTypes.string, - postfix: React.PropTypes.string, - hasError: React.PropTypes.bool, - }; - - constructor(props) { - super(props); - - this._fieldRequiredText = __("This field is required"); - this._type = null; - this._element = null; - this._extraElementProps = {}; - - this.state = { - isError: null, - errorMessage: null, - }; - } - - componentWillMount() { - if (["text", "number", "radio", "checkbox"].includes(this.props.type)) { - this._element = "input"; - this._type = this.props.type; - } else if (this.props.type == "text-number") { - this._element = "input"; - this._type = "text"; - } else if (this.props.type == "SimpleMDE") { - this._element = SimpleMDE; - this._type = "textarea"; - this._extraElementProps.options = { - hideIcons: ["guide", "heading", "image", "fullscreen", "side-by-side"], - }; - } else if (formFieldFileSelectorTypes.includes(this.props.type)) { - this._element = "input"; - this._type = "hidden"; - } else { - // Non field, e.g.