From 0d3647c7094eee241d9652931738e42849d1dfff Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 7 Apr 2017 12:15:22 +0700 Subject: [PATCH 01/56] Redux proof of concept --- app/main.js | 3 + package.json | 3 +- ui/js/actions/app.js | 206 +++++++ ui/js/app.js | 651 +++++++++++---------- ui/js/component/app/index.js | 37 ++ ui/js/component/app/view.jsx | 65 ++ ui/js/component/downloadingModal/index.jsx | 25 + ui/js/component/downloadingModal/view.jsx | 40 ++ ui/js/component/drawer/index.jsx | 29 + ui/js/component/drawer/view.jsx | 68 +++ ui/js/component/errorModal/index.jsx | 23 + ui/js/component/errorModal/view.jsx | 50 ++ ui/js/component/file-actions.js | 2 +- ui/js/component/file-tile.js | 2 +- ui/js/component/header.js | 2 +- ui/js/component/link/index.jsx | 7 + ui/js/component/link/view.js | 100 ++++ ui/js/component/load_screen.js | 2 +- ui/js/component/menu.js | 2 +- ui/js/component/modal.js | 2 +- ui/js/component/router/index.jsx | 15 + ui/js/component/router/view.jsx | 51 ++ ui/js/component/upgradeModal/index.jsx | 19 + ui/js/component/upgradeModal/view.jsx | 32 + ui/js/config/development.js | 2 + ui/js/constants/action_types.js | 21 + ui/js/main.js | 11 +- ui/js/page/developer.js | 2 +- ui/js/page/discover.js | 6 +- ui/js/page/file-list.js | 4 +- ui/js/page/help/index.jsx | 7 + ui/js/page/{help.js => help/view.jsx} | 9 +- ui/js/page/publish.js | 5 +- ui/js/page/report.js | 2 +- ui/js/page/show.js | 2 +- ui/js/page/wallet.js | 2 +- ui/js/page/watch.js | 215 ------- ui/js/reducers/app.js | 105 ++++ ui/js/selectors/app.js | 145 +++++ ui/js/store.js | 37 ++ ui/package.json | 5 + ui/webpack.config.js | 9 +- ui/webpack.dev.config.js | 11 +- 43 files changed, 1471 insertions(+), 565 deletions(-) create mode 100644 ui/js/actions/app.js create mode 100644 ui/js/component/app/index.js create mode 100644 ui/js/component/app/view.jsx create mode 100644 ui/js/component/downloadingModal/index.jsx create mode 100644 ui/js/component/downloadingModal/view.jsx create mode 100644 ui/js/component/drawer/index.jsx create mode 100644 ui/js/component/drawer/view.jsx create mode 100644 ui/js/component/errorModal/index.jsx create mode 100644 ui/js/component/errorModal/view.jsx create mode 100644 ui/js/component/link/index.jsx create mode 100644 ui/js/component/link/view.js create mode 100644 ui/js/component/router/index.jsx create mode 100644 ui/js/component/router/view.jsx create mode 100644 ui/js/component/upgradeModal/index.jsx create mode 100644 ui/js/component/upgradeModal/view.jsx create mode 100644 ui/js/config/development.js create mode 100644 ui/js/constants/action_types.js create mode 100644 ui/js/page/help/index.jsx rename ui/js/page/{help.js => help/view.jsx} (95%) delete mode 100644 ui/js/page/watch.js create mode 100644 ui/js/reducers/app.js create mode 100644 ui/js/selectors/app.js create mode 100644 ui/js/store.js diff --git a/app/main.js b/app/main.js index eb867a2d7..3acdbbab4 100644 --- a/app/main.js +++ b/app/main.js @@ -1,5 +1,8 @@ const {app, BrowserWindow, ipcMain} = require('electron'); const url = require('url'); + +require('electron-debug')({showDevTools: true}); + const path = require('path'); const jayson = require('jayson'); const semver = require('semver'); diff --git a/package.json b/package.json index 45142bbaa..b5a3c3678 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ }, "devDependencies": { "electron": "^1.4.15", - "electron-builder": "^11.7.0" + "electron-builder": "^11.7.0", + "electron-debug": "^1.1.0" }, "dependencies": {} } diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js new file mode 100644 index 000000000..14aff78f7 --- /dev/null +++ b/ui/js/actions/app.js @@ -0,0 +1,206 @@ +import * as types from 'constants/action_types' +import lbry from 'lbry' +import { + selectUpdateUrl, + selectUpgradeDownloadDir, + selectUpgradeDownloadItem, + selectUpgradeFilename, +} from 'selectors/app' + +const {remote, ipcRenderer, shell} = require('electron'); +const path = require('path'); +const app = require('electron').remote.app; +const {download} = remote.require('electron-dl'); +const fs = remote.require('fs'); + +export function doNavigate(path) { + return { + type: types.NAVIGATE, + data: { + path: path + } + } +} + +export function doLogoClick() { +} + +export function doOpenDrawer() { + return { + type: types.OPEN_DRAWER + } +} + +export function doCloseDrawer() { + return { + type: types.CLOSE_DRAWER + } +} + +export function doOpenModal(modal) { + return { + type: types.OPEN_MODAL, + data: { + modal + } + } +} + +export function doCloseModal() { + return { + type: types.CLOSE_MODAL, + } +} + +export function doUpdateBalance(balance) { + return { + type: types.UPDATE_BALANCE, + data: { + balance: balance + } + } +} + +export function doUpdateDownloadProgress(percent) { + return { + type: types.UPGRADE_DOWNLOAD_PROGRESSED, + data: { + percent: percent + } + } +} + +export function doSkipUpgrade() { + return { + type: types.SKIP_UPGRADE + } +} + +export function doStartUpgrade() { + return function(dispatch, getState) { + const state = getState() + const upgradeDownloadPath = selectUpgradeDownloadDir(state) + + ipcRenderer.send('upgrade', upgradeDownloadPath) + } +} + +export function doDownloadUpgrade() { + return function(dispatch, getState) { + const state = getState() + // Make a new directory within temp directory so the filename is guaranteed to be available + const dir = fs.mkdtempSync(app.getPath('temp') + require('path').sep); + const upgradeFilename = selectUpgradeFilename(state) + + let options = { + onProgress: (p) => dispatch(doUpdateDownloadProgress(Math.round(p * 100))), + directory: dir, + }; + download(remote.getCurrentWindow(), selectUpdateUrl(state), options) + .then(downloadItem => { + /** + * TODO: get the download path directly from the download object. It should just be + * downloadItem.getSavePath(), but the copy on the main process is being garbage collected + * too soon. + */ + + const _upgradeDownloadItem = downloadItem; + const _upgradeDownloadPath = path.join(dir, upgradeFilename); + + dispatch({ + type: types.UPGRADE_DOWNLOAD_COMPLETED, + data: { + dir, + downloadItem + } + }) + }); + + dispatch({ + type: types.UPGRADE_DOWNLOAD_STARTED + }) + dispatch({ + type: types.OPEN_MODAL, + data: { + modal: 'downloading' + } + }) + } +} + +export function doCancelUpgrade() { + return function(dispatch, getState) { + const state = getState() + const upgradeDownloadItem = selectUpgradeDownloadItem(state) + + if (upgradeDownloadItem) { + /* + * Right now the remote reference to the download item gets garbage collected as soon as the + * the download is over (maybe even earlier), so trying to cancel a finished download may + * throw an error. + */ + try { + upgradeDownloadItem.cancel(); + } catch (err) { + console.error(err) + // Do nothing + } + } + + dispatch({ type: types.UPGRADE_CANCELLED }) + } +} + +export function doCheckUpgradeAvailable() { + return function(dispatch, getState) { + const state = getState() + + lbry.checkNewVersionAvailable(({isAvailable}) => { + if (!isAvailable) { + return; + } + + lbry.getVersionInfo((versionInfo) => { + dispatch({ + type: types.UPDATE_VERSION, + data: { + version: versionInfo.lbrynet_version + } + }) + dispatch({ + type: types.OPEN_MODAL, + data: { + modal: 'upgrade' + } + }) + }); + }); + } +} + +export function doAlertError(errorList) { + return function(dispatch, getState) { + const state = getState() + + dispatch({ + type: types.OPEN_MODAL, + data: { + modal: 'error', + error: errorList + } + }) + } +} + +export function doSearch(term) { + return function(dispatch, getState) { + const state = getState() + + dispatch({ + type: types.START_SEARCH, + data: { + searchTerm: term + } + }) + } +} diff --git a/ui/js/app.js b/ui/js/app.js index 85f8891a6..834af6521 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -1,321 +1,334 @@ -import React from 'react'; -import {Line} from 'rc-progress'; +import store from 'store.js'; -import lbry from './lbry.js'; -import SettingsPage from './page/settings.js'; -import HelpPage from './page/help.js'; -import WatchPage from './page/watch.js'; -import ReportPage from './page/report.js'; -import StartPage from './page/start.js'; -import RewardsPage from './page/rewards.js'; -import RewardPage from './page/reward.js'; -import WalletPage from './page/wallet.js'; -import ShowPage from './page/show.js'; -import PublishPage from './page/publish.js'; -import SearchPage from './page/search.js'; -import DiscoverPage from './page/discover.js'; -import DeveloperPage from './page/developer.js'; -import lbryuri from './lbryuri.js'; -import {FileListDownloaded, FileListPublished} from './page/file-list.js'; -import Header from './component/header.js'; -import {Modal, ExpandableModal} from './component/modal.js'; -import {Link} from './component/link.js'; - - -const {remote, ipcRenderer, shell} = require('electron'); -const {download} = remote.require('electron-dl'); -const path = require('path'); -const app = require('electron').remote.app; -const fs = remote.require('fs'); - - -var App = React.createClass({ - _error_key_labels: { - connectionString: 'API connection string', - method: 'Method', - params: 'Parameters', - code: 'Error code', - message: 'Error message', - data: 'Error data', - }, - _fullScreenPages: ['watch'], - _storeHistoryOfNextRender: false, - - _upgradeDownloadItem: null, - _isMounted: false, - _version: null, - getUpdateUrl: function() { - switch (process.platform) { - case 'darwin': - return 'https://lbry.io/get/lbry.dmg'; - case 'linux': - return 'https://lbry.io/get/lbry.deb'; - case 'win32': - return 'https://lbry.io/get/lbry.exe'; - default: - throw 'Unknown platform'; - } - }, - // Hard code the filenames as a temporary workaround, because - // electron-dl throws errors when you try to get the filename - getUpgradeFilename: function() { - switch (process.platform) { - case 'darwin': - return `LBRY-${this._version}.dmg`; - case 'linux': - return `LBRY_${this._version}_amd64.deb`; - case 'windows': - return `LBRY.Setup.${this._version}.exe`; - default: - throw 'Unknown platform'; - } - }, - getViewingPageAndArgs: function(address) { - // For now, routes are in format ?page or ?page=args - let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/); - return { - viewingPage: viewingPage, - pageArgs: pageArgs === undefined ? null : decodeURIComponent(pageArgs) - }; - }, - getInitialState: function() { - return Object.assign(this.getViewingPageAndArgs(window.location.search), { - viewingPage: 'discover', - appUrl: null, - errorInfo: null, - modal: null, - downloadProgress: null, - downloadComplete: false, - }); - }, - componentWillMount: function() { - window.addEventListener("popstate", this.onHistoryPop); - - document.addEventListener('unhandledError', (event) => { - this.alertError(event.detail); - }); - - //open links in external browser and skip full redraw on changing page - document.addEventListener('click', (event) => { - var target = event.target; - while (target && target !== document) { - if (target.matches('a[href^="http"]')) { - event.preventDefault(); - shell.openExternal(target.href); - return; - } - if (target.matches('a[href^="?"]')) { - event.preventDefault(); - if (this._isMounted) { - let appUrl = target.getAttribute('href'); - this._storeHistoryOfNextRender = true; - this.setState(Object.assign({}, this.getViewingPageAndArgs(appUrl), { appUrl: appUrl })); - document.body.scrollTop = 0; - } - } - target = target.parentNode; - } - }); - - if (!sessionStorage.getItem('upgradeSkipped')) { - lbry.getVersionInfo().then(({remoteVersion, upgradeAvailable}) => { - if (upgradeAvailable) { - this._version = remoteVersion; - this.setState({ - modal: 'upgrade', - }); - } - }); - } - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - componentDidMount: function() { - this._isMounted = true; - }, - componentWillUnmount: function() { - this._isMounted = false; - window.removeEventListener("popstate", this.onHistoryPop); - }, - onHistoryPop: function() { - this.setState(this.getViewingPageAndArgs(location.search)); - }, - onSearch: function(term) { - this._storeHistoryOfNextRender = true; - const isShow = term.startsWith('lbry://'); - this.setState({ - viewingPage: isShow ? "show" : "search", - appUrl: (isShow ? "?show=" : "?search=") + encodeURIComponent(term), - pageArgs: term - }); - }, - onSubmit: function(uri) { - this._storeHistoryOfNextRender = true; - this.setState({ - address: uri, - appUrl: "?show=" + encodeURIComponent(uri), - viewingPage: "show", - pageArgs: uri - }) - }, - handleUpgradeClicked: function() { - // Make a new directory within temp directory so the filename is guaranteed to be available - const dir = fs.mkdtempSync(app.getPath('temp') + require('path').sep); - - let options = { - onProgress: (p) => this.setState({downloadProgress: Math.round(p * 100)}), - directory: dir, - }; - download(remote.getCurrentWindow(), this.getUpdateUrl(), options) - .then(downloadItem => { - /** - * TODO: get the download path directly from the download object. It should just be - * downloadItem.getSavePath(), but the copy on the main process is being garbage collected - * too soon. - */ - - this._upgradeDownloadItem = downloadItem; - this._upgradeDownloadPath = path.join(dir, this.getUpgradeFilename()); - this.setState({ - downloadComplete: true - }); - }); - this.setState({modal: 'downloading'}); - }, - handleStartUpgradeClicked: function() { - ipcRenderer.send('upgrade', this._upgradeDownloadPath); - }, - cancelUpgrade: function() { - if (this._upgradeDownloadItem) { - /* - * Right now the remote reference to the download item gets garbage collected as soon as the - * the download is over (maybe even earlier), so trying to cancel a finished download may - * throw an error. - */ - try { - this._upgradeDownloadItem.cancel(); - } catch (err) { - // Do nothing - } - } - this.setState({ - downloadProgress: null, - downloadComplete: false, - modal: null, - }); - }, - handleSkipClicked: function() { - sessionStorage.setItem('upgradeSkipped', true); - this.setState({ - modal: null, - }); - }, - alertError: function(error) { - var errorInfoList = []; - for (let key of Object.keys(error)) { - let val = typeof error[key] == 'string' ? error[key] : JSON.stringify(error[key]); - let label = this._error_key_labels[key]; - errorInfoList.push(
  • {label}: {val}
  • ); - } - - this.setState({ - modal: 'error', - errorInfo: , - }); - }, - getContentAndAddress: function() - { - switch(this.state.viewingPage) - { - case 'search': - return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', ]; - case 'settings': - return ["Settings", "icon-gear", ]; - case 'help': - return ["Help", "icon-question", ]; - case 'report': - return ['Report an Issue', 'icon-file', ]; - case 'downloaded': - return ["Downloads & Purchases", "icon-folder", ]; - case 'published': - return ["Publishes", "icon-folder", ]; - case 'start': - return ["Start", "icon-file", ]; - case 'rewards': - return ["Rewards", "icon-bank", ]; - case 'wallet': - case 'send': - case 'receive': - return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", ] - case 'show': - return [lbryuri.normalize(this.state.pageArgs), "icon-file", ]; - case 'publish': - return ["Publish", "icon-upload", ]; - case 'developer': - return ["Developer", "icon-file", ]; - case 'discover': - default: - return ["Home", "icon-home", ]; - } - }, - render: function() { - let [address, wunderBarIcon, mainContent] = this.getContentAndAddress(); - - lbry.setTitle(address); - - if (this._storeHistoryOfNextRender) { - this._storeHistoryOfNextRender = false; - history.pushState({}, document.title, this.state.appUrl); - } - - return ( - this._fullScreenPages.includes(this.state.viewingPage) ? - mainContent : -
    -
    -
    - {mainContent} -
    - - Your version of LBRY is out of date and may be unreliable or insecure. - - - Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}%` : null} - - {this.state.downloadComplete ? ( -
    -
    -

    Click "Begin Upgrade" to start the upgrade process.

    -

    The app will close, and you will be prompted to install the latest version of LBRY.

    -

    After the install is complete, please reopen the app.

    -
    - ) : null } -
    - {this.state.downloadComplete - ? - : null} - -
    -
    - -

    Error

    - -
    -
    -

    We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.

    -
    -
    -
    - ); +const env = process.env.NODE_ENV || 'development'; +const config = require(`./config/${env}`); +const logs = []; +const app = { + env: env, + config: config, + store: store, + logs: logs, + log: function(message) { + console.log(message); + logs.push(message); } -}); - - -export default App; +} +global.app = app; +module.exports = app; +// +// import React from 'react'; +// import {Line} from 'rc-progress'; +// +// import lbry from './lbry.js'; +// import SettingsPage from './page/settings.js'; +// import HelpPage from './page/help.js'; +// import WatchPage from './page/watch.js'; +// import ReportPage from './page/report.js'; +// import StartPage from './page/start.js'; +// import RewardsPage from './page/rewards.js'; +// import RewardPage from './page/reward.js'; +// import WalletPage from './page/wallet.js'; +// import ShowPage from './page/show.js'; +// import PublishPage from './page/publish.js'; +// import SearchPage from './page/search.js'; +// import DiscoverPage from './page/discover.js'; +// import DeveloperPage from './page/developer.js'; +// import lbryuri from './lbryuri.js'; +// import {FileListDownloaded, FileListPublished} from './page/file-list.js'; +// import Header from './component/header.js'; +// import {Modal, ExpandableModal} from './component/modal.js'; +// import {Link} from './component/link.js'; +// +// +// const {remote, ipcRenderer, shell} = require('electron'); +// const {download} = remote.require('electron-dl'); +// const path = require('path'); +// const app = require('electron').remote.app; +// const fs = remote.require('fs'); +// +// +// var App = React.createClass({ +// _error_key_labels: { +// connectionString: 'API connection string', +// method: 'Method', +// params: 'Parameters', +// code: 'Error code', +// message: 'Error message', +// data: 'Error data', +// }, +// _fullScreenPages: ['watch'], +// _storeHistoryOfNextRender: false, +// +// _upgradeDownloadItem: null, +// _isMounted: false, +// _version: null, +// getUpdateUrl: function() { +// switch (process.platform) { +// case 'darwin': +// return 'https://lbry.io/get/lbry.dmg'; +// case 'linux': +// return 'https://lbry.io/get/lbry.deb'; +// case 'win32': +// return 'https://lbry.io/get/lbry.exe'; +// default: +// throw 'Unknown platform'; +// } +// }, +// // Hard code the filenames as a temporary workaround, because +// // electron-dl throws errors when you try to get the filename +// getUpgradeFilename: function() { +// switch (process.platform) { +// case 'darwin': +// return `LBRY-${this._version}.dmg`; +// case 'linux': +// return `LBRY_${this._version}_amd64.deb`; +// case 'windows': +// return `LBRY.Setup.${this._version}.exe`; +// default: +// throw 'Unknown platform'; +// } +// }, +// getViewingPageAndArgs: function(address) { +// // For now, routes are in format ?page or ?page=args +// let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/); +// return { +// viewingPage: viewingPage, +// pageArgs: pageArgs === undefined ? null : decodeURIComponent(pageArgs) +// }; +// }, +// getInitialState: function() { +// return Object.assign(this.getViewingPageAndArgs(window.location.search), { +// viewingPage: 'discover', +// appUrl: null, +// errorInfo: null, +// modal: null, +// downloadProgress: null, +// downloadComplete: false, +// }); +// }, +// componentWillMount: function() { +// window.addEventListener("popstate", this.onHistoryPop); +// +// document.addEventListener('unhandledError', (event) => { +// this.alertError(event.detail); +// }); +// +// //open links in external browser and skip full redraw on changing page +// document.addEventListener('click', (event) => { +// var target = event.target; +// while (target && target !== document) { +// if (target.matches('a[href^="http"]')) { +// event.preventDefault(); +// shell.openExternal(target.href); +// return; +// } +// if (target.matches('a[href^="?"]')) { +// event.preventDefault(); +// if (this._isMounted) { +// let appUrl = target.getAttribute('href'); +// this._storeHistoryOfNextRender = true; +// this.setState(Object.assign({}, this.getViewingPageAndArgs(appUrl), { appUrl: appUrl })); +// document.body.scrollTop = 0; +// } +// } +// target = target.parentNode; +// } +// }); +// +// if (!sessionStorage.getItem('upgradeSkipped')) { +// lbry.getVersionInfo().then(({remoteVersion, upgradeAvailable}) => { +// if (upgradeAvailable) { +// this._version = remoteVersion; +// this.setState({ +// modal: 'upgrade', +// }); +// } +// }); +// } +// }, +// closeModal: function() { +// this.setState({ +// modal: null, +// }); +// }, +// componentDidMount: function() { +// this._isMounted = true; +// }, +// componentWillUnmount: function() { +// this._isMounted = false; +// window.removeEventListener("popstate", this.onHistoryPop); +// }, +// onHistoryPop: function() { +// this.setState(this.getViewingPageAndArgs(location.search)); +// }, +// onSearch: function(term) { +// this._storeHistoryOfNextRender = true; +// const isShow = term.startsWith('lbry://'); +// this.setState({ +// viewingPage: isShow ? "show" : "search", +// appUrl: (isShow ? "?show=" : "?search=") + encodeURIComponent(term), +// pageArgs: term +// }); +// }, +// onSubmit: function(uri) { +// this._storeHistoryOfNextRender = true; +// this.setState({ +// address: uri, +// appUrl: "?show=" + encodeURIComponent(uri), +// viewingPage: "show", +// pageArgs: uri +// }) +// }, +// handleUpgradeClicked: function() { +// // Make a new directory within temp directory so the filename is guaranteed to be available +// const dir = fs.mkdtempSync(app.getPath('temp') + require('path').sep); +// +// let options = { +// onProgress: (p) => this.setState({downloadProgress: Math.round(p * 100)}), +// directory: dir, +// }; +// download(remote.getCurrentWindow(), this.getUpdateUrl(), options) +// .then(downloadItem => { +// /** +// * TODO: get the download path directly from the download object. It should just be +// * downloadItem.getSavePath(), but the copy on the main process is being garbage collected +// * too soon. +// */ +// +// this._upgradeDownloadItem = downloadItem; +// this._upgradeDownloadPath = path.join(dir, this.getUpgradeFilename()); +// this.setState({ +// downloadComplete: true +// }); +// }); +// this.setState({modal: 'downloading'}); +// }, +// handleStartUpgradeClicked: function() { +// ipcRenderer.send('upgrade', this._upgradeDownloadPath); +// }, +// cancelUpgrade: function() { +// if (this._upgradeDownloadItem) { +// /* +// * Right now the remote reference to the download item gets garbage collected as soon as the +// * the download is over (maybe even earlier), so trying to cancel a finished download may +// * throw an error. +// */ +// try { +// this._upgradeDownloadItem.cancel(); +// } catch (err) { +// // Do nothing +// } +// } +// this.setState({ +// downloadProgress: null, +// downloadComplete: false, +// modal: null, +// }); +// }, +// handleSkipClicked: function() { +// sessionStorage.setItem('upgradeSkipped', true); +// this.setState({ +// modal: null, +// }); +// }, +// alertError: function(error) { +// var errorInfoList = []; +// for (let key of Object.keys(error)) { +// let val = typeof error[key] == 'string' ? error[key] : JSON.stringify(error[key]); +// let label = this._error_key_labels[key]; +// errorInfoList.push(
  • {label}: {val}
  • ); +// } +// +// this.setState({ +// modal: 'error', +// errorInfo:
      {errorInfoList}
    , +// }); +// }, +// getContentAndAddress: function() +// { +// switch(this.state.viewingPage) +// { +// case 'search': +// return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', ]; +// case 'settings': +// return ["Settings", "icon-gear", ]; +// case 'help': +// return ["Help", "icon-question", ]; +// case 'report': +// return ['Report an Issue', 'icon-file', ]; +// case 'downloaded': +// return ["Downloads & Purchases", "icon-folder", ]; +// case 'published': +// return ["Publishes", "icon-folder", ]; +// case 'start': +// return ["Start", "icon-file", ]; +// case 'rewards': +// return ["Rewards", "icon-bank", ]; +// case 'wallet': +// case 'send': +// case 'receive': +// return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", ] +// case 'show': +// return [lbryuri.normalize(this.state.pageArgs), "icon-file", ]; +// case 'publish': +// return ["Publish", "icon-upload", ]; +// case 'developer': +// return ["Developer", "icon-file", ]; +// case 'discover': +// default: +// return ["Home", "icon-home", ]; +// } +// }, +// render: function() { +// let [address, wunderBarIcon, mainContent] = this.getContentAndAddress(); +// +// lbry.setTitle(address); +// +// if (this._storeHistoryOfNextRender) { +// this._storeHistoryOfNextRender = false; +// history.pushState({}, document.title, this.state.appUrl); +// } +// +// return ( +// this._fullScreenPages.includes(this.state.viewingPage) ? +// mainContent : +//
    +//
    +//
    +// {mainContent} +//
    +// +// Your version of LBRY is out of date and may be unreliable or insecure. +// +// +// Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}%` : null} +// +// {this.state.downloadComplete ? ( +//
    +//
    +//

    Click "Begin Upgrade" to start the upgrade process.

    +//

    The app will close, and you will be prompted to install the latest version of LBRY.

    +//

    After the install is complete, please reopen the app.

    +//
    +// ) : null } +//
    +// {this.state.downloadComplete +// ? +// : null} +// +//
    +//
    +// +//

    Error

    +// +//
    +//
    +//

    We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.

    +//
    +//
    +//
    +// ); \ No newline at end of file diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js new file mode 100644 index 000000000..0920e51d8 --- /dev/null +++ b/ui/js/component/app/index.js @@ -0,0 +1,37 @@ +import React from 'react'; +import { connect } from 'react-redux' + +import { + selectCurrentPage, + selectCurrentModal, + selectDrawerOpen, + selectHeaderLinks, + selectSearchTerm, +} from 'selectors/app' +import { + doCheckUpgradeAvailable, + doOpenDrawer, + doCloseDrawer, + doOpenModal, + doCloseModal, + doSearch, +} from 'actions/app' +import App from './view' + +const select = (state) => ({ + currentPage: selectCurrentPage(state), + modal: selectCurrentModal(state), + drawerOpen: selectDrawerOpen(state), + headerLinks: selectHeaderLinks(state), + searchTerm: selectSearchTerm(state) +}) + +const perform = (dispatch) => ({ + checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), + openDrawer: () => dispatch(doOpenDrawer()), + closeDrawer: () => dispatch(doCloseDrawer()), + openModal: () => dispatch(doOpenModal()), + closeModal: () => dispatch(doCloseModal()), +}) + +export default connect(select, perform)(App) diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx new file mode 100644 index 000000000..336b39150 --- /dev/null +++ b/ui/js/component/app/view.jsx @@ -0,0 +1,65 @@ +import React from 'react' + +import lbry from 'lbry.js'; +import Router from 'component/router' +import Drawer from 'component/drawer'; +import Header from 'component/header.js'; +import {Modal, ExpandableModal} from 'component/modal.js'; +import ErrorModal from 'component/errorModal' +import DownloadingModal from 'component/downloadingModal' +import UpgradeModal from 'component/upgradeModal' +import Link from 'component/link'; +import {Line} from 'rc-progress'; + +const App = React.createClass({ + // Temporary workaround since electron-dl throws errors when you try to get the filename + getViewingPageAndArgs: function(address) { + // For now, routes are in format ?page or ?page=args + let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/); + return { + viewingPage: viewingPage, + pageArgs: pageArgs === undefined ? null : pageArgs + }; + }, + componentWillMount: function() { + document.addEventListener('unhandledError', (event) => { + this.props.alertError(event.detail); + }); + + if (!this.props.upgradeSkipped) { + this.props.checkUpgradeAvailable() + } + }, + render: function() { + const { + currentPage, + openDrawer, + closeDrawer, + openModal, + closeModal, + modal, + drawerOpen, + headerLinks, + search, + searchTerm, + } = this.props + const searchQuery = (currentPage == 'discover' && searchTerm ? searchTerm : '') + + return ( + currentPage == 'watch' ? + : +
    + +
    +
    + +
    + {modal == 'upgrade' && } + {modal == 'downloading' && } + {modal == 'error' && } +
    + ); + } +}); + +export default App diff --git a/ui/js/component/downloadingModal/index.jsx b/ui/js/component/downloadingModal/index.jsx new file mode 100644 index 000000000..618f335fb --- /dev/null +++ b/ui/js/component/downloadingModal/index.jsx @@ -0,0 +1,25 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + doStartUpgrade, + doCancelUpgrade, +} from 'actions/app' +import { + selectDownloadProgress, + selectDownloadComplete, +} from 'selectors/app' +import DownloadingModal from './view' + +const select = (state) => ({ + downloadProgress: selectDownloadProgress(state), + downloadComplete: selectDownloadComplete(state), +}) + +const perform = (dispatch) => ({ + startUpgrade: () => dispatch(doStartUpgrade()), + cancelUpgrade: () => dispatch(doCancelUpgrade()) +}) + +export default connect(select, perform)(DownloadingModal) diff --git a/ui/js/component/downloadingModal/view.jsx b/ui/js/component/downloadingModal/view.jsx new file mode 100644 index 000000000..b59605e28 --- /dev/null +++ b/ui/js/component/downloadingModal/view.jsx @@ -0,0 +1,40 @@ +import React from 'react' +import { + Modal +} from 'component/modal' +import {Line} from 'rc-progress'; +import Link from 'component/link' + +class DownloadingModal extends React.Component { + render() { + const { + downloadProgress, + downloadComplete, + startUpgrade, + cancelUpgrade, + } = this.props + + return ( + + Downloading Update{downloadProgress ? `: ${downloadProgress}%` : null} + + {downloadComplete ? ( +
    +
    +

    Click "Begin Upgrade" to start the upgrade process.

    +

    The app will close, and you will be prompted to install the latest version of LBRY.

    +

    After the install is complete, please reopen the app.

    +
    + ) : null } +
    + {downloadComplete + ? + : null} + +
    +
    + ) + } +} + +export default DownloadingModal diff --git a/ui/js/component/drawer/index.jsx b/ui/js/component/drawer/index.jsx new file mode 100644 index 000000000..b5f666c92 --- /dev/null +++ b/ui/js/component/drawer/index.jsx @@ -0,0 +1,29 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import Drawer from './view' +import { + doNavigate, + doCloseDrawer, + doLogoClick, + doUpdateBalance, +} from 'actions/app' +import { + selectCurrentPage, + selectBalance, +} from 'selectors/app' + +const select = (state) => ({ + currentPage: selectCurrentPage(state), + balance: selectBalance(state), +}) + +const perform = { + linkClick: doNavigate, + logoClick: doLogoClick, + closeDrawerClick: doCloseDrawer, + updateBalance: doUpdateBalance, +} + +export default connect(select, perform)(Drawer) diff --git a/ui/js/component/drawer/view.jsx b/ui/js/component/drawer/view.jsx new file mode 100644 index 000000000..d2a381b88 --- /dev/null +++ b/ui/js/component/drawer/view.jsx @@ -0,0 +1,68 @@ +import lbry from 'lbry.js'; +import React from 'react'; +import Link from 'component/link'; + +const DrawerItem = (props) => { + const { + currentPage, + href, + subPages, + label, + linkClick, + icon, + } = props + const isSelected = ( + currentPage == href.substr(1) || + (subPages && subPages.indexOf(currentPage) != -1) + ) + + return linkClick(href)} className={ 'drawer-item ' + (isSelected ? 'drawer-item-selected' : '') } /> +} + +var drawerImageStyle = { //@TODO: remove this, img should be properly scaled once size is settled + height: '36px' +}; + +class Drawer extends React.Component { + constructor(props) { + super(props) + this._balanceSubscribeId = null + } + + componentDidMount() { + const { updateBalance } = this.props + + this._balanceSubscribeId = lbry.balanceSubscribe(function(balance) { + updateBalance(balance) + }.bind(this)); + } + componentWillUnmount() { + if (this._balanceSubscribeId) { + lbry.balanceUnsubscribe(this._balanceSubscribeId) + } + } + + render() { + const { + closeDrawerClick, + logoClick, + currentPage, + balance, + } = this.props + + return() + } +} + +export default Drawer; diff --git a/ui/js/component/errorModal/index.jsx b/ui/js/component/errorModal/index.jsx new file mode 100644 index 000000000..c7db4cef4 --- /dev/null +++ b/ui/js/component/errorModal/index.jsx @@ -0,0 +1,23 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + selectCurrentModal, + selectError, +} from 'selectors/app' +import { + doCloseModal, +} from 'actions/app' +import ErrorModal from './view' + +const select = (state) => ({ + modal: selectCurrentModal(state), + error: selectError(state), +}) + +const perform = (dispatch) => ({ + closeModal: () => dispatch(doCloseModal()) +}) + +export default connect(select, perform)(ErrorModal) diff --git a/ui/js/component/errorModal/view.jsx b/ui/js/component/errorModal/view.jsx new file mode 100644 index 000000000..676a2d52b --- /dev/null +++ b/ui/js/component/errorModal/view.jsx @@ -0,0 +1,50 @@ +import React from 'react' +import lbry from 'lbry' +import { + ExpandableModal +} from 'component/modal' + +class ErrorModal extends React.Component { + render() { + const { + modal, + closeModal, + error, + } = this.props + + const _error_key_labels = { + connectionString: 'API connection string', + method: 'Method', + params: 'Parameters', + code: 'Error code', + message: 'Error message', + data: 'Error data', + } + const errorInfo =
      + const errorInfoList = [] + for (let key of Object.keys(error)) { + let val = typeof error[key] == 'string' ? error[key] : JSON.stringify(error[key]); + let label = this._error_key_labels[key]; + errorInfoList.push(
    • {label}: {val}
    • ); + } + + return( + +

      Error

      + +
      +
      +

      We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.

      +
      +
      + ) + } +} + +export default ErrorModal diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index 21d25eb47..aef2f5380 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -1,10 +1,10 @@ import React from 'react'; import lbry from '../lbry.js'; import lbryuri from '../lbryuri.js'; -import {Link} from '../component/link.js'; import {Icon, FilePrice} from '../component/common.js'; import {Modal} from './modal.js'; import {FormField} from './form.js'; +import Link from 'component/link'; import {ToolTip} from '../component/tooltip.js'; import {DropDownMenu, DropDownMenuItem} from './menu.js'; diff --git a/ui/js/component/file-tile.js b/ui/js/component/file-tile.js index 73adb63b2..ae8e0e3fe 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/file-tile.js @@ -1,7 +1,7 @@ import React from 'react'; import lbry from '../lbry.js'; import lbryuri from '../lbryuri.js'; -import {Link} from '../component/link.js'; +import Link from 'component/link'; import {FileActions} from '../component/file-actions.js'; import {BusyMessage, TruncatedText, FilePrice} from '../component/common.js'; import UriIndicator from '../component/channel-indicator.js'; diff --git a/ui/js/component/header.js b/ui/js/component/header.js index 848f076a2..4ab99c061 100644 --- a/ui/js/component/header.js +++ b/ui/js/component/header.js @@ -1,6 +1,6 @@ import React from 'react'; import lbryuri from '../lbryuri.js'; -import {Link} from './link.js'; +import Link from 'component/link'; import {Icon, CreditAmount} from './common.js'; var Header = React.createClass({ diff --git a/ui/js/component/link/index.jsx b/ui/js/component/link/index.jsx new file mode 100644 index 000000000..2ad86dc05 --- /dev/null +++ b/ui/js/component/link/index.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import Link from './view' + +export default connect(null, null)(Link) diff --git a/ui/js/component/link/view.js b/ui/js/component/link/view.js new file mode 100644 index 000000000..e4b361241 --- /dev/null +++ b/ui/js/component/link/view.js @@ -0,0 +1,100 @@ +import React from 'react'; +import {Icon} from 'component/common.js'; + +const Link = (props) => { + const { + href, + title, + onClick, + style, + label, + icon, + badge, + button, + hidden, + disabled, + } = props + + + const className = (props.className || '') + + (!props.className && !props.button ? 'button-text' : '') + // Non-button links get the same look as text buttons + (props.button ? ' button-block button-' + props.button + ' button-set-item' : '') + + (props.disabled ? ' disabled' : ''); + + + let content; + if (props.children) { + content = this.props.children + } else { + content = ( + + {'icon' in props ? : null} + {label ? {label} : null} + {'badge' in props ? {badge} : null} + + ) + } + + return ( + + {content} + + ); +} + +export default Link + +// export let Link = React.createClass({ +// propTypes: { +// label: React.PropTypes.string, +// icon: React.PropTypes.string, +// button: React.PropTypes.string, +// badge: React.PropTypes.string, +// hidden: React.PropTypes.bool, +// }, +// getDefaultProps: function() { +// return { +// hidden: false, +// disabled: false, +// }; +// }, +// handleClick: function(e) { +// if (this.props.onClick) { +// this.props.onClick(e); +// } +// }, +// render: function() { +// if (this.props.hidden) { +// return null; +// } + +// /* The way the class name is generated here is a mess -- refactor */ + +// const className = (this.props.className || '') + +// (!this.props.className && !this.props.button ? 'button-text' : '') + // Non-button links get the same look as text buttons +// (this.props.button ? ' button-block button-' + this.props.button + ' button-set-item' : '') + +// (this.props.disabled ? ' disabled' : ''); + +// let content; +// if (this.props.children) { // Custom content +// content = this.props.children; +// } else { +// content = ( +// +// {'icon' in this.props ? : null} +// {{this.props.label}} +// {'badge' in this.props ? {this.props.badge} : null} +// +// ); +// } + +// return ( +// +// {content} +// +// ); +// } +// }); diff --git a/ui/js/component/load_screen.js b/ui/js/component/load_screen.js index 4f191d891..4002d5191 100644 --- a/ui/js/component/load_screen.js +++ b/ui/js/component/load_screen.js @@ -1,7 +1,7 @@ import React from 'react'; import lbry from '../lbry.js'; import {BusyMessage, Icon} from './common.js'; -import {Link} from '../component/link.js' +import Link from 'component/link' var LoadScreen = React.createClass({ propTypes: { diff --git a/ui/js/component/menu.js b/ui/js/component/menu.js index c35952426..c43d81f0e 100644 --- a/ui/js/component/menu.js +++ b/ui/js/component/menu.js @@ -1,6 +1,6 @@ import React from 'react'; import {Icon} from './common.js'; -import {Link} from '../component/link.js'; +import Link from 'component/link'; export let DropDownMenuItem = React.createClass({ propTypes: { diff --git a/ui/js/component/modal.js b/ui/js/component/modal.js index dbb8ff646..9dfedba6b 100644 --- a/ui/js/component/modal.js +++ b/ui/js/component/modal.js @@ -1,6 +1,6 @@ import React from 'react'; import ReactModal from 'react-modal'; -import {Link} from './link.js'; +import Link from 'component/link'; export const Modal = React.createClass({ diff --git a/ui/js/component/router/index.jsx b/ui/js/component/router/index.jsx new file mode 100644 index 000000000..c75222949 --- /dev/null +++ b/ui/js/component/router/index.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import Router from './view.jsx'; +import { + selectCurrentPage +} from 'selectors/app.js'; + +const select = (state) => ({ + currentPage: selectCurrentPage(state) +}) + +const perform = { +} + +export default connect(select, null)(Router); diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx new file mode 100644 index 000000000..c6aad0588 --- /dev/null +++ b/ui/js/component/router/view.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import SettingsPage from 'page/settings.js'; +import HelpPage from 'page/help'; +import WatchPage from 'page/watch.js'; +import ReportPage from 'page/report.js'; +import StartPage from 'page/start.js'; +import ClaimCodePage from 'page/claim_code.js'; +import ReferralPage from 'page/referral.js'; +import WalletPage from 'page/wallet.js'; +import DetailPage from 'page/show.js'; +import PublishPage from 'page/publish.js'; +import DiscoverPage from 'page/discover.js'; +import SplashScreen from 'component/splash.js'; +import DeveloperPage from 'page/developer.js'; +import { + FileListDownloaded, + FileListPublished +} from 'page/file-list.js'; + +const route = (page, routesMap) => { + const component = routesMap[page] + + return component +}; + + +const Router = (props) => { + const { + currentPage, + } = props; + + return route(currentPage, { + 'settings': , + 'help': , + 'watch': , + 'report': , + 'downloaded': , + 'published': , + 'start': , + 'claim': , + 'wallet': , + 'send': , + 'receive': , + 'show': , + 'publish': , + 'developer': , + 'discover': , + }) +} + +export default Router diff --git a/ui/js/component/upgradeModal/index.jsx b/ui/js/component/upgradeModal/index.jsx new file mode 100644 index 000000000..e2872978c --- /dev/null +++ b/ui/js/component/upgradeModal/index.jsx @@ -0,0 +1,19 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + doDownloadUpgrade, + doSkipUpgrade, +} from 'actions/app' +import UpgradeModal from './view' + +const select = (state) => ({ +}) + +const perform = (dispatch) => ({ + downloadUpgrade: () => dispatch(doDownloadUpgrade()), + skipUpgrade: () => dispatch(doSkipUpgrade()), +}) + +export default connect(select, perform)(UpgradeModal) diff --git a/ui/js/component/upgradeModal/view.jsx b/ui/js/component/upgradeModal/view.jsx new file mode 100644 index 000000000..a2a181c79 --- /dev/null +++ b/ui/js/component/upgradeModal/view.jsx @@ -0,0 +1,32 @@ +import React from 'react' +import { + Modal +} from 'component/modal' +import { + downloadUpgrade, + skipUpgrade +} from 'actions/app' + +class UpgradeModal extends React.Component { + render() { + const { + downloadUpgrade, + skipUpgrade + } = this.props + + return ( + + Your version of LBRY is out of date and may be unreliable or insecure. + + ) + } +} + +export default UpgradeModal diff --git a/ui/js/config/development.js b/ui/js/config/development.js new file mode 100644 index 000000000..631f37565 --- /dev/null +++ b/ui/js/config/development.js @@ -0,0 +1,2 @@ +module.exports = { +} diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js new file mode 100644 index 000000000..17f05441a --- /dev/null +++ b/ui/js/constants/action_types.js @@ -0,0 +1,21 @@ +export const UPDATE_BALANCE = 'UPDATE_BALANCE' +export const NAVIGATE = 'NAVIGATE' + +// Upgrades +export const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED' +export const DOWNLOAD_UPGRADE = 'DOWNLOAD_UPGRADE' +export const UPGRADE_DOWNLOAD_STARTED = 'UPGRADE_DOWNLOAD_STARTED' +export const UPGRADE_DOWNLOAD_COMPLETED = 'UPGRADE_DOWNLOAD_COMPLETED' +export const UPGRADE_DOWNLOAD_PROGRESSED = 'UPGRADE_DOWNLOAD_PROGRESSED' +export const CHECK_UPGRADE_AVAILABLE = 'CHECK_UPGRADE_AVAILABLE' +export const UPDATE_VERSION = 'UPDATE_VERSION' +export const SKIP_UPGRADE = 'SKIP_UPGRADE' +export const START_UPGRADE = 'START_UPGRADE' + +export const OPEN_MODAL = 'OPEN_MODAL' +export const CLOSE_MODAL = 'CLOSE_MODAL' + +export const OPEN_DRAWER = 'OPEN_DRAWER' +export const CLOSE_DRAWER = 'CLOSE_DRAWER' + +export const START_SEARCH = 'START_SEARCH' diff --git a/ui/js/main.js b/ui/js/main.js index 610ca8594..c346a0e5c 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -3,13 +3,16 @@ import ReactDOM from 'react-dom'; import lbry from './lbry.js'; import lbryio from './lbryio.js'; import lighthouse from './lighthouse.js'; -import App from './app.js'; +import App from './component/app/index.js'; import SplashScreen from './component/splash.js'; import SnackBar from './component/snack-bar.js'; import {AuthOverlay} from './component/auth.js'; +import { Provider } from 'react-redux'; +import store from 'store.js'; const {remote} = require('electron'); const contextMenu = remote.require('./menu/context-menu'); +const app = require('./app') lbry.showMenuIfNeeded(); @@ -19,7 +22,9 @@ window.addEventListener('contextmenu', (event) => { event.preventDefault(); }); -let init = function() { +const initialState = app.store.getState(); + +var init = function() { window.lbry = lbry; window.lighthouse = lighthouse; let canvas = document.getElementById('canvas'); @@ -30,7 +35,7 @@ let 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 - ReactDOM.render(
      { lbryio.enabled ? : '' }
      , canvas) + ReactDOM.render(
      { lbryio.enabled ? : '' }
      , canvas) } if (window.sessionStorage.getItem('loaded') == 'y') { diff --git a/ui/js/page/developer.js b/ui/js/page/developer.js index 377204852..31b3b6634 100644 --- a/ui/js/page/developer.js +++ b/ui/js/page/developer.js @@ -1,7 +1,7 @@ import lbry from '../lbry.js'; import React from 'react'; import {FormField} from '../component/form.js'; -import {Link} from '../component/link.js'; +import Link from '../component/link'; const fs = require('fs'); const {ipcRenderer} = require('electron'); diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index d522a99f8..912a38f8c 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -1,6 +1,8 @@ import React from 'react'; -import lbryio from '../lbryio.js'; -import {FileTile, FileTileStream} from '../component/file-tile.js'; +import lbry from '../lbry.js'; +import lighthouse from '../lighthouse.js'; +import {FileTile} from '../component/file-tile.js'; +import Link from 'component/link'; import {ToolTip} from '../component/tooltip.js'; const communityCategoryToolTipText = ('Community Content is a public space where anyone can share content with the ' + diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index 71f8e2fc2..8ae732dfe 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -1,8 +1,8 @@ import React from 'react'; import lbry from '../lbry.js'; import lbryuri from '../lbryuri.js'; -import {Link} from '../component/link.js'; -import {FormField} from '../component/form.js'; +import Link from 'component/link'; +import FormField from '../component/form.js'; import {SubHeader} from '../component/header.js'; import {FileTileStream} from '../component/file-tile.js'; import rewards from '../rewards.js'; diff --git a/ui/js/page/help/index.jsx b/ui/js/page/help/index.jsx new file mode 100644 index 000000000..0663d5ffa --- /dev/null +++ b/ui/js/page/help/index.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import HelpPage from './view' + +export default connect(null, null)(HelpPage) diff --git a/ui/js/page/help.js b/ui/js/page/help/view.jsx similarity index 95% rename from ui/js/page/help.js rename to ui/js/page/help/view.jsx index d6a28ae99..5db7f6472 100644 --- a/ui/js/page/help.js +++ b/ui/js/page/help/view.jsx @@ -1,10 +1,9 @@ //@TODO: Customize advice based on OS -//@TODO: Customize advice based on OS import React from 'react'; -import lbry from '../lbry.js'; -import {Link} from '../component/link.js'; +import lbry from 'lbry.js'; +import Link from 'component/link'; import {SettingsNav} from './settings.js'; -import {version as uiVersion} from 'json!../../package.json'; +import {version as uiVersion} from 'json!../../../package.json'; var HelpPage = React.createClass({ getInitialState: function() { @@ -119,4 +118,4 @@ var HelpPage = React.createClass({ } }); -export default HelpPage; \ No newline at end of file +export default HelpPage; diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 03583136b..d443d8737 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -1,9 +1,8 @@ import React from 'react'; import lbry from '../lbry.js'; -import {FormField, FormRow} from '../component/form.js'; -import {Link} from '../component/link.js'; +import FormField from '../component/form.js'; +import Link from 'component/link'; import rewards from '../rewards.js'; -import lbryio from '../lbryio.js'; import Modal from '../component/modal.js'; var PublishPage = React.createClass({ diff --git a/ui/js/page/report.js b/ui/js/page/report.js index e76905d4b..03f0bb915 100644 --- a/ui/js/page/report.js +++ b/ui/js/page/report.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Link} from '../component/link.js'; +import Link from 'component/link'; import Modal from '../component/modal.js'; import lbry from '../lbry.js'; diff --git a/ui/js/page/show.js b/ui/js/page/show.js index c72a8bde1..e9013716b 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -5,7 +5,7 @@ import lbryuri from '../lbryuri.js'; import {Video} from '../page/watch.js' import {TruncatedText, Thumbnail, FilePrice, BusyMessage} from '../component/common.js'; import {FileActions} from '../component/file-actions.js'; -import {Link} from '../component/link.js'; +import Link from '../component/link'; import UriIndicator from '../component/channel-indicator.js'; var FormatItem = React.createClass({ diff --git a/ui/js/page/wallet.js b/ui/js/page/wallet.js index f50f4e1be..a9b3a92a0 100644 --- a/ui/js/page/wallet.js +++ b/ui/js/page/wallet.js @@ -1,6 +1,6 @@ import React from 'react'; import lbry from '../lbry.js'; -import {Link} from '../component/link.js'; +import Link from 'component/link'; import Modal from '../component/modal.js'; import {SubHeader} from '../component/header.js'; import {FormField, FormRow} from '../component/form.js'; diff --git a/ui/js/page/watch.js b/ui/js/page/watch.js deleted file mode 100644 index 2f01ae93b..000000000 --- a/ui/js/page/watch.js +++ /dev/null @@ -1,215 +0,0 @@ -import React from 'react'; -import {Icon, Thumbnail, FilePrice} from '../component/common.js'; -import {Link} from '../component/link.js'; -import lbry from '../lbry.js'; -import Modal from '../component/modal.js'; -import lbryio from '../lbryio.js'; -import rewards from '../rewards.js'; -import LoadScreen from '../component/load_screen.js' - -const fs = require('fs'); -const VideoStream = require('videostream'); - -export let WatchLink = React.createClass({ - propTypes: { - uri: React.PropTypes.string, - metadata: React.PropTypes.object, - downloadStarted: React.PropTypes.bool, - onGet: React.PropTypes.func, - }, - getInitialState: function() { - affirmedPurchase: false - }, - play: function() { - lbry.get({uri: this.props.uri}).then((streamInfo) => { - if (streamInfo === null || typeof streamInfo !== 'object') { - this.setState({ - modal: 'timedOut', - attemptingDownload: false, - }); - } - - lbryio.call('file', 'view', { - uri: this.props.uri, - outpoint: streamInfo.outpoint, - claimId: streamInfo.claim_id - }).catch(() => {}) - }); - if (this.props.onGet) { - this.props.onGet() - } - }, - onWatchClick: function() { - this.setState({ - loading: true - }); - lbry.getCostInfo(this.props.uri).then(({cost}) => { - lbry.getBalance((balance) => { - if (cost > balance) { - this.setState({ - modal: 'notEnoughCredits', - attemptingDownload: false, - }); - } else if (cost <= 0.01) { - this.play() - } else { - lbry.file_list({outpoint: this.props.outpoint}).then((fileInfo) => { - if (fileInfo) { // Already downloaded - this.play(); - } else { - this.setState({ - modal: 'affirmPurchase' - }); - } - }); - } - }); - }); - }, - getInitialState: function() { - return { - modal: null, - loading: false, - }; - }, - closeModal: function() { - this.setState({ - loading: false, - modal: null, - }); - }, - render: function() { - return (
      - - - You don't have enough LBRY credits to pay for this stream. - - - Are you sure you'd like to buy {this.props.metadata.title} for credits? - -
      ); - } -}); - - -export let Video = React.createClass({ - _isMounted: false, - _controlsHideDelay: 3000, // Note: this needs to be shorter than the built-in delay in Electron, or Electron will hide the controls before us - _controlsHideTimeout: null, - - propTypes: { - uri: React.PropTypes.string.isRequired, - metadata: React.PropTypes.object, - outpoint: React.PropTypes.string, - }, - getInitialState: function() { - return { - downloadStarted: false, - readyToPlay: false, - isPlaying: false, - isPurchased: false, - loadStatusMessage: "Requesting stream... it may sit here for like 15-20 seconds in a really awkward way... we're working on it", - mimeType: null, - controlsShown: false, - }; - }, - onGet: function() { - lbry.get({uri: this.props.uri}).then((fileInfo) => { - this.updateLoadStatus(); - }); - this.setState({ - isPlaying: true - }) - }, - componentDidMount: function() { - if (this.props.autoplay) { - this.start() - } - }, - handleMouseMove: function() { - if (this._controlsTimeout) { - clearTimeout(this._controlsTimeout); - } - - if (!this.state.controlsShown) { - this.setState({ - controlsShown: true, - }); - } - this._controlsTimeout = setTimeout(() => { - if (!this.isMounted) { - return; - } - - this.setState({ - controlsShown: false, - }); - }, this._controlsHideDelay); - }, - handleMouseLeave: function() { - if (this._controlsTimeout) { - clearTimeout(this._controlsTimeout); - } - - if (this.state.controlsShown) { - this.setState({ - controlsShown: false, - }); - } - }, - updateLoadStatus: function() { - lbry.file_list({ - outpoint: this.props.outpoint, - full_status: true, - }).then(([status]) => { - if (!status || status.written_bytes == 0) { - // Download hasn't started yet, so update status message (if available) then try again - // TODO: Would be nice to check if we have the MOOV before starting playing - if (status) { - this.setState({ - loadStatusMessage: status.message - }); - } - setTimeout(() => { this.updateLoadStatus() }, 250); - } else { - this.setState({ - readyToPlay: true, - mimeType: status.mime_type, - }) - const mediaFile = { - createReadStream: function (opts) { - // Return a readable stream that provides the bytes - // between offsets "start" and "end" inclusive - console.log('Stream between ' + opts.start + ' and ' + opts.end + '.'); - return fs.createReadStream(status.download_path, opts) - } - }; - - rewards.claimNextPurchaseReward() - - var elem = this.refs.video; - var videostream = VideoStream(mediaFile, elem); - elem.play(); - } - }); - }, - render: function() { - return ( -
      { - this.state.isPlaying ? - !this.state.readyToPlay ? - this is the world's worst loading screen and we shipped our software with it anyway...

      {this.state.loadStatusMessage}
      : - : -
      - -
      - }
      - ); - } -}) diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js new file mode 100644 index 000000000..6859b60f3 --- /dev/null +++ b/ui/js/reducers/app.js @@ -0,0 +1,105 @@ +import * as types from 'constants/action_types' + +const reducers = {} +const defaultState = { + isLoaded: false, + currentPage: 'discover', + platform: process.platform, + drawerOpen: sessionStorage.getItem('drawerOpen') || true, + upgradeSkipped: sessionStorage.getItem('upgradeSkipped') +} + +reducers[types.UPDATE_BALANCE] = function(state, action) { + return Object.assign({}, state, { + balance: action.data.balance + }) +} + +reducers[types.NAVIGATE] = function(state, action) { + return Object.assign({}, state, { + currentPage: action.data.path + }) +} + +reducers[types.UPGRADE_CANCELLED] = function(state, action) { + return Object.assign({}, state, { + downloadProgress: null, + downloadComplete: false, + modal: null, + }) +} + +reducers[types.UPGRADE_DOWNLOAD_COMPLETED] = function(state, action) { + return Object.assign({}, state, { + downloadDir: action.data.dir, + downloadComplete: true, + }) +} + +reducers[types.UPGRADE_DOWNLOAD_STARTED] = function(state, action) { + return Object.assign({}, state, { + upgradeDownloading: true + }) +} + +reducers[types.UPGRADE_DOWNLOAD_COMPLETED] = function(state, action) { + return Object.assign({}, state, { + upgradeDownloading: false, + upgradeDownloadCompleted: true + }) +} + +reducers[types.SKIP_UPGRADE] = function(state, action) { + sessionStorage.setItem('upgradeSkipped', true); + + return Object.assign({}, state, { + upgradeSkipped: true, + modal: null + }) +} + +reducers[types.UPDATE_VERSION] = function(state, action) { + return Object.assign({}, state, { + version: action.data.version + }) +} + +reducers[types.OPEN_MODAL] = function(state, action) { + return Object.assign({}, state, { + modal: action.data.modal, + extraContent: action.data.errorList + }) +} + +reducers[types.CLOSE_MODAL] = function(state, action) { + return Object.assign({}, state, { + modal: undefined, + extraContent: undefined + }) +} + +reducers[types.OPEN_DRAWER] = function(state, action) { + sessionStorage.setItem('drawerOpen', false) + return Object.assign({}, state, { + drawerOpen: true + }) +} + +reducers[types.CLOSE_DRAWER] = function(state, action) { + sessionStorage.setItem('drawerOpen', false) + return Object.assign({}, state, { + drawerOpen: false + }) +} + +reducers[types.UPGRADE_DOWNLOAD_PROGRESSED] = function(state, action) { + return Object.assign({}, state, { + downloadProgress: action.data.percent + }) +} + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js new file mode 100644 index 000000000..0bb22541b --- /dev/null +++ b/ui/js/selectors/app.js @@ -0,0 +1,145 @@ +import { createSelector } from 'reselect' + +export const _selectState = state => state.app || {} + +export const selectIsLoaded = createSelector( + _selectState, + (state) => { + return state.isLoaded + } +) + +export const selectCurrentPage = createSelector( + _selectState, + (state) => { + return state.currentPage + } +) + +export const selectBalance = createSelector( + _selectState, + (state) => { + return state.balance || 0 + } +) + +export const selectPlatform = createSelector( + _selectState, + (state) => { + return state.platform + } +) + +export const selectUpdateUrl = createSelector( + selectPlatform, + (platform) => { + switch (platform) { + case 'darwin': + return 'https://lbry.io/get/lbry.dmg'; + case 'linux': + return 'https://lbry.io/get/lbry.deb'; + case 'win32': + return 'https://lbry.io/get/lbry.exe'; + default: + throw 'Unknown platform'; + } + } +) + +export const selectVersion = createSelector( + _selectState, + (state) => { + return state.version + } +) + +export const selectUpgradeFilename = createSelector( + selectPlatform, + selectVersion, + (platform, version) => { + switch (platform) { + case 'darwin': + return `LBRY-${version}.dmg`; + case 'linux': + return `LBRY_${version}_amd64.deb`; + case 'windows': + return `LBRY.Setup.${version}.exe`; + default: + throw 'Unknown platform'; + } + } +) + +export const selectCurrentModal = createSelector( + _selectState, + (state) => state.modal +) + +export const selectDownloadProgress = createSelector( + _selectState, + (state) => state.downloadProgress +) + +export const selectDownloadComplete = createSelector( + _selectState, + (state) => state.upgradeDownloadCompleted +) + +export const selectDrawerOpen = createSelector( + _selectState, + (state) => state.drawerOpen +) + +export const selectHeaderLinks = createSelector( + selectCurrentPage, + (page) => { + switch(page) + { + case 'wallet': + case 'send': + case 'receive': + case 'claim': + case 'referral': + return { + '?wallet' : 'Overview', + '?send' : 'Send', + '?receive' : 'Receive', + '?claim' : 'Claim Beta Code', + '?referral' : 'Check Referral Credit', + }; + case 'downloaded': + case 'published': + return { + '?downloaded': 'Downloaded', + '?published': 'Published', + }; + default: + return null; + } + } +) + +export const selectUpgradeSkipped = createSelector( + _selectState, + (state) => state.upgradeSkipped +) + +export const selectUpgradeDownloadDir = createSelector( + _selectState, + (state) => state.downloadDir +) + +export const selectUpgradeDownloadItem = createSelector( + _selectState, + (state) => state.downloadItem +) + +export const selectSearchTerm = createSelector( + _selectState, + (state) => state.searchTerm +) + +export const selectError = createSelector( + _selectState, + (state) => state.error +) diff --git a/ui/js/store.js b/ui/js/store.js new file mode 100644 index 000000000..9d6ac13c4 --- /dev/null +++ b/ui/js/store.js @@ -0,0 +1,37 @@ +const redux = require('redux'); +const thunk = require("redux-thunk").default; +const env = process.env.NODE_ENV || 'development'; + +import { + createLogger +} from 'redux-logger' +import appReducer from 'reducers/app'; + +function isFunction(object) { + return typeof object === 'function'; +} + +function isNotFunction(object) { + return !isFunction(object); +} + +const reducers = redux.combineReducers({ + app: appReducer, +}); + +var middleware = [thunk] + +if (env === 'development') { + const logger = createLogger({ + collapsed: true + }); + middleware.push(logger) +} + +var createStoreWithMiddleware = redux.compose( + redux.applyMiddleware(...middleware) +)(redux.createStore); + +var reduxStore = createStoreWithMiddleware(reducers); + +export default reduxStore; diff --git a/ui/package.json b/ui/package.json index ead9a1e2b..4c4b9e937 100644 --- a/ui/package.json +++ b/ui/package.json @@ -27,6 +27,11 @@ "react": "^15.4.0", "react-dom": "^15.4.0", "react-modal": "^1.5.2", + "react-redux": "^5.0.3", + "redux": "^3.6.0", + "redux-logger": "^3.0.1", + "redux-thunk": "^2.2.0", + "reselect": "^3.0.0", "videostream": "^2.4.2" }, "devDependencies": { diff --git a/ui/webpack.config.js b/ui/webpack.config.js index be349baa9..9afb1d82f 100644 --- a/ui/webpack.config.js +++ b/ui/webpack.config.js @@ -1,4 +1,5 @@ const path = require('path'); +const appPath = path.resolve(__dirname, 'js'); const PATHS = { app: path.join(__dirname, 'app'), @@ -13,6 +14,10 @@ module.exports = { filename: "bundle.js" }, devtool: 'source-map', + resolve: { + root: appPath, + extensions: ['', '.js', '.jsx', '.css'], + }, module: { preLoaders: [ { @@ -25,8 +30,8 @@ module.exports = { loaders: [ { test: /\.css$/, loader: "style!css" }, { - test: /\.jsx?$/, - loader: 'babel', + test: /\.jsx?$/, + loader: 'babel', query: { cacheDirectory: true, presets:[ 'es2015', 'react', 'stage-2' ] diff --git a/ui/webpack.dev.config.js b/ui/webpack.dev.config.js index 358d6c04d..cb284e233 100644 --- a/ui/webpack.dev.config.js +++ b/ui/webpack.dev.config.js @@ -1,4 +1,5 @@ const path = require('path'); +const appPath = path.resolve(__dirname, 'js'); const PATHS = { app: path.join(__dirname, 'app'), @@ -16,6 +17,10 @@ module.exports = { debug: true, cache: true, devtool: 'eval', + resolve: { + root: appPath, + extensions: ['', '.js', '.jsx', '.css'], + }, module: { preLoaders: [ { @@ -28,9 +33,9 @@ module.exports = { loaders: [ { test: /\.css$/, loader: "style!css" }, { - test: /\.jsx?$/, - loader: 'babel', - query: { + test: /\.jsx?$/, + loader: 'babel', + query: { cacheDirectory: true, presets:[ 'es2015', 'react', 'stage-2' ] } -- 2.43.4 From 3d5e1db5e3b9fe921acb90e39d84f9cd3fd82b7f Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 7 Apr 2017 12:15:22 +0700 Subject: [PATCH 02/56] Redux proof of concept --- package.json | 3 +- ui/js/app.js | 1 + ui/js/component/file-actions.js | 6 +- ui/js/component/header.js | 2 +- ui/js/component/link.js | 133 -------------------------------- ui/js/component/splash.js | 1 - ui/js/page/help/view.jsx | 105 +++++++++++-------------- ui/js/page/show.js | 2 +- 8 files changed, 54 insertions(+), 199 deletions(-) delete mode 100644 ui/js/component/link.js diff --git a/package.json b/package.json index b5a3c3678..b1fed994b 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,5 @@ "electron": "^1.4.15", "electron-builder": "^11.7.0", "electron-debug": "^1.1.0" - }, - "dependencies": {} + } } diff --git a/ui/js/app.js b/ui/js/app.js index 834af6521..a7c7dd188 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -15,6 +15,7 @@ const app = { } global.app = app; module.exports = app; + // // import React from 'react'; // import {Line} from 'rc-progress'; diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index aef2f5380..2829615cc 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -1,10 +1,10 @@ import React from 'react'; import lbry from '../lbry.js'; import lbryuri from '../lbryuri.js'; -import {Icon, FilePrice} from '../component/common.js'; -import {Modal} from './modal.js'; -import {FormField} from './form.js'; import Link from 'component/link'; +import {Icon, FilePrice} from '../component/common.js'; +import Modal from './modal.js'; +import FormField from './form.js'; import {ToolTip} from '../component/tooltip.js'; import {DropDownMenu, DropDownMenuItem} from './menu.js'; diff --git a/ui/js/component/header.js b/ui/js/component/header.js index 4ab99c061..f0d66caee 100644 --- a/ui/js/component/header.js +++ b/ui/js/component/header.js @@ -1,7 +1,7 @@ import React from 'react'; import lbryuri from '../lbryuri.js'; -import Link from 'component/link'; import {Icon, CreditAmount} from './common.js'; +import Link from 'component/link'; var Header = React.createClass({ _balanceSubscribeId: null, diff --git a/ui/js/component/link.js b/ui/js/component/link.js deleted file mode 100644 index d46fcda25..000000000 --- a/ui/js/component/link.js +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react'; -import {Icon} from './common.js'; -import Modal from '../component/modal.js'; -import rewards from '../rewards.js'; - -export let Link = React.createClass({ - propTypes: { - label: React.PropTypes.string, - icon: React.PropTypes.string, - button: React.PropTypes.string, - badge: React.PropTypes.string, - hidden: React.PropTypes.bool, - }, - getDefaultProps: function() { - return { - hidden: false, - disabled: false, - }; - }, - handleClick: function(e) { - if (this.props.onClick) { - this.props.onClick(e); - } - }, - render: function() { - if (this.props.hidden) { - return null; - } - - /* The way the class name is generated here is a mess -- refactor */ - - const className = (this.props.className || '') + - (!this.props.className && !this.props.button ? 'button-text' : '') + // Non-button links get the same look as text buttons - (this.props.button ? ' button-block button-' + this.props.button + ' button-set-item' : '') + - (this.props.disabled ? ' disabled' : ''); - - let content; - if (this.props.children) { // Custom content - content = this.props.children; - } else { - content = ( - - {'icon' in this.props ? : null} - {this.props.label ? {this.props.label} : null} - {'badge' in this.props ? {this.props.badge} : null} - - ); - } - - return ( - - {content} - - ); - } -}); - -export let RewardLink = React.createClass({ - propTypes: { - type: React.PropTypes.string.isRequired, - claimed: React.PropTypes.bool, - onRewardClaim: React.PropTypes.func, - onRewardFailure: React.PropTypes.func - }, - refreshClaimable: function() { - switch(this.props.type) { - case 'new_user': - this.setState({ claimable: true }); - return; - - case 'first_publish': - lbry.claim_list_mine().then((list) => { - this.setState({ - claimable: list.length > 0 - }) - }); - return; - } - }, - componentWillMount: function() { - this.refreshClaimable(); - }, - getInitialState: function() { - return { - claimable: true, - pending: false, - errorMessage: null - } - }, - claimReward: function() { - this.setState({ - pending: true - }) - rewards.claimReward(this.props.type).then((reward) => { - this.setState({ - pending: false, - errorMessage: null - }) - if (this.props.onRewardClaim) { - this.props.onRewardClaim(reward); - } - }).catch((error) => { - this.setState({ - errorMessage: error.message, - pending: false - }) - }) - }, - clearError: function() { - if (this.props.onRewardFailure) { - this.props.onRewardFailure() - } - this.setState({ - errorMessage: null - }) - }, - render: function() { - return ( -
      - {this.props.claimed - ? Reward claimed. - : } - {this.state.errorMessage ? - - {this.state.errorMessage} - - : ''} -
      - ); - } -}); \ No newline at end of file diff --git a/ui/js/component/splash.js b/ui/js/component/splash.js index a156718b4..b0831b6a2 100644 --- a/ui/js/component/splash.js +++ b/ui/js/component/splash.js @@ -29,7 +29,6 @@ var SplashScreen = React.createClass({ }); lbry.resolve({uri: 'lbry://one'}).then(() => { - window.sessionStorage.setItem('loaded', 'y') this.props.onLoadDone(); }); return; diff --git a/ui/js/page/help/view.jsx b/ui/js/page/help/view.jsx index 5db7f6472..eba7a6273 100644 --- a/ui/js/page/help/view.jsx +++ b/ui/js/page/help/view.jsx @@ -2,7 +2,6 @@ import React from 'react'; import lbry from 'lbry.js'; import Link from 'component/link'; -import {SettingsNav} from './settings.js'; import {version as uiVersion} from 'json!../../../package.json'; var HelpPage = React.createClass({ @@ -24,6 +23,9 @@ var HelpPage = React.createClass({ }); }); }, + componentDidMount: function() { + document.title = "Help"; + }, render: function() { let ver, osName, platform, newVerLink; if (this.state.versionInfo) { @@ -46,71 +48,58 @@ var HelpPage = React.createClass({ } return ( -
      - +
      -
      -

      Read the FAQ

      -
      -
      -

      Our FAQ answers many common questions.

      -

      -
      +

      Read the FAQ

      +

      Our FAQ answers many common questions.

      +

      -
      -

      Get Live Help

      -
      -
      -

      - Live help is available most hours in the #help channel of our Slack chat room. -

      -

      - -

      -
      +

      Get Live Help

      +

      + Live help is available most hours in the #help channel of our Slack chat room. +

      +

      + +

      -

      Report a Bug

      -
      -

      Did you find something wrong?

      -

      -
      Thanks! LBRY is made by its users.
      -
      +

      Report a Bug

      +

      Did you find something wrong?

      +

      +
      Thanks! LBRY is made by its users.
      {!ver ? null :
      -

      About

      -
      - {ver.lbrynet_update_available || ver.lbryum_update_available ? -

      A newer version of LBRY is available.

      - :

      Your copy of LBRY is up to date.

      - } - - - - - - - - - - - - - - - - - - - - - - - -
      daemon (lbrynet){ver.lbrynet_version}
      wallet (lbryum){ver.lbryum_version}
      interface{uiVersion}
      Platform{platform}
      Installation ID{this.state.lbryId}
      -
      +

      About

      + {ver.lbrynet_update_available || ver.lbryum_update_available ? +

      A newer version of LBRY is available.

      + :

      Your copy of LBRY is up to date.

      + } + + + + + + + + + + + + + + + + + + + + + + + +
      daemon (lbrynet){ver.lbrynet_version}
      wallet (lbryum){ver.lbryum_version}
      interface{uiVersion}
      Platform{platform}
      Installation ID{this.state.lbryId}
      }
      diff --git a/ui/js/page/show.js b/ui/js/page/show.js index e9013716b..f37994b7a 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -5,8 +5,8 @@ import lbryuri from '../lbryuri.js'; import {Video} from '../page/watch.js' import {TruncatedText, Thumbnail, FilePrice, BusyMessage} from '../component/common.js'; import {FileActions} from '../component/file-actions.js'; -import Link from '../component/link'; import UriIndicator from '../component/channel-indicator.js'; +import Link from 'component/link'; var FormatItem = React.createClass({ propTypes: { -- 2.43.4 From d5b411b5721c6ce35c6a4512ba5ccf8c6d8b3e42 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 20 Apr 2017 21:08:23 -0400 Subject: [PATCH 03/56] fix drawer highlight and drawer balance --- ui/js/component/drawer/view.jsx | 12 ++++++------ ui/js/component/link/view.js | 2 +- ui/js/main.js | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/js/component/drawer/view.jsx b/ui/js/component/drawer/view.jsx index d2a381b88..da65cd523 100644 --- a/ui/js/component/drawer/view.jsx +++ b/ui/js/component/drawer/view.jsx @@ -7,16 +7,17 @@ const DrawerItem = (props) => { currentPage, href, subPages, + badge, label, linkClick, icon, } = props const isSelected = ( - currentPage == href.substr(1) || + currentPage == href.substr(0) || (subPages && subPages.indexOf(currentPage) != -1) ) - return linkClick(href)} className={ 'drawer-item ' + (isSelected ? 'drawer-item-selected' : '') } /> + return linkClick(href)} className={ 'drawer-item ' + (isSelected ? 'drawer-item-selected' : '') } /> } var drawerImageStyle = { //@TODO: remove this, img should be properly scaled once size is settled @@ -32,9 +33,9 @@ class Drawer extends React.Component { componentDidMount() { const { updateBalance } = this.props - this._balanceSubscribeId = lbry.balanceSubscribe(function(balance) { + this._balanceSubscribeId = lbry.balanceSubscribe((balance) => { updateBalance(balance) - }.bind(this)); + }); } componentWillUnmount() { if (this._balanceSubscribeId) { @@ -46,7 +47,6 @@ class Drawer extends React.Component { const { closeDrawerClick, logoClick, - currentPage, balance, } = this.props @@ -58,7 +58,7 @@ class Drawer extends React.Component { - + ) diff --git a/ui/js/component/link/view.js b/ui/js/component/link/view.js index e4b361241..65f2e1992 100644 --- a/ui/js/component/link/view.js +++ b/ui/js/component/link/view.js @@ -15,7 +15,7 @@ const Link = (props) => { disabled, } = props - +console.log(props); const className = (props.className || '') + (!props.className && !props.button ? 'button-text' : '') + // Non-button links get the same look as text buttons (props.button ? ' button-block button-' + props.button + ' button-set-item' : '') + diff --git a/ui/js/main.js b/ui/js/main.js index c346a0e5c..14c22c9c0 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -35,7 +35,7 @@ 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 - ReactDOM.render(
      { lbryio.enabled ? : '' }
      , canvas) + ReactDOM.render(
      { lbryio.enabled ? : '' }
      , canvas) } if (window.sessionStorage.getItem('loaded') == 'y') { -- 2.43.4 From a123ace6861f608cc6b61fe37ee4c4dcc96b10ae Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 20 Apr 2017 21:11:58 -0400 Subject: [PATCH 04/56] remove console --- ui/js/component/link/view.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/js/component/link/view.js b/ui/js/component/link/view.js index 65f2e1992..7b11e67d8 100644 --- a/ui/js/component/link/view.js +++ b/ui/js/component/link/view.js @@ -14,8 +14,7 @@ const Link = (props) => { hidden, disabled, } = props - -console.log(props); + const className = (props.className || '') + (!props.className && !props.button ? 'button-text' : '') + // Non-button links get the same look as text buttons (props.button ? ' button-block button-' + props.button + ' button-set-item' : '') + -- 2.43.4 From cee2f2a626f7ff737941611e555b841b809cabfd Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 20 Apr 2017 22:31:50 -0400 Subject: [PATCH 05/56] watch page is ded --- ui/js/component/app/view.jsx | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 336b39150..eef017835 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -45,20 +45,16 @@ const App = React.createClass({ } = this.props const searchQuery = (currentPage == 'discover' && searchTerm ? searchTerm : '') - return ( - currentPage == 'watch' ? - : -
      - -
      -
      - -
      - {modal == 'upgrade' && } - {modal == 'downloading' && } - {modal == 'error' && } -
      - ); + return
      + +
      +
      + +
      + {modal == 'upgrade' && } + {modal == 'downloading' && } + {modal == 'error' && } +
      } }); -- 2.43.4 From 089572880fff99e0832b427820ab5f682309ffc6 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 22 Apr 2017 20:17:01 +0700 Subject: [PATCH 06/56] Wallet progress --- ui/js/actions/app.js | 15 +- ui/js/actions/wallet.js | 60 ++++ ui/js/component/app/view.jsx | 3 +- ui/js/component/drawer/index.jsx | 8 +- ui/js/component/header/index.js | 23 ++ .../component/{header.js => header/view.jsx} | 22 +- ui/js/component/router/view.jsx | 4 +- ui/js/component/sub-header.js | 22 ++ ui/js/component/wallet-nav.js | 12 + ui/js/constants/action_types.js | 25 +- ui/js/main.js | 5 +- ui/js/page/help/view.jsx | 2 +- ui/js/page/wallet.js | 326 ------------------ ui/js/page/wallet/index.js | 39 +++ ui/js/page/wallet/view.jsx | 219 ++++++++++++ ui/js/reducers/app.js | 16 +- ui/js/reducers/wallet.js | 52 +++ ui/js/selectors/app.js | 26 +- ui/js/selectors/wallet.js | 109 ++++++ ui/js/store.js | 2 + ui/js/triggers.js | 33 ++ 21 files changed, 629 insertions(+), 394 deletions(-) create mode 100644 ui/js/actions/wallet.js create mode 100644 ui/js/component/header/index.js rename ui/js/component/{header.js => header/view.jsx} (90%) create mode 100644 ui/js/component/sub-header.js create mode 100644 ui/js/component/wallet-nav.js delete mode 100644 ui/js/page/wallet.js create mode 100644 ui/js/page/wallet/index.js create mode 100644 ui/js/page/wallet/view.jsx create mode 100644 ui/js/reducers/wallet.js create mode 100644 ui/js/selectors/wallet.js create mode 100644 ui/js/triggers.js diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 14aff78f7..073ae9099 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -52,15 +52,6 @@ export function doCloseModal() { } } -export function doUpdateBalance(balance) { - return { - type: types.UPDATE_BALANCE, - data: { - balance: balance - } - } -} - export function doUpdateDownloadProgress(percent) { return { type: types.UPGRADE_DOWNLOAD_PROGRESSED, @@ -204,3 +195,9 @@ export function doSearch(term) { }) } } + +export function doDaemonReady() { + return { + type: types.DAEMON_READY + } +} diff --git a/ui/js/actions/wallet.js b/ui/js/actions/wallet.js new file mode 100644 index 000000000..3486b9be5 --- /dev/null +++ b/ui/js/actions/wallet.js @@ -0,0 +1,60 @@ +import * as types from 'constants/action_types' +import lbry from 'lbry' + +export function doUpdateBalance(balance) { + return { + type: types.UPDATE_BALANCE, + data: { + balance: balance + } + } +} + +export function doFetchTransactions() { + return function(dispatch, getState) { + dispatch({ + type: types.FETCH_TRANSACTIONS_STARTED + }) + + lbry.call('get_transaction_history', {}, (results) => { + dispatch({ + type: types.FETCH_TRANSACTIONS_COMPLETED, + data: { + transactions: results + } + }) + }) + } +} + +export function doGetNewAddress() { + return function(dispatch, getState) { + dispatch({ + type: types.GET_NEW_ADDRESS_STARTED + }) + + lbry.wallet_new_address().then(function(address) { + localStorage.setItem('wallet_address', address); + dispatch({ + type: types.GET_NEW_ADDRESS_COMPLETED, + data: { address } + }) + }) + } +} + +export function doCheckAddressIsMine(address) { + return function(dispatch, getState) { + dispatch({ + type: types.CHECK_ADDRESS_IS_MINE_STARTED + }) + + lbry.checkAddressIsMine(address, (isMine) => { + if (!isMine) dispatch(doGetNewAddress()) + + dispatch({ + type: types.CHECK_ADDRESS_IS_MINE_COMPLETED + }) + }) + } +} diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index eef017835..c160e8146 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -2,8 +2,7 @@ import React from 'react' import lbry from 'lbry.js'; import Router from 'component/router' -import Drawer from 'component/drawer'; -import Header from 'component/header.js'; +import Header from 'component/header'; import {Modal, ExpandableModal} from 'component/modal.js'; import ErrorModal from 'component/errorModal' import DownloadingModal from 'component/downloadingModal' diff --git a/ui/js/component/drawer/index.jsx b/ui/js/component/drawer/index.jsx index b5f666c92..3c4c524b0 100644 --- a/ui/js/component/drawer/index.jsx +++ b/ui/js/component/drawer/index.jsx @@ -7,12 +7,16 @@ import { doNavigate, doCloseDrawer, doLogoClick, - doUpdateBalance, } from 'actions/app' +import { + doUpdateBalance, +} from 'actions/wallet' import { selectCurrentPage, - selectBalance, } from 'selectors/app' +import { + selectBalance, +} from 'selectors/wallet' const select = (state) => ({ currentPage: selectCurrentPage(state), diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js new file mode 100644 index 000000000..3b9cb57ef --- /dev/null +++ b/ui/js/component/header/index.js @@ -0,0 +1,23 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + selectCurrentPage, + selectHeaderLinks, +} from 'selectors/app' +import { + doNavigate, +} from 'actions/app' +import Header from './view' + +const select = (state) => ({ + currentPage: selectCurrentPage(state), + subLinks: selectHeaderLinks(state), +}) + +const perform = (dispatch) => ({ + navigate: (path) => dispatch(doNavigate(path)), +}) + +export default connect(select, perform)(Header) diff --git a/ui/js/component/header.js b/ui/js/component/header/view.jsx similarity index 90% rename from ui/js/component/header.js rename to ui/js/component/header/view.jsx index f0d66caee..34627e14a 100644 --- a/ui/js/component/header.js +++ b/ui/js/component/header/view.jsx @@ -3,7 +3,7 @@ import lbryuri from '../lbryuri.js'; import {Icon, CreditAmount} from './common.js'; import Link from 'component/link'; -var Header = React.createClass({ +let Header = React.createClass({ _balanceSubscribeId: null, _isMounted: false, @@ -190,24 +190,4 @@ class WunderBar extends React.PureComponent { } } -export let SubHeader = React.createClass({ - render: function() { - let links = [], - viewingUrl = '?' + this.props.viewingPage; - - for (let link of Object.keys(this.props.links)) { - links.push( - - {this.props.links[link]} - - ); - } - return ( - - ); - } -}); - export default Header; diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index c6aad0588..208bb3f6d 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -4,9 +4,7 @@ import HelpPage from 'page/help'; import WatchPage from 'page/watch.js'; import ReportPage from 'page/report.js'; import StartPage from 'page/start.js'; -import ClaimCodePage from 'page/claim_code.js'; -import ReferralPage from 'page/referral.js'; -import WalletPage from 'page/wallet.js'; +import WalletPage from 'page/wallet'; import DetailPage from 'page/show.js'; import PublishPage from 'page/publish.js'; import DiscoverPage from 'page/discover.js'; diff --git a/ui/js/component/sub-header.js b/ui/js/component/sub-header.js new file mode 100644 index 000000000..061b0e942 --- /dev/null +++ b/ui/js/component/sub-header.js @@ -0,0 +1,22 @@ +const SubHeader = (props) => { + const { + subLinks, + currentPage, + navigate, + } = props + + const links = [], + viewingUrl = '?' + this.props.viewingPage; + + for(let link of Object.keys(subLinks)) { + links.push( + navigate(link)} key={link} className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected' }> + {subLinks[link]} + + ); + } + + return ( + + ) +} \ No newline at end of file diff --git a/ui/js/component/wallet-nav.js b/ui/js/component/wallet-nav.js new file mode 100644 index 000000000..51cd9278b --- /dev/null +++ b/ui/js/component/wallet-nav.js @@ -0,0 +1,12 @@ +import {SubHeader} from '../component/sub-header.js'; + +export let WalletNav = React.createClass({ + render: function () { + return ; + } +}); \ No newline at end of file diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 17f05441a..cafea5c7d 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -1,5 +1,13 @@ -export const UPDATE_BALANCE = 'UPDATE_BALANCE' export const NAVIGATE = 'NAVIGATE' +export const OPEN_MODAL = 'OPEN_MODAL' +export const CLOSE_MODAL = 'CLOSE_MODAL' + +export const OPEN_DRAWER = 'OPEN_DRAWER' +export const CLOSE_DRAWER = 'CLOSE_DRAWER' + +export const START_SEARCH = 'START_SEARCH' + +export const DAEMON_READY = 'DAEMON_READY' // Upgrades export const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED' @@ -12,10 +20,11 @@ export const UPDATE_VERSION = 'UPDATE_VERSION' export const SKIP_UPGRADE = 'SKIP_UPGRADE' export const START_UPGRADE = 'START_UPGRADE' -export const OPEN_MODAL = 'OPEN_MODAL' -export const CLOSE_MODAL = 'CLOSE_MODAL' - -export const OPEN_DRAWER = 'OPEN_DRAWER' -export const CLOSE_DRAWER = 'CLOSE_DRAWER' - -export const START_SEARCH = 'START_SEARCH' +// Wallet +export const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED' +export const GET_NEW_ADDRESS_COMPLETED = 'GET_NEW_ADDRESS_COMPLETED' +export const FETCH_TRANSACTIONS_STARTED = 'FETCH_TRANSACTIONS_STARTED' +export const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED' +export const UPDATE_BALANCE = 'UPDATE_BALANCE' +export const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED' +export const CHECK_ADDRESS_IS_MINE_COMPLETED = 'CHECK_ADDRESS_IS_MINE_COMPLETED' diff --git a/ui/js/main.js b/ui/js/main.js index 14c22c9c0..1f1d4c92f 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -9,6 +9,7 @@ import SnackBar from './component/snack-bar.js'; import {AuthOverlay} from './component/auth.js'; import { Provider } from 'react-redux'; import store from 'store.js'; +import { runTriggers } from 'triggers' const {remote} = require('electron'); const contextMenu = remote.require('./menu/context-menu'); @@ -23,6 +24,8 @@ window.addEventListener('contextmenu', (event) => { }); const initialState = app.store.getState(); +app.store.subscribe(runTriggers); +runTriggers(); var init = function() { window.lbry = lbry; @@ -35,7 +38,7 @@ 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 - ReactDOM.render(
      { lbryio.enabled ? : '' }
      , canvas) + ReactDOM.render(
      { lbryio.enabled ? : '' }
      , canvas) } if (window.sessionStorage.getItem('loaded') == 'y') { diff --git a/ui/js/page/help/view.jsx b/ui/js/page/help/view.jsx index eba7a6273..b5fe61787 100644 --- a/ui/js/page/help/view.jsx +++ b/ui/js/page/help/view.jsx @@ -48,7 +48,7 @@ var HelpPage = React.createClass({ } return ( -
      +

      Read the FAQ

      Our FAQ answers many common questions.

      diff --git a/ui/js/page/wallet.js b/ui/js/page/wallet.js deleted file mode 100644 index a9b3a92a0..000000000 --- a/ui/js/page/wallet.js +++ /dev/null @@ -1,326 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import Link from 'component/link'; -import Modal from '../component/modal.js'; -import {SubHeader} from '../component/header.js'; -import {FormField, FormRow} from '../component/form.js'; -import {Address, BusyMessage, CreditAmount} from '../component/common.js'; - -var AddressSection = React.createClass({ - _refreshAddress: function(event) { - if (typeof event !== 'undefined') { - event.preventDefault(); - } - - lbry.getUnusedAddress((address) => { - window.localStorage.setItem('wallet_address', address); - this.setState({ - address: address, - }); - }); - }, - - _getNewAddress: function(event) { - if (typeof event !== 'undefined') { - event.preventDefault(); - } - - lbry.getNewAddress((address) => { - window.localStorage.setItem('wallet_address', address); - this.setState({ - address: address, - }); - }); - }, - - getInitialState: function() { - return { - address: null, - modal: null, - } - }, - componentWillMount: function() { - var address = window.localStorage.getItem('wallet_address'); - if (address === null) { - this._refreshAddress(); - } else { - lbry.checkAddressIsMine(address, (isMine) => { - if (isMine) { - this.setState({ - address: address, - }); - } else { - this._refreshAddress(); - } - }); - } - }, - render: function() { - return ( -
      -
      -

      Wallet Address

      -
      -
      -
      -
      -
      - -
      -
      -
      -

      Other LBRY users may send credits to you by entering this address on the "Send" page.

      -

      You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.

      -
      -
      -
      - ); - } -}); - -var SendToAddressSection = React.createClass({ - handleSubmit: function(event) { - if (typeof event !== 'undefined') { - event.preventDefault(); - } - - if ((this.state.balance - this.state.amount) < 1) - { - this.setState({ - modal: 'insufficientBalance', - }); - return; - } - - this.setState({ - results: "", - }); - - lbry.sendToAddress(this.state.amount, this.state.address, (results) => { - if(results === true) - { - this.setState({ - results: "Your transaction was successfully placed in the queue.", - }); - } - else - { - this.setState({ - results: "Something went wrong: " + results - }); - } - }, (error) => { - this.setState({ - results: "Something went wrong: " + error.message - }) - }); - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - getInitialState: function() { - return { - address: "", - amount: 0.0, - balance: , - results: "", - } - }, - componentWillMount: function() { - lbry.getBalance((results) => { - this.setState({ - balance: results, - }); - }); - }, - setAmount: function(event) { - this.setState({ - amount: parseFloat(event.target.value), - }) - }, - setAddress: function(event) { - this.setState({ - address: event.target.value, - }) - }, - render: function() { - return ( -
      -
      -
      -

      Send Credits

      -
      -
      - -
      -
      - -
      -
      - 0.0) || this.state.address == ""} /> - -
      - { - this.state.results ? -
      -

      Results

      - {this.state.results} -
      : '' - } -
      - - Insufficient balance: after this transaction you would have less than 1 LBC in your wallet. - -
      - ); - } -}); - - -var TransactionList = React.createClass({ - getInitialState: function() { - return { - transactionItems: null, - } - }, - componentWillMount: function() { - lbry.call('get_transaction_history', {}, (results) => { - if (results.length == 0) { - this.setState({ transactionItems: [] }) - } else { - var transactionItems = [], - condensedTransactions = {}; - results.forEach(function(tx) { - var txid = tx["txid"]; - if (!(txid in condensedTransactions)) { - condensedTransactions[txid] = 0; - } - condensedTransactions[txid] += parseFloat(tx["value"]); - }); - results.reverse().forEach(function(tx) { - var txid = tx["txid"]; - if (condensedTransactions[txid] && condensedTransactions[txid] != 0) - { - transactionItems.push({ - id: txid, - date: tx["timestamp"] ? (new Date(parseInt(tx["timestamp"]) * 1000)) : null, - amount: condensedTransactions[txid] - }); - delete condensedTransactions[txid]; - } - }); - - this.setState({ transactionItems: transactionItems }); - } - }); - }, - render: function() { - var rows = []; - if (this.state.transactionItems && this.state.transactionItems.length > 0) - { - this.state.transactionItems.forEach(function(item) { - rows.push( - - { (item.amount > 0 ? '+' : '' ) + item.amount } - { item.date ? item.date.toLocaleDateString() : (Transaction pending) } - { item.date ? item.date.toLocaleTimeString() : (Transaction pending) } - - {item.id.substr(0, 7)} - - - ); - }); - } - return ( -
      -
      -

      Transaction History

      -
      -
      - { this.state.transactionItems === null ? : '' } - { this.state.transactionItems && rows.length === 0 ?
      You have no transactions.
      : '' } - { this.state.transactionItems && rows.length > 0 ? - - - - - - - - - - - {rows} - -
      AmountDateTimeTransaction
      - : '' - } -
      -
      - ); - } -}); - -export let WalletNav = React.createClass({ - render: function() { - return ; - } -}); - -var WalletPage = React.createClass({ - _balanceSubscribeId: null, - - propTypes: { - viewingPage: React.PropTypes.string, - }, - /* - Below should be refactored so that balance is shared all of wallet page. Or even broader? - What is the proper React pattern for sharing a global state like balance? - */ - getInitialState: function() { - return { - balance: null, - } - }, - componentWillMount: function() { - this._balanceSubscribeId = lbry.balanceSubscribe((results) => { - this.setState({ - balance: results, - }) - }); - }, - componentWillUnmount: function() { - if (this._balanceSubscribeId) { - lbry.balanceUnsubscribe(this._balanceSubscribeId); - } - }, - render: function() { - return ( -
      - -
      -
      -

      Balance

      -
      -
      - { this.state.balance === null ? : ''} - { this.state.balance !== null ? : '' } -
      -
      - { this.props.viewingPage === 'wallet' ? : '' } - { this.props.viewingPage === 'send' ? : '' } - { this.props.viewingPage === 'receive' ? : '' } -
      - ); - } -}); - -export default WalletPage; diff --git a/ui/js/page/wallet/index.js b/ui/js/page/wallet/index.js new file mode 100644 index 000000000..29a56012b --- /dev/null +++ b/ui/js/page/wallet/index.js @@ -0,0 +1,39 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + doCloseModal, +} from 'actions/app' +import { + doGetNewAddress, +} from 'actions/wallet' +import { + selectCurrentPage, +} from 'selectors/app' +import { + selectBalance, + selectTransactions, + selectTransactionItems, + selectIsFetchingTransactions, + selectReceiveAddress, + selectGettingNewAddress, +} from 'selectors/wallet' +import WalletPage from './view' + +const select = (state) => ({ + currentPage: selectCurrentPage(state), + balance: selectBalance(state), + transactions: selectTransactions(state), + fetchingTransactions: selectIsFetchingTransactions(state), + transactionItems: selectTransactionItems(state), + receiveAddress: selectReceiveAddress(state), + gettingNewAddress: selectGettingNewAddress(state), +}) + +const perform = (dispatch) => ({ + closeModal: () => dispatch(doCloseModal()), + getNewAddress: () => dispatch(doGetNewAddress()), +}) + +export default connect(select, perform)(WalletPage) diff --git a/ui/js/page/wallet/view.jsx b/ui/js/page/wallet/view.jsx new file mode 100644 index 000000000..7a7f9de0b --- /dev/null +++ b/ui/js/page/wallet/view.jsx @@ -0,0 +1,219 @@ +import React from 'react'; +import lbry from 'lbry.js'; +import Link from 'component/link'; +import Modal from 'component/modal'; +import { + FormField, + FormRow +} from 'component/form'; +import { + Address, + BusyMessage, + CreditAmount +} from 'component/common'; + +const AddressSection = (props) => { + const { + receiveAddress, + getNewAddress, + gettingNewAddress, + } = props + + return ( +
      +
      +

      Wallet Address

      +
      +
      +
      +
      +
      + +
      +
      +
      +

      Other LBRY users may send credits to you by entering this address on the "Send" page.

      +

      You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.

      +
      +
      +
      + ); +} + +var SendToAddressSection = React.createClass({ + handleSubmit: function(event) { + if (typeof event !== 'undefined') { + event.preventDefault(); + } + + if ((this.state.balance - this.state.amount) < 1) + { + this.setState({ + modal: 'insufficientBalance', + }); + return; + } + + this.setState({ + results: "", + }); + + lbry.sendToAddress(this.state.amount, this.state.address, (results) => { + if(results === true) + { + this.setState({ + results: "Your transaction was successfully placed in the queue.", + }); + } + else + { + this.setState({ + results: "Something went wrong: " + results + }); + } + }, (error) => { + this.setState({ + results: "Something went wrong: " + error.message + }) + }); + }, + closeModal: function() { + this.setState({ + modal: null, + }); + }, + getInitialState: function() { + return { + address: "", + amount: 0.0, + balance: , + results: "", + } + }, + componentWillMount: function() { + lbry.getBalance((results) => { + this.setState({ + balance: results, + }); + }); + }, + setAmount: function(event) { + this.setState({ + amount: parseFloat(event.target.value), + }) + }, + setAddress: function(event) { + this.setState({ + address: event.target.value, + }) + }, + render: function() { + return ( +
      +
      +
      +

      Send Credits

      +
      +
      + +
      +
      + +
      +
      + 0.0) || this.state.address == ""} /> + +
      + { + this.state.results ? +
      +

      Results

      + {this.state.results} +
      : '' + } +
      + + Insufficient balance: after this transaction you would have less than 1 LBC in your wallet. + +
      + ); + } +}); + +const TransactionList = (props) => { + const { + transactions, + fetchingTransactions, + transactionItems, + } = props + + const rows = [] + if (transactions.length > 0) { + transactionItems.forEach(function(item) { + rows.push( + + { (item.amount > 0 ? '+' : '' ) + item.amount } + { item.date ? item.date.toLocaleDateString() : (Transaction pending) } + { item.date ? item.date.toLocaleTimeString() : (Transaction pending) } + + {item.id.substr(0, 7)} + + + ); + }); + } + + return ( +
      +
      +

      Transaction History

      +
      +
      + { fetchingTransactions ? : '' } + { !fetchingTransactions && rows.length === 0 ?
      You have no transactions.
      : '' } + { rows.length > 0 ? + + + + + + + + + + + {rows} + +
      AmountDateTimeTransaction
      + : '' + } +
      +
      + ) +} + +const WalletPage = (props) => { + const { + balance, + currentPage + } = props + + return ( +
      +
      +
      +

      Balance

      +
      +
      + +
      +
      + { currentPage === 'wallet' ? : '' } + { currentPage === 'send' ? : '' } + { currentPage === 'receive' ? : '' } +
      + ) +} + +export default WalletPage; diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index 6859b60f3..2d49207be 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -6,13 +6,8 @@ const defaultState = { currentPage: 'discover', platform: process.platform, drawerOpen: sessionStorage.getItem('drawerOpen') || true, - upgradeSkipped: sessionStorage.getItem('upgradeSkipped') -} - -reducers[types.UPDATE_BALANCE] = function(state, action) { - return Object.assign({}, state, { - balance: action.data.balance - }) + upgradeSkipped: sessionStorage.getItem('upgradeSkipped'), + daemonReady: false, } reducers[types.NAVIGATE] = function(state, action) { @@ -98,6 +93,13 @@ reducers[types.UPGRADE_DOWNLOAD_PROGRESSED] = function(state, action) { }) } +reducers[types.DAEMON_READY] = function(state, action) { + // sessionStorage.setItem('loaded', 'y'); + return Object.assign({}, state, { + daemonReady: true + }) +} + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/reducers/wallet.js b/ui/js/reducers/wallet.js new file mode 100644 index 000000000..7a719f417 --- /dev/null +++ b/ui/js/reducers/wallet.js @@ -0,0 +1,52 @@ +import * as types from 'constants/action_types' + +const reducers = {} +const address = sessionStorage.getItem('receiveAddress') +const defaultState = { + balance: 0, + transactions: [], + fetchingTransactions: false, + receiveAddress: address, + gettingNewAddress: false, +} + +reducers[types.FETCH_TRANSACTIONS_STARTED] = function(state, action) { + return Object.assign({}, state, { + fetchingTransactions: true + }) +} + +reducers[types.FETCH_TRANSACTIONS_COMPLETED] = function(state, action) { + return Object.assign({}, state, { + transactions: action.data.transactions, + fetchingTransactions: false + }) +} + +reducers[types.GET_NEW_ADDRESS_STARTED] = function(state, action) { + return Object.assign({}, state, { + gettingNewAddress: true + }) +} + +reducers[types.GET_NEW_ADDRESS_COMPLETED] = function(state, action) { + const { address } = action.data + + sessionStorage.setItem('receiveAddress', address) + return Object.assign({}, state, { + gettingNewAddress: false, + receiveAddress: address + }) +} + +reducers[types.UPDATE_BALANCE] = function(state, action) { + return Object.assign({}, state, { + balance: action.data.balance + }) +} + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index 0bb22541b..fffd8b581 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -16,13 +16,6 @@ export const selectCurrentPage = createSelector( } ) -export const selectBalance = createSelector( - _selectState, - (state) => { - return state.balance || 0 - } -) - export const selectPlatform = createSelector( _selectState, (state) => { @@ -101,17 +94,17 @@ export const selectHeaderLinks = createSelector( case 'claim': case 'referral': return { - '?wallet' : 'Overview', - '?send' : 'Send', - '?receive' : 'Receive', - '?claim' : 'Claim Beta Code', - '?referral' : 'Check Referral Credit', + 'wallet' : 'Overview', + 'send' : 'Send', + 'receive' : 'Receive', + 'claim' : 'Claim Beta Code', + 'referral' : 'Check Referral Credit', }; case 'downloaded': case 'published': return { - '?downloaded': 'Downloaded', - '?published': 'Published', + 'downloaded': 'Downloaded', + 'published': 'Published', }; default: return null; @@ -143,3 +136,8 @@ export const selectError = createSelector( _selectState, (state) => state.error ) + +export const selectDaemonReady = createSelector( + _selectState, + (state) => state.daemonReady +) diff --git a/ui/js/selectors/wallet.js b/ui/js/selectors/wallet.js new file mode 100644 index 000000000..5e9576ff9 --- /dev/null +++ b/ui/js/selectors/wallet.js @@ -0,0 +1,109 @@ +import { createSelector } from 'reselect' +import { + selectCurrentPage, +} from 'selectors/app' + +export const _selectState = state => state.wallet || {} + +export const selectBalance = createSelector( + _selectState, + (state) => { + return state.balance || 0 + } +) + +export const selectTransactions = createSelector( + _selectState, + (state) => state.transactions +) + +export const selectTransactionItems = createSelector( + selectTransactions, + (transactions) => { + if (transactions.length == 0) return transactions + + const transactionItems = [] + const condensedTransactions = {} + + transactions.forEach(function(tx) { + const txid = tx["txid"]; + if (!(txid in condensedTransactions)) { + condensedTransactions[txid] = 0; + } + condensedTransactions[txid] += parseFloat(tx["value"]); + }); + transactions.reverse().forEach(function(tx) { + const txid = tx["txid"]; + if (condensedTransactions[txid] && condensedTransactions[txid] != 0) + { + transactionItems.push({ + id: txid, + date: tx["timestamp"] ? (new Date(parseInt(tx["timestamp"]) * 1000)) : null, + amount: condensedTransactions[txid] + }); + delete condensedTransactions[txid]; + } + }); + + return transactionItems + } +) + +export const selectIsFetchingTransactions = createSelector( + _selectState, + (state) => state.fetchingTransactions +) + +export const shouldFetchTransactions = createSelector( + selectCurrentPage, + selectTransactions, + selectIsFetchingTransactions, + (page, transactions, fetching) => { + if (page != 'wallet') return false + if (fetching) return false + if (transactions.length != 0) return false + + return true + } +) + +export const selectReceiveAddress = createSelector( + _selectState, + (state) => state.receiveAddress +) + +export const selectGettingNewAddress = createSelector( + _selectState, + (state) => state.gettingNewAddress +) + +export const selectDaemonReady = createSelector( + () => sessionStorage.getItem('loaded') == 'y' +) + +export const shouldGetReceiveAddress = createSelector( + selectReceiveAddress, + selectGettingNewAddress, + selectDaemonReady, + (address, fetching, daemonReady) => { + if (!daemonReady) return false + if (fetching) return false + if (address !== undefined) return false + + return true + } +) + +export const shouldCheckAddressIsMine = createSelector( + _selectState, + selectCurrentPage, + selectReceiveAddress, + selectDaemonReady, + (state, page, address, daemonReady) => { + if (!daemonReady) return false + if (address === undefined) return false + if (state.addressOwnershipChecked) return false + + return true + } +) diff --git a/ui/js/store.js b/ui/js/store.js index 9d6ac13c4..3d40d0d92 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -6,6 +6,7 @@ import { createLogger } from 'redux-logger' import appReducer from 'reducers/app'; +import walletReducer from 'reducers/wallet' function isFunction(object) { return typeof object === 'function'; @@ -17,6 +18,7 @@ function isNotFunction(object) { const reducers = redux.combineReducers({ app: appReducer, + wallet: walletReducer, }); var middleware = [thunk] diff --git a/ui/js/triggers.js b/ui/js/triggers.js new file mode 100644 index 000000000..85d44f360 --- /dev/null +++ b/ui/js/triggers.js @@ -0,0 +1,33 @@ +import { + shouldFetchTransactions, + shouldGetReceiveAddress, +} from 'selectors/wallet' +import { + doFetchTransactions, + doGetNewAddress, +} from 'actions/wallet' + +const triggers = [] + +triggers.push({ + selector: shouldFetchTransactions, + action: doFetchTransactions, +}) + +triggers.push({ + selector: shouldGetReceiveAddress, + action: doGetNewAddress +}) + +const runTriggers = function() { + triggers.forEach(function(trigger) { + const state = app.store.getState(); + const should = trigger.selector(state) + if (trigger.selector(state)) app.store.dispatch(trigger.action()) + }); +} + +module.exports = { + triggers: triggers, + runTriggers: runTriggers +} -- 2.43.4 From 573d85c3028ced0f0afbb39beb81997bb02a082a Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 22 Apr 2017 20:45:47 +0700 Subject: [PATCH 07/56] Quick hack to speed up local building --- .gitignore | 2 ++ build/build.sh | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d1b68b9b7..17a8ad330 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ dist *.pyc .#* + +build/daemon.zip diff --git a/build/build.sh b/build/build.sh index 1b3340054..5d1eeb55d 100755 --- a/build/build.sh +++ b/build/build.sh @@ -78,9 +78,9 @@ else OSNAME="linux" fi DAEMON_URL="$(cat "$BUILD_DIR/DAEMON_URL" | sed "s/OSNAME/${OSNAME}/")" -wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip" +# wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip" unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/app/dist/" -rm "$BUILD_DIR/daemon.zip" +# rm "$BUILD_DIR/daemon.zip" ################### # Build the app # -- 2.43.4 From bd103ee956516c3b7bff9fdbd6f5f557b1541511 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 22 Apr 2017 20:46:04 +0700 Subject: [PATCH 08/56] Fix for generating a wallet address on load --- ui/js/main.js | 5 ++++- ui/js/reducers/app.js | 2 +- ui/js/selectors/wallet.js | 7 ++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/js/main.js b/ui/js/main.js index 1f1d4c92f..d94093f24 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -10,6 +10,9 @@ import {AuthOverlay} from './component/auth.js'; import { Provider } from 'react-redux'; import store from 'store.js'; import { runTriggers } from 'triggers' +import { + doDaemonReady +} from 'actions/app' const {remote} = require('electron'); const contextMenu = remote.require('./menu/context-menu'); @@ -37,7 +40,7 @@ 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 + app.store.dispatch(doDaemonReady()) ReactDOM.render(
      { lbryio.enabled ? : '' }
      , canvas) } diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index 2d49207be..7d02f582e 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -94,7 +94,7 @@ reducers[types.UPGRADE_DOWNLOAD_PROGRESSED] = function(state, action) { } reducers[types.DAEMON_READY] = function(state, action) { - // sessionStorage.setItem('loaded', 'y'); + window.sessionStorage.setItem('loaded', 'y') return Object.assign({}, state, { daemonReady: true }) diff --git a/ui/js/selectors/wallet.js b/ui/js/selectors/wallet.js index 5e9576ff9..6e3809cce 100644 --- a/ui/js/selectors/wallet.js +++ b/ui/js/selectors/wallet.js @@ -1,6 +1,7 @@ import { createSelector } from 'reselect' import { selectCurrentPage, + selectDaemonReady, } from 'selectors/app' export const _selectState = state => state.wallet || {} @@ -77,10 +78,6 @@ export const selectGettingNewAddress = createSelector( (state) => state.gettingNewAddress ) -export const selectDaemonReady = createSelector( - () => sessionStorage.getItem('loaded') == 'y' -) - export const shouldGetReceiveAddress = createSelector( selectReceiveAddress, selectGettingNewAddress, @@ -88,7 +85,7 @@ export const shouldGetReceiveAddress = createSelector( (address, fetching, daemonReady) => { if (!daemonReady) return false if (fetching) return false - if (address !== undefined) return false + if (address) return false return true } -- 2.43.4 From 823a936c19dce161b2919e81027f1fc16bc90072 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 22 Apr 2017 21:01:57 +0700 Subject: [PATCH 09/56] Check address ownership --- ui/js/page/wallet/index.js | 2 ++ ui/js/page/wallet/view.jsx | 54 +++++++++++++++++++++----------------- ui/js/reducers/wallet.js | 12 +++++++++ 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/ui/js/page/wallet/index.js b/ui/js/page/wallet/index.js index 29a56012b..7976808f1 100644 --- a/ui/js/page/wallet/index.js +++ b/ui/js/page/wallet/index.js @@ -7,6 +7,7 @@ import { } from 'actions/app' import { doGetNewAddress, + doCheckAddressIsMine, } from 'actions/wallet' import { selectCurrentPage, @@ -34,6 +35,7 @@ const select = (state) => ({ const perform = (dispatch) => ({ closeModal: () => dispatch(doCloseModal()), getNewAddress: () => dispatch(doGetNewAddress()), + checkAddressIsMine: (address) => dispatch(doCheckAddressIsMine(address)) }) export default connect(select, perform)(WalletPage) diff --git a/ui/js/page/wallet/view.jsx b/ui/js/page/wallet/view.jsx index 7a7f9de0b..042b6415d 100644 --- a/ui/js/page/wallet/view.jsx +++ b/ui/js/page/wallet/view.jsx @@ -12,32 +12,38 @@ import { CreditAmount } from 'component/common'; -const AddressSection = (props) => { - const { - receiveAddress, - getNewAddress, - gettingNewAddress, - } = props +class AddressSection extends React.Component { + componentWillMount() { + this.props.checkAddressIsMine(this.props.receiveAddress) + } - return ( -
      -
      -

      Wallet Address

      -
      -
      -
      -
      -
      - -
      -
      -
      -

      Other LBRY users may send credits to you by entering this address on the "Send" page.

      -

      You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.

      + render() { + const { + receiveAddress, + getNewAddress, + gettingNewAddress, + } = this.props + + return ( +
      +
      +

      Wallet Address

      -
      -
      - ); +
      +
      +
      +
      + +
      +
      +
      +

      Other LBRY users may send credits to you by entering this address on the "Send" page.

      +

      You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.

      +
      +
      +
      + ); + } } var SendToAddressSection = React.createClass({ diff --git a/ui/js/reducers/wallet.js b/ui/js/reducers/wallet.js index 7a719f417..6628f394f 100644 --- a/ui/js/reducers/wallet.js +++ b/ui/js/reducers/wallet.js @@ -45,6 +45,18 @@ reducers[types.UPDATE_BALANCE] = function(state, action) { }) } +reducers[types.CHECK_ADDRESS_IS_MINE_STARTED] = function(state, action) { + return Object.assign({}, state, { + checkingAddressOwnership: true + }) +} + +reducers[types.CHECK_ADDRESS_IS_MINE_COMPLETED] = function(state, action) { + return Object.assign({}, state, { + checkingAddressOwnership: false + }) +} + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); -- 2.43.4 From 4c07198b030b1db2dd081ed59ce2b64fdecc8471 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 22 Apr 2017 23:07:46 +0700 Subject: [PATCH 10/56] Some transactions cleanup and starting on sending --- ui/js/actions/wallet.js | 5 + ui/js/page/wallet/index.js | 10 +- ui/js/page/wallet/view.jsx | 241 ++++++++++++++++++++++--------------- ui/js/reducers/wallet.js | 14 ++- ui/js/selectors/wallet.js | 48 +++----- 5 files changed, 187 insertions(+), 131 deletions(-) diff --git a/ui/js/actions/wallet.js b/ui/js/actions/wallet.js index 3486b9be5..f28ddcbdc 100644 --- a/ui/js/actions/wallet.js +++ b/ui/js/actions/wallet.js @@ -58,3 +58,8 @@ export function doCheckAddressIsMine(address) { }) } } + +export function doSendToAddress() { + return function(dispatch, getState) { + } +} diff --git a/ui/js/page/wallet/index.js b/ui/js/page/wallet/index.js index 7976808f1..03d6fc48b 100644 --- a/ui/js/page/wallet/index.js +++ b/ui/js/page/wallet/index.js @@ -8,9 +8,11 @@ import { import { doGetNewAddress, doCheckAddressIsMine, + doSendToAddress, } from 'actions/wallet' import { selectCurrentPage, + selectCurrentModal, } from 'selectors/app' import { selectBalance, @@ -30,12 +32,18 @@ const select = (state) => ({ transactionItems: selectTransactionItems(state), receiveAddress: selectReceiveAddress(state), gettingNewAddress: selectGettingNewAddress(state), + modal: selectCurrentModal(state), + address: null, + amount: 0.0, }) const perform = (dispatch) => ({ closeModal: () => dispatch(doCloseModal()), getNewAddress: () => dispatch(doGetNewAddress()), - checkAddressIsMine: (address) => dispatch(doCheckAddressIsMine(address)) + checkAddressIsMine: (address) => dispatch(doCheckAddressIsMine(address)), + sendToAddress: () => dispatch(doSendToAddress()), + setAmount: () => console.log('set amount'), + setAddress: () => console.log('set address'), }) export default connect(select, perform)(WalletPage) diff --git a/ui/js/page/wallet/view.jsx b/ui/js/page/wallet/view.jsx index 042b6415d..c4b1c4acb 100644 --- a/ui/js/page/wallet/view.jsx +++ b/ui/js/page/wallet/view.jsx @@ -46,116 +46,159 @@ class AddressSection extends React.Component { } } -var SendToAddressSection = React.createClass({ - handleSubmit: function(event) { - if (typeof event !== 'undefined') { - event.preventDefault(); - } +const SendToAddressSection = (props) => { + const { + sendToAddress, + closeModal, + modal, + setAmount, + setAddress, + amount, + address, + } = props - if ((this.state.balance - this.state.amount) < 1) - { - this.setState({ - modal: 'insufficientBalance', - }); - return; - } + const results = null - this.setState({ - results: "", - }); + return ( +
      +
      +
      +

      Send Credits

      +
      +
      + +
      +
      + +
      +
      + 0.0) || !address} /> + +
      + { + results ? +
      +

      Results

      + {results} +
      : '' + } +
      + {modal == 'insufficientBalance' && + Insufficient balance: after this transaction you would have less than 1 LBC in your wallet. + } +
      + ) +} - lbry.sendToAddress(this.state.amount, this.state.address, (results) => { - if(results === true) - { - this.setState({ - results: "Your transaction was successfully placed in the queue.", - }); - } - else - { - this.setState({ - results: "Something went wrong: " + results - }); - } - }, (error) => { - this.setState({ - results: "Something went wrong: " + error.message - }) - }); - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - getInitialState: function() { - return { - address: "", - amount: 0.0, - balance: , - results: "", - } - }, - componentWillMount: function() { - lbry.getBalance((results) => { - this.setState({ - balance: results, - }); - }); - }, - setAmount: function(event) { - this.setState({ - amount: parseFloat(event.target.value), - }) - }, - setAddress: function(event) { - this.setState({ - address: event.target.value, - }) - }, - render: function() { - return ( -
      -
      -
      -

      Send Credits

      -
      -
      - -
      -
      - -
      -
      - 0.0) || this.state.address == ""} /> - -
      - { - this.state.results ? -
      -

      Results

      - {this.state.results} -
      : '' - } -
      - - Insufficient balance: after this transaction you would have less than 1 LBC in your wallet. - -
      - ); - } -}); +// var SendToAddressSection = React.createClass({ +// handleSubmit: function(event) { +// if (typeof event !== 'undefined') { +// event.preventDefault(); +// } + +// if ((this.state.balance - this.state.amount) < 1) +// { +// this.setState({ +// modal: 'insufficientBalance', +// }); +// return; +// } + +// this.setState({ +// results: "", +// }); + +// lbry.sendToAddress(this.state.amount, this.state.address, (results) => { +// if(results === true) +// { +// this.setState({ +// results: "Your transaction was successfully placed in the queue.", +// }); +// } +// else +// { +// this.setState({ +// results: "Something went wrong: " + results +// }); +// } +// }, (error) => { +// this.setState({ +// results: "Something went wrong: " + error.message +// }) +// }); +// }, +// closeModal: function() { +// this.setState({ +// modal: null, +// }); +// }, +// getInitialState: function() { +// return { +// address: "", +// amount: 0.0, +// balance: , +// results: "", +// } +// }, +// componentWillMount: function() { +// lbry.getBalance((results) => { +// this.setState({ +// balance: results, +// }); +// }); +// }, +// setAmount: function(event) { +// this.setState({ +// amount: parseFloat(event.target.value), +// }) +// }, +// setAddress: function(event) { +// this.setState({ +// address: event.target.value, +// }) +// }, +// render: function() { +// return ( +//
      +//
      +//
      +//

      Send Credits

      +//
      +//
      +// +//
      +//
      +// +//
      +//
      +// 0.0) || this.state.address == ""} /> +// +//
      +// { +// this.state.results ? +//
      +//

      Results

      +// {this.state.results} +//
      : '' +// } +//
      +// +// Insufficient balance: after this transaction you would have less than 1 LBC in your wallet. +// +//
      +// ); +// } +// }); const TransactionList = (props) => { const { - transactions, fetchingTransactions, transactionItems, } = props const rows = [] - if (transactions.length > 0) { + if (transactionItems.length > 0) { transactionItems.forEach(function(item) { rows.push( diff --git a/ui/js/reducers/wallet.js b/ui/js/reducers/wallet.js index 6628f394f..4b4f46db1 100644 --- a/ui/js/reducers/wallet.js +++ b/ui/js/reducers/wallet.js @@ -17,8 +17,20 @@ reducers[types.FETCH_TRANSACTIONS_STARTED] = function(state, action) { } reducers[types.FETCH_TRANSACTIONS_COMPLETED] = function(state, action) { + const oldTransactions = Object.assign({}, state.transactions) + const byId = Object.assign({}, oldTransactions.byId) + const { transactions } = action.data + + transactions.forEach((transaction) => { + byId[transaction.txid] = transaction + }) + + const newTransactions = Object.assign({}, oldTransactions, { + byId: byId + }) + return Object.assign({}, state, { - transactions: action.data.transactions, + transactions: newTransactions, fetchingTransactions: false }) } diff --git a/ui/js/selectors/wallet.js b/ui/js/selectors/wallet.js index 6e3809cce..f0f9d1254 100644 --- a/ui/js/selectors/wallet.js +++ b/ui/js/selectors/wallet.js @@ -8,44 +8,32 @@ export const _selectState = state => state.wallet || {} export const selectBalance = createSelector( _selectState, - (state) => { - return state.balance || 0 - } + (state) => state.balance || 0 ) export const selectTransactions = createSelector( _selectState, - (state) => state.transactions + (state) => state.transactions || {} +) + +export const selectTransactionsById = createSelector( + selectTransactions, + (transactions) => transactions.byId || {} ) export const selectTransactionItems = createSelector( - selectTransactions, - (transactions) => { - if (transactions.length == 0) return transactions - + selectTransactionsById, + (byId) => { const transactionItems = [] - const condensedTransactions = {} - - transactions.forEach(function(tx) { - const txid = tx["txid"]; - if (!(txid in condensedTransactions)) { - condensedTransactions[txid] = 0; - } - condensedTransactions[txid] += parseFloat(tx["value"]); - }); - transactions.reverse().forEach(function(tx) { - const txid = tx["txid"]; - if (condensedTransactions[txid] && condensedTransactions[txid] != 0) - { - transactionItems.push({ - id: txid, - date: tx["timestamp"] ? (new Date(parseInt(tx["timestamp"]) * 1000)) : null, - amount: condensedTransactions[txid] - }); - delete condensedTransactions[txid]; - } - }); - + const txids = Object.keys(byId) + txids.forEach((txid) => { + const tx = byId[txid] + transactionItems.push({ + id: txid, + date: tx.timestamp ? (new Date(parseInt(tx.timestamp) * 1000)) : null, + amount: parseFloat(tx.value) + }) + }) return transactionItems } ) -- 2.43.4 From 4fe3fe5dcb307c4d75e67f6f8423bfa8afc59402 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sun, 23 Apr 2017 12:55:47 +0700 Subject: [PATCH 11/56] Progress on sending transactions --- ui/js/actions/wallet.js | 62 ++++++++++++++++++++++++++++++++- ui/js/constants/action_types.js | 5 +++ ui/js/page/wallet/index.js | 16 +++++---- ui/js/page/wallet/view.jsx | 21 +++++------ ui/js/reducers/wallet.js | 55 +++++++++++++++++++++++++++++ ui/js/selectors/wallet.js | 15 ++++++++ 6 files changed, 155 insertions(+), 19 deletions(-) diff --git a/ui/js/actions/wallet.js b/ui/js/actions/wallet.js index f28ddcbdc..79b22d8d6 100644 --- a/ui/js/actions/wallet.js +++ b/ui/js/actions/wallet.js @@ -1,5 +1,13 @@ import * as types from 'constants/action_types' import lbry from 'lbry' +import { + selectDraftTransaction, + selectDraftTransactionAmount, + selectBalance, +} from 'selectors/wallet' +import { + doOpenModal, +} from 'actions/app' export function doUpdateBalance(balance) { return { @@ -59,7 +67,59 @@ export function doCheckAddressIsMine(address) { } } -export function doSendToAddress() { +export function doSendDraftTransaction() { return function(dispatch, getState) { + const state = getState() + const draftTx = selectDraftTransaction(state) + const balance = selectBalance(state) + const amount = selectDraftTransactionAmount(state) + + if (balance - amount < 1) { + return dispatch(doOpenModal('insufficientBalance')) + } + + dispatch({ + type: types.SEND_TRANSACTION_STARTED, + }) + + const successCallback = (results) => { + if(results === true) { + dispatch({ + type: types.SEND_TRANSACTION_COMPLETED, + }) + dispatch(doOpenModal('transactionSuccessful')) + } + else { + dispatch({ + type: types.SEND_TRANSACTION_FAILED, + data: { error: results } + }) + dispatch(doOpenModal('transactionFailed')) + } + } + + const errorCallback = (error) => { + dispatch({ + type: types.SEND_TRANSACTION_FAILED, + data: { error: error.message } + }) + dispatch(doOpenModal('transactionFailed')) + } + + lbry.sendToAddress(draftTx.amount, draftTx.address, successCallback, errorCallback); + } +} + +export function doSetDraftTransactionAmount(amount) { + return { + type: types.SET_DRAFT_TRANSACTION_AMOUNT, + data: { amount } + } +} + +export function doSetDraftTransactionAddress(address) { + return { + type: types.SET_DRAFT_TRANSACTION_ADDRESS, + data: { address } } } diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index cafea5c7d..a17303b86 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -28,3 +28,8 @@ export const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED' export const UPDATE_BALANCE = 'UPDATE_BALANCE' export const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED' export const CHECK_ADDRESS_IS_MINE_COMPLETED = 'CHECK_ADDRESS_IS_MINE_COMPLETED' +export const SET_DRAFT_TRANSACTION_AMOUNT = 'SET_DRAFT_TRANSACTION_AMOUNT' +export const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS' +export const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED' +export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED' +export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED' diff --git a/ui/js/page/wallet/index.js b/ui/js/page/wallet/index.js index 03d6fc48b..ac9095938 100644 --- a/ui/js/page/wallet/index.js +++ b/ui/js/page/wallet/index.js @@ -8,7 +8,9 @@ import { import { doGetNewAddress, doCheckAddressIsMine, - doSendToAddress, + doSendDraftTransaction, + doSetDraftTransactionAmount, + doSetDraftTransactionAddress, } from 'actions/wallet' import { selectCurrentPage, @@ -21,6 +23,8 @@ import { selectIsFetchingTransactions, selectReceiveAddress, selectGettingNewAddress, + selectDraftTransactionAmount, + selectDraftTransactionAddress, } from 'selectors/wallet' import WalletPage from './view' @@ -33,17 +37,17 @@ const select = (state) => ({ receiveAddress: selectReceiveAddress(state), gettingNewAddress: selectGettingNewAddress(state), modal: selectCurrentModal(state), - address: null, - amount: 0.0, + address: selectDraftTransactionAddress(state), + amount: selectDraftTransactionAmount(state), }) const perform = (dispatch) => ({ closeModal: () => dispatch(doCloseModal()), getNewAddress: () => dispatch(doGetNewAddress()), checkAddressIsMine: (address) => dispatch(doCheckAddressIsMine(address)), - sendToAddress: () => dispatch(doSendToAddress()), - setAmount: () => console.log('set amount'), - setAddress: () => console.log('set address'), + sendToAddress: () => dispatch(doSendDraftTransaction()), + setAmount: (event) => dispatch(doSetDraftTransactionAmount(event.target.value)), + setAddress: (event) => dispatch(doSetDraftTransactionAddress(event.target.value)), }) export default connect(select, perform)(WalletPage) diff --git a/ui/js/page/wallet/view.jsx b/ui/js/page/wallet/view.jsx index c4b1c4acb..e1c458f68 100644 --- a/ui/js/page/wallet/view.jsx +++ b/ui/js/page/wallet/view.jsx @@ -33,7 +33,7 @@ class AddressSection extends React.Component {
      - +
      @@ -57,8 +57,6 @@ const SendToAddressSection = (props) => { address, } = props - const results = null - return (
      @@ -66,26 +64,25 @@ const SendToAddressSection = (props) => {

      Send Credits

      - +
      - +
      0.0) || !address} />
      - { - results ? -
      -

      Results

      - {results} -
      : '' - } {modal == 'insufficientBalance' && Insufficient balance: after this transaction you would have less than 1 LBC in your wallet. } + {modal == 'transactionSuccessful' && + Your transaction was successfully placed in the queue. + } + {modal == 'transactionFailed' && + Something went wrong: + } ) } diff --git a/ui/js/reducers/wallet.js b/ui/js/reducers/wallet.js index 4b4f46db1..02cfe2485 100644 --- a/ui/js/reducers/wallet.js +++ b/ui/js/reducers/wallet.js @@ -2,12 +2,18 @@ import * as types from 'constants/action_types' const reducers = {} const address = sessionStorage.getItem('receiveAddress') +const buildDraftTransaction = () => ({ + amount: undefined, + address: undefined +}) + const defaultState = { balance: 0, transactions: [], fetchingTransactions: false, receiveAddress: address, gettingNewAddress: false, + draftTransaction: buildDraftTransaction() } reducers[types.FETCH_TRANSACTIONS_STARTED] = function(state, action) { @@ -69,6 +75,55 @@ reducers[types.CHECK_ADDRESS_IS_MINE_COMPLETED] = function(state, action) { }) } +reducers[types.SET_DRAFT_TRANSACTION_AMOUNT] = function(state, action) { + const oldDraft = state.draftTransaction + const newDraft = Object.assign({}, oldDraft, { + amount: parseFloat(action.data.amount) + }) + + return Object.assign({}, state, { + draftTransaction: newDraft + }) +} + +reducers[types.SET_DRAFT_TRANSACTION_ADDRESS] = function(state, action) { + const oldDraft = state.draftTransaction + const newDraft = Object.assign({}, oldDraft, { + address: action.data.address + }) + + return Object.assign({}, state, { + draftTransaction: newDraft + }) +} + +reducers[types.SEND_TRANSACTION_STARTED] = function(state, action) { + const newDraftTransaction = Object.assign({}, state.draftTransaction, { + sending: true + }) + + return Object.assign({}, state, { + draftTransaction: newDraftTransaction + }) +} + +reducers[types.SEND_TRANSACTION_COMPLETED] = function(state, action) { + return Object.assign({}, state, { + draftTransaction: buildDraftTransaction() + }) +} + +reducers[types.SEND_TRANSACTION_FAILED] = function(state, action) { + const newDraftTransaction = Object.assign({}, state.draftTransaction, { + sending: false, + error: action.data.error + }) + + return Object.assign({}, state, { + draftTransaction: newDraftTransaction + }) +} + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/wallet.js b/ui/js/selectors/wallet.js index f0f9d1254..5b8dc96a9 100644 --- a/ui/js/selectors/wallet.js +++ b/ui/js/selectors/wallet.js @@ -92,3 +92,18 @@ export const shouldCheckAddressIsMine = createSelector( return true } ) + +export const selectDraftTransaction = createSelector( + _selectState, + (state) => state.draftTransaction || buildDraftTransaction() +) + +export const selectDraftTransactionAmount = createSelector( + selectDraftTransaction, + (draft) => draft.amount +) + +export const selectDraftTransactionAddress = createSelector( + selectDraftTransaction, + (draft) => draft.address +) -- 2.43.4 From ecd2063fed856bfff8df96768b5b136c925afb09 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sun, 23 Apr 2017 16:56:50 +0700 Subject: [PATCH 12/56] Progress on featured content --- ui/js/actions/content.js | 29 ++++ ui/js/app.js | 2 +- ui/js/component/auth.js | 2 +- ui/js/component/fileTile/index.js | 13 ++ .../{file-tile.js => fileTile/view.jsx} | 14 +- ui/js/component/fileTileStream/index.js | 13 ++ ui/js/component/fileTileStream/view.jsx | 129 +++++++++++++++++ ui/js/component/header/view.jsx | 4 +- ui/js/component/router/view.jsx | 4 +- ui/js/component/video/index.js | 42 ++++++ ui/js/component/video/view.jsx | 130 ++++++++++++++++++ ui/js/constants/action_types.js | 4 + ui/js/page/discover/index.js | 17 +++ ui/js/page/{discover.js => discover/view.jsx} | 35 +++-- ui/js/page/file-list.js | 17 ++- ui/js/page/reward.js | 2 +- ui/js/page/rewards.js | 2 +- ui/js/page/search.js | 2 +- ui/js/page/settings.js | 2 +- ui/js/page/show.js | 2 +- ui/js/reducers/content.js | 31 +++++ ui/js/selectors/content.js | 37 +++++ ui/js/store.js | 2 + ui/js/triggers.js | 11 ++ 24 files changed, 500 insertions(+), 46 deletions(-) create mode 100644 ui/js/actions/content.js create mode 100644 ui/js/component/fileTile/index.js rename ui/js/component/{file-tile.js => fileTile/view.jsx} (96%) create mode 100644 ui/js/component/fileTileStream/index.js create mode 100644 ui/js/component/fileTileStream/view.jsx create mode 100644 ui/js/component/video/index.js create mode 100644 ui/js/component/video/view.jsx create mode 100644 ui/js/page/discover/index.js rename ui/js/page/{discover.js => discover/view.jsx} (64%) create mode 100644 ui/js/reducers/content.js create mode 100644 ui/js/selectors/content.js diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js new file mode 100644 index 000000000..a4e582ffe --- /dev/null +++ b/ui/js/actions/content.js @@ -0,0 +1,29 @@ +import * as types from 'constants/action_types' +import lbry from 'lbry' +import lbryio from 'lbryio'; + +export function doFetchFeaturedContent() { + return function(dispatch, getState) { + const state = getState() + + dispatch({ + type: types.FETCH_FEATURED_CONTENT_STARTED, + }) + + const success = ({ Categories, Uris }) => { + dispatch({ + type: types.FETCH_FEATURED_CONTENT_COMPLETED, + data: { + categories: Categories, + uris: Uris, + } + }) + } + + const failure = () => { + } + + lbryio.call('discover', 'list', { version: "early-access" } ) + .then(success, failure) + } +} diff --git a/ui/js/app.js b/ui/js/app.js index a7c7dd188..88ad0eef8 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -38,7 +38,7 @@ module.exports = app; // import {FileListDownloaded, FileListPublished} from './page/file-list.js'; // import Header from './component/header.js'; // import {Modal, ExpandableModal} from './component/modal.js'; -// import {Link} from './component/link.js'; +// import {Link} from './component/link'; // // // const {remote, ipcRenderer, shell} = require('electron'); diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index dc36367be..2575d2e9c 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -2,7 +2,7 @@ import React from "react"; import lbryio from "../lbryio.js"; import Modal from "./modal.js"; import ModalPage from "./modal-page.js"; -import {Link, RewardLink} from "../component/link.js"; +import {Link, RewardLink} from "../component/link"; import {FormRow} from "../component/form.js"; import {CreditAmount, Address} from "../component/common.js"; import {getLocal, getSession, setSession, setLocal} from '../utils.js'; diff --git a/ui/js/component/fileTile/index.js b/ui/js/component/fileTile/index.js new file mode 100644 index 000000000..b410bc9c2 --- /dev/null +++ b/ui/js/component/fileTile/index.js @@ -0,0 +1,13 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import FileTile from './view' + +const select = (state) => ({ +}) + +const perform = (dispatch) => ({ +}) + +export default connect(select, perform)(FileTile) diff --git a/ui/js/component/file-tile.js b/ui/js/component/fileTile/view.jsx similarity index 96% rename from ui/js/component/file-tile.js rename to ui/js/component/fileTile/view.jsx index ae8e0e3fe..b85b0ec6a 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/fileTile/view.jsx @@ -1,10 +1,10 @@ import React from 'react'; -import lbry from '../lbry.js'; -import lbryuri from '../lbryuri.js'; +import lbry from 'lbry.js'; +import lbryuri from 'lbryuri.js'; import Link from 'component/link'; -import {FileActions} from '../component/file-actions.js'; -import {BusyMessage, TruncatedText, FilePrice} from '../component/common.js'; -import UriIndicator from '../component/channel-indicator.js'; +import {FileActions} from 'component/file-actions.js'; +import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js'; +import UriIndicator from 'component/channel-indicator.js'; /*should be merged into FileTile once FileTile is refactored to take a single id*/ export let FileTileStream = React.createClass({ @@ -217,7 +217,7 @@ export let FileCardStream = React.createClass({ } }); -export let FileTile = React.createClass({ +let FileTile = React.createClass({ _isMounted: false, _isResolvePending: false, @@ -281,4 +281,4 @@ export let FileTile = React.createClass({ ; } -}); +}); \ No newline at end of file diff --git a/ui/js/component/fileTileStream/index.js b/ui/js/component/fileTileStream/index.js new file mode 100644 index 000000000..b1536b363 --- /dev/null +++ b/ui/js/component/fileTileStream/index.js @@ -0,0 +1,13 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import FileTileStream from './view' + +const select = (state) => ({ +}) + +const perform = (dispatch) => ({ +}) + +export default connect(select, perform)(FileTileStream) diff --git a/ui/js/component/fileTileStream/view.jsx b/ui/js/component/fileTileStream/view.jsx new file mode 100644 index 000000000..d9c851352 --- /dev/null +++ b/ui/js/component/fileTileStream/view.jsx @@ -0,0 +1,129 @@ +import React from 'react'; +import lbry from 'lbry.js'; +import lbryuri from 'lbryuri.js'; +import Link from 'component/link'; +import { + FileActions +} from 'component/file-actions.js'; +import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js'; +import UriIndicator from 'component/channel-indicator.js'; + +/*should be merged into FileTile once FileTile is refactored to take a single id*/ +const FileTileStream = React.createClass({ + _fileInfoSubscribeId: null, + _isMounted: null, + + propTypes: { + uri: React.PropTypes.string, + metadata: React.PropTypes.object, + contentType: React.PropTypes.string.isRequired, + outpoint: React.PropTypes.string, + hasSignature: React.PropTypes.bool, + signatureIsValid: React.PropTypes.bool, + hideOnRemove: React.PropTypes.bool, + hidePrice: React.PropTypes.bool, + obscureNsfw: React.PropTypes.bool + }, + getInitialState: function() { + return { + showNsfwHelp: false, + isHidden: false, + } + }, + getDefaultProps: function() { + return { + obscureNsfw: !lbry.getClientSetting('showNsfw'), + hidePrice: false, + hasSignature: false, + } + }, + componentDidMount: function() { + this._isMounted = true; + if (this.props.hideOnRemove) { + this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); + } + }, + componentWillUnmount: function() { + if (this._fileInfoSubscribeId) { + lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); + } + }, + onFileInfoUpdate: function(fileInfo) { + if (!fileInfo && this._isMounted && this.props.hideOnRemove) { + this.setState({ + isHidden: true + }); + } + }, + handleMouseOver: function() { + if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { + this.setState({ + showNsfwHelp: true, + }); + } + }, + handleMouseOut: function() { + if (this.state.showNsfwHelp) { + this.setState({ + showNsfwHelp: false, + }); + } + }, + render: function() { + if (this.state.isHidden) { + return null; + } + + const uri = lbryuri.normalize(this.props.uri); + const metadata = this.props.metadata; + const isConfirmed = !!metadata; + const title = isConfirmed ? metadata.title : uri; + const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; + return ( +
      +
      +
      + +
      +
      +
      + { !this.props.hidePrice + ? + : null} + +

      + + + {title} + + +

      +
      +
      + +
      +
      +

      + + {isConfirmed + ? metadata.description + : This file is pending confirmation.} + +

      +
      +
      +
      + {this.state.showNsfwHelp + ?
      +

      + This content is Not Safe For Work. + To view adult content, please change your . +

      +
      + : null} +
      + ); + } +}); + +export default FileTileStream diff --git a/ui/js/component/header/view.jsx b/ui/js/component/header/view.jsx index 34627e14a..e54bf3751 100644 --- a/ui/js/component/header/view.jsx +++ b/ui/js/component/header/view.jsx @@ -1,6 +1,6 @@ import React from 'react'; -import lbryuri from '../lbryuri.js'; -import {Icon, CreditAmount} from './common.js'; +import lbryuri from 'lbryuri.js'; +import {Icon, CreditAmount} from 'component/common.js'; import Link from 'component/link'; let Header = React.createClass({ diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index 208bb3f6d..9f553c9b7 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -1,13 +1,12 @@ import React from 'react'; import SettingsPage from 'page/settings.js'; import HelpPage from 'page/help'; -import WatchPage from 'page/watch.js'; import ReportPage from 'page/report.js'; import StartPage from 'page/start.js'; import WalletPage from 'page/wallet'; import DetailPage from 'page/show.js'; import PublishPage from 'page/publish.js'; -import DiscoverPage from 'page/discover.js'; +import DiscoverPage from 'page/discover'; import SplashScreen from 'component/splash.js'; import DeveloperPage from 'page/developer.js'; import { @@ -30,7 +29,6 @@ const Router = (props) => { return route(currentPage, { 'settings': , 'help': , - 'watch': , 'report': , 'downloaded': , 'published': , diff --git a/ui/js/component/video/index.js b/ui/js/component/video/index.js new file mode 100644 index 000000000..94978f702 --- /dev/null +++ b/ui/js/component/video/index.js @@ -0,0 +1,42 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import { + doCloseModal, +} from 'actions/app' +import { + selectCurrentModal, +} from 'selectors/app' +import { + doWatchVideo, + doLoadVideo, +} from 'actions/content' +import { + selectLoadingCurrentUri, + selectCurrentUriFileReadyToPlay, + selectCurrentUriIsPlaying, + selectCurrentUriFileInfo, + selectDownloadingCurrentUri, +} from 'selectors/file_info' +import { + selectCurrentUriCostInfo, +} from 'selectors/cost_info' +import Video from './view' + +const select = (state) => ({ + costInfo: selectCurrentUriCostInfo(state), + fileInfo: selectCurrentUriFileInfo(state), + modal: selectCurrentModal(state), + isLoading: selectLoadingCurrentUri(state), + readyToPlay: selectCurrentUriFileReadyToPlay(state), + isDownloading: selectDownloadingCurrentUri(state), +}) + +const perform = (dispatch) => ({ + loadVideo: () => dispatch(doLoadVideo()), + watchVideo: (elem) => dispatch(doWatchVideo()), + closeModal: () => dispatch(doCloseModal()), +}) + +export default connect(select, perform)(Video) \ No newline at end of file diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx new file mode 100644 index 000000000..86d9a99ea --- /dev/null +++ b/ui/js/component/video/view.jsx @@ -0,0 +1,130 @@ +import React from 'react'; +import { + Icon, + Thumbnail, +} from 'component/common'; +import FilePrice from 'component/filePrice' +import Link from 'component/link'; +import Modal from 'component/modal'; + +const plyr = require('plyr') + +class Video extends React.Component { + constructor(props) { + super(props) + + // TODO none of this mouse handling stuff seems to actually do anything? + this._controlsHideDelay = 3000 // Note: this needs to be shorter than the built-in delay in Electron, or Electron will hide the controls before us + this._controlsHideTimeout = null + this.state = {} + } + handleMouseMove() { + if (this._controlsTimeout) { + clearTimeout(this._controlsTimeout); + } + + if (!this.state.controlsShown) { + this.setState({ + controlsShown: true, + }); + } + this._controlsTimeout = setTimeout(() => { + if (!this.isMounted) { + return; + } + + this.setState({ + controlsShown: false, + }); + }, this._controlsHideDelay); + } + + handleMouseLeave() { + if (this._controlsTimeout) { + clearTimeout(this._controlsTimeout); + } + + if (this.state.controlsShown) { + this.setState({ + controlsShown: false, + }); + } + } + + onWatchClick() { + this.props.watchVideo().then(() => { + if (!this.props.modal) { + this.setState({ + isPlaying: true + }) + } + }) + } + + startPlaying() { + this.setState({ + isPlaying: true + }) + } + + render() { + const { + readyToPlay = false, + thumbnail, + metadata, + isLoading, + isDownloading, + fileInfo, + } = this.props + const { + isPlaying = false, + } = this.state + + let loadStatusMessage = '' + + if (isLoading) { + loadStatusMessage = "Requesting stream... it may sit here for like 15-20 seconds in a really awkward way... we're working on it" + } else if (isDownloading) { + loadStatusMessage = "Downloading stream... not long left now!" + } + + return ( +
      { + isPlaying ? + !readyToPlay ? + this is the world's worst loading screen and we shipped our software with it anyway...

      {loadStatusMessage}
      : + : +
      + +
      + }
      + ); + } +} + +class VideoPlayer extends React.PureComponent { + componentDidMount() { + const elem = this.refs.video + const { + downloadPath, + contentType, + } = this.props + const players = plyr.setup(elem) + players[0].play() + } + + render() { + const { + downloadPath, + contentType, + } = this.props + + return ( + + ) + } +} + +export default Video \ No newline at end of file diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index a17303b86..e0fae5d24 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -33,3 +33,7 @@ export const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS' export const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED' export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED' export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED' + +// Content +export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED' +export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED' diff --git a/ui/js/page/discover/index.js b/ui/js/page/discover/index.js new file mode 100644 index 000000000..cdea28f62 --- /dev/null +++ b/ui/js/page/discover/index.js @@ -0,0 +1,17 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + selectFeaturedContentByCategory +} from 'selectors/content' +import DiscoverPage from './view' + +const select = (state) => ({ + featuredContentByCategory: selectFeaturedContentByCategory(state), +}) + +const perform = (dispatch) => ({ +}) + +export default connect(select, perform)(DiscoverPage) diff --git a/ui/js/page/discover.js b/ui/js/page/discover/view.jsx similarity index 64% rename from ui/js/page/discover.js rename to ui/js/page/discover/view.jsx index 912a38f8c..a8ad512b7 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover/view.jsx @@ -1,27 +1,26 @@ import React from 'react'; -import lbry from '../lbry.js'; -import lighthouse from '../lighthouse.js'; -import {FileTile} from '../component/file-tile.js'; -import Link from 'component/link'; -import {ToolTip} from '../component/tooltip.js'; +import lbryio from 'lbryio.js'; +import FileTile from 'component/fileTile'; +import { FileTileStream } from 'component/fileTileStream' +import {ToolTip} from 'component/tooltip.js'; const communityCategoryToolTipText = ('Community Content is a public space where anyone can share content with the ' + 'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' + '"five" to put your content here!'); -let FeaturedCategory = React.createClass({ - render: function() { - return (
      - { this.props.category ? -

      {this.props.category} - { this.props.category.match(/^community/i) ? - - : '' }

      - : '' } - { this.props.names.map((name) => { return }) } -
      ) - } -}) +const FeaturedCategory = (props) => { + const { + category, + names, + } = props + + return
      +

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

      + {names.map(name => )} +
      +} let DiscoverPage = React.createClass({ getInitialState: function() { diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index 8ae732dfe..7d609b3f4 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -1,14 +1,13 @@ import React from 'react'; -import lbry from '../lbry.js'; -import lbryuri from '../lbryuri.js'; +import lbry from 'lbry.js'; +import lbryuri from 'lbryuri.js'; import Link from 'component/link'; -import FormField from '../component/form.js'; -import {SubHeader} from '../component/header.js'; -import {FileTileStream} from '../component/file-tile.js'; -import rewards from '../rewards.js'; -import lbryio from '../lbryio.js'; -import {BusyMessage, Thumbnail} from '../component/common.js'; - +import {FormField} from 'component/form.js'; +import SubHeader from '../component/sub-header'; +import {FileTileStream} from 'component/fileTile'; +import rewards from 'rewards.js'; +import lbryio from 'lbryio.js'; +import {BusyMessage, Thumbnail} from 'component/common.js'; export let FileListNav = React.createClass({ render: function() { diff --git a/ui/js/page/reward.js b/ui/js/page/reward.js index 2fb5b3e64..0a8aeebc6 100644 --- a/ui/js/page/reward.js +++ b/ui/js/page/reward.js @@ -1,6 +1,6 @@ import React from 'react'; import lbryio from '../lbryio.js'; -import {Link} from '../component/link.js'; +import {Link} from '../component/link'; import Notice from '../component/notice.js'; import {CreditAmount} from '../component/common.js'; // diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index 3462517c9..429a49a5e 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -5,7 +5,7 @@ import {CreditAmount, Icon} from '../component/common.js'; import rewards from '../rewards.js'; import Modal from '../component/modal.js'; import {WalletNav} from './wallet.js'; -import {RewardLink} from '../component/link.js'; +import {RewardLink} from '../component/link'; const RewardTile = React.createClass({ propTypes: { diff --git a/ui/js/page/search.js b/ui/js/page/search.js index dafeb30cf..f51541bcc 100644 --- a/ui/js/page/search.js +++ b/ui/js/page/search.js @@ -4,7 +4,7 @@ import lbryio from '../lbryio.js'; import lbryuri from '../lbryuri.js'; import lighthouse from '../lighthouse.js'; import {FileTile, FileTileStream} from '../component/file-tile.js'; -import {Link} from '../component/link.js'; +import {Link} from '../component/link'; import {ToolTip} from '../component/tooltip.js'; import {BusyMessage} from '../component/common.js'; diff --git a/ui/js/page/settings.js b/ui/js/page/settings.js index cbc0765a8..09f45a9c5 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings.js @@ -1,6 +1,6 @@ import React from 'react'; import {FormField, FormRow} from '../component/form.js'; -import {SubHeader} from '../component/header.js'; +import {SubHeader} from '../component/sub-header.js'; import lbry from '../lbry.js'; export let SettingsNav = React.createClass({ diff --git a/ui/js/page/show.js b/ui/js/page/show.js index f37994b7a..7624a8e17 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -2,7 +2,7 @@ import React from 'react'; import lbry from '../lbry.js'; import lighthouse from '../lighthouse.js'; import lbryuri from '../lbryuri.js'; -import {Video} from '../page/watch.js' +import Video from 'component/video' import {TruncatedText, Thumbnail, FilePrice, BusyMessage} from '../component/common.js'; import {FileActions} from '../component/file-actions.js'; import UriIndicator from '../component/channel-indicator.js'; diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js new file mode 100644 index 000000000..aefdb11c7 --- /dev/null +++ b/ui/js/reducers/content.js @@ -0,0 +1,31 @@ +import * as types from 'constants/action_types' + +const reducers = {} +const defaultState = { +} + +reducers[types.FETCH_FEATURED_CONTENT_STARTED] = function(state, action) { + return Object.assign({}, state, { + fetchingFeaturedContent: true + }) +} + +reducers[types.FETCH_FEATURED_CONTENT_COMPLETED] = function(state, action) { + const { + uris + } = action.data + const newFeaturedContent = Object.assign({}, state.featuredContent, { + byCategory: uris, + }) + + return Object.assign({}, state, { + fetchingFeaturedContent: false, + featuredContent: newFeaturedContent + }) +} + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js new file mode 100644 index 000000000..7e5905ecb --- /dev/null +++ b/ui/js/selectors/content.js @@ -0,0 +1,37 @@ +import { createSelector } from 'reselect' +import { + selectDaemonReady, + selectCurrentPage, +} from 'selectors/app' + +export const _selectState = state => state.content || {} + +export const selectFeaturedContent = createSelector( + _selectState, + (state) => state.featuredContent || {} +) + +export const selectFeaturedContentByCategory = createSelector( + selectFeaturedContent, + (featuredContent) => featuredContent.byCategory || {} +) + +export const selectFetchingFeaturedContent = createSelector( + _selectState, + (state) => !!state.fetchingFeaturedContent +) + +export const shouldFetchFeaturedContent = createSelector( + selectDaemonReady, + selectCurrentPage, + selectFetchingFeaturedContent, + selectFeaturedContentByCategory, + (daemonReady, page, fetching, byCategory) => { + if (!daemonReady) return false + if (page != 'discover') return false + if (fetching) return false + if (Object.keys(byCategory).length != 0) return false + + return true + } +) diff --git a/ui/js/store.js b/ui/js/store.js index 3d40d0d92..8b3d9e8a4 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -6,6 +6,7 @@ import { createLogger } from 'redux-logger' import appReducer from 'reducers/app'; +import contentReducer from 'reducers/content'; import walletReducer from 'reducers/wallet' function isFunction(object) { @@ -18,6 +19,7 @@ function isNotFunction(object) { const reducers = redux.combineReducers({ app: appReducer, + content: contentReducer, wallet: walletReducer, }); diff --git a/ui/js/triggers.js b/ui/js/triggers.js index 85d44f360..8fe951df8 100644 --- a/ui/js/triggers.js +++ b/ui/js/triggers.js @@ -2,10 +2,16 @@ import { shouldFetchTransactions, shouldGetReceiveAddress, } from 'selectors/wallet' +import { + shouldFetchFeaturedContent, +} from 'selectors/content' import { doFetchTransactions, doGetNewAddress, } from 'actions/wallet' +import { + doFetchFeaturedContent, +} from 'actions/content' const triggers = [] @@ -19,6 +25,11 @@ triggers.push({ action: doGetNewAddress }) +triggers.push({ + selector: shouldFetchFeaturedContent, + action: doFetchFeaturedContent, +}) + const runTriggers = function() { triggers.forEach(function(trigger) { const state = app.store.getState(); -- 2.43.4 From 5c3772dc863151895801e5256f2c37a4eb91931d Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sun, 23 Apr 2017 21:01:00 +0700 Subject: [PATCH 13/56] Featured content --- ui/js/actions/content.js | 27 +++ ui/js/component/fileCardStream/index.js | 7 + ui/js/component/fileCardStream/view.jsx | 110 +++++++++ ui/js/component/fileTile/index.js | 4 + ui/js/component/fileTile/view.jsx | 295 ++---------------------- ui/js/constants/action_types.js | 2 + ui/js/page/discover/view.jsx | 1 + ui/js/reducers/content.js | 42 ++++ ui/js/selectors/content.js | 5 + 9 files changed, 219 insertions(+), 274 deletions(-) create mode 100644 ui/js/component/fileCardStream/index.js create mode 100644 ui/js/component/fileCardStream/view.jsx diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index a4e582ffe..08411bab3 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -2,6 +2,29 @@ import * as types from 'constants/action_types' import lbry from 'lbry' import lbryio from 'lbryio'; +export function doResolveUri(dispatch, uri) { + dispatch({ + type: types.RESOLVE_URI_STARTED, + data: { uri } + }) + + lbry.resolve({uri: uri}).then((resolutionInfo) => { + const { + claim, + certificate, + } = resolutionInfo + + dispatch({ + type: types.RESOLVE_URI_COMPLETED, + data: { + uri, + claim, + certificate, + } + }) + }) +} + export function doFetchFeaturedContent() { return function(dispatch, getState) { const state = getState() @@ -18,6 +41,10 @@ export function doFetchFeaturedContent() { uris: Uris, } }) + + Object.keys(Uris).forEach((category) => { + Uris[category].forEach((uri) => doResolveUri(dispatch, uri)) + }) } const failure = () => { diff --git a/ui/js/component/fileCardStream/index.js b/ui/js/component/fileCardStream/index.js new file mode 100644 index 000000000..ec4800f32 --- /dev/null +++ b/ui/js/component/fileCardStream/index.js @@ -0,0 +1,7 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import FileCardStream from './view' + +export default connect()(FileCardStream) diff --git a/ui/js/component/fileCardStream/view.jsx b/ui/js/component/fileCardStream/view.jsx new file mode 100644 index 000000000..1dca3d48f --- /dev/null +++ b/ui/js/component/fileCardStream/view.jsx @@ -0,0 +1,110 @@ +import React from 'react'; +import lbry from 'lbry.js'; +import lbryuri from 'lbryuri.js'; +import Link from 'component/link'; +import {FileActions} from 'component/file-actions.js'; +import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js'; +import UriIndicator from 'component/channel-indicator.js'; + +const FileCardStream = React.createClass({ + _fileInfoSubscribeId: null, + _isMounted: null, + _metadata: null, + + + propTypes: { + uri: React.PropTypes.string, + claimInfo: React.PropTypes.object, + outpoint: React.PropTypes.string, + hideOnRemove: React.PropTypes.bool, + hidePrice: React.PropTypes.bool, + obscureNsfw: React.PropTypes.bool + }, + getInitialState: function() { + return { + showNsfwHelp: false, + isHidden: false, + } + }, + getDefaultProps: function() { + return { + obscureNsfw: !lbry.getClientSetting('showNsfw'), + hidePrice: false, + hasSignature: false, + } + }, + componentDidMount: function() { + this._isMounted = true; + if (this.props.hideOnRemove) { + this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); + } + }, + componentWillUnmount: function() { + if (this._fileInfoSubscribeId) { + lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); + } + }, + onFileInfoUpdate: function(fileInfo) { + if (!fileInfo && this._isMounted && this.props.hideOnRemove) { + this.setState({ + isHidden: true + }); + } + }, + handleMouseOver: function() { + this.setState({ + hovered: true, + }); + }, + handleMouseOut: function() { + this.setState({ + hovered: false, + }); + }, + render: function() { + if (this.state.isHidden) { + return null; + } + + const uri = lbryuri.normalize(this.props.uri); + const metadata = this.props.metadata; + const isConfirmed = !!metadata; + const title = isConfirmed ? metadata.title : uri; + const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; + const primaryUrl = '?show=' + uri; + return ( +
      +
      + +
      +
      {title}
      +
      + { !this.props.hidePrice ? : null} + +
      +
      +
      +
      + + {isConfirmed + ? metadata.description + : This file is pending confirmation.} + +
      +
      + {this.state.showNsfwHelp && this.state.hovered + ?
      +

      + This content is Not Safe For Work. + To view adult content, please change your . +

      +
      + : null} +
      +
      + ); + } +}); + +export default FileCardStream diff --git a/ui/js/component/fileTile/index.js b/ui/js/component/fileTile/index.js index b410bc9c2..e24dade66 100644 --- a/ui/js/component/fileTile/index.js +++ b/ui/js/component/fileTile/index.js @@ -2,9 +2,13 @@ import React from 'react' import { connect } from 'react-redux' +import { + selectResolvedUris, +} from 'selectors/content' import FileTile from './view' const select = (state) => ({ + resolvedUris: selectResolvedUris(state), }) const perform = (dispatch) => ({ diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index b85b0ec6a..b80616dcc 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -2,283 +2,30 @@ import React from 'react'; import lbry from 'lbry.js'; import lbryuri from 'lbryuri.js'; import Link from 'component/link'; -import {FileActions} from 'component/file-actions.js'; -import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js'; -import UriIndicator from 'component/channel-indicator.js'; +import FileCardStream from 'component/fileCardStream' +import FileTileStream from 'component/fileTileStream' +import FileActions from 'component/fileActions'; -/*should be merged into FileTile once FileTile is refactored to take a single id*/ -export let FileTileStream = React.createClass({ - _fileInfoSubscribeId: null, - _isMounted: null, +class FileTile extends React.Component { + render() { + const { + displayStyle, + uri, + claim, + } = this.props - propTypes: { - uri: React.PropTypes.string, - metadata: React.PropTypes.object, - contentType: React.PropTypes.string.isRequired, - outpoint: React.PropTypes.string, - hasSignature: React.PropTypes.bool, - signatureIsValid: React.PropTypes.bool, - hideOnRemove: React.PropTypes.bool, - hidePrice: React.PropTypes.bool, - obscureNsfw: React.PropTypes.bool - }, - getInitialState: function() { - return { - showNsfwHelp: false, - isHidden: false, - } - }, - getDefaultProps: function() { - return { - obscureNsfw: !lbry.getClientSetting('showNsfw'), - hidePrice: false, - hasSignature: false, - } - }, - componentDidMount: function() { - this._isMounted = true; - if (this.props.hideOnRemove) { - this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); - } - }, - componentWillUnmount: function() { - if (this._fileInfoSubscribeId) { - lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); - } - }, - onFileInfoUpdate: function(fileInfo) { - if (!fileInfo && this._isMounted && this.props.hideOnRemove) { - this.setState({ - isHidden: true - }); - } - }, - handleMouseOver: function() { - if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { - this.setState({ - showNsfwHelp: true, - }); - } - }, - handleMouseOut: function() { - if (this.state.showNsfwHelp) { - this.setState({ - showNsfwHelp: false, - }); - } - }, - render: function() { - if (this.state.isHidden) { - return null; - } - - const uri = lbryuri.normalize(this.props.uri); - const metadata = this.props.metadata; - const isConfirmed = !!metadata; - const title = isConfirmed ? metadata.title : uri; - const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; - const primaryUrl = "?show=" + uri; - return ( -
      - -
      -
      -
      -
      -
      - { !this.props.hidePrice - ? - : null} -
      {uri}
      -

      {title}

      -
      -
      - - {isConfirmed - ? metadata.description - : This file is pending confirmation.} - -
      -
      -
      -
      - {this.state.showNsfwHelp - ?
      -

      - This content is Not Safe For Work. - To view adult content, please change your . -

      -
      - : null} -
      - ); - } -}); - -export let FileCardStream = React.createClass({ - _fileInfoSubscribeId: null, - _isMounted: null, - _metadata: null, - - - propTypes: { - uri: React.PropTypes.string, - claimInfo: React.PropTypes.object, - outpoint: React.PropTypes.string, - hideOnRemove: React.PropTypes.bool, - hidePrice: React.PropTypes.bool, - obscureNsfw: React.PropTypes.bool - }, - getInitialState: function() { - return { - showNsfwHelp: false, - isHidden: false, - } - }, - getDefaultProps: function() { - return { - obscureNsfw: !lbry.getClientSetting('showNsfw'), - hidePrice: false, - hasSignature: false, - } - }, - componentDidMount: function() { - this._isMounted = true; - if (this.props.hideOnRemove) { - this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); - } - }, - componentWillUnmount: function() { - if (this._fileInfoSubscribeId) { - lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); - } - }, - onFileInfoUpdate: function(fileInfo) { - if (!fileInfo && this._isMounted && this.props.hideOnRemove) { - this.setState({ - isHidden: true - }); - } - }, - handleMouseOver: function() { - this.setState({ - hovered: true, - }); - }, - handleMouseOut: function() { - this.setState({ - hovered: false, - }); - }, - render: function() { - if (this.state.isHidden) { - return null; - } - - const uri = lbryuri.normalize(this.props.uri); - const metadata = this.props.metadata; - const isConfirmed = !!metadata; - const title = isConfirmed ? metadata.title : uri; - const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; - const primaryUrl = '?show=' + uri; - return ( -
      -
      - -
      -
      {title}
      -
      - { !this.props.hidePrice ? : null} - -
      -
      -
      -
      - - {isConfirmed - ? metadata.description - : This file is pending confirmation.} - -
      -
      - {this.state.showNsfwHelp && this.state.hovered - ?
      -

      - This content is Not Safe For Work. - To view adult content, please change your . -

      -
      - : null} -
      -
      - ); - } -}); - -let FileTile = React.createClass({ - _isMounted: false, - _isResolvePending: false, - - propTypes: { - uri: React.PropTypes.string.isRequired, - }, - - getInitialState: function() { - return { - outpoint: null, - claimInfo: null - } - }, - resolve: function(uri) { - this._isResolvePending = true; - lbry.resolve({uri: uri}).then((resolutionInfo) => { - this._isResolvePending = false; - if (this._isMounted && resolutionInfo && resolutionInfo.claim && resolutionInfo.claim.value && - resolutionInfo.claim.value.stream && resolutionInfo.claim.value.stream.metadata) { - // In case of a failed lookup, metadata will be null, in which case the component will never display - this.setState({ - claimInfo: resolutionInfo.claim, - }); + if(!claim) { + if (displayStyle == 'card') { + return } - }); - }, - componentWillReceiveProps: function(nextProps) { - if (nextProps.uri != this.props.uri) { - this.setState(this.getInitialState()); - this.resolve(nextProps.uri); - } - }, - componentDidMount: function() { - this._isMounted = true; - this.resolve(this.props.uri); - }, - componentWillUnmount: function() { - this._isMounted = false; - }, - render: function() { - if (!this.state.claimInfo) { - if (this.props.displayStyle == 'card') { - return - } - if (this.props.showEmpty) - { - return this._isResolvePending ? - : -
      {lbryuri.normalize(this.props.uri)} is unclaimed.
      ; - } - return null; + return null } - const {txid, nout, has_signature, signature_is_valid, - value: {stream: {metadata, source: {contentType}}}} = this.state.claimInfo; - - return this.props.displayStyle == 'card' ? - : - ; + return displayStyle == 'card' ? + + : + } -}); \ No newline at end of file +} + +export default FileTile \ No newline at end of file diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index e0fae5d24..0172662f6 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -37,3 +37,5 @@ export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED' // Content export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED' export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED' +export const RESOLVE_URI_STARTED = 'RESOLVE_URI_STARTED' +export const RESOLVE_URI_COMPLETED = 'RESOLVE_URI_COMPLETED' diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index a8ad512b7..30d434ef3 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -11,6 +11,7 @@ const communityCategoryToolTipText = ('Community Content is a public space where const FeaturedCategory = (props) => { const { category, + resolvedUris, names, } = props diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index aefdb11c7..5b9cc2e04 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -24,6 +24,48 @@ reducers[types.FETCH_FEATURED_CONTENT_COMPLETED] = function(state, action) { }) } +reducers[types.RESOLVE_URI_STARTED] = function(state, action) { + const { + uri + } = action.data + + const oldResolving = state.resolvingUris || [] + const newResolving = Object.assign([], oldResolving) + if (newResolving.indexOf(uri) == -1) newResolving.push(uri) + + return Object.assign({}, state, { + resolvingUris: newResolving + }) +} + +reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { + const { + uri, + claim, + certificate, + } = action.data + const resolvedUris = Object.assign({}, state.resolvedUris) + const resolvingUris = state.resolvingUris + const index = state.resolvingUris.indexOf(uri) + const newResolvingUris = [ + ...resolvingUris.slice(0, index), + ...resolvingUris.slice(index + 1) + ] + + resolvedUris[uri] = { + claim: claim, + certificate: certificate, + } + + + const newState = Object.assign({}, state, { + resolvedUris: resolvedUris, + resolvingUris: newResolvingUris, + }) + + return Object.assign({}, state, newState) +} + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index 7e5905ecb..e2222cb91 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -35,3 +35,8 @@ export const shouldFetchFeaturedContent = createSelector( return true } ) + +export const selectResolvedUris = createSelector( + _selectState, + (state) => state.resolvedUris || {} +) -- 2.43.4 From 6c4ad8cee92f71989b3c07cc6460b4109a3f4113 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sun, 23 Apr 2017 23:10:45 +0700 Subject: [PATCH 14/56] Starting on downloaded/published content --- ui/js/actions/content.js | 46 ++++++ ui/js/component/fileList/index.js | 7 + ui/js/component/fileList/view.jsx | 103 ++++++++++++ ui/js/component/router/view.jsx | 6 +- ui/js/constants/action_types.js | 4 + ui/js/page/file-list.js | 217 ------------------------- ui/js/page/fileListDownloaded/index.js | 17 ++ ui/js/page/fileListDownloaded/view.jsx | 69 ++++++++ ui/js/page/fileListPublished/index.js | 7 + ui/js/page/fileListPublished/view.jsx | 86 ++++++++++ ui/js/reducers/content.js | 40 +++++ ui/js/selectors/content.js | 50 ++++++ ui/js/triggers.js | 16 ++ 13 files changed, 447 insertions(+), 221 deletions(-) create mode 100644 ui/js/component/fileList/index.js create mode 100644 ui/js/component/fileList/view.jsx delete mode 100644 ui/js/page/file-list.js create mode 100644 ui/js/page/fileListDownloaded/index.js create mode 100644 ui/js/page/fileListDownloaded/view.jsx create mode 100644 ui/js/page/fileListPublished/index.js create mode 100644 ui/js/page/fileListPublished/view.jsx diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 08411bab3..8b5499dc5 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -25,6 +25,52 @@ export function doResolveUri(dispatch, uri) { }) } +export function doFetchDownloadedContent() { + return function(dispatch, getState) { + const state = getState() + + dispatch({ + type: types.FETCH_DOWNLOADED_CONTENT_STARTED, + }) + + lbry.claim_list_mine().then((myClaimInfos) => { + lbry.file_list().then((fileInfos) => { + const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout); + + dispatch({ + type: types.FETCH_DOWNLOADED_CONTENT_COMPLETED, + data: { + fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), + } + }) + }); + }); + } +} + +export function doFetchPublishedContent() { + return function(dispatch, getState) { + const state = getState() + + dispatch({ + type: types.FETCH_PUBLISHED_CONTENT_STARTED, + }) + + lbry.claim_list_mine().then((claimInfos) => { + lbry.file_list().then((fileInfos) => { + const myClaimOutpoints = claimInfos.map(({txid, nout}) => txid + ':' + nout) + + dispatch({ + type: types.FETCH_PUBLISHED_CONTENT_COMPLETED, + data: { + fileInfos: fileInfos.filter(({outpoint}) => myClaimOutpoints.includes(outpoint)), + } + }) + }) + }) + } +} + export function doFetchFeaturedContent() { return function(dispatch, getState) { const state = getState() diff --git a/ui/js/component/fileList/index.js b/ui/js/component/fileList/index.js new file mode 100644 index 000000000..0a6c65c70 --- /dev/null +++ b/ui/js/component/fileList/index.js @@ -0,0 +1,7 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import FileList from './view' + +export default connect()(FileList) diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx new file mode 100644 index 000000000..7d1cc9409 --- /dev/null +++ b/ui/js/component/fileList/view.jsx @@ -0,0 +1,103 @@ +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 FileTileStream from 'component/fileTile'; +import rewards from 'rewards.js'; +import lbryio from 'lbryio.js'; +import {BusyMessage, Thumbnail} from 'component/common.js'; + +const FileList = React.createClass({ + _sortFunctions: { + date: function(fileInfos) { + return fileInfos.slice().reverse(); + }, + title: function(fileInfos) { + return fileInfos.slice().sort(function(fileInfo1, fileInfo2) { + const title1 = fileInfo1.metadata ? fileInfo1.metadata.title.toLowerCase() : fileInfo1.name; + const title2 = fileInfo2.metadata ? fileInfo2.metadata.title.toLowerCase() : fileInfo2.name; + if (title1 < title2) { + return -1; + } else if (title1 > title2) { + return 1; + } else { + return 0; + } + }); + }, + filename: function(fileInfos) { + return fileInfos.slice().sort(function({file_name: fileName1}, {file_name: fileName2}) { + const fileName1Lower = fileName1.toLowerCase(); + const fileName2Lower = fileName2.toLowerCase(); + if (fileName1Lower < fileName2Lower) { + return -1; + } else if (fileName2Lower > fileName1Lower) { + return 1; + } else { + return 0; + } + }); + }, + }, + propTypes: { + fileInfos: React.PropTypes.array.isRequired, + hidePrices: React.PropTypes.bool, + }, + getDefaultProps: function() { + return { + hidePrices: false, + }; + }, + getInitialState: function() { + return { + sortBy: 'date', + }; + }, + handleSortChanged: function(event) { + this.setState({ + sortBy: event.target.value, + }); + }, + render: function() { + var content = [], + seenUris = {}; + + const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); + for (let {outpoint, name, channel_name, metadata, mime_type, claim_id, has_signature, signature_is_valid} of fileInfosSorted) { + if (seenUris[name] || !claim_id) { + continue; + } + + let streamMetadata; + if (metadata) { + streamMetadata = metadata.stream.metadata; + } else { + streamMetadata = null; + } + + + const uri = lbryuri.build({contentName: name, channelName: channel_name}); + seenUris[name] = true; + content.push(); + } + + return ( +
      + + Sort by { ' ' } + + + + + + + {content} +
      + ); + } +}); + +export default FileList diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index 9f553c9b7..940c41646 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -9,10 +9,8 @@ import PublishPage from 'page/publish.js'; import DiscoverPage from 'page/discover'; import SplashScreen from 'component/splash.js'; import DeveloperPage from 'page/developer.js'; -import { - FileListDownloaded, - FileListPublished -} from 'page/file-list.js'; +import FileListDownloaded from 'page/fileListDownloaded' +import FileListPublished from 'page/fileListPublished' const route = (page, routesMap) => { const component = routesMap[page] diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 0172662f6..776da726b 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -39,3 +39,7 @@ export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED' export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED' export const RESOLVE_URI_STARTED = 'RESOLVE_URI_STARTED' export const RESOLVE_URI_COMPLETED = 'RESOLVE_URI_COMPLETED' +export const FETCH_DOWNLOADED_CONTENT_STARTED = 'FETCH_DOWNLOADED_CONTENT_STARTED' +export const FETCH_DOWNLOADED_CONTENT_COMPLETED = 'FETCH_DOWNLOADED_CONTENT_COMPLETED' +export const FETCH_PUBLISHED_CONTENT_STARTED = 'FETCH_PUBLISHED_CONTENT_STARTED' +export const FETCH_PUBLISHED_CONTENT_COMPLETED = 'FETCH_PUBLISHED_CONTENT_COMPLETED' diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js deleted file mode 100644 index 7d609b3f4..000000000 --- a/ui/js/page/file-list.js +++ /dev/null @@ -1,217 +0,0 @@ -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 SubHeader from '../component/sub-header'; -import {FileTileStream} from 'component/fileTile'; -import rewards from 'rewards.js'; -import lbryio from 'lbryio.js'; -import {BusyMessage, Thumbnail} from 'component/common.js'; - -export let FileListNav = React.createClass({ - render: function() { - return ; - } -}); - -export let FileListDownloaded = React.createClass({ - _isMounted: false, - - getInitialState: function() { - return { - fileInfos: null, - }; - }, - componentDidMount: function() { - this._isMounted = true; - - lbry.claim_list_mine().then((myClaimInfos) => { - if (!this._isMounted) { return; } - - lbry.file_list().then((fileInfos) => { - if (!this._isMounted) { return; } - - const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout); - this.setState({ - fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), - }); - }); - }); - }, - componentWillUnmount: function() { - this._isMounted = false; - }, - render: function() { - let content = ""; - if (this.state.fileInfos === null) { - content = ; - } else if (!this.state.fileInfos.length) { - content = You haven't downloaded anything from LBRY yet. Go !; - } else { - content = ; - } - return ( -
      - - {content} -
      - ); - } -}); - -export let FileListPublished = React.createClass({ - _isMounted: false, - - getInitialState: function () { - return { - fileInfos: null, - }; - }, - _requestPublishReward: function() { - lbryio.call('reward', 'list', {}).then(function(userRewards) { - //already rewarded - if (userRewards.filter(function (reward) { - return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID; - }).length) { - return; - } - else { - rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {}) - } - }, () => {}); - }, - componentDidMount: function () { - this._isMounted = true; - this._requestPublishReward(); - - lbry.claim_list_mine().then((claimInfos) => { - if (!this._isMounted) { return; } - - lbry.file_list().then((fileInfos) => { - if (!this._isMounted) { return; } - - const myClaimOutpoints = claimInfos.map(({txid, nout}) => txid + ':' + nout); - this.setState({ - fileInfos: fileInfos.filter(({outpoint}) => myClaimOutpoints.includes(outpoint)), - }); - }); - }); - }, - componentWillUnmount: function() { - this._isMounted = false; - }, - render: function () { - let content = null; - if (this.state.fileInfos === null) { - content = ; - } - else if (!this.state.fileInfos.length) { - content = You haven't published anything to LBRY yet. Try !; - } - else { - content = ; - } - return ( -
      - - {content} -
      - ); - } -}); - -export let FileList = React.createClass({ - _sortFunctions: { - date: function(fileInfos) { - return fileInfos.slice().reverse(); - }, - title: function(fileInfos) { - return fileInfos.slice().sort(function(fileInfo1, fileInfo2) { - const title1 = fileInfo1.metadata ? fileInfo1.metadata.title.toLowerCase() : fileInfo1.name; - const title2 = fileInfo2.metadata ? fileInfo2.metadata.title.toLowerCase() : fileInfo2.name; - if (title1 < title2) { - return -1; - } else if (title1 > title2) { - return 1; - } else { - return 0; - } - }); - }, - filename: function(fileInfos) { - return fileInfos.slice().sort(function({file_name: fileName1}, {file_name: fileName2}) { - const fileName1Lower = fileName1.toLowerCase(); - const fileName2Lower = fileName2.toLowerCase(); - if (fileName1Lower < fileName2Lower) { - return -1; - } else if (fileName2Lower > fileName1Lower) { - return 1; - } else { - return 0; - } - }); - }, - }, - propTypes: { - fileInfos: React.PropTypes.array.isRequired, - hidePrices: React.PropTypes.bool, - }, - getDefaultProps: function() { - return { - hidePrices: false, - }; - }, - getInitialState: function() { - return { - sortBy: 'date', - }; - }, - handleSortChanged: function(event) { - this.setState({ - sortBy: event.target.value, - }); - }, - render: function() { - var content = [], - seenUris = {}; - - const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); - for (let {outpoint, name, channel_name, metadata, mime_type, claim_id, has_signature, signature_is_valid} of fileInfosSorted) { - if (seenUris[name] || !claim_id) { - continue; - } - - let streamMetadata; - if (metadata) { - streamMetadata = metadata.stream.metadata; - } else { - streamMetadata = null; - } - - - const uri = lbryuri.build({contentName: name, channelName: channel_name}); - seenUris[name] = true; - content.push(); - } - - return ( -
      - - Sort by { ' ' } - - - - - - - {content} -
      - ); - } -}); \ No newline at end of file diff --git a/ui/js/page/fileListDownloaded/index.js b/ui/js/page/fileListDownloaded/index.js new file mode 100644 index 000000000..e808533bf --- /dev/null +++ b/ui/js/page/fileListDownloaded/index.js @@ -0,0 +1,17 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + selectDownloadedContent, +} from 'selectors/content' +import FileListDownloaded from './view' + +const select = (state) => ({ + downloadedContent: selectDownloadedContent(state), +}) + +const perform = (dispatch) => ({ +}) + +export default connect(select, perform)(FileListDownloaded) diff --git a/ui/js/page/fileListDownloaded/view.jsx b/ui/js/page/fileListDownloaded/view.jsx new file mode 100644 index 000000000..8893a158b --- /dev/null +++ b/ui/js/page/fileListDownloaded/view.jsx @@ -0,0 +1,69 @@ +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 {FileTileStream} from 'component/fileTile'; +import rewards from 'rewards.js'; +import lbryio from 'lbryio.js'; +import {BusyMessage, Thumbnail} from 'component/common.js'; +import FileList from 'component/fileList' + +const FileListDownloaded = (props) => { + // + return ( +
      item
      + ) +} +// const FileListDownloaded = React.createClass({ +// _isMounted: false, + +// getInitialState: function() { +// return { +// fileInfos: null, +// }; +// }, +// componentDidMount: function() { +// this._isMounted = true; +// document.title = "Downloaded Files"; + +// lbry.claim_list_mine().then((myClaimInfos) => { +// if (!this._isMounted) { return; } + +// lbry.file_list().then((fileInfos) => { +// if (!this._isMounted) { return; } + +// const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout); +// this.setState({ +// fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), +// }); +// }); +// }); +// }, +// componentWillUnmount: function() { +// this._isMounted = false; +// }, +// render: function() { +// if (this.state.fileInfos === null) { +// return ( +//
      +// +//
      +// ); +// } else if (!this.state.fileInfos.length) { +// return ( +//
      +// You haven't downloaded anything from LBRY yet. Go ! +//
      +// ); +// } else { +// return ( +//
      +// +//
      +// ); +// } +// } +// }); + +export default FileListDownloaded diff --git a/ui/js/page/fileListPublished/index.js b/ui/js/page/fileListPublished/index.js new file mode 100644 index 000000000..3bf2724cf --- /dev/null +++ b/ui/js/page/fileListPublished/index.js @@ -0,0 +1,7 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import FileListPublished from './view' + +export default connect()(FileListPublished) diff --git a/ui/js/page/fileListPublished/view.jsx b/ui/js/page/fileListPublished/view.jsx new file mode 100644 index 000000000..70d170079 --- /dev/null +++ b/ui/js/page/fileListPublished/view.jsx @@ -0,0 +1,86 @@ +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 {FileTileStream} from 'component/fileTile'; +import rewards from 'rewards.js'; +import lbryio from 'lbryio.js'; +import {BusyMessage, Thumbnail} from 'component/common.js'; +import FileList from 'component/fileList' + +const FileListPublished = (props) => { + // + return ( +
      published content
      + ) +} + +// const FileListPublished = React.createClass({ +// _isMounted: false, + +// getInitialState: function () { +// return { +// fileInfos: null, +// }; +// }, +// _requestPublishReward: function() { +// lbryio.call('reward', 'list', {}).then(function(userRewards) { +// //already rewarded +// if (userRewards.filter(function (reward) { +// return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID; +// }).length) { +// return; +// } +// else { +// rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {}) +// } +// }); +// }, +// componentDidMount: function () { +// this._isMounted = true; +// this._requestPublishReward(); +// document.title = "Published Files"; + +// lbry.claim_list_mine().then((claimInfos) => { +// if (!this._isMounted) { return; } + +// lbry.file_list().then((fileInfos) => { +// if (!this._isMounted) { return; } + +// const myClaimOutpoints = claimInfos.map(({txid, nout}) => txid + ':' + nout); +// this.setState({ +// fileInfos: fileInfos.filter(({outpoint}) => myClaimOutpoints.includes(outpoint)), +// }); +// }); +// }); +// }, +// componentWillUnmount: function() { +// this._isMounted = false; +// }, +// render: function () { +// if (this.state.fileInfos === null) { +// return ( +//
      +// +//
      +// ); +// } +// else if (!this.state.fileInfos.length) { +// return ( +//
      +// You haven't published anything to LBRY yet. Try ! +//
      +// ); +// } +// else { +// return ( +//
      +// +//
      +// ); +// } +// } +// }); + +export default FileListPublished diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index 5b9cc2e04..496bdcb79 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -66,6 +66,46 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { return Object.assign({}, state, newState) } +reducers[types.FETCH_DOWNLOADED_CONTENT_STARTED] = function(state, action) { + return Object.assign({}, state, { + fetchingDownloadedContent: true, + }) +} + +reducers[types.FETCH_DOWNLOADED_CONTENT_COMPLETED] = function(state, action) { + const { + fileInfos + } = action.data + const newDownloadedContent = Object.assign({}, state.downloadedContent, { + fileInfos + }) + + return Object.assign({}, state, { + downloadedContent: newDownloadedContent, + fetchingDownloadedContent: false, + }) +} + +reducers[types.FETCH_PUBLISHED_CONTENT_STARTED] = function(state, action) { + return Object.assign({}, state, { + fetchingPublishedContent: true, + }) +} + +reducers[types.FETCH_PUBLISHED_CONTENT_COMPLETED] = function(state, action) { + const { + fileInfos + } = action.data + const newPublishedContent = Object.assign({}, state.publishedContent, { + fileInfos + }) + + return Object.assign({}, state, { + publishedContent: newPublishedContent, + fetchingPublishedContent: 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/content.js b/ui/js/selectors/content.js index e2222cb91..f854cf90d 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -40,3 +40,53 @@ export const selectResolvedUris = createSelector( _selectState, (state) => state.resolvedUris || {} ) + +export const selectFetchingDownloadedContent = createSelector( + _selectState, + (state) => !!state.fetchingDownloadedContent +) + +export const selectDownloadedContent = createSelector( + _selectState, + (state) => state.downloadedContent || {} +) + +export const shouldFetchDownloadedContent = createSelector( + selectDaemonReady, + selectCurrentPage, + selectFetchingDownloadedContent, + selectDownloadedContent, + (daemonReady, page, fetching, content) => { + if (!daemonReady) return false + if (page != 'downloaded') return false + if (fetching) return false + if (Object.keys(content).length != 0) return false + + return true + } +) + +export const selectFetchingPublishedContent = createSelector( + _selectState, + (state) => !!state.fetchingPublishedContent +) + +export const selectPublishedContent = createSelector( + _selectState, + (state) => state.publishedContent || {} +) + +export const shouldFetchPublishedContent = createSelector( + selectDaemonReady, + selectCurrentPage, + selectFetchingPublishedContent, + selectPublishedContent, + (daemonReady, page, fetching, content) => { + if (!daemonReady) return false + if (page != 'published') return false + if (fetching) return false + if (Object.keys(content).length != 0) return false + + return true + } +) diff --git a/ui/js/triggers.js b/ui/js/triggers.js index 8fe951df8..e6e360220 100644 --- a/ui/js/triggers.js +++ b/ui/js/triggers.js @@ -4,6 +4,8 @@ import { } from 'selectors/wallet' import { shouldFetchFeaturedContent, + shouldFetchDownloadedContent, + shouldFetchPublishedContent, } from 'selectors/content' import { doFetchTransactions, @@ -11,6 +13,8 @@ import { } from 'actions/wallet' import { doFetchFeaturedContent, + doFetchDownloadedContent, + doFetchPublishedContent, } from 'actions/content' const triggers = [] @@ -30,6 +34,18 @@ triggers.push({ action: doFetchFeaturedContent, }) +triggers.push({ + selector: shouldFetchDownloadedContent, + action: doFetchDownloadedContent, +}) + +triggers.push({ + selector: shouldFetchPublishedContent, + action: doFetchPublishedContent, +}) + +console.log(triggers) + const runTriggers = function() { triggers.forEach(function(trigger) { const state = app.store.getState(); -- 2.43.4 From bb9a71805bf93ce9fa4ec8ebb485c58fbc4feb52 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 24 Apr 2017 14:25:27 +0700 Subject: [PATCH 15/56] Loading show page content fast, resolving cost and file info. Rewards and uploaded/downloaded files currently broken --- ui/js/actions/content.js | 58 ++++++++++ ui/js/actions/rewards.js | 36 ++++++ ui/js/component/fileCardStream/index.js | 12 +- ui/js/component/fileCardStream/view.jsx | 4 +- ui/js/component/router/view.jsx | 4 +- ui/js/constants/action_types.js | 4 + ui/js/page/discover/view.jsx | 2 +- ui/js/page/showPage/index.js | 27 +++++ ui/js/page/{show.js => showPage/view.jsx} | 130 ++++++++++++++++++---- ui/js/reducers/app.js | 4 +- ui/js/reducers/content.js | 72 ++++++++++++ ui/js/reducers/rewards.js | 11 ++ ui/js/selectors/app.js | 24 ++-- ui/js/selectors/content.js | 104 +++++++++++++++++ ui/js/selectors/rewards.js | 3 + ui/js/selectors/wallet.js | 2 +- ui/js/store.js | 6 +- ui/js/triggers.js | 14 ++- 18 files changed, 473 insertions(+), 44 deletions(-) create mode 100644 ui/js/actions/rewards.js create mode 100644 ui/js/page/showPage/index.js rename ui/js/page/{show.js => showPage/view.jsx} (67%) create mode 100644 ui/js/reducers/rewards.js create mode 100644 ui/js/selectors/rewards.js diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 8b5499dc5..20d1e8423 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -1,6 +1,12 @@ import * as types from 'constants/action_types' import lbry from 'lbry' import lbryio from 'lbryio'; +import { + selectCurrentUri, +} from 'selectors/app' +import { + selectCurrentResolvedUriClaimOutpoint, +} from 'selectors/content' export function doResolveUri(dispatch, uri) { dispatch({ @@ -25,6 +31,34 @@ export function doResolveUri(dispatch, uri) { }) } +export function doFetchCurrentUriFileInfo() { + return function(dispatch, getState) { + const state = getState() + const uri = selectCurrentUri(state) + const outpoint = selectCurrentResolvedUriClaimOutpoint(state) + + console.log(outpoint) + + dispatch({ + type: types.FETCH_FILE_INFO_STARTED, + data: { + uri, + outpoint, + } + }) + + lbry.file_list({ outpoint }).then(fileInfo => { + dispatch({ + type: types.FETCH_FILE_INFO_COMPLETED, + data: { + uri, + fileInfo, + } + }) + }) + } +} + export function doFetchDownloadedContent() { return function(dispatch, getState) { const state = getState() @@ -100,3 +134,27 @@ export function doFetchFeaturedContent() { .then(success, failure) } } + +export function doFetchCurrentUriCostInfo() { + return function(dispatch, getState) { + const state = getState() + const uri = selectCurrentUri(state) + + dispatch({ + type: types.FETCH_COST_INFO_STARTED, + data: { + uri, + } + }) + + lbry.getCostInfo(uri).then(costInfo => { + dispatch({ + type: types.FETCH_COST_INFO_COMPLETED, + data: { + uri, + costInfo, + } + }) + }) + } +} diff --git a/ui/js/actions/rewards.js b/ui/js/actions/rewards.js new file mode 100644 index 000000000..84f65047f --- /dev/null +++ b/ui/js/actions/rewards.js @@ -0,0 +1,36 @@ +import * as types from 'constants/action_types' +import lbry from 'lbry' +import lbryio from 'lbryio'; +import rewards from 'rewards' + +export function doFetchRewards() { + return function(dispatch, getState) { + const state = getState() + + dispatch({ + type: types.FETCH_REWARDS_STARTED, + }) + + lbryio.call('reward', 'list', {}).then(function(userRewards) { + dispatch({ + type: types.FETCH_REWARDS_COMPLETED, + data: { userRewards } + }) + }); + } +} + +export function doClaimReward(rewardType) { + return function(dispatch, getState) { + try { + rewards.claimReward(rewards[rewardType]) + dispatch({ + type: types.REWARD_CLAIMED, + data: { + reward: rewards[rewardType] + } + }) + } catch(err) { + } + } +} diff --git a/ui/js/component/fileCardStream/index.js b/ui/js/component/fileCardStream/index.js index ec4800f32..854ae624f 100644 --- a/ui/js/component/fileCardStream/index.js +++ b/ui/js/component/fileCardStream/index.js @@ -2,6 +2,16 @@ import React from 'react' import { connect } from 'react-redux' +import { + doNavigate, +} from 'actions/app' import FileCardStream from './view' -export default connect()(FileCardStream) +const select = (state) => ({ +}) + +const perform = (dispatch) => ({ + navigate: (path) => dispatch(doNavigate(path)), +}) + +export default connect(select, perform)(FileCardStream) diff --git a/ui/js/component/fileCardStream/view.jsx b/ui/js/component/fileCardStream/view.jsx index 1dca3d48f..74c01d863 100644 --- a/ui/js/component/fileCardStream/view.jsx +++ b/ui/js/component/fileCardStream/view.jsx @@ -71,11 +71,11 @@ const FileCardStream = React.createClass({ const isConfirmed = !!metadata; const title = isConfirmed ? metadata.title : uri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; - const primaryUrl = '?show=' + uri; + const primaryUrl = 'show=' + uri; return (
      - + this.props.navigate(primaryUrl)} className="card__link">
      {title}
      diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index 940c41646..8f9a258f8 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -4,7 +4,7 @@ import HelpPage from 'page/help'; import ReportPage from 'page/report.js'; import StartPage from 'page/start.js'; import WalletPage from 'page/wallet'; -import DetailPage from 'page/show.js'; +import ShowPage from 'page/showPage'; import PublishPage from 'page/publish.js'; import DiscoverPage from 'page/discover'; import SplashScreen from 'component/splash.js'; @@ -35,7 +35,7 @@ const Router = (props) => { 'wallet': , 'send': , 'receive': , - 'show': , + 'show': , 'publish': , 'developer': , 'discover': , diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 776da726b..869208b4d 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -43,3 +43,7 @@ export const FETCH_DOWNLOADED_CONTENT_STARTED = 'FETCH_DOWNLOADED_CONTENT_STARTE export const FETCH_DOWNLOADED_CONTENT_COMPLETED = 'FETCH_DOWNLOADED_CONTENT_COMPLETED' export const FETCH_PUBLISHED_CONTENT_STARTED = 'FETCH_PUBLISHED_CONTENT_STARTED' export const FETCH_PUBLISHED_CONTENT_COMPLETED = 'FETCH_PUBLISHED_CONTENT_COMPLETED' +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' +export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED' diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index 30d434ef3..f9edc00dd 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -14,7 +14,7 @@ const FeaturedCategory = (props) => { resolvedUris, names, } = props - + return

      {category} {category && category.match(/^community/i) && } diff --git a/ui/js/page/showPage/index.js b/ui/js/page/showPage/index.js new file mode 100644 index 000000000..e6d08ac3a --- /dev/null +++ b/ui/js/page/showPage/index.js @@ -0,0 +1,27 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + selectCurrentUri, +} from 'selectors/app' +import { + selectCurrentResolvedUriClaim, + selectCurrentUriIsDownloaded, + selectCurrentUriFileInfo, + selectCurrentUriCostInfo, +} from 'selectors/content' +import ShowPage from './view' + +const select = (state) => ({ + claim: selectCurrentResolvedUriClaim(state), + uri: selectCurrentUri(state), + isDownloaded: selectCurrentUriIsDownloaded(state), + fileInfo: selectCurrentUriFileInfo(state), + costInfo: selectCurrentUriCostInfo(state), +}) + +const perform = (dispatch) => ({ +}) + +export default connect(select, perform)(ShowPage) diff --git a/ui/js/page/show.js b/ui/js/page/showPage/view.jsx similarity index 67% rename from ui/js/page/show.js rename to ui/js/page/showPage/view.jsx index 7624a8e17..4905738a6 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/showPage/view.jsx @@ -1,12 +1,17 @@ import React from 'react'; -import lbry from '../lbry.js'; -import lighthouse from '../lighthouse.js'; -import lbryuri from '../lbryuri.js'; -import Video from 'component/video' -import {TruncatedText, Thumbnail, FilePrice, BusyMessage} from '../component/common.js'; -import {FileActions} from '../component/file-actions.js'; -import UriIndicator from '../component/channel-indicator.js'; +import lbry from 'lbry.js'; +import lighthouse from 'lighthouse.js'; +import lbryuri from 'lbryuri.js'; +import {Video} from 'page/watch.js' +import { + TruncatedText, + Thumbnail, + FilePrice, + BusyMessage +} from 'component/common.js'; +import {FileActions} from 'component/file-actions.js'; import Link from 'component/link'; +import UriIndicator from 'component/channel-indicator.js'; var FormatItem = React.createClass({ propTypes: { @@ -16,11 +21,8 @@ var FormatItem = React.createClass({ outpoint: React.PropTypes.string, }, render: function() { - const {author, language, license} = this.props.metadata; - - if (!this.props.contentType && [author, language, license].filter((val) => {return !!val; }).length === 0) { - return null; - } + const {thumbnail, author, title, description, language, license} = this.props.metadata; + const mediaType = lbry.getMediaType(this.props.contentType); return ( @@ -243,21 +245,21 @@ let ShowPage = React.createClass({ render: function() { const metadata = this.state.metadata, - title = metadata ? this.state.metadata.title : this._uri; + title = metadata ? this.state.metadata.title : this._uri; let innerContent = ""; if (!this.state.uriLookupComplete || this.state.isFailed) { innerContent =
      -
      -

      {title}

      -
      -
      - { this.state.uriLookupComplete ? -

      This location is not yet in use. { ' ' }.

      : - - } -
      +
      +

      {title}

      +
      +
      + { this.state.uriLookupComplete ? +

      This location is not yet in use. { ' ' }.

      : + + } +
      ; } else if (this.state.claimType == "channel") { innerContent = @@ -282,3 +284,87 @@ let ShowPage = React.createClass({ }); export default ShowPage; + +// +// const ShowPage = (props) => { +// const { +// claim, +// uri, +// isDownloaded, +// fileInfo, +// costInfo, +// } = props +// const { +// txid, +// nout, +// has_signature, +// signature_is_valid, +// value, +// } = claim +// const { +// stream, +// } = value +// const { +// metadata, +// source, +// } = stream +// const { +// title, +// } = metadata +// const { +// contentType, +// } = source +// const { +// cost, +// includesData, +// } = costInfo +// const costIncludesData = includesData +// +// const outpoint = txid + ':' + nout; +// const uriLookupComplete = !!claim +// const hasSignature = has_signature +// const signatureIsValid = signature_is_valid +// +// return ( +//
      +//
      +// { contentType && contentType.startsWith('video/') ? +//
      +//
      +//
      +//
      +// {isDownloaded === false +// ? +// : null} +//

      {title}

      +// { uriLookupComplete ? +//
      +//
      +// +//
      +//
      +// +//
      +//
      : '' } +//
      +// { uriLookupComplete ? +//
      +//
      +// {metadata.description} +//
      +//
      +// :
      } +//
      +// { metadata ? +//
      +// +//
      : '' } +//
      +// +//
      +//
      +//
      +// ) +// } \ No newline at end of file diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index 7d02f582e..ffc13cd74 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -3,7 +3,7 @@ import * as types from 'constants/action_types' const reducers = {} const defaultState = { isLoaded: false, - currentPage: 'discover', + currentPath: 'discover', platform: process.platform, drawerOpen: sessionStorage.getItem('drawerOpen') || true, upgradeSkipped: sessionStorage.getItem('upgradeSkipped'), @@ -12,7 +12,7 @@ const defaultState = { reducers[types.NAVIGATE] = function(state, action) { return Object.assign({}, state, { - currentPage: action.data.path + currentPath: action.data.path, }) } diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index 496bdcb79..0d420237d 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -106,6 +106,78 @@ reducers[types.FETCH_PUBLISHED_CONTENT_COMPLETED] = function(state, action) { }) } +reducers[types.FETCH_FILE_INFO_STARTED] = function(state, action) { + const { + uri, + output, + } = action.data + const newFetchingFileInfos = Object.assign({}, state.fetchingFileInfos) + + newFetchingFileInfos[uri] = true + + return Object.assign({}, state, { + fetchingFileInfos: newFetchingFileInfos, + }) +} + +reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) { + const { + uri, + fileInfo, + } = action.data + const newFetchingFileInfos = Object.assign({}, state.fetchingFileInfos) + const fileInfos = Object.assign({}, state.fileInfos) + const byUri = Object.assign({}, fileInfos.byUri) + + byUri[uri] = fileInfo + delete newFetchingFileInfos[uri] + + const newFileInfos = Object.assign({}, fileInfos, { + byUri: byUri, + }) + + return Object.assign({}, state, { + fetchingFileInfos: newFetchingFileInfos, + fileInfos: newFileInfos, + }) +} + +reducers[types.FETCH_COST_INFO_STARTED] = function(state, action) { + const { + uri, + } = action.data + const fetchingCostInfos = Object.assign({}, state.fetchingCostInfos) + + fetchingCostInfos[uri] = true + + return Object.assign({}, state, { + fetchingCostInfos, + }) +} + +reducers[types.FETCH_COST_INFO_COMPLETED] = function(state, action) { + const { + uri, + costInfo, + } = action.data + + const newFetchingCostInfos = Object.assign({}, state.fetchingCostInfos) + const costInfos = Object.assign({}, state.costInfos) + const byUri = Object.assign({}, costInfos.byUri) + + byUri[uri] = costInfo + delete newFetchingCostInfos[uri] + + const newCostInfos = Object.assign({}, costInfos, { + byUri: byUri, + }) + + return Object.assign({}, state, { + fetchingCostInfos: newFetchingCostInfos, + costInfos: newCostInfos, + }) +} + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/reducers/rewards.js b/ui/js/reducers/rewards.js new file mode 100644 index 000000000..db20378e2 --- /dev/null +++ b/ui/js/reducers/rewards.js @@ -0,0 +1,11 @@ +import * as types from 'constants/action_types' + +const reducers = {} +const defaultState = { +} + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index fffd8b581..929967c87 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -4,23 +4,27 @@ export const _selectState = state => state.app || {} export const selectIsLoaded = createSelector( _selectState, - (state) => { - return state.isLoaded - } + (state) => state.isLoaded +) + +export const selectCurrentPath = createSelector( + _selectState, + (state) => state.currentPath ) export const selectCurrentPage = createSelector( - _selectState, - (state) => { - return state.currentPage - } + selectCurrentPath, + (path) => path.split('=')[0] +) + +export const selectCurrentUri = createSelector( + selectCurrentPath, + (path) => path.split('://')[1] ) export const selectPlatform = createSelector( _selectState, - (state) => { - return state.platform - } + (state) => state.platform ) export const selectUpdateUrl = createSelector( diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index f854cf90d..e4e82bc38 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -2,6 +2,7 @@ import { createSelector } from 'reselect' import { selectDaemonReady, selectCurrentPage, + selectCurrentUri, } from 'selectors/app' export const _selectState = state => state.content || {} @@ -41,6 +42,109 @@ export const selectResolvedUris = createSelector( (state) => state.resolvedUris || {} ) +export const selectCurrentResolvedUri = createSelector( + selectCurrentUri, + selectResolvedUris, + (uri, resolvedUris) => resolvedUris[uri] || {} +) + +export const selectCurrentResolvedUriClaim = createSelector( + selectCurrentResolvedUri, + (uri) => uri.claim || {} +) + +export const selectCurrentResolvedUriClaimOutpoint = createSelector( + selectCurrentResolvedUriClaim, + (claim) => `${claim.txid}:${claim.nout}` +) + +export const selectFileInfos = createSelector( + _selectState, + (state) => state.fileInfos || {} +) + +export const selectFileInfosByUri = createSelector( + selectFileInfos, + (fileInfos) => fileInfos.byUri || {} +) + +export const selectCurrentUriFileInfo = createSelector( + selectCurrentUri, + selectFileInfosByUri, + (uri, byUri) => byUri[uri] +) + +export const selectCurrentUriIsDownloaded = createSelector( + selectCurrentUriFileInfo, + (fileInfo) => fileInfo && fileInfo.length > 0 +) + +export const selectFetchingFileInfos = createSelector( + _selectState, + (state) => state.fetchingFileInfos || {} +) + +export const selectIsFetchingCurrentUriFileInfo = createSelector( + selectFetchingFileInfos, + selectCurrentUri, + (fetching, uri) => !!fetching[uri] +) + +export const selectCostInfos = createSelector( + _selectState, + (state) => state.costInfos || {} +) + +export const selectCostInfosByUri = createSelector( + selectCostInfos, + (costInfos) => costInfos.byUri || {} +) + +export const selectFetchingCostInfos = createSelector( + _selectState, + (state) => state.fetchingCostInfos || {} +) + +export const selectIsFetchingCurrentUriCostInfo = createSelector( + selectFetchingCostInfos, + selectCurrentUri, + (fetching, uri) => !!fetching[uri] +) + +export const selectCurrentUriCostInfo = createSelector( + selectCurrentUri, + selectCostInfosByUri, + (uri, byUri) => byUri[uri] || {} +) + +export const shouldFetchCurrentUriCostInfo = createSelector( + selectCurrentPage, + selectCurrentUri, + selectIsFetchingCurrentUriCostInfo, + selectCurrentUriCostInfo, + (page, uri, fetching, costInfo) => { + if (page != 'show') return false + if (fetching) return false + if (Object.keys(costInfo).length != 0) return false + + return true + } +) + +export const shouldFetchCurrentUriFileInfo = createSelector( + selectCurrentPage, + selectCurrentUri, + selectIsFetchingCurrentUriFileInfo, + selectCurrentUriFileInfo, + (page, uri, fetching, fileInfo) => { + if (page != 'show') return false + if (fetching) return false + if (fileInfo != undefined) return false + + return true + } +) + export const selectFetchingDownloadedContent = createSelector( _selectState, (state) => !!state.fetchingDownloadedContent diff --git a/ui/js/selectors/rewards.js b/ui/js/selectors/rewards.js new file mode 100644 index 000000000..30f8ecbf7 --- /dev/null +++ b/ui/js/selectors/rewards.js @@ -0,0 +1,3 @@ +import { createSelector } from 'reselect' + +export const _selectState = state => state.rewards || {} diff --git a/ui/js/selectors/wallet.js b/ui/js/selectors/wallet.js index 5b8dc96a9..2dd02ede8 100644 --- a/ui/js/selectors/wallet.js +++ b/ui/js/selectors/wallet.js @@ -95,7 +95,7 @@ export const shouldCheckAddressIsMine = createSelector( export const selectDraftTransaction = createSelector( _selectState, - (state) => state.draftTransaction || buildDraftTransaction() + (state) => state.draftTransaction || {} ) export const selectDraftTransactionAmount = createSelector( diff --git a/ui/js/store.js b/ui/js/store.js index 8b3d9e8a4..2aeef6730 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -7,6 +7,7 @@ import { } from 'redux-logger' import appReducer from 'reducers/app'; import contentReducer from 'reducers/content'; +import rewardsReducer from 'reducers/rewards' import walletReducer from 'reducers/wallet' function isFunction(object) { @@ -20,6 +21,7 @@ function isNotFunction(object) { const reducers = redux.combineReducers({ app: appReducer, content: contentReducer, + rewards: rewardsReducer, wallet: walletReducer, }); @@ -32,10 +34,10 @@ if (env === 'development') { middleware.push(logger) } -var createStoreWithMiddleware = redux.compose( +const createStoreWithMiddleware = redux.compose( redux.applyMiddleware(...middleware) )(redux.createStore); -var reduxStore = createStoreWithMiddleware(reducers); +const reduxStore = createStoreWithMiddleware(reducers); export default reduxStore; diff --git a/ui/js/triggers.js b/ui/js/triggers.js index e6e360220..63dddafcf 100644 --- a/ui/js/triggers.js +++ b/ui/js/triggers.js @@ -6,6 +6,8 @@ import { shouldFetchFeaturedContent, shouldFetchDownloadedContent, shouldFetchPublishedContent, + shouldFetchCurrentUriFileInfo, + shouldFetchCurrentUriCostInfo, } from 'selectors/content' import { doFetchTransactions, @@ -15,6 +17,8 @@ import { doFetchFeaturedContent, doFetchDownloadedContent, doFetchPublishedContent, + doFetchCurrentUriFileInfo, + doFetchCurrentUriCostInfo, } from 'actions/content' const triggers = [] @@ -44,7 +48,15 @@ triggers.push({ action: doFetchPublishedContent, }) -console.log(triggers) +triggers.push({ + selector: shouldFetchCurrentUriFileInfo, + action: doFetchCurrentUriFileInfo, +}) + +triggers.push({ + selector: shouldFetchCurrentUriCostInfo, + action: doFetchCurrentUriCostInfo, +}) const runTriggers = function() { triggers.forEach(function(trigger) { -- 2.43.4 From 768326fb4a23c81cdaebcc7a9272d54895abfe87 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 24 Apr 2017 21:17:36 +0700 Subject: [PATCH 16/56] Working on search --- ui/js/actions/content.js | 5 ++- ui/js/actions/search.js | 28 +++++++++++++ ui/js/component/header/index.js | 4 ++ ui/js/constants/action_types.js | 4 ++ ui/js/page/discover/index.js | 11 ++++++ ui/js/page/discover/view.jsx | 4 +- ui/js/page/showPage/view.jsx | 69 ++++++++++++++++++--------------- ui/js/reducers/search.js | 36 +++++++++++++++++ ui/js/selectors/search.js | 29 ++++++++++++++ ui/js/store.js | 2 + 10 files changed, 158 insertions(+), 34 deletions(-) create mode 100644 ui/js/actions/search.js create mode 100644 ui/js/reducers/search.js create mode 100644 ui/js/selectors/search.js diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 20d1e8423..d29b40003 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -1,9 +1,12 @@ import * as types from 'constants/action_types' import lbry from 'lbry' -import lbryio from 'lbryio'; +import lbryio from 'lbryio' import { selectCurrentUri, } from 'selectors/app' +import { + selectSearchTerm, +} from 'selectors/content' import { selectCurrentResolvedUriClaimOutpoint, } from 'selectors/content' diff --git a/ui/js/actions/search.js b/ui/js/actions/search.js new file mode 100644 index 000000000..e88515bcf --- /dev/null +++ b/ui/js/actions/search.js @@ -0,0 +1,28 @@ +import * as types from 'constants/action_types' +import lbry from 'lbry' +import lbryio from 'lbryio' +import lighthouse from 'lighthouse' +import { + selectSearchQuery, +} from 'selectors/search' + +export function doSearchContent(query) { + return function(dispatch, getState) { + const state = getState() + + dispatch({ + type: types.SEARCH_STARTED, + data: { query } + }) + + lighthouse.search(query).then(results => { + dispatch({ + type: types.SEARCH_COMPLETED, + data: { + query, + results, + } + }) + }) + } +} diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js index 3b9cb57ef..90b9293f6 100644 --- a/ui/js/component/header/index.js +++ b/ui/js/component/header/index.js @@ -9,6 +9,9 @@ import { import { doNavigate, } from 'actions/app' +import { + doSearchContent, +} from 'actions/search' import Header from './view' const select = (state) => ({ @@ -18,6 +21,7 @@ const select = (state) => ({ const perform = (dispatch) => ({ navigate: (path) => dispatch(doNavigate(path)), + search: (query) => dispatch(doSearchContent(query)), }) export default connect(select, perform)(Header) diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 869208b4d..85347e43a 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -47,3 +47,7 @@ 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' export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED' + +// Search +export const SEARCH_STARTED = 'SEARCH_STARTED' +export const SEARCH_COMPLETED = 'SEARCH_COMPLETED' diff --git a/ui/js/page/discover/index.js b/ui/js/page/discover/index.js index cdea28f62..e279ff4d6 100644 --- a/ui/js/page/discover/index.js +++ b/ui/js/page/discover/index.js @@ -5,10 +5,21 @@ import { import { selectFeaturedContentByCategory } from 'selectors/content' +import { + doSearchContent, +} from 'actions/search' +import { + selectIsSearching, + selectSearchQuery, + selectCurrentSearchResults, +} from 'selectors/search' import DiscoverPage from './view' const select = (state) => ({ featuredContentByCategory: selectFeaturedContentByCategory(state), + isSearching: selectIsSearching(state), + query: selectSearchQuery(state), + results: selectCurrentSearchResults(state), }) const perform = (dispatch) => ({ diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index f9edc00dd..fc0eb8220 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -14,7 +14,7 @@ const FeaturedCategory = (props) => { resolvedUris, names, } = props - + return

      {category} {category && category.match(/^community/i) && } @@ -60,6 +60,6 @@ let DiscoverPage = React.createClass({

      }; } -}); +}) export default DiscoverPage; diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx index 4905738a6..55c88d5ed 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/showPage/view.jsx @@ -2,7 +2,7 @@ import React from 'react'; import lbry from 'lbry.js'; import lighthouse from 'lighthouse.js'; import lbryuri from 'lbryuri.js'; -import {Video} from 'page/watch.js' +import Video from 'page/video' import { TruncatedText, Thumbnail, @@ -13,37 +13,44 @@ import {FileActions} from 'component/file-actions.js'; import Link from 'component/link'; import UriIndicator from 'component/channel-indicator.js'; -var FormatItem = React.createClass({ - propTypes: { - metadata: React.PropTypes.object, - contentType: React.PropTypes.string, - uri: React.PropTypes.string, - outpoint: React.PropTypes.string, - }, - render: function() { - const {thumbnail, author, title, description, language, license} = this.props.metadata; - const mediaType = lbry.getMediaType(this.props.contentType); +const FormatItem = (props) => { + const { + contentType, + metadata, + cost, + uri, + outpoint, + costIncludesData, + } = props + const { + thumbnail, + author, + title, + description, + language, + license + } = metadata; + const mediaType = lbry.getMediaType(contentType); - return ( -
      - - - - - - - - - - - - - - -
      Content-Type{this.props.contentType}
      Author{author}
      Language{language}
      License{license}
      - ); - } -}); + return ( + + + + + + + + + + + + + + + +
      Content-Type{contentType}
      Author{author}
      Language{language}
      License{license}
      + ) +} let ChannelPage = React.createClass({ render: function() { diff --git a/ui/js/reducers/search.js b/ui/js/reducers/search.js new file mode 100644 index 000000000..cabb98b61 --- /dev/null +++ b/ui/js/reducers/search.js @@ -0,0 +1,36 @@ +import * as types from 'constants/action_types' + +const reducers = {} +const defaultState = { +} + +reducers[types.SEARCH_STARTED] = function(state, action) { + const { + query, + } = action.data + + return Object.assign({}, state, { + searching: true, + query: query, + }) +} + +reducers[types.SEARCH_COMPLETED] = function(state, action) { + const { + query, + } = action.data + const newResults = Object.assign({}, state.results) + const newByQuery = Object.assign({}, newResults.byQuery) + newByQuery[query] = action.data.results + + return Object.assign({}, state, { + searching: false, + results: newResults, + }) +} + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/selectors/search.js b/ui/js/selectors/search.js new file mode 100644 index 000000000..240a4b6bc --- /dev/null +++ b/ui/js/selectors/search.js @@ -0,0 +1,29 @@ +import { createSelector } from 'reselect' + +export const _selectState = state => state.search || {} + +export const selectSearchQuery = createSelector( + _selectState, + (state) => state.query +) + +export const selectIsSearching = createSelector( + _selectState, + (state) => !!state.searching +) + +export const selectSearchResults = createSelector( + _selectState, + (state) => state.results || {} +) + +export const selectSearchResultsByQuery = createSelector( + selectSearchResults, + (results) => results.byQuery || {} +) + +export const selectCurrentSearchResults = createSelector( + selectSearchQuery, + selectSearchResultsByQuery, + (query, byQuery) => byQuery[query] || [] +) diff --git a/ui/js/store.js b/ui/js/store.js index 2aeef6730..ac789a824 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -8,6 +8,7 @@ import { import appReducer from 'reducers/app'; import contentReducer from 'reducers/content'; import rewardsReducer from 'reducers/rewards' +import searchReducer from 'reducers/search' import walletReducer from 'reducers/wallet' function isFunction(object) { @@ -22,6 +23,7 @@ const reducers = redux.combineReducers({ app: appReducer, content: contentReducer, rewards: rewardsReducer, + search: searchReducer, wallet: walletReducer, }); -- 2.43.4 From a9b7e8e45fd1b0557ff38434a43e31c0ea81461b Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 25 Apr 2017 12:47:12 +0700 Subject: [PATCH 17/56] webpack-dev-server --- ui/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/package.json b/ui/package.json index 4c4b9e937..4b1c563fe 100644 --- a/ui/package.json +++ b/ui/package.json @@ -3,7 +3,8 @@ "version": "0.10.0rc8", "description": "LBRY UI", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "webpack-dev-server --devtool eval --progress --colors --inline" }, "keywords": [ "lbry" @@ -52,6 +53,7 @@ "json-loader": "^0.5.4", "node-sass": "^3.13.0", "webpack": "^1.13.3", + "webpack-dev-server": "^2.4.4", "webpack-target-electron-renderer": "^0.4.0" } } -- 2.43.4 From 67b79fc3a0cc7ea9aa3e6361bb79284bd57ab3f5 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 25 Apr 2017 12:47:21 +0700 Subject: [PATCH 18/56] Search working --- ui/js/actions/search.js | 19 +++++ ui/js/component/fileTileStream/index.js | 4 + ui/js/component/fileTileStream/view.jsx | 3 +- ui/js/constants/action_types.js | 1 + ui/js/page/fileListDownloaded/index.js | 6 +- ui/js/page/fileListDownloaded/view.jsx | 32 ++++++-- ui/js/page/showPage/view.jsx | 104 ++---------------------- ui/js/reducers/search.js | 14 +++- ui/js/selectors/content.js | 5 ++ 9 files changed, 83 insertions(+), 105 deletions(-) diff --git a/ui/js/actions/search.js b/ui/js/actions/search.js index e88515bcf..154a52fd3 100644 --- a/ui/js/actions/search.js +++ b/ui/js/actions/search.js @@ -1,21 +1,40 @@ import * as types from 'constants/action_types' import lbry from 'lbry' import lbryio from 'lbryio' +import lbryuri from 'lbryuri' import lighthouse from 'lighthouse' import { selectSearchQuery, } from 'selectors/search' +import { + doResolveUri, +} from 'actions/content' export function doSearchContent(query) { return function(dispatch, getState) { const state = getState() + if (!query) { + return dispatch({ + type: types.SEARCH_CANCELLED, + }) + } + dispatch({ type: types.SEARCH_STARTED, data: { query } }) lighthouse.search(query).then(results => { + results.forEach(result => { + const uri = lbryuri.build({ + channelName: result.channel_name, + contentName: result.name, + claimId: result.channel_id || result.claim_id, + }) + doResolveUri(dispatch, uri.split('://')[1]) + }) + dispatch({ type: types.SEARCH_COMPLETED, data: { diff --git a/ui/js/component/fileTileStream/index.js b/ui/js/component/fileTileStream/index.js index b1536b363..03aaa299a 100644 --- a/ui/js/component/fileTileStream/index.js +++ b/ui/js/component/fileTileStream/index.js @@ -2,12 +2,16 @@ import React from 'react' import { connect } from 'react-redux' +import { + doNavigate, +} from 'actions/app' import FileTileStream from './view' const select = (state) => ({ }) const perform = (dispatch) => ({ + navigate: (path) => dispatch(doNavigate(path)) }) export default connect(select, perform)(FileTileStream) diff --git a/ui/js/component/fileTileStream/view.jsx b/ui/js/component/fileTileStream/view.jsx index d9c851352..ec710b717 100644 --- a/ui/js/component/fileTileStream/view.jsx +++ b/ui/js/component/fileTileStream/view.jsx @@ -79,11 +79,12 @@ const FileTileStream = React.createClass({ const isConfirmed = !!metadata; const title = isConfirmed ? metadata.title : uri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; + const { navigate } = this.props return (
      diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 85347e43a..5ca93c0ca 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -51,3 +51,4 @@ export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED' // Search export const SEARCH_STARTED = 'SEARCH_STARTED' export const SEARCH_COMPLETED = 'SEARCH_COMPLETED' +export const SEARCH_CANCELLED = 'SEARCH_CANCELLED' diff --git a/ui/js/page/fileListDownloaded/index.js b/ui/js/page/fileListDownloaded/index.js index e808533bf..ed27152c5 100644 --- a/ui/js/page/fileListDownloaded/index.js +++ b/ui/js/page/fileListDownloaded/index.js @@ -3,12 +3,14 @@ import { connect } from 'react-redux' import { - selectDownloadedContent, + selectDownloadedContentFileInfos, + selectFetchingDownloadedContent, } from 'selectors/content' import FileListDownloaded from './view' const select = (state) => ({ - downloadedContent: selectDownloadedContent(state), + downloadedContent: selectDownloadedContentFileInfos(state), + fetching: selectFetchingDownloadedContent(state), }) const perform = (dispatch) => ({ diff --git a/ui/js/page/fileListDownloaded/view.jsx b/ui/js/page/fileListDownloaded/view.jsx index 8893a158b..715eae94e 100644 --- a/ui/js/page/fileListDownloaded/view.jsx +++ b/ui/js/page/fileListDownloaded/view.jsx @@ -9,11 +9,33 @@ import lbryio from 'lbryio.js'; import {BusyMessage, Thumbnail} from 'component/common.js'; import FileList from 'component/fileList' -const FileListDownloaded = (props) => { - // - return ( -
      item
      - ) +class FileListDownloaded extends React.Component { + render() { + const { + downloadedContent, + fetching, + } = this.props + + if (fetching) { + return ( +
      + +
      + ); + } else if (!downloadedContent.length) { + return ( +
      + You haven't downloaded anything from LBRY yet. Go ! +
      + ); + } else { + return ( +
      + +
      + ); + } + } } // const FileListDownloaded = React.createClass({ // _isMounted: false, diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx index 55c88d5ed..81096317f 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/showPage/view.jsx @@ -17,19 +17,19 @@ const FormatItem = (props) => { const { contentType, metadata, + metadata: { + thumbnail, + author, + title, + description, + language, + license, + }, cost, uri, outpoint, costIncludesData, } = props - const { - thumbnail, - author, - title, - description, - language, - license - } = metadata; const mediaType = lbry.getMediaType(contentType); return ( @@ -288,90 +288,4 @@ let ShowPage = React.createClass({ return
      {innerContent}
      ; } -}); - -export default ShowPage; - -// -// const ShowPage = (props) => { -// const { -// claim, -// uri, -// isDownloaded, -// fileInfo, -// costInfo, -// } = props -// const { -// txid, -// nout, -// has_signature, -// signature_is_valid, -// value, -// } = claim -// const { -// stream, -// } = value -// const { -// metadata, -// source, -// } = stream -// const { -// title, -// } = metadata -// const { -// contentType, -// } = source -// const { -// cost, -// includesData, -// } = costInfo -// const costIncludesData = includesData -// -// const outpoint = txid + ':' + nout; -// const uriLookupComplete = !!claim -// const hasSignature = has_signature -// const signatureIsValid = signature_is_valid -// -// return ( -//
      -//
      -// { contentType && contentType.startsWith('video/') ? -//
      -//
      -//
      -//
      -// {isDownloaded === false -// ? -// : null} -//

      {title}

      -// { uriLookupComplete ? -//
      -//
      -// -//
      -//
      -// -//
      -//
      : '' } -//
      -// { uriLookupComplete ? -//
      -//
      -// {metadata.description} -//
      -//
      -// :
      } -//
      -// { metadata ? -//
      -// -//
      : '' } -//
      -// -//
      -//
      -//
      -// ) -// } \ No newline at end of file +}); \ No newline at end of file diff --git a/ui/js/reducers/search.js b/ui/js/reducers/search.js index cabb98b61..1fd73d10b 100644 --- a/ui/js/reducers/search.js +++ b/ui/js/reducers/search.js @@ -19,9 +19,12 @@ reducers[types.SEARCH_COMPLETED] = function(state, action) { const { query, } = action.data - const newResults = Object.assign({}, state.results) - const newByQuery = Object.assign({}, newResults.byQuery) + const oldResults = Object.assign({}, state.results) + const newByQuery = Object.assign({}, oldResults.byQuery) newByQuery[query] = action.data.results + const newResults = Object.assign({}, oldResults, { + byQuery: newByQuery + }) return Object.assign({}, state, { searching: false, @@ -29,6 +32,13 @@ reducers[types.SEARCH_COMPLETED] = function(state, action) { }) } +reducers[types.SEARCH_CANCELLED] = function(state, action) { + return Object.assign({}, state, { + searching: false, + query: undefined, + }) +} + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index e4e82bc38..fa8559bce 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -155,6 +155,11 @@ export const selectDownloadedContent = createSelector( (state) => state.downloadedContent || {} ) +export const selectDownloadedContentFileInfos = createSelector( + selectDownloadedContent, + (downloadedContent) => downloadedContent.fileInfos || [] +) + export const shouldFetchDownloadedContent = createSelector( selectDaemonReady, selectCurrentPage, -- 2.43.4 From 31513cc1a7ea7da008e65c8dcae3391bbd64f3e8 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Thu, 27 Apr 2017 00:08:11 +0700 Subject: [PATCH 19/56] put build back the way it was --- build/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.sh b/build/build.sh index 5d1eeb55d..1b3340054 100755 --- a/build/build.sh +++ b/build/build.sh @@ -78,9 +78,9 @@ else OSNAME="linux" fi DAEMON_URL="$(cat "$BUILD_DIR/DAEMON_URL" | sed "s/OSNAME/${OSNAME}/")" -# wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip" +wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip" unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/app/dist/" -# rm "$BUILD_DIR/daemon.zip" +rm "$BUILD_DIR/daemon.zip" ################### # Build the app # -- 2.43.4 From 674fafd31af52793b734dca40e8f4ef39b651953 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Thu, 27 Apr 2017 00:08:26 +0700 Subject: [PATCH 20/56] Able to watch already downloaded videos --- ui/js/actions/content.js | 173 ++++++++++++++++++++++++++---- ui/js/actions/search.js | 2 +- ui/js/component/fileTile/index.js | 2 +- ui/js/component/fileTile/view.jsx | 4 +- ui/js/constants/action_types.js | 7 ++ ui/js/page/discover/view.jsx | 2 +- ui/js/reducers/content.js | 75 ++++++++++++- ui/js/selectors/content.js | 27 +++++ ui/js/store.js | 27 ++++- ui/js/util/batchActions.js | 9 ++ 10 files changed, 300 insertions(+), 28 deletions(-) create mode 100644 ui/js/util/batchActions.js diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index d29b40003..f26bd045a 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -4,34 +4,45 @@ import lbryio from 'lbryio' import { selectCurrentUri, } from 'selectors/app' +import { + selectBalance, +} from 'selectors/wallet' import { selectSearchTerm, + selectCurrentUriCostInfo, + selectCurrentUriFileInfo, } from 'selectors/content' import { selectCurrentResolvedUriClaimOutpoint, } from 'selectors/content' +import { + doOpenModal, +} from 'actions/app' +import batchActions from 'util/batchActions' -export function doResolveUri(dispatch, uri) { - dispatch({ - type: types.RESOLVE_URI_STARTED, - data: { uri } - }) - - lbry.resolve({uri: uri}).then((resolutionInfo) => { - const { - claim, - certificate, - } = resolutionInfo - +export function doResolveUri(uri) { + return function(dispatch, getState) { dispatch({ - type: types.RESOLVE_URI_COMPLETED, - data: { - uri, + type: types.RESOLVE_URI_STARTED, + data: { uri } + }) + + lbry.resolve({ uri }).then((resolutionInfo) => { + const { claim, certificate, - } + } = resolutionInfo + + dispatch({ + type: types.RESOLVE_URI_COMPLETED, + data: { + uri, + claim, + certificate, + } + }) }) - }) + } } export function doFetchCurrentUriFileInfo() { @@ -40,8 +51,6 @@ export function doFetchCurrentUriFileInfo() { const uri = selectCurrentUri(state) const outpoint = selectCurrentResolvedUriClaimOutpoint(state) - console.log(outpoint) - dispatch({ type: types.FETCH_FILE_INFO_STARTED, data: { @@ -50,7 +59,7 @@ export function doFetchCurrentUriFileInfo() { } }) - lbry.file_list({ outpoint }).then(fileInfo => { + lbry.file_list({ outpoint }).then(([fileInfo]) => { dispatch({ type: types.FETCH_FILE_INFO_COMPLETED, data: { @@ -126,7 +135,7 @@ export function doFetchFeaturedContent() { }) Object.keys(Uris).forEach((category) => { - Uris[category].forEach((uri) => doResolveUri(dispatch, uri)) + Uris[category].forEach((uri) => dispatch(doResolveUri(uri))) }) } @@ -161,3 +170,125 @@ export function doFetchCurrentUriCostInfo() { }) } } + +export function doUpdateLoadStatus(uri, outpoint) { + return function(dispatch, getState) { + const state = getState() + + lbry.file_list({ + outpoint: outpoint, + full_status: true, + }).then(([fileInfo]) => { + if(!fileInfo || fileInfo.written_bytes == 0) { + // download hasn't started yet + setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250) + } else if (fileInfo.completed) { + dispatch({ + type: types.DOWNLOADING_COMPLETED, + data: { + uri, + fileInfo, + } + }) + } else { + // ready to play + const { + total_bytes, + written_bytes, + } = fileInfo + const progress = (written_bytes / total_bytes) * 100 + + dispatch({ + type: types.DOWNLOADING_PROGRESSED, + data: { + uri, + fileInfo, + progress, + } + }) + setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250) + } + }) + } +} + +export function doPlayVideo(uri) { + return { + type: types.PLAY_VIDEO_STARTED, + data: { uri } + } +} + +export function doDownloadFile(uri, streamInfo) { + return function(dispatch, getState) { + const state = getState() + + dispatch({ + type: types.DOWNLOADING_STARTED, + data: { + uri, + } + }) + + lbryio.call('file', 'view', { + uri: uri, + outpoint: streamInfo.outpoint, + claimId: streamInfo.claim_id, + }).catch(() => {}) + dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint)) + } +} + +export function doLoadVideo() { + return function(dispatch, getState) { + const state = getState() + const uri = selectCurrentUri(state) + + dispatch({ + type: types.LOADING_VIDEO_STARTED, + data: { + uri + } + }) + + lbry.get({ uri }).then(streamInfo => { + if (streamInfo === null || typeof streamInfo !== 'object') { + dispatch({ + type: types.LOADING_VIDEO_FAILED, + data: { uri } + }) + dispatch(doOpenModal('timedOut')) + } else { + dispatch(doDownloadFile(uri, streamInfo)) + } + }) + } +} + +export function doWatchVideo() { + return function(dispatch, getState) { + const state = getState() + const uri = selectCurrentUri(state) + const balance = selectBalance(state) + const fileInfo = selectCurrentUriFileInfo(state) + const costInfo = selectCurrentUriCostInfo(state) + const { cost } = costInfo + + // TODO does > 0 mean the file is downloaded? We don't have the total_bytes + console.log(fileInfo) + console.log(fileInfo.written_bytes) + console.log('wtf') + if (fileInfo.written_bytes > 0) { + console.debug('this file is already downloaded') + dispatch(doPlayVideo(uri)) + } else { + if (cost > balance) { + dispatch(doOpenModal('notEnoughCredits')) + } else if (cost <= 0.01) { + dispatch(doLoadVideo()) + } else { + dispatch(doOpenModal('affirmPurchase')) + } + } + } +} diff --git a/ui/js/actions/search.js b/ui/js/actions/search.js index 154a52fd3..a44ec964f 100644 --- a/ui/js/actions/search.js +++ b/ui/js/actions/search.js @@ -32,7 +32,7 @@ export function doSearchContent(query) { contentName: result.name, claimId: result.channel_id || result.claim_id, }) - doResolveUri(dispatch, uri.split('://')[1]) + dispatch(doResolveUri(uri.split('://')[1])) }) dispatch({ diff --git a/ui/js/component/fileTile/index.js b/ui/js/component/fileTile/index.js index e24dade66..feccdb71b 100644 --- a/ui/js/component/fileTile/index.js +++ b/ui/js/component/fileTile/index.js @@ -8,7 +8,7 @@ import { import FileTile from './view' const select = (state) => ({ - resolvedUris: selectResolvedUris(state), + resolvedUris: (uri) => selectResolvedUris(state)[uri], }) const perform = (dispatch) => ({ diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index b80616dcc..50e506883 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -13,7 +13,9 @@ class FileTile extends React.Component { uri, claim, } = this.props - + const resolvedUri = this.props.resolvedUris(uri) || {} + const claimInfo = resolvedUri.claim + if(!claim) { if (displayStyle == 'card') { return diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 5ca93c0ca..568a85e70 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -47,6 +47,13 @@ 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' export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED' +export const LOADING_VIDEO_STARTED = 'LOADING_VIDEO_STARTED' +export const LOADING_VIDEO_COMPLETED = 'LOADING_VIDEO_COMPLETED' +export const LOADING_VIDEO_FAILED = 'LOADING_VIDEO_FAILED' +export const DOWNLOADING_STARTED = 'DOWNLOADING_STARTED' +export const DOWNLOADING_PROGRESSED = 'DOWNLOADING_PROGRESSED' +export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED' +export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED' // Search export const SEARCH_STARTED = 'SEARCH_STARTED' diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index fc0eb8220..999af9604 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -62,4 +62,4 @@ let DiscoverPage = React.createClass({ } }) -export default DiscoverPage; +export default DiscoverPage; \ No newline at end of file diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index 0d420237d..720877177 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -129,7 +129,7 @@ reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) { const fileInfos = Object.assign({}, state.fileInfos) const byUri = Object.assign({}, fileInfos.byUri) - byUri[uri] = fileInfo + byUri[uri] = Object.assign({}, fileInfo) delete newFetchingFileInfos[uri] const newFileInfos = Object.assign({}, fileInfos, { @@ -178,6 +178,79 @@ reducers[types.FETCH_COST_INFO_COMPLETED] = function(state, action) { }) } +reducers[types.LOADING_VIDEO_STARTED] = function(state, action) { + const { + uri, + } = action.data + const newLoading = Object.assign({}, state.loading) + const newByUri = Object.assign({}, newLoading.byUri) + + newByUri[uri] = true + newLoading.byUri = newByUri + + return Object.assign({}, state, { + loading: newLoading, + }) +} + +reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { + const { + uri, + } = action.data + const newLoading = Object.assign({}, state.loading) + const newByUri = Object.assign({}, newLoading.byUri) + + delete newByUri[uri] + newLoading.byUri = newByUri + + return Object.assign({}, state, { + loading: newLoading, + }) +} + +reducers[types.DOWNLOADING_STARTED] = function(state, action) { + const { + uri, + } = action.data + const newDownloading = Object.assign({}, state.downloading) + const newByUri = Object.assign({}, newDownloading.byUri) + + newByUri[uri] = true + newDownloading.byUri = newByUri + + return Object.assign({}, state, { + downloading: newDownloading, + }) +} + +reducers[types.DOWNLOADING_COMPLETED] = +reducers[types.DOWNLOADING_PROGRESSED] = function(state, action) { + const { + uri, + fileInfo, + } = action.data + const fileInfos = Object.assign({}, state.fileInfos) + const byUri = Object.assign({}, fileInfos.byUri) + + byUri[uri] = fileInfo + fileInfos.byUri = byUri + + return Object.assign({}, state, { + fileInfos: fileInfos, + }) +} + + +reducers[types.PLAY_VIDEO_STARTED] = function(state, action) { + const { + uri, + } = action.data + + return Object.assign({}, state, { + nowPlaying: uri, + }) +} + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index fa8559bce..274d98d3d 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -84,12 +84,23 @@ export const selectFetchingFileInfos = createSelector( (state) => state.fetchingFileInfos || {} ) +export const selectCurrentUriFileReadyToPlay = createSelector( + selectCurrentUriFileInfo, + (fileInfo) => (fileInfo || {}).written_bytes > 0 +) + export const selectIsFetchingCurrentUriFileInfo = createSelector( selectFetchingFileInfos, selectCurrentUri, (fetching, uri) => !!fetching[uri] ) +export const selectCurrentUriIsPlaying = createSelector( + _selectState, + selectCurrentUri, + (state, uri) => state.nowPlaying == uri +) + export const selectCostInfos = createSelector( _selectState, (state) => state.costInfos || {} @@ -185,6 +196,22 @@ export const selectPublishedContent = createSelector( (state) => state.publishedContent || {} ) +export const selectLoading = createSelector( + _selectState, + (state) => state.loading || {} +) + +export const selectLoadingByUri = createSelector( + selectLoading, + (loading) => loading.byUri || {} +) + +export const selectLoadingCurrentUri = createSelector( + selectLoadingByUri, + selectCurrentUri, + (byUri, uri) => byUri[uri] +) + export const shouldFetchPublishedContent = createSelector( selectDaemonReady, selectCurrentPage, diff --git a/ui/js/store.js b/ui/js/store.js index ac789a824..ffc6d9d88 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -19,6 +19,28 @@ function isNotFunction(object) { return !isFunction(object); } +function createBulkThunkMiddleware() { + return ({ dispatch, getState }) => next => (action) => { + if (action.type === 'BATCH_ACTIONS') { + action.actions.filter(isFunction).map(actionFn => + actionFn(dispatch, getState) + ) + } + return next(action) + } +} + +function enableBatching(reducer) { + return function batchingReducer(state, action) { + switch (action.type) { + case 'BATCH_ACTIONS': + return action.actions.filter(isNotFunction).reduce(batchingReducer, state) + default: + return reducer(state, action) + } + } +} + const reducers = redux.combineReducers({ app: appReducer, content: contentReducer, @@ -27,7 +49,8 @@ const reducers = redux.combineReducers({ wallet: walletReducer, }); -var middleware = [thunk] +const bulkThunk = createBulkThunkMiddleware() +const middleware = [thunk, bulkThunk] if (env === 'development') { const logger = createLogger({ @@ -40,6 +63,6 @@ const createStoreWithMiddleware = redux.compose( redux.applyMiddleware(...middleware) )(redux.createStore); -const reduxStore = createStoreWithMiddleware(reducers); +const reduxStore = createStoreWithMiddleware(enableBatching(reducers)); export default reduxStore; diff --git a/ui/js/util/batchActions.js b/ui/js/util/batchActions.js new file mode 100644 index 000000000..eedab1cc6 --- /dev/null +++ b/ui/js/util/batchActions.js @@ -0,0 +1,9 @@ +// https://github.com/reactjs/redux/issues/911 +function batchActions(...actions) { + return { + type: 'BATCH_ACTIONS', + actions: actions + }; +} + +export default batchActions -- 2.43.4 From e0b78368abad0fb2ca34b30eaec2dbbde6d8ae67 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Thu, 27 Apr 2017 11:45:02 +0700 Subject: [PATCH 21/56] Playing videos working better --- ui/js/actions/content.js | 4 ---- ui/js/component/video/index.js | 9 +++------ ui/js/reducers/app.js | 1 + ui/js/reducers/content.js | 5 +++++ ui/js/selectors/content.js | 18 +++++++++++++++++- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index f26bd045a..0cd0d529e 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -275,11 +275,7 @@ export function doWatchVideo() { const { cost } = costInfo // TODO does > 0 mean the file is downloaded? We don't have the total_bytes - console.log(fileInfo) - console.log(fileInfo.written_bytes) - console.log('wtf') if (fileInfo.written_bytes > 0) { - console.debug('this file is already downloaded') dispatch(doPlayVideo(uri)) } else { if (cost > balance) { diff --git a/ui/js/component/video/index.js b/ui/js/component/video/index.js index 94978f702..667b7a883 100644 --- a/ui/js/component/video/index.js +++ b/ui/js/component/video/index.js @@ -16,12 +16,8 @@ import { selectLoadingCurrentUri, selectCurrentUriFileReadyToPlay, selectCurrentUriIsPlaying, - selectCurrentUriFileInfo, selectDownloadingCurrentUri, -} from 'selectors/file_info' -import { - selectCurrentUriCostInfo, -} from 'selectors/cost_info' +} from 'selectors/content' import Video from './view' const select = (state) => ({ @@ -30,11 +26,12 @@ const select = (state) => ({ modal: selectCurrentModal(state), isLoading: selectLoadingCurrentUri(state), readyToPlay: selectCurrentUriFileReadyToPlay(state), - isDownloading: selectDownloadingCurrentUri(state), + isDownloading: ntUri(state), }) const perform = (dispatch) => ({ loadVideo: () => dispatch(doLoadVideo()), + play: () => dispatch(doPlayVideo()), watchVideo: (elem) => dispatch(doWatchVideo()), closeModal: () => dispatch(doCloseModal()), }) diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index ffc13cd74..855330de3 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -8,6 +8,7 @@ const defaultState = { drawerOpen: sessionStorage.getItem('drawerOpen') || true, upgradeSkipped: sessionStorage.getItem('upgradeSkipped'), daemonReady: false, + platform: window.navigator.platform, } reducers[types.NAVIGATE] = function(state, action) { diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index 720877177..9d70bd438 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -214,12 +214,17 @@ reducers[types.DOWNLOADING_STARTED] = function(state, action) { } = action.data const newDownloading = Object.assign({}, state.downloading) const newByUri = Object.assign({}, newDownloading.byUri) + const newLoading = Object.assign({}, state.loading) + const newLoadingByUri = Object.assign({}, newLoading.byUri) newByUri[uri] = true newDownloading.byUri = newByUri + delete newLoadingByUri[uri] + newLoading.byUri = newLoadingByUri return Object.assign({}, state, { downloading: newDownloading, + loading: newLoading, }) } diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index 274d98d3d..50e4facf2 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -209,7 +209,23 @@ export const selectLoadingByUri = createSelector( export const selectLoadingCurrentUri = createSelector( selectLoadingByUri, selectCurrentUri, - (byUri, uri) => byUri[uri] + (byUri, uri) => !!byUri[uri] +) + +export const selectDownloading = createSelector( + _selectState, + (state) => state.downloading || {} +) + +export const selectDownloadingByUri = createSelector( + selectDownloading, + (downloading) => downloading.byUri || {} +) + +export const selectDownloadingCurrentUri = createSelector( + selectCurrentUri, + selectDownloadingByUri, + (uri, byUri) => byUri[uri] ) export const shouldFetchPublishedContent = createSelector( -- 2.43.4 From 0d658b2767da4a467170db7b392318e9fecf5e10 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Thu, 27 Apr 2017 14:05:41 +0700 Subject: [PATCH 22/56] Playing videos actually working now --- ui/js/actions/content.js | 29 +++++++++++++++++------------ ui/js/component/video/index.js | 5 ++--- ui/js/page/showPage/view.jsx | 2 +- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 0cd0d529e..8dad326a9 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -59,7 +59,7 @@ export function doFetchCurrentUriFileInfo() { } }) - lbry.file_list({ outpoint }).then(([fileInfo]) => { + lbry.file_list({ outpoint: outpoint, full_status: true }).then(([fileInfo]) => { dispatch({ type: types.FETCH_FILE_INFO_COMPLETED, data: { @@ -274,17 +274,22 @@ export function doWatchVideo() { const costInfo = selectCurrentUriCostInfo(state) const { cost } = costInfo - // TODO does > 0 mean the file is downloaded? We don't have the total_bytes - if (fileInfo.written_bytes > 0) { - dispatch(doPlayVideo(uri)) - } else { - if (cost > balance) { - dispatch(doOpenModal('notEnoughCredits')) - } else if (cost <= 0.01) { - dispatch(doLoadVideo()) - } else { - dispatch(doOpenModal('affirmPurchase')) - } + // NOTE: we have to check written bytes because a file may be "completed" + // but then deleted on the file system. In which case we are going to need + // to dispatch a load event again to redownload it + if (fileInfo.completed && fileInfo.written_bytes > 0) { + return Promise.resolve() } + + if (cost > balance) { + dispatch(doOpenModal('notEnoughCredits')) + } + else if (cost <= 0.01) { + dispatch(doLoadVideo()) + } else { + dispatch(doOpenModal('affirmPurchase')) + } + + return Promise.resolve() } } diff --git a/ui/js/component/video/index.js b/ui/js/component/video/index.js index 667b7a883..379778930 100644 --- a/ui/js/component/video/index.js +++ b/ui/js/component/video/index.js @@ -26,12 +26,11 @@ const select = (state) => ({ modal: selectCurrentModal(state), isLoading: selectLoadingCurrentUri(state), readyToPlay: selectCurrentUriFileReadyToPlay(state), - isDownloading: ntUri(state), + isDownloading: selectDownloadingCurrentUri(state), }) const perform = (dispatch) => ({ - loadVideo: () => dispatch(doLoadVideo()), - play: () => dispatch(doPlayVideo()), + play: () => dispatch(doLoadVideo()), watchVideo: (elem) => dispatch(doWatchVideo()), closeModal: () => dispatch(doCloseModal()), }) diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx index 81096317f..8ff3b9ca9 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/showPage/view.jsx @@ -2,7 +2,7 @@ import React from 'react'; import lbry from 'lbry.js'; import lighthouse from 'lighthouse.js'; import lbryuri from 'lbryuri.js'; -import Video from 'page/video' +import Video from 'component/video' import { TruncatedText, Thumbnail, -- 2.43.4 From 13008f5f31c00654368a0e06f8cf47985cddf793 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Thu, 27 Apr 2017 14:25:13 +0700 Subject: [PATCH 23/56] Videos playing properly immediately after purchase --- ui/js/component/video/index.js | 2 +- ui/js/component/video/view.jsx | 5 +++-- ui/js/selectors/content.js | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/js/component/video/index.js b/ui/js/component/video/index.js index 379778930..05b0dac6e 100644 --- a/ui/js/component/video/index.js +++ b/ui/js/component/video/index.js @@ -30,7 +30,7 @@ const select = (state) => ({ }) const perform = (dispatch) => ({ - play: () => dispatch(doLoadVideo()), + loadVideo: () => dispatch(doLoadVideo()), watchVideo: (elem) => dispatch(doWatchVideo()), closeModal: () => dispatch(doCloseModal()), }) diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index 86d9a99ea..1b559ae30 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -91,11 +91,12 @@ class Video extends React.Component { return (
      { isPlaying ? - !readyToPlay ? + !readyToPlay ? this is the world's worst loading screen and we shipped our software with it anyway...

      {loadStatusMessage}
      : :
      - +
      }
      ); diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index 50e4facf2..c40f93c65 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -84,6 +84,8 @@ export const selectFetchingFileInfos = createSelector( (state) => state.fetchingFileInfos || {} ) +// TODO make this smarter so it doesn't start playing and immediately freeze +// while downloading more. export const selectCurrentUriFileReadyToPlay = createSelector( selectCurrentUriFileInfo, (fileInfo) => (fileInfo || {}).written_bytes > 0 -- 2.43.4 From f3aefe43410f7eecd61b379d120297647de2c71b Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Thu, 27 Apr 2017 14:40:09 +0700 Subject: [PATCH 24/56] Don't re-render the video player when fileinfo has changed --- 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 8dad326a9..f8854b5ae 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -284,7 +284,7 @@ export function doWatchVideo() { if (cost > balance) { dispatch(doOpenModal('notEnoughCredits')) } - else if (cost <= 0.01) { + else if (cost <= 0.01 || fileInfo.written_bytes > 0) { dispatch(doLoadVideo()) } else { dispatch(doOpenModal('affirmPurchase')) -- 2.43.4 From 0419b0eb2483a9685684911ed1de7288580c3e21 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Thu, 27 Apr 2017 22:10:39 +0700 Subject: [PATCH 25/56] Add fileinfo to action data when download starts --- ui/js/actions/content.js | 21 +++++++++++++-------- ui/js/reducers/content.js | 6 ++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index f8854b5ae..ad69d9d22 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -223,11 +223,14 @@ export function doDownloadFile(uri, streamInfo) { return function(dispatch, getState) { const state = getState() - dispatch({ - type: types.DOWNLOADING_STARTED, - data: { - uri, - } + lbry.file_list({ outpoint: streamInfo.outpoint, full_status: true }).then(([fileInfo]) => { + dispatch({ + type: types.DOWNLOADING_STARTED, + data: { + uri, + fileInfo, + } + }) }) lbryio.call('file', 'view', { @@ -281,11 +284,13 @@ export function doWatchVideo() { return Promise.resolve() } + if (cost <= 0.01 || fileInfo.download_directory) { + dispatch(doLoadVideo()) + return Promise.resolve() + } + if (cost > balance) { dispatch(doOpenModal('notEnoughCredits')) - } - else if (cost <= 0.01 || fileInfo.written_bytes > 0) { - dispatch(doLoadVideo()) } else { dispatch(doOpenModal('affirmPurchase')) } diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index 9d70bd438..f3de01f28 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -211,20 +211,26 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { reducers[types.DOWNLOADING_STARTED] = function(state, action) { const { uri, + fileInfo, } = action.data const newDownloading = Object.assign({}, state.downloading) const newByUri = Object.assign({}, newDownloading.byUri) const newLoading = Object.assign({}, state.loading) const newLoadingByUri = Object.assign({}, newLoading.byUri) + const newFileInfos = Object.assign({}, state.fileInfos) + const newFileInfosByUri = Object.assign({}, newFileInfos.byUri) newByUri[uri] = true newDownloading.byUri = newByUri delete newLoadingByUri[uri] newLoading.byUri = newLoadingByUri + newFileInfosByUri[uri] = fileInfo + newFileInfos.byUri = newFileInfosByUri return Object.assign({}, state, { downloading: newDownloading, loading: newLoading, + fileInfos: newFileInfos, }) } -- 2.43.4 From b0f32aebc7e8885eb2d73f92bc057beb2ef608c2 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 28 Apr 2017 22:14:44 +0700 Subject: [PATCH 26/56] Refactoring --- ui/js/actions/content.js | 65 ++------------ ui/js/actions/cost_info.js | 30 +++++++ ui/js/actions/file_info.js | 34 ++++++++ ui/js/component/fileTile/index.js | 6 +- ui/js/component/fileTile/view.jsx | 10 +-- ui/js/component/video/index.js | 8 +- ui/js/page/discover/view.jsx | 3 +- ui/js/page/showPage/index.js | 12 ++- ui/js/reducers/certificates.js | 27 ++++++ ui/js/reducers/claims.js | 24 ++++++ ui/js/reducers/content.js | 136 ------------------------------ ui/js/reducers/cost_info.js | 40 +++++++++ ui/js/reducers/file_info.js | 92 ++++++++++++++++++++ ui/js/selectors/claims.js | 24 ++++++ ui/js/selectors/content.js | 133 ++--------------------------- ui/js/selectors/cost_info.js | 44 ++++++++++ ui/js/selectors/file_info.js | 74 ++++++++++++++++ ui/js/store.js | 8 ++ ui/js/triggers.js | 16 +++- 19 files changed, 450 insertions(+), 336 deletions(-) create mode 100644 ui/js/actions/cost_info.js create mode 100644 ui/js/actions/file_info.js create mode 100644 ui/js/reducers/certificates.js create mode 100644 ui/js/reducers/claims.js create mode 100644 ui/js/reducers/cost_info.js create mode 100644 ui/js/reducers/file_info.js create mode 100644 ui/js/selectors/claims.js create mode 100644 ui/js/selectors/cost_info.js create mode 100644 ui/js/selectors/file_info.js diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index ad69d9d22..c37ad032c 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -9,9 +9,13 @@ import { } from 'selectors/wallet' import { selectSearchTerm, - selectCurrentUriCostInfo, - selectCurrentUriFileInfo, } from 'selectors/content' +import { + selectCurrentUriFileInfo, +} from 'selectors/file_info' +import { + selectCurrentUriCostInfo, +} from 'selectors/cost_info' import { selectCurrentResolvedUriClaimOutpoint, } from 'selectors/content' @@ -45,32 +49,6 @@ export function doResolveUri(uri) { } } -export function doFetchCurrentUriFileInfo() { - return function(dispatch, getState) { - const state = getState() - const uri = selectCurrentUri(state) - const outpoint = selectCurrentResolvedUriClaimOutpoint(state) - - dispatch({ - type: types.FETCH_FILE_INFO_STARTED, - data: { - uri, - outpoint, - } - }) - - lbry.file_list({ outpoint: outpoint, full_status: true }).then(([fileInfo]) => { - dispatch({ - type: types.FETCH_FILE_INFO_COMPLETED, - data: { - uri, - fileInfo, - } - }) - }) - } -} - export function doFetchDownloadedContent() { return function(dispatch, getState) { const state = getState() @@ -147,30 +125,6 @@ export function doFetchFeaturedContent() { } } -export function doFetchCurrentUriCostInfo() { - return function(dispatch, getState) { - const state = getState() - const uri = selectCurrentUri(state) - - dispatch({ - type: types.FETCH_COST_INFO_STARTED, - data: { - uri, - } - }) - - lbry.getCostInfo(uri).then(costInfo => { - dispatch({ - type: types.FETCH_COST_INFO_COMPLETED, - data: { - uri, - costInfo, - } - }) - }) - } -} - export function doUpdateLoadStatus(uri, outpoint) { return function(dispatch, getState) { const state = getState() @@ -277,13 +231,12 @@ export function doWatchVideo() { const costInfo = selectCurrentUriCostInfo(state) const { cost } = costInfo - // NOTE: we have to check written bytes because a file may be "completed" - // but then deleted on the file system. In which case we are going to need - // to dispatch a load event again to redownload it - if (fileInfo.completed && fileInfo.written_bytes > 0) { + // we already fully downloaded the file + if (fileInfo && fileInfo.completed) { return Promise.resolve() } + // the file is free or we have partially downloaded it if (cost <= 0.01 || fileInfo.download_directory) { dispatch(doLoadVideo()) return Promise.resolve() diff --git a/ui/js/actions/cost_info.js b/ui/js/actions/cost_info.js new file mode 100644 index 000000000..2d513e578 --- /dev/null +++ b/ui/js/actions/cost_info.js @@ -0,0 +1,30 @@ +import * as types from 'constants/action_types' +import { + selectCurrentUri, +} from 'selectors/app' +import lbry from 'lbry' + +export function doFetchCurrentUriCostInfo() { + return function(dispatch, getState) { + const state = getState() + const uri = selectCurrentUri(state) + + dispatch({ + type: types.FETCH_COST_INFO_STARTED, + data: { + uri, + } + }) + + lbry.getCostInfo(uri).then(costInfo => { + dispatch({ + type: types.FETCH_COST_INFO_COMPLETED, + data: { + uri, + costInfo, + } + }) + }) + } +} + diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js new file mode 100644 index 000000000..9005e6f01 --- /dev/null +++ b/ui/js/actions/file_info.js @@ -0,0 +1,34 @@ +import * as types from 'constants/action_types' +import lbry from 'lbry' +import { + selectCurrentUri, +} from 'selectors/app' +import { + selectCurrentUriClaimOutpoint, +} from 'selectors/claims' + +export function doFetchCurrentUriFileInfo() { + return function(dispatch, getState) { + const state = getState() + const uri = selectCurrentUri(state) + const outpoint = selectCurrentUriClaimOutpoint(state) + + dispatch({ + type: types.FETCH_FILE_INFO_STARTED, + data: { + uri, + outpoint, + } + }) + + lbry.file_list({ outpoint: outpoint, full_status: true }).then(([fileInfo]) => { + dispatch({ + type: types.FETCH_FILE_INFO_COMPLETED, + data: { + uri, + fileInfo, + } + }) + }) + } +} diff --git a/ui/js/component/fileTile/index.js b/ui/js/component/fileTile/index.js index feccdb71b..07c696f81 100644 --- a/ui/js/component/fileTile/index.js +++ b/ui/js/component/fileTile/index.js @@ -3,12 +3,12 @@ import { connect } from 'react-redux' import { - selectResolvedUris, -} from 'selectors/content' + selectClaimsByUri, +} from 'selectors/claims' import FileTile from './view' const select = (state) => ({ - resolvedUris: (uri) => selectResolvedUris(state)[uri], + claims: (uri) => selectClaimsByUri(state)[uri], }) const perform = (dispatch) => ({ diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index 50e506883..dfea32731 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -10,13 +10,11 @@ class FileTile extends React.Component { render() { const { displayStyle, - uri, - claim, + uri } = this.props - const resolvedUri = this.props.resolvedUris(uri) || {} - const claimInfo = resolvedUri.claim - - if(!claim) { + const claimInfo = this.props.claims(uri) + + if(!claimInfo) { if (displayStyle == 'card') { return } diff --git a/ui/js/component/video/index.js b/ui/js/component/video/index.js index 05b0dac6e..e258e55f7 100644 --- a/ui/js/component/video/index.js +++ b/ui/js/component/video/index.js @@ -16,8 +16,14 @@ import { selectLoadingCurrentUri, selectCurrentUriFileReadyToPlay, selectCurrentUriIsPlaying, - selectDownloadingCurrentUri, } from 'selectors/content' +import { + selectCurrentUriFileInfo, + selectDownloadingCurrentUri, +} from 'selectors/file_info' +import { + selectCurrentUriCostInfo, +} from 'selectors/cost_info' import Video from './view' const select = (state) => ({ diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index 999af9604..82151ccd9 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -11,7 +11,6 @@ const communityCategoryToolTipText = ('Community Content is a public space where const FeaturedCategory = (props) => { const { category, - resolvedUris, names, } = props @@ -19,7 +18,7 @@ const FeaturedCategory = (props) => {

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

      - {names.map(name => )} + {names && names.map(name => )}
      } diff --git a/ui/js/page/showPage/index.js b/ui/js/page/showPage/index.js index e6d08ac3a..4ba0ba729 100644 --- a/ui/js/page/showPage/index.js +++ b/ui/js/page/showPage/index.js @@ -6,15 +6,21 @@ import { selectCurrentUri, } from 'selectors/app' import { - selectCurrentResolvedUriClaim, selectCurrentUriIsDownloaded, +} from 'selectors/file_info' +import { + selectCurrentUriClaim, +} from 'selectors/claims' +import { selectCurrentUriFileInfo, +} from 'selectors/file_info' +import { selectCurrentUriCostInfo, -} from 'selectors/content' +} from 'selectors/cost_info' import ShowPage from './view' const select = (state) => ({ - claim: selectCurrentResolvedUriClaim(state), + claim: selectCurrentUriClaim(state), uri: selectCurrentUri(state), isDownloaded: selectCurrentUriIsDownloaded(state), fileInfo: selectCurrentUriFileInfo(state), diff --git a/ui/js/reducers/certificates.js b/ui/js/reducers/certificates.js new file mode 100644 index 000000000..ebeaed986 --- /dev/null +++ b/ui/js/reducers/certificates.js @@ -0,0 +1,27 @@ +import * as types from 'constants/action_types' + +const reducers = {} +const defaultState = { +} + +reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { + const { + uri, + certificate, + } = action.data + if (!certificate) return state + + const newByUri = Object.assign({}, state.byUri) + + newByUri[uri] = certificate + return Object.assign({}, state, { + byUri: newByUri, + }) +} + + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js new file mode 100644 index 000000000..30d6fa4e2 --- /dev/null +++ b/ui/js/reducers/claims.js @@ -0,0 +1,24 @@ +import * as types from 'constants/action_types' + +const reducers = {} +const defaultState = { +} + +reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { + const { + uri, + claim, + } = action.data + const newByUri = Object.assign({}, state.byUri) + + newByUri[uri] = claim + return Object.assign({}, state, { + byUri: newByUri, + }) +} + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index f3de01f28..a5e4eba74 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -41,10 +41,7 @@ reducers[types.RESOLVE_URI_STARTED] = function(state, action) { reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { const { uri, - claim, - certificate, } = action.data - const resolvedUris = Object.assign({}, state.resolvedUris) const resolvingUris = state.resolvingUris const index = state.resolvingUris.indexOf(uri) const newResolvingUris = [ @@ -52,14 +49,7 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { ...resolvingUris.slice(index + 1) ] - resolvedUris[uri] = { - claim: claim, - certificate: certificate, - } - - const newState = Object.assign({}, state, { - resolvedUris: resolvedUris, resolvingUris: newResolvingUris, }) @@ -106,78 +96,6 @@ reducers[types.FETCH_PUBLISHED_CONTENT_COMPLETED] = function(state, action) { }) } -reducers[types.FETCH_FILE_INFO_STARTED] = function(state, action) { - const { - uri, - output, - } = action.data - const newFetchingFileInfos = Object.assign({}, state.fetchingFileInfos) - - newFetchingFileInfos[uri] = true - - return Object.assign({}, state, { - fetchingFileInfos: newFetchingFileInfos, - }) -} - -reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) { - const { - uri, - fileInfo, - } = action.data - const newFetchingFileInfos = Object.assign({}, state.fetchingFileInfos) - const fileInfos = Object.assign({}, state.fileInfos) - const byUri = Object.assign({}, fileInfos.byUri) - - byUri[uri] = Object.assign({}, fileInfo) - delete newFetchingFileInfos[uri] - - const newFileInfos = Object.assign({}, fileInfos, { - byUri: byUri, - }) - - return Object.assign({}, state, { - fetchingFileInfos: newFetchingFileInfos, - fileInfos: newFileInfos, - }) -} - -reducers[types.FETCH_COST_INFO_STARTED] = function(state, action) { - const { - uri, - } = action.data - const fetchingCostInfos = Object.assign({}, state.fetchingCostInfos) - - fetchingCostInfos[uri] = true - - return Object.assign({}, state, { - fetchingCostInfos, - }) -} - -reducers[types.FETCH_COST_INFO_COMPLETED] = function(state, action) { - const { - uri, - costInfo, - } = action.data - - const newFetchingCostInfos = Object.assign({}, state.fetchingCostInfos) - const costInfos = Object.assign({}, state.costInfos) - const byUri = Object.assign({}, costInfos.byUri) - - byUri[uri] = costInfo - delete newFetchingCostInfos[uri] - - const newCostInfos = Object.assign({}, costInfos, { - byUri: byUri, - }) - - return Object.assign({}, state, { - fetchingCostInfos: newFetchingCostInfos, - costInfos: newCostInfos, - }) -} - reducers[types.LOADING_VIDEO_STARTED] = function(state, action) { const { uri, @@ -208,60 +126,6 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { }) } -reducers[types.DOWNLOADING_STARTED] = function(state, action) { - const { - uri, - fileInfo, - } = action.data - const newDownloading = Object.assign({}, state.downloading) - const newByUri = Object.assign({}, newDownloading.byUri) - const newLoading = Object.assign({}, state.loading) - const newLoadingByUri = Object.assign({}, newLoading.byUri) - const newFileInfos = Object.assign({}, state.fileInfos) - const newFileInfosByUri = Object.assign({}, newFileInfos.byUri) - - newByUri[uri] = true - newDownloading.byUri = newByUri - delete newLoadingByUri[uri] - newLoading.byUri = newLoadingByUri - newFileInfosByUri[uri] = fileInfo - newFileInfos.byUri = newFileInfosByUri - - return Object.assign({}, state, { - downloading: newDownloading, - loading: newLoading, - fileInfos: newFileInfos, - }) -} - -reducers[types.DOWNLOADING_COMPLETED] = -reducers[types.DOWNLOADING_PROGRESSED] = function(state, action) { - const { - uri, - fileInfo, - } = action.data - const fileInfos = Object.assign({}, state.fileInfos) - const byUri = Object.assign({}, fileInfos.byUri) - - byUri[uri] = fileInfo - fileInfos.byUri = byUri - - return Object.assign({}, state, { - fileInfos: fileInfos, - }) -} - - -reducers[types.PLAY_VIDEO_STARTED] = function(state, action) { - const { - uri, - } = action.data - - return Object.assign({}, state, { - nowPlaying: uri, - }) -} - export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/reducers/cost_info.js b/ui/js/reducers/cost_info.js new file mode 100644 index 000000000..eafcf4bf3 --- /dev/null +++ b/ui/js/reducers/cost_info.js @@ -0,0 +1,40 @@ +import * as types from 'constants/action_types' + +const reducers = {} +const defaultState = { +} + +reducers[types.FETCH_COST_INFO_STARTED] = function(state, action) { + const { + uri, + } = action.data + const newFetching = Object.assign({}, state.fetching) + newFetching[uri] = true + + return Object.assign({}, state, { + fetching: newFetching, + }) +} + +reducers[types.FETCH_COST_INFO_COMPLETED] = function(state, action) { + const { + uri, + costInfo, + } = action.data + const newByUri = Object.assign({}, state.byUri) + const newFetching = Object.assign({}, state.fetching) + + newByUri[uri] = costInfo + delete newFetching[uri] + + return Object.assign({}, state, { + byUri: newByUri, + fetching: newFetching, + }) +} + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js new file mode 100644 index 000000000..1467f0ee8 --- /dev/null +++ b/ui/js/reducers/file_info.js @@ -0,0 +1,92 @@ +import * as types from 'constants/action_types' + +const reducers = {} +const defaultState = { +} + +reducers[types.FETCH_FILE_INFO_STARTED] = function(state, action) { + const { + uri, + } = action.data + const newFetching = Object.assign({}, state.fetching) + + newFetching[uri] = true + + return Object.assign({}, state, { + fetching: newFetching, + }) +} + +reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) { + const { + uri, + fileInfo, + } = action.data + const newByUri = Object.assign({}, state.byUri) + const newFetching = Object.assign({}, state.fetching) + + newByUri[uri] = fileInfo || {} + delete newFetching[uri] + + return Object.assign({}, state, { + byUri: newByUri, + fetching: newFetching, + }) +} + +reducers[types.DOWNLOADING_STARTED] = function(state, action) { + const { + uri, + fileInfo, + } = action.data + const newByUri = Object.assign({}, state.byUri) + const newDownloading = Object.assign({}, state.downloading) + + newDownloading[uri] = true + newByUri[uri] = fileInfo + + return Object.assign({}, state, { + downloading: newDownloading, + byUri: newByUri, + }) +} + +reducers[types.DOWNLOADING_PROGRESSED] = function(state, action) { + const { + uri, + fileInfo, + } = action.data + const newByUri = Object.assign({}, state.byUri) + const newDownloading = Object.assign({}, state.downloading) + + newByUri[uri] = fileInfo + newDownloading[uri] = true + + return Object.assign({}, state, { + byUri: newByUri, + downloading: newDownloading + }) +} + +reducers[types.DOWNLOADING_COMPLETED] = function(state, action) { + const { + uri, + fileInfo, + } = action.data + const newByUri = Object.assign({}, state.byUri) + const newDownloading = Object.assign({}, state.downloading) + + newByUri[uri] = fileInfo + delete newDownloading[uri] + + return Object.assign({}, state, { + byUri: newByUri, + downloading: newDownloading + }) +} + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js new file mode 100644 index 000000000..1b43b4900 --- /dev/null +++ b/ui/js/selectors/claims.js @@ -0,0 +1,24 @@ +import { + createSelector, +} from 'reselect' +import { + selectCurrentUri, +} from 'selectors/app' + +export const _selectState = state => state.claims || {} + +export const selectClaimsByUri = createSelector( + _selectState, + (state) => state.byUri || {} +) + +export const selectCurrentUriClaim = createSelector( + selectCurrentUri, + selectClaimsByUri, + (uri, byUri) => byUri[uri] || {} +) + +export const selectCurrentUriClaimOutpoint = createSelector( + selectCurrentUriClaim, + (claim) => `${claim.txid}:${claim.nout}` +) diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index c40f93c65..c8c7a1386 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -4,6 +4,14 @@ import { selectCurrentPage, selectCurrentUri, } from 'selectors/app' +import { + selectCurrentUriCostInfo, + selectFetchingCurrentUriCostInfo, +} from 'selectors/cost_info' +import { + selectCurrentUriFileInfo, + selectFetchingCurrentUriFileInfo, +} from 'selectors/file_info' export const _selectState = state => state.content || {} @@ -37,48 +45,6 @@ export const shouldFetchFeaturedContent = createSelector( } ) -export const selectResolvedUris = createSelector( - _selectState, - (state) => state.resolvedUris || {} -) - -export const selectCurrentResolvedUri = createSelector( - selectCurrentUri, - selectResolvedUris, - (uri, resolvedUris) => resolvedUris[uri] || {} -) - -export const selectCurrentResolvedUriClaim = createSelector( - selectCurrentResolvedUri, - (uri) => uri.claim || {} -) - -export const selectCurrentResolvedUriClaimOutpoint = createSelector( - selectCurrentResolvedUriClaim, - (claim) => `${claim.txid}:${claim.nout}` -) - -export const selectFileInfos = createSelector( - _selectState, - (state) => state.fileInfos || {} -) - -export const selectFileInfosByUri = createSelector( - selectFileInfos, - (fileInfos) => fileInfos.byUri || {} -) - -export const selectCurrentUriFileInfo = createSelector( - selectCurrentUri, - selectFileInfosByUri, - (uri, byUri) => byUri[uri] -) - -export const selectCurrentUriIsDownloaded = createSelector( - selectCurrentUriFileInfo, - (fileInfo) => fileInfo && fileInfo.length > 0 -) - export const selectFetchingFileInfos = createSelector( _selectState, (state) => state.fetchingFileInfos || {} @@ -91,73 +57,6 @@ export const selectCurrentUriFileReadyToPlay = createSelector( (fileInfo) => (fileInfo || {}).written_bytes > 0 ) -export const selectIsFetchingCurrentUriFileInfo = createSelector( - selectFetchingFileInfos, - selectCurrentUri, - (fetching, uri) => !!fetching[uri] -) - -export const selectCurrentUriIsPlaying = createSelector( - _selectState, - selectCurrentUri, - (state, uri) => state.nowPlaying == uri -) - -export const selectCostInfos = createSelector( - _selectState, - (state) => state.costInfos || {} -) - -export const selectCostInfosByUri = createSelector( - selectCostInfos, - (costInfos) => costInfos.byUri || {} -) - -export const selectFetchingCostInfos = createSelector( - _selectState, - (state) => state.fetchingCostInfos || {} -) - -export const selectIsFetchingCurrentUriCostInfo = createSelector( - selectFetchingCostInfos, - selectCurrentUri, - (fetching, uri) => !!fetching[uri] -) - -export const selectCurrentUriCostInfo = createSelector( - selectCurrentUri, - selectCostInfosByUri, - (uri, byUri) => byUri[uri] || {} -) - -export const shouldFetchCurrentUriCostInfo = createSelector( - selectCurrentPage, - selectCurrentUri, - selectIsFetchingCurrentUriCostInfo, - selectCurrentUriCostInfo, - (page, uri, fetching, costInfo) => { - if (page != 'show') return false - if (fetching) return false - if (Object.keys(costInfo).length != 0) return false - - return true - } -) - -export const shouldFetchCurrentUriFileInfo = createSelector( - selectCurrentPage, - selectCurrentUri, - selectIsFetchingCurrentUriFileInfo, - selectCurrentUriFileInfo, - (page, uri, fetching, fileInfo) => { - if (page != 'show') return false - if (fetching) return false - if (fileInfo != undefined) return false - - return true - } -) - export const selectFetchingDownloadedContent = createSelector( _selectState, (state) => !!state.fetchingDownloadedContent @@ -214,22 +113,6 @@ export const selectLoadingCurrentUri = createSelector( (byUri, uri) => !!byUri[uri] ) -export const selectDownloading = createSelector( - _selectState, - (state) => state.downloading || {} -) - -export const selectDownloadingByUri = createSelector( - selectDownloading, - (downloading) => downloading.byUri || {} -) - -export const selectDownloadingCurrentUri = createSelector( - selectCurrentUri, - selectDownloadingByUri, - (uri, byUri) => byUri[uri] -) - export const shouldFetchPublishedContent = createSelector( selectDaemonReady, selectCurrentPage, diff --git a/ui/js/selectors/cost_info.js b/ui/js/selectors/cost_info.js new file mode 100644 index 000000000..55a1416ea --- /dev/null +++ b/ui/js/selectors/cost_info.js @@ -0,0 +1,44 @@ +import { createSelector } from 'reselect' +import { + selectCurrentUri, + selectCurrentPage, +} from 'selectors/app' + +export const _selectState = state => state.costInfo || {} + +export const selectAllCostInfoByUri = createSelector( + _selectState, + (state) => state.byUri || {} +) + +export const selectCurrentUriCostInfo = createSelector( + selectCurrentUri, + selectAllCostInfoByUri, + (uri, byUri) => byUri[uri] || {} +) + +export const selectFetchingCostInfo = createSelector( + _selectState, + (state) => state.fetching || {} +) + +export const selectFetchingCurrentUriCostInfo = createSelector( + selectCurrentUri, + selectFetchingCostInfo, + (uri, byUri) => !!byUri[uri] +) + +export const shouldFetchCurrentUriCostInfo = createSelector( + selectCurrentPage, + selectCurrentUri, + selectFetchingCurrentUriCostInfo, + selectCurrentUriCostInfo, + (page, uri, fetching, costInfo) => { + if (page != 'show') return false + if (fetching) return false + if (Object.keys(costInfo).length != 0) return false + + return true + } +) + diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js new file mode 100644 index 000000000..298be45f3 --- /dev/null +++ b/ui/js/selectors/file_info.js @@ -0,0 +1,74 @@ +import { + createSelector, +} from 'reselect' +import { + selectCurrentUri, + selectCurrentPage, +} from 'selectors/app' + +export const _selectState = state => state.fileInfo || {} + +export const selectAllFileInfoByUri = createSelector( + _selectState, + (state) => state.byUri || {} +) + +export const selectCurrentUriRawFileInfo = createSelector( + selectCurrentUri, + selectAllFileInfoByUri, + (uri, byUri) => byUri[uri] +) + +export const selectCurrentUriFileInfo = createSelector( + selectCurrentUriRawFileInfo, + (fileInfo) => fileInfo +) + +export const selectFetchingFileInfo = createSelector( + _selectState, + (state) => state.fetching || {} +) + +export const selectFetchingCurrentUriFileInfo = createSelector( + selectCurrentUri, + selectFetchingFileInfo, + (uri, byUri) => !!byUri[uri] +) + +export const selectDownloading = createSelector( + _selectState, + (state) => state.downloading || {} +) + +export const selectDownloadingByUri = createSelector( + selectDownloading, + (downloading) => downloading.byUri || {} +) + +export const selectDownloadingCurrentUri = createSelector( + selectCurrentUri, + selectDownloadingByUri, + (uri, byUri) => !!byUri[uri] +) + +export const selectCurrentUriIsDownloaded = createSelector( + selectCurrentUriFileInfo, + (fileInfo) => { + return fileInfo && (fileInfo.written_bytes > 0 || fileInfo.completed) + } +) + +export const shouldFetchCurrentUriFileInfo = createSelector( + selectCurrentPage, + selectCurrentUri, + selectFetchingCurrentUriFileInfo, + selectCurrentUriFileInfo, + (page, uri, fetching, fileInfo) => { + if (page != 'show') return false + if (fetching) return false + if (fileInfo != undefined) return false + + return true + } +) + diff --git a/ui/js/store.js b/ui/js/store.js index ffc6d9d88..fce2c0ef3 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -6,7 +6,11 @@ import { createLogger } from 'redux-logger' import appReducer from 'reducers/app'; +import certificatesReducer from 'reducers/certificates' +import claimsReducer from 'reducers/claims' import contentReducer from 'reducers/content'; +import costInfoReducer from 'reducers/cost_info' +import fileInfoReducer from 'reducers/file_info' import rewardsReducer from 'reducers/rewards' import searchReducer from 'reducers/search' import walletReducer from 'reducers/wallet' @@ -43,7 +47,11 @@ function enableBatching(reducer) { const reducers = redux.combineReducers({ app: appReducer, + certificates: certificatesReducer, + claims: claimsReducer, + fileInfo: fileInfoReducer, content: contentReducer, + costInfo: costInfoReducer, rewards: rewardsReducer, search: searchReducer, wallet: walletReducer, diff --git a/ui/js/triggers.js b/ui/js/triggers.js index 63dddafcf..0d35f97d7 100644 --- a/ui/js/triggers.js +++ b/ui/js/triggers.js @@ -6,9 +6,13 @@ import { shouldFetchFeaturedContent, shouldFetchDownloadedContent, shouldFetchPublishedContent, - shouldFetchCurrentUriFileInfo, - shouldFetchCurrentUriCostInfo, } from 'selectors/content' +import { + shouldFetchCurrentUriFileInfo, +} from 'selectors/file_info' +import { + shouldFetchCurrentUriCostInfo, +} from 'selectors/cost_info' import { doFetchTransactions, doGetNewAddress, @@ -17,9 +21,13 @@ import { doFetchFeaturedContent, doFetchDownloadedContent, doFetchPublishedContent, - doFetchCurrentUriFileInfo, - doFetchCurrentUriCostInfo, } from 'actions/content' +import { + doFetchCurrentUriFileInfo, +} from 'actions/file_info' +import { + doFetchCurrentUriCostInfo, +} from 'actions/cost_info' const triggers = [] -- 2.43.4 From f08ce3d30078afb82462008bc07aadac1f777372 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 29 Apr 2017 16:50:29 +0700 Subject: [PATCH 27/56] A bunch of improvements and broken the back of the file rendering code now --- ui/js/actions/availability.js | 21 +++++ ui/js/actions/content.js | 5 +- ui/js/actions/search.js | 2 +- ui/js/component/fileActions/index.js | 21 +++++ .../{file-actions.js => fileActions/view.jsx} | 20 ++--- ui/js/component/fileCardStream/index.js | 33 +++++++- ui/js/component/fileCardStream/view.jsx | 75 ++++++++---------- ui/js/component/fileList/view.jsx | 4 +- ui/js/component/fileTile/index.js | 20 +++-- ui/js/component/fileTile/view.jsx | 10 +-- ui/js/component/fileTileStream/index.js | 39 +++++++++- ui/js/component/fileTileStream/view.jsx | 77 ++++++++----------- ui/js/constants/action_types.js | 2 + ui/js/page/showPage/view.jsx | 2 +- ui/js/reducers/app.js | 4 + ui/js/reducers/availability.js | 45 +++++++++++ ui/js/reducers/claims.js | 1 + ui/js/reducers/search.js | 4 +- ui/js/selectors/app.js | 23 +++++- ui/js/selectors/availability.js | 42 ++++++++++ ui/js/selectors/claims.js | 41 ++++++++++ ui/js/selectors/file_info.js | 10 +++ ui/js/store.js | 2 + 23 files changed, 385 insertions(+), 118 deletions(-) create mode 100644 ui/js/actions/availability.js create mode 100644 ui/js/component/fileActions/index.js rename ui/js/component/{file-actions.js => fileActions/view.jsx} (95%) create mode 100644 ui/js/reducers/availability.js create mode 100644 ui/js/selectors/availability.js diff --git a/ui/js/actions/availability.js b/ui/js/actions/availability.js new file mode 100644 index 000000000..d5d14fa19 --- /dev/null +++ b/ui/js/actions/availability.js @@ -0,0 +1,21 @@ +import * as types from 'constants/action_types' +import lbry from 'lbry' + +export function doFetchUriAvailability(uri) { + return function(dispatch, getState) { + dispatch({ + type: types.FETCH_AVAILABILITY_STARTED, + data: { uri } + }) + + lbry.get_availability({ uri }, (availability) => { + dispatch({ + type: types.FETCH_AVAILABILITY_COMPLETED', + data: { + availability, + uri, + } + }) + } + } +} diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index c37ad032c..76f7d5af8 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -1,6 +1,7 @@ import * as types from 'constants/action_types' import lbry from 'lbry' import lbryio from 'lbryio' +import lbryuri from 'lbryuri' import { selectCurrentUri, } from 'selectors/app' @@ -113,7 +114,9 @@ export function doFetchFeaturedContent() { }) Object.keys(Uris).forEach((category) => { - Uris[category].forEach((uri) => dispatch(doResolveUri(uri))) + Uris[category].forEach((uri) => { + dispatch(doResolveUri(lbryuri.normalize(uri))) + }) }) } diff --git a/ui/js/actions/search.js b/ui/js/actions/search.js index a44ec964f..a4b1e83c4 100644 --- a/ui/js/actions/search.js +++ b/ui/js/actions/search.js @@ -32,7 +32,7 @@ export function doSearchContent(query) { contentName: result.name, claimId: result.channel_id || result.claim_id, }) - dispatch(doResolveUri(uri.split('://')[1])) + dispatch(doResolveUri(uri)) }) dispatch({ diff --git a/ui/js/component/fileActions/index.js b/ui/js/component/fileActions/index.js new file mode 100644 index 000000000..d17e57af8 --- /dev/null +++ b/ui/js/component/fileActions/index.js @@ -0,0 +1,21 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import { + selectObscureNsfw, + selectHidePrice, + selectHasSignature, +} from 'selectors/app' +import FileActions from './view' + +const select = (state) => ({ + obscureNsfw: selectObscureNsfw(state), + hidePrice: selectHidePrice(state), + hasSignature: selectHasSignature(state), +}) + +const perform = { +} + +export default connect(select, perform)(FileActions) diff --git a/ui/js/component/file-actions.js b/ui/js/component/fileActions/view.jsx similarity index 95% rename from ui/js/component/file-actions.js rename to ui/js/component/fileActions/view.jsx index 2829615cc..96267dbd8 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/fileActions/view.jsx @@ -1,16 +1,16 @@ import React from 'react'; -import lbry from '../lbry.js'; -import lbryuri from '../lbryuri.js'; +import lbry from 'lbry'; +import lbryuri from 'lbryuri'; +import {Icon, FilePrice} from 'component/common'; +import {Modal} from 'component/modal'; +import {FormField} from 'component/form'; import Link from 'component/link'; -import {Icon, FilePrice} from '../component/common.js'; -import Modal from './modal.js'; -import FormField from './form.js'; -import {ToolTip} from '../component/tooltip.js'; -import {DropDownMenu, DropDownMenuItem} from './menu.js'; +import {ToolTip} from 'component/tooltip'; +import {DropDownMenu, DropDownMenuItem} from 'component/menu'; const {shell} = require('electron'); -let FileActionsRow = React.createClass({ +const FileActionsRow = React.createClass({ _isMounted: false, _fileInfoSubscribeId: null, @@ -192,7 +192,7 @@ let FileActionsRow = React.createClass({ } }); -export let FileActions = React.createClass({ +const FileActions = React.createClass({ _isMounted: false, _fileInfoSubscribeId: null, @@ -268,3 +268,5 @@ export let FileActions = React.createClass({
      ); } }); + +export default FileActions diff --git a/ui/js/component/fileCardStream/index.js b/ui/js/component/fileCardStream/index.js index 854ae624f..14e3dc7f9 100644 --- a/ui/js/component/fileCardStream/index.js +++ b/ui/js/component/fileCardStream/index.js @@ -5,13 +5,40 @@ import { import { doNavigate, } from 'actions/app' +import { + selectHidePrice, + selectObscureNsfw, +} from 'selectors/app' +import { + makeSelectClaimForUri, + makeSelectSourceForUri, + makeSelectMetadataForUri, +} from 'selectors/claims' +import { + makeSelectFileInfoForUri, +} from 'selectors/file_info' import FileCardStream from './view' -const select = (state) => ({ -}) +const makeSelect = () => { + const selectClaimForUri = makeSelectClaimForUri() + const selectFileInfoForUri = makeSelectFileInfoForUri() + const selectMetadataForUri = makeSelectMetadataForUri() + const selectSourceForUri = makeSelectSourceForUri() + const select = (state, props) => ({ + claim: selectClaimForUri(state, props), + fileInfo: selectFileInfoForUri(state, props), + hidePrice: selectHidePrice(state), + obscureNsfw: selectObscureNsfw(state), + hasSignature: false, + metadata: selectMetadataForUri(state, props), + source: selectSourceForUri(state, props), + }) + + return select +} const perform = (dispatch) => ({ navigate: (path) => dispatch(doNavigate(path)), }) -export default connect(select, perform)(FileCardStream) +export default connect(makeSelect, perform)(FileCardStream) diff --git a/ui/js/component/fileCardStream/view.jsx b/ui/js/component/fileCardStream/view.jsx index 74c01d863..d2bd9a1c3 100644 --- a/ui/js/component/fileCardStream/view.jsx +++ b/ui/js/component/fileCardStream/view.jsx @@ -2,70 +2,63 @@ import React from 'react'; import lbry from 'lbry.js'; import lbryuri from 'lbryuri.js'; import Link from 'component/link'; -import {FileActions} from 'component/file-actions.js'; -import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js'; -import UriIndicator from 'component/channel-indicator.js'; +import {Thumbnail, TruncatedText, FilePrice} from 'component/common'; +import UriIndicator from 'component/channel-indicator'; -const FileCardStream = React.createClass({ - _fileInfoSubscribeId: null, - _isMounted: null, - _metadata: null, - - - propTypes: { - uri: React.PropTypes.string, - claimInfo: React.PropTypes.object, - outpoint: React.PropTypes.string, - hideOnRemove: React.PropTypes.bool, - hidePrice: React.PropTypes.bool, - obscureNsfw: React.PropTypes.bool - }, - getInitialState: function() { - return { +class FileCardStream extends React.Component { + constructor(props) { + super(props) + this._fileInfoSubscribeId = null + this._isMounted = null + this._metadata = null + this.state = { showNsfwHelp: false, isHidden: false, } - }, - getDefaultProps: function() { - return { - obscureNsfw: !lbry.getClientSetting('showNsfw'), - hidePrice: false, - hasSignature: false, - } - }, - componentDidMount: function() { + } + + componentDidMount() { this._isMounted = true; if (this.props.hideOnRemove) { this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); } - }, - componentWillUnmount: function() { + } + + componentWillUnmount() { if (this._fileInfoSubscribeId) { lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); } - }, - onFileInfoUpdate: function(fileInfo) { + } + + onFileInfoUpdate(fileInfo) { if (!fileInfo && this._isMounted && this.props.hideOnRemove) { this.setState({ isHidden: true }); } - }, - handleMouseOver: function() { + } + + handleMouseOver() { this.setState({ hovered: true, }); - }, - handleMouseOut: function() { + } + + handleMouseOut() { this.setState({ hovered: false, }); - }, - render: function() { + } + + render() { if (this.state.isHidden) { return null; } + if (!this.props.metadata) { + return null + } + const uri = lbryuri.normalize(this.props.uri); const metadata = this.props.metadata; const isConfirmed = !!metadata; @@ -73,13 +66,13 @@ const FileCardStream = React.createClass({ const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; const primaryUrl = 'show=' + uri; return ( -
      +
      ); } -}); +} export default FileCardStream diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index 7d1cc9409..a119f8792 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -79,9 +79,7 @@ const FileList = React.createClass({ const uri = lbryuri.build({contentName: name, channelName: channel_name}); seenUris[name] = true; - content.push(); + content.push() } return ( diff --git a/ui/js/component/fileTile/index.js b/ui/js/component/fileTile/index.js index 07c696f81..5703e28cd 100644 --- a/ui/js/component/fileTile/index.js +++ b/ui/js/component/fileTile/index.js @@ -3,15 +3,25 @@ import { connect } from 'react-redux' import { - selectClaimsByUri, + makeSelectClaimForUri, } from 'selectors/claims' +import { + makeSelectFileInfoForUri, +} from 'selectors/file_info' import FileTile from './view' -const select = (state) => ({ - claims: (uri) => selectClaimsByUri(state)[uri], -}) +const makeSelect = () => { + const selectClaimForUri = makeSelectClaimForUri() + const selectFileInfoForUri = makeSelectFileInfoForUri() + const select = (state, props) => ({ + claim: selectClaimForUri(state, props), + fileInfo: selectFileInfoForUri(state, props), + }) + + return select +} const perform = (dispatch) => ({ }) -export default connect(select, perform)(FileTile) +export default connect(makeSelect, perform)(FileTile) diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index dfea32731..c85309995 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -10,11 +10,11 @@ class FileTile extends React.Component { render() { const { displayStyle, - uri + uri, + claim, } = this.props - const claimInfo = this.props.claims(uri) - if(!claimInfo) { + if(!claim) { if (displayStyle == 'card') { return } @@ -22,9 +22,9 @@ class FileTile extends React.Component { } return displayStyle == 'card' ? - + : - + } } diff --git a/ui/js/component/fileTileStream/index.js b/ui/js/component/fileTileStream/index.js index 03aaa299a..c297eec25 100644 --- a/ui/js/component/fileTileStream/index.js +++ b/ui/js/component/fileTileStream/index.js @@ -5,13 +5,46 @@ import { import { doNavigate, } from 'actions/app' +import { + makeSelectClaimForUri, + makeSelectSourceForUri, + makeSelectMetadataForUri, +} from 'selectors/claims' +import { + makeSelectFileInfoForUri, +} from 'selectors/file_info' +import { + makeSelectFetchingAvailabilityForUri, + makeSelectAvailabilityForUri, +} from 'selectors/availability' +import { + selectObscureNsfw, +} from 'selectors/app' import FileTileStream from './view' -const select = (state) => ({ -}) +const makeSelect = () => { + const selectClaimForUri = makeSelectClaimForUri() + const selectFileInfoForUri = makeSelectFileInfoForUri() + const selectFetchingAvailabilityForUri = makeSelectFetchingAvailabilityForUri() + const selectAvailabilityForUri = makeSelectAvailabilityForUri() + const selectMetadataForUri = makeSelectMetadataForUri() + const selectSourceForUri = makeSelectSourceForUri() + + const select = (state, props) => ({ + claim: selectClaimForUri(state, props), + fileInfo: selectFileInfoForUri(state, props), + fetchingAvailability: selectFetchingAvailabilityForUri(state, props), + selectAvailabilityForUri: selectAvailabilityForUri(state, props), + obscureNswf: selectObscureNsfw(state), + metadata: selectMetadataForUri(state, props), + source: selectSourceForUri(state, props), + }) + + return select +} const perform = (dispatch) => ({ navigate: (path) => dispatch(doNavigate(path)) }) -export default connect(select, perform)(FileTileStream) +export default connect(makeSelect, perform)(FileTileStream) diff --git a/ui/js/component/fileTileStream/view.jsx b/ui/js/component/fileTileStream/view.jsx index ec710b717..f5a0c218a 100644 --- a/ui/js/component/fileTileStream/view.jsx +++ b/ui/js/component/fileTileStream/view.jsx @@ -2,86 +2,76 @@ import React from 'react'; import lbry from 'lbry.js'; import lbryuri from 'lbryuri.js'; import Link from 'component/link'; -import { - FileActions -} from 'component/file-actions.js'; +import FileActions from 'component/fileActions'; import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js'; import UriIndicator from 'component/channel-indicator.js'; /*should be merged into FileTile once FileTile is refactored to take a single id*/ -const FileTileStream = React.createClass({ - _fileInfoSubscribeId: null, - _isMounted: null, - - propTypes: { - uri: React.PropTypes.string, - metadata: React.PropTypes.object, - contentType: React.PropTypes.string.isRequired, - outpoint: React.PropTypes.string, - hasSignature: React.PropTypes.bool, - signatureIsValid: React.PropTypes.bool, - hideOnRemove: React.PropTypes.bool, - hidePrice: React.PropTypes.bool, - obscureNsfw: React.PropTypes.bool - }, - getInitialState: function() { - return { +class FileTileStream extends React.Component { + constructor(props) { + super(props) + this._fileInfoSubscribeId = null + this._isMounted = null + this.state = { showNsfwHelp: false, isHidden: false, } - }, - getDefaultProps: function() { - return { - obscureNsfw: !lbry.getClientSetting('showNsfw'), - hidePrice: false, - hasSignature: false, - } - }, - componentDidMount: function() { + } + + componentDidMount() { this._isMounted = true; if (this.props.hideOnRemove) { this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); } - }, - componentWillUnmount: function() { + } + + componentWillUnmount() { if (this._fileInfoSubscribeId) { lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); } - }, - onFileInfoUpdate: function(fileInfo) { + } + + onFileInfoUpdate(fileInfo) { if (!fileInfo && this._isMounted && this.props.hideOnRemove) { this.setState({ isHidden: true }); } - }, - handleMouseOver: function() { + } + + handleMouseOver() { if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { this.setState({ showNsfwHelp: true, }); } - }, - handleMouseOut: function() { + } + + handleMouseOut() { if (this.state.showNsfwHelp) { this.setState({ showNsfwHelp: false, }); } - }, - render: function() { + } + + render() { if (this.state.isHidden) { return null; } + const { + metadata, + navigate, + } = this.props + const uri = lbryuri.normalize(this.props.uri); - const metadata = this.props.metadata; const isConfirmed = !!metadata; const title = isConfirmed ? metadata.title : uri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; - const { navigate } = this.props + return ( -
      +
      navigate(`show=${uri}`)}> @@ -101,7 +91,6 @@ const FileTileStream = React.createClass({

      -

      @@ -125,6 +114,6 @@ const FileTileStream = React.createClass({

      ); } -}); +} export default FileTileStream diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 568a85e70..cb5657d59 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -54,6 +54,8 @@ export const DOWNLOADING_STARTED = 'DOWNLOADING_STARTED' export const DOWNLOADING_PROGRESSED = 'DOWNLOADING_PROGRESSED' export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED' export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED' +export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED' +export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED' // Search export const SEARCH_STARTED = 'SEARCH_STARTED' diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx index 8ff3b9ca9..9ba48cbf3 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/showPage/view.jsx @@ -9,7 +9,7 @@ import { FilePrice, BusyMessage } from 'component/common.js'; -import {FileActions} from 'component/file-actions.js'; +import FileActions from 'component/fileActions'; import Link from 'component/link'; import UriIndicator from 'component/channel-indicator.js'; diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index 855330de3..4fa7442ba 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -1,4 +1,5 @@ import * as types from 'constants/action_types' +import lbry from 'lbry' const reducers = {} const defaultState = { @@ -9,6 +10,9 @@ const defaultState = { upgradeSkipped: sessionStorage.getItem('upgradeSkipped'), daemonReady: false, platform: window.navigator.platform, + obscureNsfw: !lbry.getClientSetting('showNsfw'), + hidePrice: false, + hasSignature: false, } reducers[types.NAVIGATE] = function(state, action) { diff --git a/ui/js/reducers/availability.js b/ui/js/reducers/availability.js new file mode 100644 index 000000000..6c0e28a8b --- /dev/null +++ b/ui/js/reducers/availability.js @@ -0,0 +1,45 @@ +import * as types from 'constants/action_types' + +const reducers = {} +const defaultState = { +} + +reducers[types.FETCH_AVAILABILITY_STARTED] = function(state, action) { + const { + uri, + } = action.data + const newFetching = Object.assign({}, state.fetching) + const newByUri = Object.assign({}, newFetching.byUri) + + newByUri[uri] = true + newFetching.byUri = newByUri + + return Object.assign({}, state, { + fetching: newFetching, + }) +} + +reducers[types.FETCH_AVAILABILITY_COMPLETED] = function(state, action) { + const { + uri, + availability, + } = action.data + const newFetching = Object.assign({}, state.fetching) + const newFetchingByUri = Object.assign({}, newFetching.byUri) + const newAvailabilityByUri = Object.assign({}, state.byUri) + + delete newFetchingByUri[uri] + newFetching.byUri = newFetchingByUri + newAvailabilityByUri[uri] = availability + + return Object.assign({}, state, { + fetching: newFetching, + byUri: newAvailabilityByUri + }) +} + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 30d6fa4e2..3fbf181d6 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -1,4 +1,5 @@ import * as types from 'constants/action_types' +import lbryuri from 'lbryuri' const reducers = {} const defaultState = { diff --git a/ui/js/reducers/search.js b/ui/js/reducers/search.js index 1fd73d10b..50a38966f 100644 --- a/ui/js/reducers/search.js +++ b/ui/js/reducers/search.js @@ -1,4 +1,5 @@ import * as types from 'constants/action_types' +import lbryuri from 'lbryuri' const reducers = {} const defaultState = { @@ -18,10 +19,11 @@ reducers[types.SEARCH_STARTED] = function(state, action) { reducers[types.SEARCH_COMPLETED] = function(state, action) { const { query, + results, } = action.data const oldResults = Object.assign({}, state.results) const newByQuery = Object.assign({}, oldResults.byQuery) - newByQuery[query] = action.data.results + newByQuery[query] = results const newResults = Object.assign({}, oldResults, { byQuery: newByQuery }) diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index 929967c87..7105bc16b 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -19,7 +19,13 @@ export const selectCurrentPage = createSelector( export const selectCurrentUri = createSelector( selectCurrentPath, - (path) => path.split('://')[1] + (path) => { + if (path.match(/=/)) { + return path.split('=')[1] + } else { + return undefined + } + } ) export const selectPlatform = createSelector( @@ -145,3 +151,18 @@ export const selectDaemonReady = createSelector( _selectState, (state) => state.daemonReady ) + +export const selectObscureNsfw = createSelector( + _selectState, + (state) => !!state.obscureNsfw +) + +export const selectHidePrice = createSelector( + _selectState, + (state) => !!state.hidePrice +) + +export const selectHasSignature = createSelector( + _selectState, + (state) => !!state.hasSignature +) diff --git a/ui/js/selectors/availability.js b/ui/js/selectors/availability.js new file mode 100644 index 000000000..0588c1528 --- /dev/null +++ b/ui/js/selectors/availability.js @@ -0,0 +1,42 @@ +import { + createSelector, +} from 'reselect' + +const _selectState = state => state.availability + +export const selectAvailabilityByUri = createSelector( + _selectState, + (state) => state.byUri || {} +) + +export const selectFetchingAvailability = createSelector( + _selectState, + (state) => state.fetching || {} +) + +export const selectFetchingAvailabilityByUri = createSelector( + selectFetchingAvailability, + (fetching) => fetching.byUri || {} +) + +const selectAvailabilityForUri = (state, props) => { + return selectAvailabilityByUri(state)[props.uri] +} + +export const makeSelectAvailabilityForUri = () => { + return createSelector( + selectAvailabilityForUri, + (availability) => availability + ) +} + +const selectFetchingAvailabilityForUri = (state, props) => { + return selectFetchingAvailabilityByUri(state)[props.uri] +} + +export const makeSelectFetchingAvailabilityForUri = () => { + return createSelector( + selectFetchingAvailabilityForUri, + (fetching) => fetching + ) +} diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index 1b43b4900..3b35ce92f 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -1,6 +1,7 @@ import { createSelector, } from 'reselect' +import lbryuri from 'lbryuri' import { selectCurrentUri, } from 'selectors/app' @@ -22,3 +23,43 @@ export const selectCurrentUriClaimOutpoint = createSelector( selectCurrentUriClaim, (claim) => `${claim.txid}:${claim.nout}` ) + +const selectClaimForUri = (state, props) => { + const uri = lbryuri.normalize(props.uri) + return selectClaimsByUri(state)[uri] +} + +export const makeSelectClaimForUri = () => { + return createSelector( + selectClaimForUri, + (claim) => claim + ) +} + +const selectMetadataForUri = (state, props) => { + const claim = selectClaimForUri(state, props) + const metadata = claim && claim.value && claim.value.stream && claim.value.stream.metadata + + return metadata ? metadata : undefined +} + +export const makeSelectMetadataForUri = () => { + return createSelector( + selectMetadataForUri, + (metadata) => metadata + ) +} + +const selectSourceForUri = (state, props) => { + const claim = selectClaimForUri(state, props) + const source = claim && claim.value && claim.value.stream && claim.value.stream.source + + return source ? source : undefined +} + +export const makeSelectSourceForUri = () => { + return createSelector( + selectSourceForUri, + (source) => source + ) +} diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 298be45f3..c26b5dcc8 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -72,3 +72,13 @@ export const shouldFetchCurrentUriFileInfo = createSelector( } ) +const selectFileInfoForUri = (state, props) => { + return selectAllFileInfoByUri(state)[props.uri] +} + +export const makeSelectFileInfoForUri = () => { + return createSelector( + selectFileInfoForUri, + (fileInfo) => fileInfo + ) +} diff --git a/ui/js/store.js b/ui/js/store.js index fce2c0ef3..462c6a5d0 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -6,6 +6,7 @@ import { createLogger } from 'redux-logger' import appReducer from 'reducers/app'; +import availabilityReducer from 'reducers/availability' import certificatesReducer from 'reducers/certificates' import claimsReducer from 'reducers/claims' import contentReducer from 'reducers/content'; @@ -47,6 +48,7 @@ function enableBatching(reducer) { const reducers = redux.combineReducers({ app: appReducer, + availability: availabilityReducer, certificates: certificatesReducer, claims: claimsReducer, fileInfo: fileInfoReducer, -- 2.43.4 From 1d16c73cf40794a5c3027564c291c2a0318b80e3 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sun, 30 Apr 2017 00:02:25 +0700 Subject: [PATCH 28/56] More refactoring/fixing of file actions, tiles, etc --- ui/js/actions/availability.js | 22 +- ui/js/actions/content.js | 5 + ui/js/actions/cost_info.js | 14 +- ui/js/actions/file_info.js | 44 +++ ui/js/component/common.js | 48 --- ui/js/component/fileActions/index.js | 60 +++- ui/js/component/fileActions/view.jsx | 458 ++++++++++++++---------- ui/js/component/fileCardStream/view.jsx | 5 +- ui/js/component/filePrice/index.js | 22 ++ ui/js/component/filePrice/view.jsx | 21 ++ ui/js/component/fileTileStream/view.jsx | 3 +- ui/js/component/video/index.js | 2 - ui/js/constants/action_types.js | 2 + ui/js/lbry.js | 2 +- ui/js/page/showPage/view.jsx | 16 +- ui/js/reducers/content.js | 30 -- ui/js/reducers/file_info.js | 78 +++- ui/js/selectors/availability.js | 32 ++ ui/js/selectors/content.js | 31 -- ui/js/selectors/cost_info.js | 10 + ui/js/selectors/file_info.js | 53 ++- ui/js/triggers.js | 11 + 22 files changed, 635 insertions(+), 334 deletions(-) create mode 100644 ui/js/component/filePrice/index.js create mode 100644 ui/js/component/filePrice/view.jsx diff --git a/ui/js/actions/availability.js b/ui/js/actions/availability.js index d5d14fa19..74c85eaa3 100644 --- a/ui/js/actions/availability.js +++ b/ui/js/actions/availability.js @@ -1,5 +1,8 @@ import * as types from 'constants/action_types' import lbry from 'lbry' +import { + selectCurrentUri, +} from 'selectors/app' export function doFetchUriAvailability(uri) { return function(dispatch, getState) { @@ -8,14 +11,29 @@ export function doFetchUriAvailability(uri) { data: { uri } }) - lbry.get_availability({ uri }, (availability) => { + const successCallback = (availability) => { dispatch({ - type: types.FETCH_AVAILABILITY_COMPLETED', + type: types.FETCH_AVAILABILITY_COMPLETED, data: { availability, uri, } }) } + + const errorCallback = () => { + console.debug('error') + } + + lbry.get_availability({ uri }, successCallback, errorCallback) + } +} + +export function doFetchCurrentUriAvailability() { + return function(dispatch, getState) { + const state = getState() + const uri = selectCurrentUri(state) + + dispatch(doFetchUriAvailability(uri)) } } diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 76f7d5af8..9e9f90e3d 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -23,6 +23,9 @@ import { import { doOpenModal, } from 'actions/app' +import { + doFetchCostInfoForUri, +} from 'actions/cost_info' import batchActions from 'util/batchActions' export function doResolveUri(uri) { @@ -46,6 +49,8 @@ export function doResolveUri(uri) { certificate, } }) + + dispatch(doFetchCostInfoForUri(uri)) }) } } diff --git a/ui/js/actions/cost_info.js b/ui/js/actions/cost_info.js index 2d513e578..278f1d7b4 100644 --- a/ui/js/actions/cost_info.js +++ b/ui/js/actions/cost_info.js @@ -4,11 +4,8 @@ import { } from 'selectors/app' import lbry from 'lbry' -export function doFetchCurrentUriCostInfo() { +export function doFetchCostInfoForUri(uri) { return function(dispatch, getState) { - const state = getState() - const uri = selectCurrentUri(state) - dispatch({ type: types.FETCH_COST_INFO_STARTED, data: { @@ -28,3 +25,12 @@ export function doFetchCurrentUriCostInfo() { } } +export function doFetchCurrentUriCostInfo() { + return function(dispatch, getState) { + const state = getState() + const uri = selectCurrentUri(state) + + dispatch(doFetchCostInfoForUri(uri)) + } +} + diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index 9005e6f01..77763352b 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -6,6 +6,13 @@ import { import { selectCurrentUriClaimOutpoint, } from 'selectors/claims' +import { + doCloseModal, +} from 'actions/app' + +const { + shell, +} = require('electron') export function doFetchCurrentUriFileInfo() { return function(dispatch, getState) { @@ -32,3 +39,40 @@ export function doFetchCurrentUriFileInfo() { }) } } + +export function doOpenFileInShell(fileInfo) { + return function(dispatch, getState) { + shell.openItem(fileInfo.download_path) + } +} + +export function doOpenFileInFolder(fileInfo) { + return function(dispatch, getState) { + shell.showItemInFolder(fileInfo.download_path) + } +} + +export function doDeleteFile(uri, fileInfo, deleteFromComputer) { + return function(dispatch, getState) { + dispatch({ + type: types.DELETE_FILE_STARTED, + data: { + uri, + fileInfo, + deleteFromComputer, + } + }) + + const successCallback = () => { + dispatch({ + type: types.DELETE_FILE_COMPLETED, + data: { + uri, + } + }) + dispatch(doCloseModal()) + } + + lbry.removeFile(fileInfo.outpoint, deleteFromComputer, successCallback) + } +} diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 8da20ca8e..aac91004a 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -82,54 +82,6 @@ export let CreditAmount = React.createClass({ } }); -export let FilePrice = React.createClass({ - _isMounted: false, - - propTypes: { - uri: React.PropTypes.string.isRequired, - look: React.PropTypes.oneOf(['indicator', 'plain']), - }, - - getDefaultProps: function() { - return { - look: 'indicator', - } - }, - - componentWillMount: function() { - this.setState({ - cost: null, - isEstimate: null, - }); - }, - - componentDidMount: function() { - this._isMounted = true; - lbry.getCostInfo(this.props.uri).then(({cost, includesData}) => { - if (this._isMounted) { - this.setState({ - cost: cost, - isEstimate: !includesData, - }); - } - }, (err) => { - // If we get an error looking up cost information, do nothing - }); - }, - - componentWillUnmount: function() { - this._isMounted = false; - }, - - render: function() { - if (this.state.cost === null) { - return ???; - } - - return - } -}); - var addressStyle = { fontFamily: '"Consolas", "Lucida Console", "Adobe Source Code Pro", monospace', }; diff --git a/ui/js/component/fileActions/index.js b/ui/js/component/fileActions/index.js index d17e57af8..19e66dfdb 100644 --- a/ui/js/component/fileActions/index.js +++ b/ui/js/component/fileActions/index.js @@ -6,16 +6,62 @@ import { selectObscureNsfw, selectHidePrice, selectHasSignature, + selectPlatform, } from 'selectors/app' +import { + makeSelectFileInfoForUri, + makeSelectDownloadingForUri, + makeSelectLoadingForUri, +} from 'selectors/file_info' +import { + makeSelectAvailabilityForUri, +} from 'selectors/availability' +import { + selectCurrentModal, +} from 'selectors/app' +import { + doCloseModal, + doOpenModal, +} from 'actions/app' +import { + doOpenFileInShell, + doOpenFileInFolder, + doDeleteFile, +} from 'actions/file_info' +import { + doWatchVideo, +} from 'actions/content' import FileActions from './view' -const select = (state) => ({ - obscureNsfw: selectObscureNsfw(state), - hidePrice: selectHidePrice(state), - hasSignature: selectHasSignature(state), -}) +const makeSelect = () => { + const selectFileInfoForUri = makeSelectFileInfoForUri() + const selectAvailabilityForUri = makeSelectAvailabilityForUri() + const selectDownloadingForUri = makeSelectDownloadingForUri() + const selectLoadingForUri = makeSelectLoadingForUri() -const perform = { + const select = (state, props) => ({ + obscureNsfw: selectObscureNsfw(state), + hidePrice: selectHidePrice(state), + hasSignature: selectHasSignature(state), + fileInfo: selectFileInfoForUri(state, props), + availability: selectAvailabilityForUri(state, props), + platform: selectPlatform(state), + modal: selectCurrentModal(state), + downloading: selectDownloadingForUri(state, props), + loading: selectLoadingForUri(state, props), + }) + + return select } -export default connect(select, perform)(FileActions) +const perform = (dispatch) => ({ + closeModal: () => dispatch(doCloseModal()), + openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)), + openInShell: (fileInfo) => dispatch(doOpenFileInShell(fileInfo)), + affirmPurchase: () => console.log('affirm purchase'), + deleteFile: (fileInfo, deleteFromComputer) => dispatch(doDeleteFile(fileInfo, deleteFromComputer)), + openModal: (modal) => dispatch(doOpenModal(modal)), + downloadClick: () => dispatch(doWatchVideo()), +}) + +export default connect(makeSelect, perform)(FileActions) diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index 96267dbd8..8b880502d 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -1,149 +1,66 @@ import React from 'react'; import lbry from 'lbry'; import lbryuri from 'lbryuri'; -import {Icon, FilePrice} from 'component/common'; +import {Icon,} from 'component/common'; +import FilePrice from 'component/filePrice' import {Modal} from 'component/modal'; import {FormField} from 'component/form'; import Link from 'component/link'; import {ToolTip} from 'component/tooltip'; import {DropDownMenu, DropDownMenuItem} from 'component/menu'; -const {shell} = require('electron'); - -const FileActionsRow = React.createClass({ - _isMounted: false, - _fileInfoSubscribeId: null, - - propTypes: { - uri: React.PropTypes.string, - outpoint: React.PropTypes.string.isRequired, - metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]), - contentType: React.PropTypes.string.isRequired, - }, - getInitialState: function() { - return { - fileInfo: null, - modal: null, - menuOpen: false, +class FileActionsRow extends React.Component { + constructor(props) { + super(props) + this.state = { deleteChecked: false, - attemptingDownload: false, - attemptingRemove: false, } - }, - onFileInfoUpdate: function(fileInfo) { - if (this._isMounted) { - this.setState({ - fileInfo: fileInfo ? fileInfo : false, - attemptingDownload: fileInfo ? false : this.state.attemptingDownload - }); - } - }, - tryDownload: function() { - this.setState({ - attemptingDownload: true, - attemptingRemove: false - }); - lbry.getCostInfo(this.props.uri).then(({cost}) => { - lbry.getBalance((balance) => { - if (cost > balance) { - this.setState({ - modal: 'notEnoughCredits', - attemptingDownload: false, - }); - } else if (this.state.affirmedPurchase) { - lbry.get({uri: this.props.uri}).then((streamInfo) => { - if (streamInfo === null || typeof streamInfo !== 'object') { - this.setState({ - modal: 'timedOut', - attemptingDownload: false, - }); - } - }); - } else { - this.setState({ - attemptingDownload: false, - modal: 'affirmPurchase' - }) - } - }); - }); - }, - closeModal: function() { - this.setState({ - modal: null, - }) - }, - onDownloadClick: function() { - if (!this.state.fileInfo && !this.state.attemptingDownload) { - this.tryDownload(); - } - }, - onOpenClick: function() { - if (this.state.fileInfo && this.state.fileInfo.download_path) { - shell.openItem(this.state.fileInfo.download_path); - } - }, - handleDeleteCheckboxClicked: function(event) { + } + + handleDeleteCheckboxClicked(event) { this.setState({ deleteChecked: event.target.checked, - }); - }, - handleRevealClicked: function() { - if (this.state.fileInfo && this.state.fileInfo.download_path) { - shell.showItemInFolder(this.state.fileInfo.download_path); - } - }, - handleRemoveClicked: function() { - this.setState({ - modal: 'confirmRemove', - }); - }, - handleRemoveConfirmed: function() { - lbry.removeFile(this.props.outpoint, this.state.deleteChecked); - this.setState({ - modal: null, - fileInfo: false, - attemptingDownload: false - }); - }, - onAffirmPurchase: function() { - this.setState({ - affirmedPurchase: true, - modal: null - }); - this.tryDownload(); - }, - openMenu: function() { - this.setState({ - menuOpen: !this.state.menuOpen, - }); - }, - componentDidMount: function() { - this._isMounted = true; - this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); - }, - componentWillUnmount: function() { - this._isMounted = false; - if (this._fileInfoSubscribeId) { - lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); - } - }, - render: function() { - if (this.state.fileInfo === null) + }) + } + + render() { + const { + fileInfo, + platform, + downloading, + loading, + uri, + deleteFile, + openInFolder, + openInShell, + modal, + openModal, + affirmPurchase, + closeModal, + downloadClick, + } = this.props + + const { + deleteChecked, + } = this.state + + const metadata = fileInfo ? fileInfo.metadata : null + + if (!fileInfo) { return null; } - const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder', - showMenu = !!this.state.fileInfo; + const openInFolderMessage = platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder', + showMenu = Object.keys(fileInfo).length != 0; let linkBlock; - if (this.state.fileInfo === false && !this.state.attemptingDownload) { - linkBlock = ; - } else if (this.state.attemptingDownload || (!this.state.fileInfo.completed && !this.state.fileInfo.isMine)) { + if (Object.keys(fileInfo).length == 0 && !downloading && !loading) { + linkBlock = ; + } else if (downloading || loading) { const - progress = this.state.fileInfo ? this.state.fileInfo.written_bytes / this.state.fileInfo.total_bytes * 100 : 0, - label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', + progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0, + label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', labelWithIcon = {label}; linkBlock = ( @@ -153,101 +70,253 @@ const FileActionsRow = React.createClass({
      ); } else { - linkBlock = ; + linkBlock = openInShell(fileInfo)} />; } - const uri = lbryuri.normalize(this.props.uri); - const title = this.props.metadata ? this.props.metadata.title : uri; + const title = metadata ? metadata.title : uri; return (
      - {this.state.fileInfo !== null || this.state.fileInfo.isMine + {fileInfo !== null || fileInfo.isMine ? linkBlock : null} { showMenu ? - - + openInFolder(fileInfo)} label={openInFolderMessage} /> + openModal('confirmRemove')} label="Remove..." /> : '' } - - Are you sure you'd like to buy {title} for credits? + + Are you sure you'd like to buy {title} for credits? - + You don't have enough LBRY credits to pay for this stream. - + LBRY was unable to download the stream {uri}. - + deleteFile(uri, fileInfo, deleteChecked)} + onAborted={closeModal}>

      Are you sure you'd like to remove {title} from LBRY?

      - +
      ); } -}); +} -const FileActions = React.createClass({ - _isMounted: false, - _fileInfoSubscribeId: null, +// const FileActionsRow = React.createClass({ +// _isMounted: false, +// _fileInfoSubscribeId: null, - propTypes: { - uri: React.PropTypes.string, - outpoint: React.PropTypes.string.isRequired, - metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]), - contentType: React.PropTypes.string, - }, - getInitialState: function() { - return { +// propTypes: { +// uri: React.PropTypes.string, +// outpoint: React.PropTypes.string.isRequired, +// metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]), +// contentType: React.PropTypes.string.isRequired, +// }, +// getInitialState: function() { +// return { +// fileInfo: null, +// modal: null, +// menuOpen: false, +// deleteChecked: false, +// attemptingDownload: false, +// attemptingRemove: false, +// } +// }, +// onFileInfoUpdate: function(fileInfo) { +// if (this._isMounted) { +// this.setState({ +// fileInfo: fileInfo ? fileInfo : false, +// attemptingDownload: fileInfo ? false : this.state.attemptingDownload +// }); +// } +// }, +// tryDownload: function() { +// this.setState({ +// attemptingDownload: true, +// attemptingRemove: false +// }); +// lbry.getCostInfo(this.props.uri).then(({cost}) => { +// lbry.getBalance((balance) => { +// if (cost > balance) { +// this.setState({ +// modal: 'notEnoughCredits', +// attemptingDownload: false, +// }); +// } else if (this.state.affirmedPurchase) { +// lbry.get({uri: this.props.uri}).then((streamInfo) => { +// if (streamInfo === null || typeof streamInfo !== 'object') { +// this.setState({ +// modal: 'timedOut', +// attemptingDownload: false, +// }); +// } +// }); +// } else { +// this.setState({ +// attemptingDownload: false, +// modal: 'affirmPurchase' +// }) +// } +// }); +// }); +// }, +// closeModal: function() { +// this.setState({ +// modal: null, +// }) +// }, +// onDownloadClick: function() { +// if (!this.state.fileInfo && !this.state.attemptingDownload) { +// this.tryDownload(); +// } +// }, +// onOpenClick: function() { +// if (this.state.fileInfo && this.state.fileInfo.download_path) { +// shell.openItem(this.state.fileInfo.download_path); +// } +// }, +// handleDeleteCheckboxClicked: function(event) { +// this.setState({ +// deleteChecked: event.target.checked, +// }); +// }, +// handleRevealClicked: function() { +// if (this.state.fileInfo && this.state.fileInfo.download_path) { +// shell.showItemInFolder(this.state.fileInfo.download_path); +// } +// }, +// handleRemoveClicked: function() { +// this.setState({ +// modal: 'confirmRemove', +// }); +// }, +// handleRemoveConfirmed: function() { +// lbry.removeFile(this.props.outpoint, this.state.deleteChecked); +// this.setState({ +// modal: null, +// fileInfo: false, +// attemptingDownload: false +// }); +// }, +// onAffirmPurchase: function() { +// this.setState({ +// affirmedPurchase: true, +// modal: null +// }); +// this.tryDownload(); +// }, +// openMenu: function() { +// this.setState({ +// menuOpen: !this.state.menuOpen, +// }); +// }, +// componentDidMount: function() { +// this._isMounted = true; +// this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); +// }, +// componentWillUnmount: function() { +// this._isMounted = false; +// if (this._fileInfoSubscribeId) { +// lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); +// } +// }, +// render: function() { +// if (this.state.fileInfo === null) +// { +// return null; +// } + +// const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder', +// showMenu = !!this.state.fileInfo; + +// let linkBlock; +// if (this.state.fileInfo === false && !this.state.attemptingDownload) { +// linkBlock = ; +// } else if (this.state.attemptingDownload || (!this.state.fileInfo.completed && !this.state.fileInfo.isMine)) { +// const +// progress = this.state.fileInfo ? this.state.fileInfo.written_bytes / this.state.fileInfo.total_bytes * 100 : 0, +// label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', +// labelWithIcon = {label}; + +// linkBlock = ( +//
      +//
      {labelWithIcon}
      +// {labelWithIcon} +//
      +// ); +// } else { +// linkBlock = ; +// } + +// const uri = lbryuri.normalize(this.props.uri); +// const title = this.props.metadata ? this.props.metadata.title : uri; +// return ( +//
      +// {this.state.fileInfo !== null || this.state.fileInfo.isMine +// ? linkBlock +// : null} +// { showMenu ? +// +// +// +// : '' } +// +// Are you sure you'd like to buy {title} for credits? +// +// +// You don't have enough LBRY credits to pay for this stream. +// +// +// LBRY was unable to download the stream {uri}. +// +// +//

      Are you sure you'd like to remove {title} from LBRY?

      + +// +//
      +//
      +// ); +// } +// }); + +class FileActions extends React.Component { + constructor(props) { + super(props) + this._isMounted = false + this._fileInfoSubscribeId = null + this.state = { available: true, forceShowActions: false, fileInfo: null, } - }, - onShowFileActionsRowClicked: function() { + } + + onShowFileActionsRowClicked() { this.setState({ forceShowActions: true, }); - }, - onFileInfoUpdate: function(fileInfo) { - if (this.isMounted) { - this.setState({ - fileInfo: fileInfo, - }); - } - }, - componentDidMount: function() { - this._isMounted = true; - this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); + } + + render() { + const { + fileInfo, + availability, + } = this.props - lbry.get_availability({uri: this.props.uri}, (availability) => { - if (this._isMounted) { - this.setState({ - available: availability > 0, - }); - } - }, () => { - // Take any error to mean the file is unavailable - if (this._isMounted) { - this.setState({ - available: false, - }); - } - }); - }, - componentWillUnmount: function() { - this._isMounted = false; - if (this._fileInfoSubscribeId) { - lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); - } - }, - render: function() { - const fileInfo = this.state.fileInfo; if (fileInfo === null) { return null; } @@ -255,18 +324,17 @@ const FileActions = React.createClass({ return (
      { fileInfo || this.state.available || this.state.forceShowActions - ? + ? :
      Content unavailable.
      - +
      }
      ); } -}); +} export default FileActions diff --git a/ui/js/component/fileCardStream/view.jsx b/ui/js/component/fileCardStream/view.jsx index d2bd9a1c3..a3b6d9b56 100644 --- a/ui/js/component/fileCardStream/view.jsx +++ b/ui/js/component/fileCardStream/view.jsx @@ -2,7 +2,8 @@ import React from 'react'; import lbry from 'lbry.js'; import lbryuri from 'lbryuri.js'; import Link from 'component/link'; -import {Thumbnail, TruncatedText, FilePrice} from 'component/common'; +import {Thumbnail, TruncatedText,} from 'component/common'; +import FilePrice from 'component/filePrice' import UriIndicator from 'component/channel-indicator'; class FileCardStream extends React.Component { @@ -72,7 +73,7 @@ class FileCardStream extends React.Component {
      {title}
      - { !this.props.hidePrice ? : null} + { !this.props.hidePrice ? : null}
      diff --git a/ui/js/component/filePrice/index.js b/ui/js/component/filePrice/index.js new file mode 100644 index 000000000..c5a9497d0 --- /dev/null +++ b/ui/js/component/filePrice/index.js @@ -0,0 +1,22 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import { + makeSelectCostInfoForUri, +} from 'selectors/cost_info' +import FilePrice from './view' + +const makeSelect = () => { + const selectCostInfoForUri = makeSelectCostInfoForUri() + const select = (state, props) => ({ + costInfo: selectCostInfoForUri(state, props), + }) + + return select +} + +const perform = (dispatch) => ({ +}) + +export default connect(makeSelect, perform)(FilePrice) diff --git a/ui/js/component/filePrice/view.jsx b/ui/js/component/filePrice/view.jsx new file mode 100644 index 000000000..17b830bf2 --- /dev/null +++ b/ui/js/component/filePrice/view.jsx @@ -0,0 +1,21 @@ +import React from 'react' +import { + CreditAmount, +} from 'component/common' + +const FilePrice = (props) => { + const { + costInfo, + look = 'indicator', + } = props + + const isEstimate = costInfo ? !costInfo.includesData : null + + if (!costInfo) { + return ???; + } + + return +} + +export default FilePrice diff --git a/ui/js/component/fileTileStream/view.jsx b/ui/js/component/fileTileStream/view.jsx index f5a0c218a..fd3a2b32c 100644 --- a/ui/js/component/fileTileStream/view.jsx +++ b/ui/js/component/fileTileStream/view.jsx @@ -3,7 +3,8 @@ import lbry from 'lbry.js'; import lbryuri from 'lbryuri.js'; import Link from 'component/link'; import FileActions from 'component/fileActions'; -import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js'; +import {Thumbnail, TruncatedText,} from 'component/common.js'; +import FilePrice from 'component/filePrice' import UriIndicator from 'component/channel-indicator.js'; /*should be merged into FileTile once FileTile is refactored to take a single id*/ diff --git a/ui/js/component/video/index.js b/ui/js/component/video/index.js index e258e55f7..94978f702 100644 --- a/ui/js/component/video/index.js +++ b/ui/js/component/video/index.js @@ -16,8 +16,6 @@ import { selectLoadingCurrentUri, selectCurrentUriFileReadyToPlay, selectCurrentUriIsPlaying, -} from 'selectors/content' -import { selectCurrentUriFileInfo, selectDownloadingCurrentUri, } from 'selectors/file_info' diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index cb5657d59..f00739ade 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -56,6 +56,8 @@ export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED' export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED' export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED' export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED' +export const DELETE_FILE_STARTED = 'DELETE_FILE_STARTED' +export const DELETE_FILE_COMPLETED = 'DELETE_FILE_COMPLETED' // Search export const SEARCH_STARTED = 'SEARCH_STARTED' diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 4c0d511d4..2046efc83 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -301,7 +301,7 @@ lbry.getMyClaims = function(callback) { lbry.removeFile = function(outpoint, deleteTargetFile=true, callback) { this._removedFiles.push(outpoint); - this._updateFileInfoSubscribers(outpoint); + // this._updateFileInfoSubscribers(outpoint); lbry.file_delete({ outpoint: outpoint, diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx index 9ba48cbf3..2a3498bf9 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/showPage/view.jsx @@ -6,9 +6,9 @@ import Video from 'component/video' import { TruncatedText, Thumbnail, - FilePrice, - BusyMessage -} from 'component/common.js'; + BusyMessage, +} from 'component/common'; +import FilePrice from 'component/filePrice' import FileActions from 'component/fileActions'; import Link from 'component/link'; import UriIndicator from 'component/channel-indicator.js'; @@ -121,7 +121,7 @@ let FilePage = React.createClass({ render: function() { const metadata = this.props.metadata, title = metadata ? this.props.metadata.title : this.props.uri, - uriIndicator = ; + uriIndicator = return (
      @@ -134,14 +134,14 @@ let FilePage = React.createClass({
      {this.state.isDownloaded === false - ? + ? : null}

      {title}

      { this.props.channelUri ? {uriIndicator} : uriIndicator} -
      +
      @@ -288,4 +288,6 @@ let ShowPage = React.createClass({ return
      {innerContent}
      ; } -}); \ No newline at end of file +}); + +export default ShowPage; diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index a5e4eba74..6c36daf0c 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -96,36 +96,6 @@ reducers[types.FETCH_PUBLISHED_CONTENT_COMPLETED] = function(state, action) { }) } -reducers[types.LOADING_VIDEO_STARTED] = function(state, action) { - const { - uri, - } = action.data - const newLoading = Object.assign({}, state.loading) - const newByUri = Object.assign({}, newLoading.byUri) - - newByUri[uri] = true - newLoading.byUri = newByUri - - return Object.assign({}, state, { - loading: newLoading, - }) -} - -reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { - const { - uri, - } = action.data - const newLoading = Object.assign({}, state.loading) - const newByUri = Object.assign({}, newLoading.byUri) - - delete newByUri[uri] - newLoading.byUri = newByUri - - return Object.assign({}, state, { - loading: newLoading, - }) -} - 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 1467f0ee8..2c98cf048 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -41,13 +41,20 @@ reducers[types.DOWNLOADING_STARTED] = function(state, action) { } = action.data const newByUri = Object.assign({}, state.byUri) const newDownloading = Object.assign({}, state.downloading) + const newDownloadingByUri = Object.assign({}, newDownloading.byUri) + const newLoading = Object.assign({}, state.loading) + const newLoadingByUri = Object.assign({}, newLoading) - newDownloading[uri] = true + newDownloadingByUri[uri] = true + newDownloading.byUri = newDownloadingByUri newByUri[uri] = fileInfo + delete newLoadingByUri[uri] + newLoading.byUri = newLoadingByUri return Object.assign({}, state, { downloading: newDownloading, byUri: newByUri, + loading: newLoading, }) } @@ -75,13 +82,78 @@ reducers[types.DOWNLOADING_COMPLETED] = function(state, action) { } = action.data const newByUri = Object.assign({}, state.byUri) const newDownloading = Object.assign({}, state.downloading) + const newDownloadingByUri = Object.assign({}, newDownloading.byUri) newByUri[uri] = fileInfo - delete newDownloading[uri] + delete newDownloadingByUri[uri] + newDownloading.byUri = newDownloadingByUri return Object.assign({}, state, { byUri: newByUri, - downloading: newDownloading + downloading: newDownloading, + }) +} + +reducers[types.DELETE_FILE_STARTED] = function(state, action) { + const { + uri, + } = action.data + const newDeleting = Object.assign({}, state.deleting) + const newByUri = Object.assign({}, newDeleting.byUri) + + newByUri[uri] = true + newDeleting.byUri = newByUri + + return Object.assign({}, state, { + deleting: newDeleting, + }) +} + +reducers[types.DELETE_FILE_COMPLETED] = function(state, action) { + const { + uri, + } = action.data + const newDeleting = Object.assign({}, state.deleting) + const newDeletingByUri = Object.assign({}, newDeleting.byUri) + const newByUri = Object.assign({}, state.byUri) + + delete newDeletingByUri[uri] + newDeleting.byUri = newDeletingByUri + delete newByUri[uri] + + return Object.assign({}, state, { + deleting: newDeleting, + byUri: newByUri, + }) +} + +reducers[types.LOADING_VIDEO_STARTED] = function(state, action) { + const { + uri, + } = action.data + const newLoading = Object.assign({}, state.loading) + const newByUri = Object.assign({}, newLoading.byUri) + + newByUri[uri] = true + newLoading.byUri = newByUri + + return Object.assign({}, state, { + loading: newLoading, + }) +} + +reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { + const { + uri, + } = action.data + const newLoading = Object.assign({}, state.loading) + const newByUri = Object.assign({}, newLoading.byUri) + + delete newByUri[uri] + newLoading.byUri = newByUri + + return Object.assign({}, state, { + loading: newLoading, }) } diff --git a/ui/js/selectors/availability.js b/ui/js/selectors/availability.js index 0588c1528..7aeaa6330 100644 --- a/ui/js/selectors/availability.js +++ b/ui/js/selectors/availability.js @@ -1,6 +1,11 @@ import { createSelector, } from 'reselect' +import { + selectDaemonReady, + selectCurrentPage, + selectCurrentUri, +} from 'selectors/app' const _selectState = state => state.availability @@ -40,3 +45,30 @@ export const makeSelectFetchingAvailabilityForUri = () => { (fetching) => fetching ) } + +export const selectFetchingAvailabilityForCurrentUri = createSelector( + selectCurrentUri, + selectFetchingAvailabilityByUri, + (uri, byUri) => byUri[uri] +) + +export const selectAvailabilityForCurrentUri = createSelector( + selectCurrentUri, + selectAvailabilityByUri, + (uri, byUri) => byUri[uri] +) + +export const shouldFetchCurrentUriAvailability = createSelector( + selectDaemonReady, + selectCurrentPage, + selectFetchingAvailabilityForCurrentUri, + selectAvailabilityForCurrentUri, + (daemonReady, page, fetching, availability) => { + if (!daemonReady) return false + if (page != 'show') return false + if (fetching) return false + if (availability) return false + + return true + } +) diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index c8c7a1386..be38c21b3 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -4,14 +4,6 @@ import { selectCurrentPage, selectCurrentUri, } from 'selectors/app' -import { - selectCurrentUriCostInfo, - selectFetchingCurrentUriCostInfo, -} from 'selectors/cost_info' -import { - selectCurrentUriFileInfo, - selectFetchingCurrentUriFileInfo, -} from 'selectors/file_info' export const _selectState = state => state.content || {} @@ -50,13 +42,6 @@ export const selectFetchingFileInfos = createSelector( (state) => state.fetchingFileInfos || {} ) -// TODO make this smarter so it doesn't start playing and immediately freeze -// while downloading more. -export const selectCurrentUriFileReadyToPlay = createSelector( - selectCurrentUriFileInfo, - (fileInfo) => (fileInfo || {}).written_bytes > 0 -) - export const selectFetchingDownloadedContent = createSelector( _selectState, (state) => !!state.fetchingDownloadedContent @@ -97,22 +82,6 @@ export const selectPublishedContent = createSelector( (state) => state.publishedContent || {} ) -export const selectLoading = createSelector( - _selectState, - (state) => state.loading || {} -) - -export const selectLoadingByUri = createSelector( - selectLoading, - (loading) => loading.byUri || {} -) - -export const selectLoadingCurrentUri = createSelector( - selectLoadingByUri, - selectCurrentUri, - (byUri, uri) => !!byUri[uri] -) - export const shouldFetchPublishedContent = createSelector( selectDaemonReady, selectCurrentPage, diff --git a/ui/js/selectors/cost_info.js b/ui/js/selectors/cost_info.js index 55a1416ea..d250aaf19 100644 --- a/ui/js/selectors/cost_info.js +++ b/ui/js/selectors/cost_info.js @@ -42,3 +42,13 @@ export const shouldFetchCurrentUriCostInfo = createSelector( } ) +const selectCostInfoForUri = (state, props) => { + return selectAllCostInfoByUri(state)[props.uri] +} + +export const makeSelectCostInfoForUri = () => { + return createSelector( + selectCostInfoForUri, + (costInfo) => costInfo + ) +} diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index c26b5dcc8..79b28c1d6 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -54,7 +54,11 @@ export const selectDownloadingCurrentUri = createSelector( export const selectCurrentUriIsDownloaded = createSelector( selectCurrentUriFileInfo, (fileInfo) => { - return fileInfo && (fileInfo.written_bytes > 0 || fileInfo.completed) + if (!fileInfo) return false + if (!fileInfo.completed) return false + if (!fileInfo.written_bytes > 0) return false + + return true } ) @@ -82,3 +86,50 @@ export const makeSelectFileInfoForUri = () => { (fileInfo) => fileInfo ) } + +const selectDownloadingForUri = (state, props) => { + const byUri = selectDownloadingByUri(state) + return byUri[props.uri] +} + +export const makeSelectDownloadingForUri = () => { + return createSelector( + selectDownloadingForUri, + (downloadingForUri) => !!downloadingForUri + ) +} + +export const selectLoading = createSelector( + _selectState, + (state) => state.loading || {} +) + +export const selectLoadingByUri = createSelector( + selectLoading, + (loading) => loading.byUri || {} +) + +export const selectLoadingCurrentUri = createSelector( + selectLoadingByUri, + selectCurrentUri, + (byUri, uri) => !!byUri[uri] +) + +// TODO make this smarter so it doesn't start playing and immediately freeze +// while downloading more. +export const selectCurrentUriFileReadyToPlay = createSelector( + selectCurrentUriFileInfo, + (fileInfo) => (fileInfo || {}).written_bytes > 0 +) + +const selectLoadingForUri = (state, props) => { + const byUri = selectLoadingByUri(state) + return byUri[props.uri] +} + +export const makeSelectLoadingForUri = () => { + return createSelector( + selectLoadingForUri, + (loading) => !!loading + ) +} diff --git a/ui/js/triggers.js b/ui/js/triggers.js index 0d35f97d7..41d0e0763 100644 --- a/ui/js/triggers.js +++ b/ui/js/triggers.js @@ -13,6 +13,9 @@ import { import { shouldFetchCurrentUriCostInfo, } from 'selectors/cost_info' +import { + shouldFetchCurrentUriAvailability, +} from 'selectors/availability' import { doFetchTransactions, doGetNewAddress, @@ -28,6 +31,9 @@ import { import { doFetchCurrentUriCostInfo, } from 'actions/cost_info' +import { + doFetchCurrentUriAvailability, +} from 'actions/availability' const triggers = [] @@ -66,6 +72,11 @@ triggers.push({ action: doFetchCurrentUriCostInfo, }) +triggers.push({ + selector: shouldFetchCurrentUriAvailability, + action: doFetchCurrentUriAvailability, +}) + const runTriggers = function() { triggers.forEach(function(trigger) { const state = app.store.getState(); -- 2.43.4 From e1d23667bcb3d7d569f08f3dc6c07e482daa7b42 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sun, 30 Apr 2017 10:19:59 +0700 Subject: [PATCH 29/56] Fix NSFW blurring on search and make search show/hide smarter --- ui/js/actions/search.js | 12 ++++++++++++ ui/js/component/fileTileStream/index.js | 2 +- ui/js/component/fileTileStream/view.jsx | 8 +++++++- ui/js/component/header/index.js | 4 ++++ ui/js/constants/action_types.js | 4 ++-- ui/js/page/discover/index.js | 2 ++ ui/js/page/discover/view.jsx | 18 ++++++++++++++++++ ui/js/reducers/search.js | 12 ++++++++++++ ui/js/selectors/search.js | 5 +++++ 9 files changed, 63 insertions(+), 4 deletions(-) diff --git a/ui/js/actions/search.js b/ui/js/actions/search.js index a4b1e83c4..b56c752cc 100644 --- a/ui/js/actions/search.js +++ b/ui/js/actions/search.js @@ -45,3 +45,15 @@ export function doSearchContent(query) { }) } } + +export function doActivateSearch() { + return { + type: types.ACTIVATE_SEARCH, + } +} + +export function doDeactivateSearch() { + return { + type: types.DEACTIVATE_SEARCH, + } +} diff --git a/ui/js/component/fileTileStream/index.js b/ui/js/component/fileTileStream/index.js index c297eec25..d212dfdae 100644 --- a/ui/js/component/fileTileStream/index.js +++ b/ui/js/component/fileTileStream/index.js @@ -35,7 +35,7 @@ const makeSelect = () => { fileInfo: selectFileInfoForUri(state, props), fetchingAvailability: selectFetchingAvailabilityForUri(state, props), selectAvailabilityForUri: selectAvailabilityForUri(state, props), - obscureNswf: selectObscureNsfw(state), + obscureNsfw: selectObscureNsfw(state), metadata: selectMetadataForUri(state, props), source: selectSourceForUri(state, props), }) diff --git a/ui/js/component/fileTileStream/view.jsx b/ui/js/component/fileTileStream/view.jsx index fd3a2b32c..50e221b9f 100644 --- a/ui/js/component/fileTileStream/view.jsx +++ b/ui/js/component/fileTileStream/view.jsx @@ -75,7 +75,13 @@ class FileTileStream extends React.Component {
      diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js index 90b9293f6..02ca2f2f8 100644 --- a/ui/js/component/header/index.js +++ b/ui/js/component/header/index.js @@ -11,6 +11,8 @@ import { } from 'actions/app' import { doSearchContent, + doActivateSearch, + doDeactivateSearch, } from 'actions/search' import Header from './view' @@ -22,6 +24,8 @@ const select = (state) => ({ const perform = (dispatch) => ({ navigate: (path) => dispatch(doNavigate(path)), search: (query) => dispatch(doSearchContent(query)), + activateSearch: () => dispatch(doActivateSearch()), + deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 10), }) export default connect(select, perform)(Header) diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index f00739ade..91c2593dc 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -5,8 +5,6 @@ export const CLOSE_MODAL = 'CLOSE_MODAL' export const OPEN_DRAWER = 'OPEN_DRAWER' export const CLOSE_DRAWER = 'CLOSE_DRAWER' -export const START_SEARCH = 'START_SEARCH' - export const DAEMON_READY = 'DAEMON_READY' // Upgrades @@ -63,3 +61,5 @@ export const DELETE_FILE_COMPLETED = 'DELETE_FILE_COMPLETED' export const SEARCH_STARTED = 'SEARCH_STARTED' export const SEARCH_COMPLETED = 'SEARCH_COMPLETED' export const SEARCH_CANCELLED = 'SEARCH_CANCELLED' +export const ACTIVATE_SEARCH = 'ACTIVATE_SEARCH' +export const DEACTIVATE_SEARCH = 'DEACTIVATE_SEARCH' diff --git a/ui/js/page/discover/index.js b/ui/js/page/discover/index.js index e279ff4d6..bb2d97e49 100644 --- a/ui/js/page/discover/index.js +++ b/ui/js/page/discover/index.js @@ -12,6 +12,7 @@ import { selectIsSearching, selectSearchQuery, selectCurrentSearchResults, + selectSearchActivated, } from 'selectors/search' import DiscoverPage from './view' @@ -20,6 +21,7 @@ const select = (state) => ({ isSearching: selectIsSearching(state), query: selectSearchQuery(state), results: selectCurrentSearchResults(state), + searchActive: selectSearchActivated(state), }) const perform = (dispatch) => ({ diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index 82151ccd9..e668855d2 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -61,4 +61,22 @@ let DiscoverPage = React.createClass({ } }) +// const DiscoverPage = (props) => { +// const { +// isSearching, +// query, +// results, +// searchActive, +// } = props +// +// return ( +//
      +// { (!searchActive || (!isSearching && !query)) && } +// { searchActive && isSearching ? : null } +// { searchActive && !isSearching && query && results.length ? : null } +// { searchActive && !isSearching && query && !results.length ? : null } +//
      +// ); +// } + export default DiscoverPage; \ No newline at end of file diff --git a/ui/js/reducers/search.js b/ui/js/reducers/search.js index 50a38966f..24cd757f6 100644 --- a/ui/js/reducers/search.js +++ b/ui/js/reducers/search.js @@ -41,6 +41,18 @@ reducers[types.SEARCH_CANCELLED] = function(state, action) { }) } +reducers[types.ACTIVATE_SEARCH] = function(state, action) { + return Object.assign({}, state, { + activated: true, + }) +} + +reducers[types.DEACTIVATE_SEARCH] = function(state, action) { + return Object.assign({}, state, { + activated: 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/search.js b/ui/js/selectors/search.js index 240a4b6bc..b5f7bc1a4 100644 --- a/ui/js/selectors/search.js +++ b/ui/js/selectors/search.js @@ -27,3 +27,8 @@ export const selectCurrentSearchResults = createSelector( selectSearchResultsByQuery, (query, byQuery) => byQuery[query] || [] ) + +export const selectSearchActivated = createSelector( + _selectState, + (state) => !!state.activated +) -- 2.43.4 From 8c094698586472a6571815890c18b94eb53c93f5 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sun, 30 Apr 2017 10:31:20 +0700 Subject: [PATCH 30/56] even smarter search --- ui/js/actions/search.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ui/js/actions/search.js b/ui/js/actions/search.js index b56c752cc..6bdb10592 100644 --- a/ui/js/actions/search.js +++ b/ui/js/actions/search.js @@ -9,10 +9,18 @@ import { import { doResolveUri, } from 'actions/content' +import { + doNavigate, +} from 'actions/app' +import { + selectCurrentPage, +} from 'selectors/app' export function doSearchContent(query) { return function(dispatch, getState) { const state = getState() + const page = selectCurrentPage(state) + if (!query) { return dispatch({ @@ -25,6 +33,8 @@ export function doSearchContent(query) { data: { query } }) + if(page != 'discover' && query != undefined) dispatch(doNavigate('discover')) + lighthouse.search(query).then(results => { results.forEach(result => { const uri = lbryuri.build({ @@ -47,8 +57,16 @@ export function doSearchContent(query) { } export function doActivateSearch() { - return { - type: types.ACTIVATE_SEARCH, + return function(dispatch, getState) { + const state = getState() + const page = selectCurrentPage(state) + const query = selectSearchQuery(state) + + if(page != 'discover' && query != undefined) dispatch(doNavigate('discover')) + + dispatch({ + type: types.ACTIVATE_SEARCH, + }) } } -- 2.43.4 From 7a6dd93886dd66a2a6b127b8853161cf8fd7badb Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sun, 30 Apr 2017 23:01:43 +0700 Subject: [PATCH 31/56] Downloaded file list kind of working. Not sure about sort. --- ui/js/actions/app.js | 17 +- ui/js/actions/content.js | 12 ++ ui/js/actions/file_info.js | 24 +++ ui/js/component/fileList/index.js | 8 +- ui/js/component/fileList/view.jsx | 233 +++++++++++++++++-------- ui/js/component/header/index.js | 2 + ui/js/component/header/view.jsx | 2 +- ui/js/page/fileListDownloaded/index.js | 10 +- ui/js/page/fileListDownloaded/view.jsx | 53 +----- ui/js/reducers/file_info.js | 21 +++ ui/js/selectors/app.js | 28 +++ ui/js/selectors/file_info.js | 16 ++ 12 files changed, 291 insertions(+), 135 deletions(-) diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 073ae9099..d2c7679f3 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -5,6 +5,7 @@ import { selectUpgradeDownloadDir, selectUpgradeDownloadItem, selectUpgradeFilename, + selectPageTitle, } from 'selectors/app' const {remote, ipcRenderer, shell} = require('electron'); @@ -14,11 +15,17 @@ const {download} = remote.require('electron-dl'); const fs = remote.require('fs'); export function doNavigate(path) { - return { - type: types.NAVIGATE, - data: { - path: path - } + return function(dispatch, getState) { + dispatch({ + type: types.NAVIGATE, + data: { + path, + } + }) + + const state = getState() + const pageTitle = selectPageTitle(state) + window.document.title = pageTitle } } diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 9e9f90e3d..0e5c28086 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -20,6 +20,9 @@ import { import { selectCurrentResolvedUriClaimOutpoint, } from 'selectors/content' +import { + selectClaimsByUri, +} from 'selectors/claims' import { doOpenModal, } from 'actions/app' @@ -67,6 +70,15 @@ export function doFetchDownloadedContent() { lbry.file_list().then((fileInfos) => { const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout); + fileInfos.forEach(fileInfo => { + const uri = lbryuri.build({ + channelName: fileInfo.channel_name, + contentName: fileInfo.name, + }) + const claim = selectClaimsByUri(state)[uri] + if (!claim) dispatch(doResolveUri(uri)) + }) + dispatch({ type: types.FETCH_DOWNLOADED_CONTENT_COMPLETED, data: { diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index 77763352b..b7611fb3d 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -76,3 +76,27 @@ export function doDeleteFile(uri, fileInfo, deleteFromComputer) { lbry.removeFile(fileInfo.outpoint, deleteFromComputer, successCallback) } } + +export function doFetchDownloadedContent() { + return function(dispatch, getState) { + const state = getState() + + dispatch({ + type: types.FETCH_DOWNLOADED_CONTENT_STARTED, + }) + + lbry.claim_list_mine().then((myClaimInfos) => { + lbry.file_list().then((fileInfos) => { + const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout); + + dispatch({ + type: types.FETCH_DOWNLOADED_CONTENT_COMPLETED, + data: { + fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), + } + }) + }); + }); + } +} + diff --git a/ui/js/component/fileList/index.js b/ui/js/component/fileList/index.js index 0a6c65c70..dd0209b40 100644 --- a/ui/js/component/fileList/index.js +++ b/ui/js/component/fileList/index.js @@ -4,4 +4,10 @@ import { } from 'react-redux' import FileList from './view' -export default connect()(FileList) +const select = (state) => ({ +}) + +const perform = (dispatch) => ({ +}) + +export default connect(select, perform)(FileList) diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index a119f8792..2a946746c 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -3,90 +3,81 @@ import lbry from 'lbry.js'; import lbryuri from 'lbryuri.js'; import Link from 'component/link'; import {FormField} from 'component/form.js'; -import FileTileStream from 'component/fileTile'; +import FileTileStream from 'component/fileTileStream'; import rewards from 'rewards.js'; import lbryio from 'lbryio.js'; import {BusyMessage, Thumbnail} from 'component/common.js'; -const FileList = React.createClass({ - _sortFunctions: { - date: function(fileInfos) { - return fileInfos.slice().reverse(); - }, - title: function(fileInfos) { - return fileInfos.slice().sort(function(fileInfo1, fileInfo2) { - const title1 = fileInfo1.metadata ? fileInfo1.metadata.title.toLowerCase() : fileInfo1.name; - const title2 = fileInfo2.metadata ? fileInfo2.metadata.title.toLowerCase() : fileInfo2.name; - if (title1 < title2) { - return -1; - } else if (title1 > title2) { - return 1; - } else { - return 0; - } - }); - }, - filename: function(fileInfos) { - return fileInfos.slice().sort(function({file_name: fileName1}, {file_name: fileName2}) { - const fileName1Lower = fileName1.toLowerCase(); - const fileName2Lower = fileName2.toLowerCase(); - if (fileName1Lower < fileName2Lower) { - return -1; - } else if (fileName2Lower > fileName1Lower) { - return 1; - } else { - return 0; - } - }); - }, - }, - propTypes: { - fileInfos: React.PropTypes.array.isRequired, - hidePrices: React.PropTypes.bool, - }, - getDefaultProps: function() { - return { - hidePrices: false, - }; - }, - getInitialState: function() { - return { +class FileList extends React.Component { + constructor(props) { + super(props) + + this.state = { sortBy: 'date', - }; - }, - handleSortChanged: function(event) { - this.setState({ - sortBy: event.target.value, - }); - }, - render: function() { - var content = [], - seenUris = {}; - - const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); - for (let {outpoint, name, channel_name, metadata, mime_type, claim_id, has_signature, signature_is_valid} of fileInfosSorted) { - if (seenUris[name] || !claim_id) { - continue; - } - - let streamMetadata; - if (metadata) { - streamMetadata = metadata.stream.metadata; - } else { - streamMetadata = null; - } - - - const uri = lbryuri.build({contentName: name, channelName: channel_name}); - seenUris[name] = true; - content.push() } + this._sortFunctions = { + date: function(fileInfos) { + return fileInfos.slice().reverse(); + }, + title: function(fileInfos) { + return fileInfos.slice().sort(function(fileInfo1, fileInfo2) { + const title1 = fileInfo1.metadata ? fileInfo1.metadata.stream.metadata.title.toLowerCase() : fileInfo1.name; + const title2 = fileInfo2.metadata ? fileInfo2.metadata.stream.metadata.title.toLowerCase() : fileInfo2.name; + if (title1 < title2) { + return -1; + } else if (title1 > title2) { + return 1; + } else { + return 0; + } + }) + }, + filename: function(fileInfos) { + return fileInfos.slice().sort(function({file_name: fileName1}, {file_name: fileName2}) { + const fileName1Lower = fileName1.toLowerCase(); + const fileName2Lower = fileName2.toLowerCase(); + if (fileName1Lower < fileName2Lower) { + return -1; + } else if (fileName2Lower > fileName1Lower) { + return 1; + } else { + return 0; + } + }) + }, + } + } + + handleSortChanged(event) { + this.setState({ + sortBy: event.target.value, + }) + } + + render() { + const { + handleSortChanged, + fileInfos, + hidePrices, + } = this.props + const { + sortBy, + } = this.state + const content = [] + + this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => { + const uri = lbryuri.build({ + contentName: fileInfo.name, + channelName: fileInfo.channel_name, + }) + content.push() + }) return (
      Sort by { ' ' } - + @@ -94,8 +85,100 @@ const FileList = React.createClass({ {content}
      - ); + ) } -}); +} + +// const FileList = React.createClass({ +// _sortFunctions: { +// date: function(fileInfos) { +// return fileInfos.slice().reverse(); +// }, +// title: function(fileInfos) { +// return fileInfos.slice().sort(function(fileInfo1, fileInfo2) { +// const title1 = fileInfo1.metadata ? fileInfo1.metadata.title.toLowerCase() : fileInfo1.name; +// const title2 = fileInfo2.metadata ? fileInfo2.metadata.title.toLowerCase() : fileInfo2.name; +// if (title1 < title2) { +// return -1; +// } else if (title1 > title2) { +// return 1; +// } else { +// return 0; +// } +// }); +// }, +// filename: function(fileInfos) { +// return fileInfos.slice().sort(function({file_name: fileName1}, {file_name: fileName2}) { +// const fileName1Lower = fileName1.toLowerCase(); +// const fileName2Lower = fileName2.toLowerCase(); +// if (fileName1Lower < fileName2Lower) { +// return -1; +// } else if (fileName2Lower > fileName1Lower) { +// return 1; +// } else { +// return 0; +// } +// }); +// }, +// }, +// propTypes: { +// fileInfos: React.PropTypes.array.isRequired, +// hidePrices: React.PropTypes.bool, +// }, +// getDefaultProps: function() { +// return { +// hidePrices: false, +// }; +// }, +// getInitialState: function() { +// return { +// sortBy: 'date', +// }; +// }, +// handleSortChanged: function(event) { +// this.setState({ +// sortBy: event.target.value, +// }); +// }, +// render: function() { +// var content = [], +// seenUris = {}; + +// console.debug(this.props) + +// const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); +// for (let {outpoint, name, channel_name, metadata, mime_type, claim_id, has_signature, signature_is_valid} of fileInfosSorted) { +// if (seenUris[name] || !claim_id) { +// continue; +// } + +// let streamMetadata; +// if (metadata) { +// streamMetadata = metadata.stream.metadata; +// } else { +// streamMetadata = null; +// } + + +// const uri = lbryuri.build({contentName: name, channelName: channel_name}); +// seenUris[name] = true; +// content.push() +// } + +// return ( +//
      +// +// Sort by { ' ' } +// +// +// +// +// +// +// {content} +//
      +// ); +// } +// }); export default FileList diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js index 02ca2f2f8..ce5cefdd8 100644 --- a/ui/js/component/header/index.js +++ b/ui/js/component/header/index.js @@ -5,6 +5,7 @@ import { import { selectCurrentPage, selectHeaderLinks, + selectPageTitle, } from 'selectors/app' import { doNavigate, @@ -19,6 +20,7 @@ import Header from './view' const select = (state) => ({ currentPage: selectCurrentPage(state), subLinks: selectHeaderLinks(state), + pageTitle: selectPageTitle(state), }) const perform = (dispatch) => ({ diff --git a/ui/js/component/header/view.jsx b/ui/js/component/header/view.jsx index e54bf3751..92a45cad9 100644 --- a/ui/js/component/header/view.jsx +++ b/ui/js/component/header/view.jsx @@ -104,7 +104,7 @@ class WunderBar extends React.PureComponent { this.props.onSearch(searchTerm); }, 800); // 800ms delay, tweak for faster/slower } - + componentWillReceiveProps(nextProps) { if (nextProps.viewingPage !== this.props.viewingPage || nextProps.address != this.props.address) { this.setState({ address: nextProps.address, icon: nextProps.icon }); diff --git a/ui/js/page/fileListDownloaded/index.js b/ui/js/page/fileListDownloaded/index.js index ed27152c5..6d92a1867 100644 --- a/ui/js/page/fileListDownloaded/index.js +++ b/ui/js/page/fileListDownloaded/index.js @@ -3,17 +3,23 @@ import { connect } from 'react-redux' import { - selectDownloadedContentFileInfos, selectFetchingDownloadedContent, } from 'selectors/content' +import { + selectDownloadedFileInfo, +} from 'selectors/file_info' +import { + doNavigate, +} from 'actions/app' import FileListDownloaded from './view' const select = (state) => ({ - downloadedContent: selectDownloadedContentFileInfos(state), + downloadedContent: selectDownloadedFileInfo(state), fetching: selectFetchingDownloadedContent(state), }) const perform = (dispatch) => ({ + navigate: (path) => dispatch(doNavigate(path)), }) export default connect(select, perform)(FileListDownloaded) diff --git a/ui/js/page/fileListDownloaded/view.jsx b/ui/js/page/fileListDownloaded/view.jsx index 715eae94e..cf95e0224 100644 --- a/ui/js/page/fileListDownloaded/view.jsx +++ b/ui/js/page/fileListDownloaded/view.jsx @@ -14,6 +14,7 @@ class FileListDownloaded extends React.Component { const { downloadedContent, fetching, + navigate, } = this.props if (fetching) { @@ -25,7 +26,7 @@ class FileListDownloaded extends React.Component { } else if (!downloadedContent.length) { return (
      - You haven't downloaded anything from LBRY yet. Go ! + You haven't downloaded anything from LBRY yet. Go navigate('discover')} label="search for your first download" />!
      ); } else { @@ -37,55 +38,5 @@ class FileListDownloaded extends React.Component { } } } -// const FileListDownloaded = React.createClass({ -// _isMounted: false, - -// getInitialState: function() { -// return { -// fileInfos: null, -// }; -// }, -// componentDidMount: function() { -// this._isMounted = true; -// document.title = "Downloaded Files"; - -// lbry.claim_list_mine().then((myClaimInfos) => { -// if (!this._isMounted) { return; } - -// lbry.file_list().then((fileInfos) => { -// if (!this._isMounted) { return; } - -// const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout); -// this.setState({ -// fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), -// }); -// }); -// }); -// }, -// componentWillUnmount: function() { -// this._isMounted = false; -// }, -// render: function() { -// if (this.state.fileInfos === null) { -// return ( -//
      -// -//
      -// ); -// } else if (!this.state.fileInfos.length) { -// return ( -//
      -// You haven't downloaded anything from LBRY yet. Go ! -//
      -// ); -// } else { -// return ( -//
      -// -//
      -// ); -// } -// } -// }); export default FileListDownloaded diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index 2c98cf048..0023df8f1 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -1,4 +1,5 @@ import * as types from 'constants/action_types' +import lbryuri from 'lbryuri' const reducers = {} const defaultState = { @@ -157,6 +158,26 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { }) } +reducers[types.FETCH_DOWNLOADED_CONTENT_COMPLETED] = function(state, action) { + const { + fileInfos, + } = action.data + const newByUri = Object.assign({}, state.byUri) + + fileInfos.forEach(fileInfo => { + const uri = lbryuri.build({ + channelName: fileInfo.channel_name, + contentName: fileInfo.name, + }) + + newByUri[uri] = fileInfo + }) + + return Object.assign({}, state, { + byUri: newByUri + }) +} + 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 7105bc16b..a269e98f2 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -28,6 +28,34 @@ export const selectCurrentUri = createSelector( } ) +export const selectPageTitle = createSelector( + selectCurrentPage, + selectCurrentUri, + (page, uri) => { + switch(page) + { + case 'discover': + return 'Discover' + case 'wallet': + case 'send': + case 'receive': + case 'claim': + case 'referral': + return 'Wallet' + case 'downloaded': + return 'My Files' + case 'published': + return 'My Files' + case 'publish': + return 'Publish' + case 'help': + return 'Help' + default: + return 'LBRY'; + } + } +) + export const selectPlatform = createSelector( _selectState, (state) => state.platform diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 79b28c1d6..0a114ba0f 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -133,3 +133,19 @@ export const makeSelectLoadingForUri = () => { (loading) => !!loading ) } + +export const selectDownloadedFileInfo = createSelector( + selectAllFileInfoByUri, + (byUri) => { + const fileInfoList = [] + Object.keys(byUri).forEach(key => { + const fileInfo = byUri[key] + + if (fileInfo.completed || fileInfo.written_bytes) { + fileInfoList.push(fileInfo) + } + }) + + return fileInfoList + } +) -- 2.43.4 From bef2f87efdd029d998d4affa3074e323f9c84fff Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 1 May 2017 11:51:01 +0700 Subject: [PATCH 32/56] reverse wallet transactions --- ui/js/selectors/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/selectors/wallet.js b/ui/js/selectors/wallet.js index 2dd02ede8..eedaffa3e 100644 --- a/ui/js/selectors/wallet.js +++ b/ui/js/selectors/wallet.js @@ -34,7 +34,7 @@ export const selectTransactionItems = createSelector( amount: parseFloat(tx.value) }) }) - return transactionItems + return transactionItems.reverse() } ) -- 2.43.4 From 1d549ff25898a94a13f2a54792eef9205e3b974c Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 1 May 2017 11:51:19 +0700 Subject: [PATCH 33/56] Fix purchasing from file actions and allow content to be watched when being downloaded from file actions download link --- ui/js/actions/content.js | 8 ++++++++ ui/js/component/fileActions/index.js | 3 ++- ui/js/component/fileActions/view.jsx | 7 ++++++- ui/js/component/fileTileStream/view.jsx | 6 +++--- ui/js/page/fileListPublished/index.js | 20 +++++++++++++++++++- 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 0e5c28086..f1b756491 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -13,6 +13,7 @@ import { } from 'selectors/content' import { selectCurrentUriFileInfo, + selectDownloadingByUri, } from 'selectors/file_info' import { selectCurrentUriCostInfo, @@ -249,6 +250,8 @@ export function doWatchVideo() { const balance = selectBalance(state) const fileInfo = selectCurrentUriFileInfo(state) const costInfo = selectCurrentUriCostInfo(state) + const downloadingByUri = selectDownloadingByUri(state) + const alreadyDownloading = !!downloadingByUri[uri] const { cost } = costInfo // we already fully downloaded the file @@ -256,6 +259,11 @@ export function doWatchVideo() { return Promise.resolve() } + // we are already downloading the file + if (alreadyDownloading) { + return Promise.resolve() + } + // the file is free or we have partially downloaded it if (cost <= 0.01 || fileInfo.download_directory) { dispatch(doLoadVideo()) diff --git a/ui/js/component/fileActions/index.js b/ui/js/component/fileActions/index.js index 19e66dfdb..21a2c05d0 100644 --- a/ui/js/component/fileActions/index.js +++ b/ui/js/component/fileActions/index.js @@ -30,6 +30,7 @@ import { } from 'actions/file_info' import { doWatchVideo, + doLoadVideo, } from 'actions/content' import FileActions from './view' @@ -58,10 +59,10 @@ const perform = (dispatch) => ({ closeModal: () => dispatch(doCloseModal()), openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)), openInShell: (fileInfo) => dispatch(doOpenFileInShell(fileInfo)), - affirmPurchase: () => console.log('affirm purchase'), deleteFile: (fileInfo, deleteFromComputer) => dispatch(doDeleteFile(fileInfo, deleteFromComputer)), openModal: (modal) => dispatch(doOpenModal(modal)), downloadClick: () => dispatch(doWatchVideo()), + loadVideo: () => dispatch(doLoadVideo()) }) export default connect(makeSelect, perform)(FileActions) diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index 8b880502d..5fe6a2dea 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -23,6 +23,11 @@ class FileActionsRow extends React.Component { }) } + onAffirmPurchase() { + this.props.closeModal() + this.props.loadVideo() + } + render() { const { fileInfo, @@ -85,7 +90,7 @@ class FileActionsRow extends React.Component { openModal('confirmRemove')} label="Remove..." /> : '' } + contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase.bind(this)} onAborted={closeModal}> Are you sure you'd like to buy {title} for credits? : null} - +

      - + navigate(`show=${uri}`)} title={title}> {title} @@ -114,7 +114,7 @@ class FileTileStream extends React.Component { ?

      This content is Not Safe For Work. - To view adult content, please change your . + To view adult content, please change your navigate('settings')} label="Settings" />.

      : null} diff --git a/ui/js/page/fileListPublished/index.js b/ui/js/page/fileListPublished/index.js index 3bf2724cf..f2b5c78f5 100644 --- a/ui/js/page/fileListPublished/index.js +++ b/ui/js/page/fileListPublished/index.js @@ -2,6 +2,24 @@ import React from 'react' import { connect } from 'react-redux' +import { + selectFetchingPublishedContent, +} from 'selectors/content' +import { + selectPublishedFileInfo, +} from 'selectors/file_info' +import { + doNavigate, +} from 'actions/app' import FileListPublished from './view' -export default connect()(FileListPublished) +const select = (state) => ({ + publishedContent: selectPublishedFileInfo(state), + fetching: selectFetchingPublishedContent(state), +}) + +const perform = (dispatch) => ({ + navigate: (path) => dispatch(doNavigate(path)), +}) + +export default connect(select, perform)(FileListPublished) -- 2.43.4 From 84a2f48c244090e0c5d988476469eb3ab9ffe903 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 1 May 2017 11:56:07 +0700 Subject: [PATCH 34/56] Increase deactivate search timeout --- ui/js/component/header/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js index ce5cefdd8..44efdc355 100644 --- a/ui/js/component/header/index.js +++ b/ui/js/component/header/index.js @@ -27,7 +27,7 @@ const perform = (dispatch) => ({ navigate: (path) => dispatch(doNavigate(path)), search: (query) => dispatch(doSearchContent(query)), activateSearch: () => dispatch(doActivateSearch()), - deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 10), + deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 50), }) export default connect(select, perform)(Header) -- 2.43.4 From 3c31a9fca1b3f5b4dd43d31fa528ba2e6527f3f5 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 1 May 2017 13:26:09 +0700 Subject: [PATCH 35/56] Published files working --- ui/js/actions/content.js | 6 +++ ui/js/component/fileCardStream/view.jsx | 8 ++-- ui/js/constants/action_types.js | 1 + ui/js/page/fileListPublished/view.jsx | 52 ++++++++++++++++++++++--- ui/js/reducers/claims.js | 17 ++++++++ ui/js/reducers/file_info.js | 21 ++++++++++ ui/js/selectors/claims.js | 24 ++++++++++++ ui/js/selectors/file_info.js | 21 ++++++++++ 8 files changed, 141 insertions(+), 9 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index f1b756491..510b5d028 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -100,6 +100,12 @@ export function doFetchPublishedContent() { }) lbry.claim_list_mine().then((claimInfos) => { + dispatch({ + type: types.FETCH_MY_CLAIMS_COMPLETED, + data: { + claims: claimInfos, + } + }) lbry.file_list().then((fileInfos) => { const myClaimOutpoints = claimInfos.map(({txid, nout}) => txid + ':' + nout) diff --git a/ui/js/component/fileCardStream/view.jsx b/ui/js/component/fileCardStream/view.jsx index a3b6d9b56..c3954d9b7 100644 --- a/ui/js/component/fileCardStream/view.jsx +++ b/ui/js/component/fileCardStream/view.jsx @@ -56,9 +56,9 @@ class FileCardStream extends React.Component { return null; } - if (!this.props.metadata) { - return null - } + // if (!this.props.metadata) { + // return null + // } const uri = lbryuri.normalize(this.props.uri); const metadata = this.props.metadata; @@ -78,7 +78,9 @@ class FileCardStream extends React.Component { hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />

      + {metadata &&
      + }
      {isConfirmed diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 91c2593dc..80eecab79 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -56,6 +56,7 @@ export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED' export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED' export const DELETE_FILE_STARTED = 'DELETE_FILE_STARTED' export const DELETE_FILE_COMPLETED = 'DELETE_FILE_COMPLETED' +export const FETCH_MY_CLAIMS_COMPLETED = 'FETCH_MY_CLAIMS_COMPLETED' // Search export const SEARCH_STARTED = 'SEARCH_STARTED' diff --git a/ui/js/page/fileListPublished/view.jsx b/ui/js/page/fileListPublished/view.jsx index 70d170079..56d00003b 100644 --- a/ui/js/page/fileListPublished/view.jsx +++ b/ui/js/page/fileListPublished/view.jsx @@ -9,13 +9,53 @@ import lbryio from 'lbryio.js'; import {BusyMessage, Thumbnail} from 'component/common.js'; import FileList from 'component/fileList' -const FileListPublished = (props) => { - // - return ( -
      published content
      - ) -} +class FileListPublished extends React.Component { + componentDidUpdate() { + if(this.props.publishedContent.length > 0) this._requestPublishReward() + } + _requestPublishReward() { + lbryio.call('reward', 'list', {}).then(function(userRewards) { + //already rewarded + if (userRewards.filter(function (reward) { + return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID + }).length) { + return + } + else { + rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {}) + } + }) + } + + render() { + const { + publishedContent, + fetching, + navigate, + } = this.props + + if (fetching) { + return ( +
      + +
      + ); + } else if (!publishedContent.length) { + return ( +
      + You haven't downloaded anything from LBRY yet. Go navigate('discover')} label="search for your first download" />! +
      + ); + } else { + return ( +
      + +
      + ); + } + } +} // const FileListPublished = React.createClass({ // _isMounted: false, diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 3fbf181d6..c758052bc 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -18,6 +18,23 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { }) } +reducers[types.FETCH_MY_CLAIMS_COMPLETED] = function(state, action) { + const { + claims, + } = action.data + const newMine = Object.assign({}, state.mine) + const newById = Object.assign({}, newMine.byId) + + claims.forEach(claim => { + newById[claim.claim_id] = claim + }) + newMine.byId = newById + + return Object.assign({}, state, { + mine: newMine, + }) +} + 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 0023df8f1..292508a2b 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -178,6 +178,27 @@ reducers[types.FETCH_DOWNLOADED_CONTENT_COMPLETED] = function(state, action) { }) } +reducers[types.FETCH_PUBLISHED_CONTENT_COMPLETED] = function(state, action) { + const { + fileInfos + } = action.data + const newByUri = Object.assign({}, state.byUri) + + fileInfos.forEach(fileInfo => { + const uri = lbryuri.build({ + channelName: fileInfo.channel_name, + contentName: fileInfo.name, + }) + + newByUri[uri] = fileInfo + }) + + return Object.assign({}, state, { + byUri: newByUri + }) +} + + 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 3b35ce92f..45007b94e 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -63,3 +63,27 @@ export const makeSelectSourceForUri = () => { (source) => source ) } + +export const selectMyClaims = createSelector( + _selectState, + (state) => state.mine || {} +) + +export const selectMyClaimsById = createSelector( + selectMyClaims, + (mine) => mine.byId || {} +) + +export const selectMyClaimsOutpoints = createSelector( + selectMyClaimsById, + (byId) => { + const outpoints = [] + Object.keys(byId).forEach(key => { + const claim = byId[key] + const outpoint = `${claim.txid}:${claim.nout}` + outpoints.push(outpoint) + }) + + return outpoints + } +) diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 0a114ba0f..7c9439495 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -5,6 +5,9 @@ import { selectCurrentUri, selectCurrentPage, } from 'selectors/app' +import { + selectMyClaimsOutpoints, +} from 'selectors/claims' export const _selectState = state => state.fileInfo || {} @@ -149,3 +152,21 @@ export const selectDownloadedFileInfo = createSelector( return fileInfoList } ) + +export const selectPublishedFileInfo = createSelector( + selectAllFileInfoByUri, + selectMyClaimsOutpoints, + (byUri, outpoints) => { + const fileInfos = [] + outpoints.forEach(outpoint => { + Object.keys(byUri).forEach(key => { + const fileInfo = byUri[key] + if (fileInfo.outpoint == outpoint) { + fileInfos.push(fileInfo) + } + }) + }) + + return fileInfos + } +) -- 2.43.4 From 63c3af32f83e7b5e43f2c1a32ffcb7ddd56fe0e7 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 1 May 2017 13:39:16 +0700 Subject: [PATCH 36/56] Jam claim next purchase reward in --- 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 510b5d028..d887fc318 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -2,6 +2,7 @@ import * as types from 'constants/action_types' import lbry from 'lbry' import lbryio from 'lbryio' import lbryuri from 'lbryuri' +import rewards from 'rewards' import { selectCurrentUri, } from 'selectors/app' @@ -164,6 +165,9 @@ export function doUpdateLoadStatus(uri, outpoint) { // download hasn't started yet setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250) } else if (fileInfo.completed) { + // TODO this isn't going to get called if they reload the client before + // the download finished + rewards.claimNextPurchaseReward() dispatch({ type: types.DOWNLOADING_COMPLETED, data: { -- 2.43.4 From bf5fa758925b3516fb0a422ef986dc38651cc79b Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 2 May 2017 10:57:50 +0700 Subject: [PATCH 37/56] Add missing DAEMON_READY reducer --- ui/js/reducers/app.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index 4fa7442ba..43ae54090 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -15,6 +15,12 @@ const defaultState = { hasSignature: false, } +reducers[types.DAEMON_READY] = function(state, action) { + return Object.assign({}, state, { + daemonReady: true, + }) +} + reducers[types.NAVIGATE] = function(state, action) { return Object.assign({}, state, { currentPath: action.data.path, -- 2.43.4 From 2547d85751906b12e850599a31bb815c2952234a Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 2 May 2017 11:48:16 +0700 Subject: [PATCH 38/56] Fix file card stream loading text --- ui/js/component/fileCardStream/index.js | 6 +++++ ui/js/component/fileCardStream/view.jsx | 30 ++++++++++++++++--------- ui/js/component/fileTileStream/index.js | 5 +++++ ui/js/page/discover/view.jsx | 2 +- ui/js/selectors/content.js | 16 +++++++++++++ 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/ui/js/component/fileCardStream/index.js b/ui/js/component/fileCardStream/index.js index 14e3dc7f9..1a183846f 100644 --- a/ui/js/component/fileCardStream/index.js +++ b/ui/js/component/fileCardStream/index.js @@ -17,6 +17,9 @@ import { import { makeSelectFileInfoForUri, } from 'selectors/file_info' +import { + makeSelectResolvingUri, +} from 'selectors/content' import FileCardStream from './view' const makeSelect = () => { @@ -24,6 +27,8 @@ const makeSelect = () => { const selectFileInfoForUri = makeSelectFileInfoForUri() const selectMetadataForUri = makeSelectMetadataForUri() const selectSourceForUri = makeSelectSourceForUri() + const selectResolvingUri = makeSelectResolvingUri() + const select = (state, props) => ({ claim: selectClaimForUri(state, props), fileInfo: selectFileInfoForUri(state, props), @@ -32,6 +37,7 @@ const makeSelect = () => { hasSignature: false, metadata: selectMetadataForUri(state, props), source: selectSourceForUri(state, props), + isResolvingUri: selectResolvingUri(state, props), }) return select diff --git a/ui/js/component/fileCardStream/view.jsx b/ui/js/component/fileCardStream/view.jsx index c3954d9b7..61f580739 100644 --- a/ui/js/component/fileCardStream/view.jsx +++ b/ui/js/component/fileCardStream/view.jsx @@ -56,24 +56,36 @@ class FileCardStream extends React.Component { return null; } - // if (!this.props.metadata) { - // return null - // } + const { + metadata, + isResolvingUri, + navigate, + hidePrice, + claim, + } = this.props const uri = lbryuri.normalize(this.props.uri); - const metadata = this.props.metadata; const isConfirmed = !!metadata; const title = isConfirmed ? metadata.title : uri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; const primaryUrl = 'show=' + uri; + let description = "" + if (isConfirmed) { + description = metadata.description + } else if (isResolvingUri) { + description = "Loading..." + } else { + description = This file is pending confirmation + } + return (
      - this.props.navigate(primaryUrl)} className="card__link"> + navigate(primaryUrl)} className="card__link">
      {title}
      - { !this.props.hidePrice ? : null} + { !hidePrice ? : null}
      @@ -82,11 +94,7 @@ class FileCardStream extends React.Component {
      }
      - - {isConfirmed - ? metadata.description - : This file is pending confirmation.} - + {description}
      {this.state.showNsfwHelp && this.state.hovered diff --git a/ui/js/component/fileTileStream/index.js b/ui/js/component/fileTileStream/index.js index d212dfdae..faf50f08b 100644 --- a/ui/js/component/fileTileStream/index.js +++ b/ui/js/component/fileTileStream/index.js @@ -20,6 +20,9 @@ import { import { selectObscureNsfw, } from 'selectors/app' +import { + makeSelectResolvingUri, +} from 'selectors/content' import FileTileStream from './view' const makeSelect = () => { @@ -29,6 +32,7 @@ const makeSelect = () => { const selectAvailabilityForUri = makeSelectAvailabilityForUri() const selectMetadataForUri = makeSelectMetadataForUri() const selectSourceForUri = makeSelectSourceForUri() + const selectResolvingUri = makeSelectResolvingUri() const select = (state, props) => ({ claim: selectClaimForUri(state, props), @@ -38,6 +42,7 @@ const makeSelect = () => { obscureNsfw: selectObscureNsfw(state), metadata: selectMetadataForUri(state, props), source: selectSourceForUri(state, props), + resolvingUri: selectResolvingUri(state, props), }) return select diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index e668855d2..5c4c4e511 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -18,7 +18,7 @@ const FeaturedCategory = (props) => {

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

      - {names && names.map(name => )} + {names && names.map(name => )}
      } diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index be38c21b3..87b91182d 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -96,3 +96,19 @@ export const shouldFetchPublishedContent = createSelector( return true } ) + +export const selectResolvingUris = createSelector( + _selectState, + (state) => state.resolvingUris || [] +) + +const selectResolvingUri = (state, props) => { + return selectResolvingUris(state).indexOf(props.uri) != -1 +} + +export const makeSelectResolvingUri = () => { + return createSelector( + selectResolvingUri, + (resolving) => resolving + ) +} -- 2.43.4 From b97703dc3c12c61c9a4916860474723caa18439f Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 2 May 2017 12:25:31 +0700 Subject: [PATCH 39/56] Refactor UriIndicator --- ui/js/component/channel-indicator.js | 43 ------------------------ ui/js/component/fileCardStream/view.jsx | 5 ++- ui/js/component/fileTileStream/view.jsx | 4 ++- ui/js/component/uriIndicator/index.js | 20 +++++++++++ ui/js/component/uriIndicator/view.jsx | 44 +++++++++++++++++++++++++ ui/js/page/showPage/view.jsx | 7 ++-- 6 files changed, 72 insertions(+), 51 deletions(-) delete mode 100644 ui/js/component/channel-indicator.js create mode 100644 ui/js/component/uriIndicator/index.js create mode 100644 ui/js/component/uriIndicator/view.jsx diff --git a/ui/js/component/channel-indicator.js b/ui/js/component/channel-indicator.js deleted file mode 100644 index e19850c28..000000000 --- a/ui/js/component/channel-indicator.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import lbryuri from '../lbryuri.js'; -import {Icon} from './common.js'; - -const UriIndicator = React.createClass({ - propTypes: { - uri: React.PropTypes.string.isRequired, - hasSignature: React.PropTypes.bool.isRequired, - signatureIsValid: React.PropTypes.bool, - }, - render: function() { - - const uriObj = lbryuri.parse(this.props.uri); - - if (!this.props.hasSignature || !uriObj.isChannel) { - return Anonymous; - } - - const channelUriObj = Object.assign({}, uriObj); - delete channelUriObj.path; - delete channelUriObj.contentName; - const channelUri = lbryuri.build(channelUriObj, false); - - let icon, modifier; - if (this.props.signatureIsValid) { - modifier = 'valid'; - } else { - icon = 'icon-times-circle'; - modifier = 'invalid'; - } - return ( - - {channelUri} {' '} - { !this.props.signatureIsValid ? - : - '' } - - ); - } -}); - -export default UriIndicator; \ No newline at end of file diff --git a/ui/js/component/fileCardStream/view.jsx b/ui/js/component/fileCardStream/view.jsx index 61f580739..257c9268c 100644 --- a/ui/js/component/fileCardStream/view.jsx +++ b/ui/js/component/fileCardStream/view.jsx @@ -4,7 +4,7 @@ import lbryuri from 'lbryuri.js'; import Link from 'component/link'; import {Thumbnail, TruncatedText,} from 'component/common'; import FilePrice from 'component/filePrice' -import UriIndicator from 'component/channel-indicator'; +import UriIndicator from 'component/uriIndicator'; class FileCardStream extends React.Component { constructor(props) { @@ -86,8 +86,7 @@ class FileCardStream extends React.Component {
      {title}
      { !hidePrice ? : null} - +
      {metadata && diff --git a/ui/js/component/fileTileStream/view.jsx b/ui/js/component/fileTileStream/view.jsx index 2130df284..7d2b0b360 100644 --- a/ui/js/component/fileTileStream/view.jsx +++ b/ui/js/component/fileTileStream/view.jsx @@ -5,7 +5,7 @@ import Link from 'component/link'; import FileActions from 'component/fileActions'; import {Thumbnail, TruncatedText,} from 'component/common.js'; import FilePrice from 'component/filePrice' -import UriIndicator from 'component/channel-indicator.js'; +import UriIndicator from 'component/uriIndicator'; /*should be merged into FileTile once FileTile is refactored to take a single id*/ class FileTileStream extends React.Component { @@ -71,6 +71,8 @@ class FileTileStream extends React.Component { const title = isConfirmed ? metadata.title : uri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; + console.debug(this.props) + return (
      diff --git a/ui/js/component/uriIndicator/index.js b/ui/js/component/uriIndicator/index.js new file mode 100644 index 000000000..6cadf91b3 --- /dev/null +++ b/ui/js/component/uriIndicator/index.js @@ -0,0 +1,20 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import { + makeSelectClaimForUri, +} from 'selectors/claims' +import UriIndicator from './view' + +const makeSelect = () => { + const selectClaimForUri = makeSelectClaimForUri() + + const select = (state, props) => ({ + claim: selectClaimForUri(state, props), + }) + + return select +} + +export default connect(makeSelect, null)(UriIndicator) diff --git a/ui/js/component/uriIndicator/view.jsx b/ui/js/component/uriIndicator/view.jsx new file mode 100644 index 000000000..cc3d4f0eb --- /dev/null +++ b/ui/js/component/uriIndicator/view.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import lbry from 'lbry'; +import lbryuri from 'lbryuri'; +import {Icon} from 'component/common'; + +const UriIndicator = (props) => { + const { + uri, + claim: { + has_signature: hasSignature, + signature_is_valid: signatureIsValid, + } = {}, + } = props + + const uriObj = lbryuri.parse(uri); + + if (!hasSignature || !uriObj.isChannel) { + return Anonymous; + } + + const channelUriObj = Object.assign({}, uriObj); + delete channelUriObj.path; + delete channelUriObj.contentName; + const channelUri = lbryuri.build(channelUriObj, false); + + let icon, modifier; + if (signatureIsValid) { + modifier = 'valid'; + } else { + icon = 'icon-times-circle'; + modifier = 'invalid'; + } + + return ( + + {channelUri} {' '} + { !signatureIsValid ? + : + '' } + + ) +} + +export default UriIndicator; \ No newline at end of file diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx index 2a3498bf9..8e46b619c 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/showPage/view.jsx @@ -11,7 +11,7 @@ import { import FilePrice from 'component/filePrice' import FileActions from 'component/fileActions'; import Link from 'component/link'; -import UriIndicator from 'component/channel-indicator.js'; +import UriIndicator from 'component/uriIndicator'; const FormatItem = (props) => { const { @@ -133,7 +133,7 @@ let FilePage = React.createClass({
      - {this.state.isDownloaded === false + {isDownloaded === false ? : null}

      {title}

      @@ -143,8 +143,7 @@ let FilePage = React.createClass({ uriIndicator}
      - -
      +
      {metadata.description} -- 2.43.4 From 001d5e21f1dbe98b2c7a7d5a66620b9381abda6f Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 2 May 2017 12:31:51 +0700 Subject: [PATCH 40/56] Get rewards page working --- ui/js/component/reward-link.js | 82 +++++++++++++++++++++ ui/js/page/reward.js | 126 --------------------------------- ui/js/page/rewards.js | 14 ++-- ui/js/selectors/app.js | 9 +-- 4 files changed, 92 insertions(+), 139 deletions(-) create mode 100644 ui/js/component/reward-link.js delete mode 100644 ui/js/page/reward.js diff --git a/ui/js/component/reward-link.js b/ui/js/component/reward-link.js new file mode 100644 index 000000000..b6ae09fed --- /dev/null +++ b/ui/js/component/reward-link.js @@ -0,0 +1,82 @@ +import React from 'react'; +import lbry from 'lbry' +import {Icon} from 'component/common'; +import Modal from 'component/modal'; +import rewards from 'rewards'; +import Link from 'component/link' + +export let RewardLink = React.createClass({ + propTypes: { + type: React.PropTypes.string.isRequired, + claimed: React.PropTypes.bool, + onRewardClaim: React.PropTypes.func, + onRewardFailure: React.PropTypes.func + }, + refreshClaimable: function() { + switch(this.props.type) { + case 'new_user': + this.setState({ claimable: true }); + return; + + case 'first_publish': + lbry.claim_list_mine().then(function(list) { + this.setState({ + claimable: list.length > 0 + }) + }.bind(this)); + return; + } + }, + componentWillMount: function() { + this.refreshClaimable(); + }, + getInitialState: function() { + return { + claimable: true, + pending: false, + errorMessage: null + } + }, + claimReward: function() { + this.setState({ + pending: true + }) + rewards.claimReward(this.props.type).then((reward) => { + this.setState({ + pending: false, + errorMessage: null + }) + if (this.props.onRewardClaim) { + this.props.onRewardClaim(reward); + } + }).catch((error) => { + this.setState({ + errorMessage: error.message, + pending: false + }) + }) + }, + clearError: function() { + if (this.props.onRewardFailure) { + this.props.onRewardFailure() + } + this.setState({ + errorMessage: null + }) + }, + render: function() { + return ( +
      + {this.props.claimed + ? Reward claimed. + : } + {this.state.errorMessage ? + + {this.state.errorMessage} + + : ''} +
      + ); + } +}); \ No newline at end of file diff --git a/ui/js/page/reward.js b/ui/js/page/reward.js deleted file mode 100644 index 0a8aeebc6..000000000 --- a/ui/js/page/reward.js +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react'; -import lbryio from '../lbryio.js'; -import {Link} from '../component/link'; -import Notice from '../component/notice.js'; -import {CreditAmount} from '../component/common.js'; -// -// const {shell} = require('electron'); -// const querystring = require('querystring'); -// -// const GITHUB_CLIENT_ID = '6baf581d32bad60519'; -// -// const LinkGithubReward = React.createClass({ -// propTypes: { -// onClaimed: React.PropTypes.func, -// }, -// _launchLinkPage: function() { -// /* const githubAuthParams = { -// client_id: GITHUB_CLIENT_ID, -// redirect_uri: 'https://lbry.io/', -// scope: 'user:email,public_repo', -// allow_signup: false, -// } -// shell.openExternal('https://github.com/login/oauth/authorize?' + querystring.stringify(githubAuthParams)); */ -// shell.openExternal('https://lbry.io'); -// }, -// handleConfirmClicked: function() { -// this.setState({ -// confirming: true, -// }); -// -// lbry.get_new_address().then((address) => { -// lbryio.call('reward', 'new', { -// reward_type: 'new_developer', -// access_token: '**access token here**', -// wallet_address: address, -// }, 'post').then((response) => { -// console.log('response:', response); -// -// this.props.onClaimed(); // This will trigger another API call to show that we succeeded -// -// this.setState({ -// confirming: false, -// error: null, -// }); -// }, (error) => { -// console.log('failed with error:', error); -// this.setState({ -// confirming: false, -// error: error, -// }); -// }); -// }); -// }, -// getInitialState: function() { -// return { -// confirming: false, -// error: null, -// }; -// }, -// render: function() { -// return ( -//
      -//

      -//
      -//

      This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.

      -//

      Once you're finished, you may confirm you've linked the account to receive your reward.

      -//
      -// {this.state.error -// ? -// {this.state.error.message} -// -// : null} -// -// -//
      -// ); -// } -// }); -// -// const RewardPage = React.createClass({ -// propTypes: { -// name: React.PropTypes.string.isRequired, -// }, -// _getRewardType: function() { -// lbryio.call('reward_type', 'get', this.props.name).then((rewardType) => { -// this.setState({ -// rewardType: rewardType, -// }); -// }); -// }, -// getInitialState: function() { -// return { -// rewardType: null, -// }; -// }, -// componentWillMount: function() { -// this._getRewardType(); -// }, -// render: function() { -// if (!this.state.rewardType) { -// return null; -// } -// -// let Reward; -// if (this.props.name == 'link_github') { -// Reward = LinkGithubReward; -// } -// -// const {title, description, value} = this.state.rewardType; -// return ( -//
      -//
      -//

      {title}

      -// -//

      {this.state.rewardType.claimed -// ? This reward has been claimed. -// : description}

      -// -//
      -//
      -// ); -// } -// }); -// -// export default RewardPage; diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index 429a49a5e..2419ec97d 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -1,11 +1,11 @@ import React from 'react'; -import lbry from '../lbry.js'; -import lbryio from '../lbryio.js'; -import {CreditAmount, Icon} from '../component/common.js'; -import rewards from '../rewards.js'; -import Modal from '../component/modal.js'; -import {WalletNav} from './wallet.js'; -import {RewardLink} from '../component/link'; +import lbry from 'lbry'; +import lbryio from 'lbryio'; +import {CreditAmount, Icon} from 'component/common.js'; +import rewards from 'rewards'; +import Modal from 'component/modal'; +import {WalletNav} from 'component/wallet-nav ewar'; +import {RewardLink} from 'component/reward-link'; const RewardTile = React.createClass({ propTypes: { diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index a269e98f2..e0d1a7a43 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -39,8 +39,7 @@ export const selectPageTitle = createSelector( case 'wallet': case 'send': case 'receive': - case 'claim': - case 'referral': + case 'rewards': return 'Wallet' case 'downloaded': return 'My Files' @@ -129,14 +128,12 @@ export const selectHeaderLinks = createSelector( case 'wallet': case 'send': case 'receive': - case 'claim': - case 'referral': + case 'rewards': return { 'wallet' : 'Overview', 'send' : 'Send', 'receive' : 'Receive', - 'claim' : 'Claim Beta Code', - 'referral' : 'Check Referral Credit', + 'rewards': 'Rewards', }; case 'downloaded': case 'published': -- 2.43.4 From 6d35eb69c5d109fef80c5cf3d385011a8b91260b Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 2 May 2017 13:11:43 +0700 Subject: [PATCH 41/56] File tile stream fixes --- ui/js/component/fileCardStream/view.jsx | 1 - ui/js/component/fileTileStream/index.js | 2 +- ui/js/component/fileTileStream/view.jsx | 17 +++++++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/ui/js/component/fileCardStream/view.jsx b/ui/js/component/fileCardStream/view.jsx index 257c9268c..72284eb79 100644 --- a/ui/js/component/fileCardStream/view.jsx +++ b/ui/js/component/fileCardStream/view.jsx @@ -61,7 +61,6 @@ class FileCardStream extends React.Component { isResolvingUri, navigate, hidePrice, - claim, } = this.props const uri = lbryuri.normalize(this.props.uri); diff --git a/ui/js/component/fileTileStream/index.js b/ui/js/component/fileTileStream/index.js index faf50f08b..80807f125 100644 --- a/ui/js/component/fileTileStream/index.js +++ b/ui/js/component/fileTileStream/index.js @@ -42,7 +42,7 @@ const makeSelect = () => { obscureNsfw: selectObscureNsfw(state), metadata: selectMetadataForUri(state, props), source: selectSourceForUri(state, props), - resolvingUri: selectResolvingUri(state, props), + isResolvingUri: selectResolvingUri(state, props), }) return select diff --git a/ui/js/component/fileTileStream/view.jsx b/ui/js/component/fileTileStream/view.jsx index 7d2b0b360..6a80772eb 100644 --- a/ui/js/component/fileTileStream/view.jsx +++ b/ui/js/component/fileTileStream/view.jsx @@ -63,7 +63,9 @@ class FileTileStream extends React.Component { const { metadata, + isResolvingUri, navigate, + hidePrice, } = this.props const uri = lbryuri.normalize(this.props.uri); @@ -71,7 +73,14 @@ class FileTileStream extends React.Component { const title = isConfirmed ? metadata.title : uri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; - console.debug(this.props) + let description = "" + if (isConfirmed) { + description = metadata.description + } else if (isResolvingUri) { + description = "Loading..." + } else { + description = This file is pending confirmation + } return (
      @@ -103,11 +112,7 @@ class FileTileStream extends React.Component {

      - - {isConfirmed - ? metadata.description - : This file is pending confirmation.} - + {description}

      -- 2.43.4 From 513555e4029f24d11e57ad7a1dc42a59109c0531 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 2 May 2017 14:07:13 +0700 Subject: [PATCH 42/56] Fix for timed out content --- ui/js/actions/content.js | 6 +- ui/js/component/fileActions/view.jsx | 182 --------------------------- 2 files changed, 5 insertions(+), 183 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index d887fc318..91a227665 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -240,7 +240,11 @@ export function doLoadVideo() { }) lbry.get({ uri }).then(streamInfo => { - if (streamInfo === null || typeof streamInfo !== 'object') { + const timeout = streamInfo === null || + typeof streamInfo !== 'object' || + streamInfo.error == 'Timeout' + + if(timeout) { dispatch({ type: types.LOADING_VIDEO_FAILED, data: { uri } diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index 5fe6a2dea..5d74949bc 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -116,188 +116,6 @@ class FileActionsRow extends React.Component { } } -// const FileActionsRow = React.createClass({ -// _isMounted: false, -// _fileInfoSubscribeId: null, - -// propTypes: { -// uri: React.PropTypes.string, -// outpoint: React.PropTypes.string.isRequired, -// metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]), -// contentType: React.PropTypes.string.isRequired, -// }, -// getInitialState: function() { -// return { -// fileInfo: null, -// modal: null, -// menuOpen: false, -// deleteChecked: false, -// attemptingDownload: false, -// attemptingRemove: false, -// } -// }, -// onFileInfoUpdate: function(fileInfo) { -// if (this._isMounted) { -// this.setState({ -// fileInfo: fileInfo ? fileInfo : false, -// attemptingDownload: fileInfo ? false : this.state.attemptingDownload -// }); -// } -// }, -// tryDownload: function() { -// this.setState({ -// attemptingDownload: true, -// attemptingRemove: false -// }); -// lbry.getCostInfo(this.props.uri).then(({cost}) => { -// lbry.getBalance((balance) => { -// if (cost > balance) { -// this.setState({ -// modal: 'notEnoughCredits', -// attemptingDownload: false, -// }); -// } else if (this.state.affirmedPurchase) { -// lbry.get({uri: this.props.uri}).then((streamInfo) => { -// if (streamInfo === null || typeof streamInfo !== 'object') { -// this.setState({ -// modal: 'timedOut', -// attemptingDownload: false, -// }); -// } -// }); -// } else { -// this.setState({ -// attemptingDownload: false, -// modal: 'affirmPurchase' -// }) -// } -// }); -// }); -// }, -// closeModal: function() { -// this.setState({ -// modal: null, -// }) -// }, -// onDownloadClick: function() { -// if (!this.state.fileInfo && !this.state.attemptingDownload) { -// this.tryDownload(); -// } -// }, -// onOpenClick: function() { -// if (this.state.fileInfo && this.state.fileInfo.download_path) { -// shell.openItem(this.state.fileInfo.download_path); -// } -// }, -// handleDeleteCheckboxClicked: function(event) { -// this.setState({ -// deleteChecked: event.target.checked, -// }); -// }, -// handleRevealClicked: function() { -// if (this.state.fileInfo && this.state.fileInfo.download_path) { -// shell.showItemInFolder(this.state.fileInfo.download_path); -// } -// }, -// handleRemoveClicked: function() { -// this.setState({ -// modal: 'confirmRemove', -// }); -// }, -// handleRemoveConfirmed: function() { -// lbry.removeFile(this.props.outpoint, this.state.deleteChecked); -// this.setState({ -// modal: null, -// fileInfo: false, -// attemptingDownload: false -// }); -// }, -// onAffirmPurchase: function() { -// this.setState({ -// affirmedPurchase: true, -// modal: null -// }); -// this.tryDownload(); -// }, -// openMenu: function() { -// this.setState({ -// menuOpen: !this.state.menuOpen, -// }); -// }, -// componentDidMount: function() { -// this._isMounted = true; -// this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); -// }, -// componentWillUnmount: function() { -// this._isMounted = false; -// if (this._fileInfoSubscribeId) { -// lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); -// } -// }, -// render: function() { -// if (this.state.fileInfo === null) -// { -// return null; -// } - -// const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder', -// showMenu = !!this.state.fileInfo; - -// let linkBlock; -// if (this.state.fileInfo === false && !this.state.attemptingDownload) { -// linkBlock = ; -// } else if (this.state.attemptingDownload || (!this.state.fileInfo.completed && !this.state.fileInfo.isMine)) { -// const -// progress = this.state.fileInfo ? this.state.fileInfo.written_bytes / this.state.fileInfo.total_bytes * 100 : 0, -// label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', -// labelWithIcon = {label}; - -// linkBlock = ( -//
      -//
      {labelWithIcon}
      -// {labelWithIcon} -//
      -// ); -// } else { -// linkBlock = ; -// } - -// const uri = lbryuri.normalize(this.props.uri); -// const title = this.props.metadata ? this.props.metadata.title : uri; -// return ( -//
      -// {this.state.fileInfo !== null || this.state.fileInfo.isMine -// ? linkBlock -// : null} -// { showMenu ? -// -// -// -// : '' } -// -// Are you sure you'd like to buy {title} for credits? -// -// -// You don't have enough LBRY credits to pay for this stream. -// -// -// LBRY was unable to download the stream {uri}. -// -// -//

      Are you sure you'd like to remove {title} from LBRY?

      - -// -//
      -//
      -// ); -// } -// }); - class FileActions extends React.Component { constructor(props) { super(props) -- 2.43.4 From d87fc4b09a5e39aa5fd9aca21de3a1c1763f7e82 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 2 May 2017 15:21:00 +0700 Subject: [PATCH 43/56] Start on publish page --- ui/js/page/publish/index.js | 17 +++++++++++++++++ ui/js/page/{publish.js => publish/view.jsx} | 10 +++++----- 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 ui/js/page/publish/index.js rename ui/js/page/{publish.js => publish/view.jsx} (99%) diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js new file mode 100644 index 000000000..29347b6ed --- /dev/null +++ b/ui/js/page/publish/index.js @@ -0,0 +1,17 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import { + doNavigate, +} from 'actions/app' +import PublishPage from './view' + +const select = (state) => ({ +}) + +const perform = (dispatch) => ({ + navigate: (path) => dispatch(doNavigate(path)), +}) + +export default connect(select, perform)(PublishPage) diff --git a/ui/js/page/publish.js b/ui/js/page/publish/view.jsx similarity index 99% rename from ui/js/page/publish.js rename to ui/js/page/publish/view.jsx index d443d8737..23d7b8661 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish/view.jsx @@ -1,9 +1,9 @@ import React from 'react'; -import lbry from '../lbry.js'; -import FormField from '../component/form.js'; +import lbry from 'lbry'; +import {FormField, FormRow} from 'component/form.js'; import Link from 'component/link'; -import rewards from '../rewards.js'; -import Modal from '../component/modal.js'; +import rewards from 'rewards'; +import Modal from 'component/modal'; var PublishPage = React.createClass({ _requiredFields: ['meta_title', 'name', 'bid', 'tos_agree'], @@ -147,7 +147,7 @@ var PublishPage = React.createClass({ }); }, handlePublishStartedConfirmed: function() { - window.location.href = "?published"; + this.props.navigate('published') }, handlePublishError: function(error) { this.setState({ -- 2.43.4 From 44ad47c05ff50da439ce2efb3029625cd47ad812 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 2 May 2017 15:39:06 +0700 Subject: [PATCH 44/56] Fix publish page link in router --- ui/js/component/router/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index 8f9a258f8..9bc906d19 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -5,7 +5,7 @@ import ReportPage from 'page/report.js'; import StartPage from 'page/start.js'; import WalletPage from 'page/wallet'; import ShowPage from 'page/showPage'; -import PublishPage from 'page/publish.js'; +import PublishPage from 'page/publish'; import DiscoverPage from 'page/discover'; import SplashScreen from 'component/splash.js'; import DeveloperPage from 'page/developer.js'; -- 2.43.4 From e1e172a11e560d1af582e8e5e661e4428051d377 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 2 May 2017 20:58:35 +0700 Subject: [PATCH 45/56] Switch to https://github.com/Selz/plyr video player (recommended by @fillerino). --- ui/dist/index.html | 2 +- ui/js/actions/content.js | 7 +++++ ui/js/component/video/view.jsx | 52 ++++++++++++++++++++++++++++++++++ ui/package.json | 4 +-- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/ui/dist/index.html b/ui/dist/index.html index b4239bc9a..8fbf0b5b6 100644 --- a/ui/dist/index.html +++ b/ui/dist/index.html @@ -7,7 +7,7 @@ - + diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 91a227665..69886570b 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -268,8 +268,15 @@ export function doWatchVideo() { const alreadyDownloading = !!downloadingByUri[uri] const { cost } = costInfo + // BUG if you delete a file from the file system system you're going to be + // asked to pay for it again. We need to check if the file is in the blobs + // here and then dispatch doLoadVideo() which will reconstruct it again from + // the blobs. Or perhaps there's another way to see if a file was already + // purchased? + // we already fully downloaded the file if (fileInfo && fileInfo.completed) { + dispatch(doLoadVideo()) return Promise.resolve() } diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index 1b559ae30..a1093cf40 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -7,6 +7,58 @@ import FilePrice from 'component/filePrice' import Link from 'component/link'; import Modal from 'component/modal'; +class WatchLink extends React.Component { + confirmPurchaseClick() { + this.props.closeModal() + this.props.startPlaying() + this.props.loadVideo() + } + + render() { + const { + button, + label, + className, + onWatchClick, + metadata, + metadata: { + title, + }, + uri, + modal, + closeModal, + isLoading, + costInfo, + fileInfo, + } = this.props + + return (
      + + {modal} + + You don't have enough LBRY credits to pay for this stream. + + + Are you sure you'd like to buy {this.props.metadata.title} for credits? + + + Sorry, your download timed out :( + +
      ); + } +} + const plyr = require('plyr') class Video extends React.Component { diff --git a/ui/package.json b/ui/package.json index 4b1c563fe..e021686ac 100644 --- a/ui/package.json +++ b/ui/package.json @@ -24,6 +24,7 @@ "babel-preset-react": "^6.11.1", "mediaelement": "^2.23.4", "node-sass": "^3.8.0", + "plyr": "^2.0.12", "rc-progress": "^2.0.6", "react": "^15.4.0", "react-dom": "^15.4.0", @@ -32,8 +33,7 @@ "redux": "^3.6.0", "redux-logger": "^3.0.1", "redux-thunk": "^2.2.0", - "reselect": "^3.0.0", - "videostream": "^2.4.2" + "reselect": "^3.0.0" }, "devDependencies": { "babel": "^6.5.2", -- 2.43.4 From 2ad50aa00d50dc4d7f972690a6935abf2426c491 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 2 May 2017 21:33:37 +0700 Subject: [PATCH 46/56] Fix accidental commit --- ui/js/actions/content.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 69886570b..fb24a0458 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -276,7 +276,6 @@ export function doWatchVideo() { // we already fully downloaded the file if (fileInfo && fileInfo.completed) { - dispatch(doLoadVideo()) return Promise.resolve() } -- 2.43.4 From d94ef70f602858b32a55600f606f021fc50b04e0 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 2 May 2017 19:49:02 -0400 Subject: [PATCH 47/56] more bug fixes --- ui/js/component/app/view.jsx | 5 ++--- ui/js/component/router/view.jsx | 1 - ui/js/lbry.js | 2 +- ui/js/lbryio.js | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index c160e8146..944547044 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -45,9 +45,8 @@ const App = React.createClass({ const searchQuery = (currentPage == 'discover' && searchTerm ? searchTerm : '') return
      - -
      -
      +
      +
      {modal == 'upgrade' && } diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index 9bc906d19..78246c0b6 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -31,7 +31,6 @@ const Router = (props) => { 'downloaded': , 'published': , 'start': , - 'claim': , 'wallet': , 'send': , 'receive': , diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 2046efc83..5fd96595a 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -174,7 +174,7 @@ lbry.getDaemonSettings = function(callback) { lbry.setDaemonSettings = function(settings, callback) { lbry.call('set_settings', settings, callback); } - + lbry.setDaemonSetting = function(setting, value, callback) { var setSettingsArgs = {}; setSettingsArgs[setting] = value; diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 20934bbbb..f5632d24c 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -10,7 +10,7 @@ const lbryio = { enabled: false }; -const CONNECTION_STRING = process.env.LBRY_APP_API_URL ? process.env.LBRY_APP_API_URL : 'https://api.lbry.io/'; +const CONNECTION_STRING = 'https://api.lbry.io/'; const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000; lbryio.getExchangeRates = function() { -- 2.43.4 From 36a992343f56a3f85ae7459c685cdfe56bd6193c Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 3 May 2017 23:44:08 -0400 Subject: [PATCH 48/56] much progress towards a working merge --- CHANGELOG.md | 1 - ui/js/actions/app.js | 28 +- ui/js/actions/content.js | 20 +- ui/js/actions/wallet.js | 1 + ui/js/app.js | 320 +-------------------- ui/js/component/app/index.js | 5 - ui/js/component/app/view.jsx | 35 +-- ui/js/component/auth.js | 3 +- ui/js/component/drawer/index.jsx | 33 --- ui/js/component/drawer/view.jsx | 68 ----- ui/js/component/header/index.js | 20 +- ui/js/component/header/view.jsx | 216 ++------------ ui/js/component/navMain/index.jsx | 21 ++ ui/js/component/navMain/view.jsx | 22 ++ ui/js/component/navSettings/index.jsx | 7 + ui/js/component/navSettings/view.jsx | 11 + ui/js/component/navWallet/index.jsx | 7 + ui/js/component/navWallet/view.jsx | 13 + ui/js/component/router/view.jsx | 8 +- ui/js/component/sub-header.js | 22 -- ui/js/component/wallet-nav.js | 12 - ui/js/component/wunderbar/index.js | 32 +++ ui/js/component/wunderbar/view.jsx | 136 +++++++++ ui/js/constants/action_types.js | 4 +- ui/js/lbry.js | 34 +-- ui/js/page/channel/index.js | 17 ++ ui/js/page/channel/view.jsx | 22 ++ ui/js/page/discover/index.js | 8 +- ui/js/page/discover/view.jsx | 97 ++++--- ui/js/page/{showPage => filePage}/index.js | 4 +- ui/js/page/{showPage => filePage}/view.jsx | 135 +++++++-- ui/js/page/help/view.jsx | 102 ++++--- ui/js/page/rewards.js | 9 +- ui/js/page/settings.js | 13 +- ui/js/page/wallet/view.jsx | 2 + ui/js/reducers/app.js | 15 - ui/js/reducers/content.js | 10 +- ui/js/rewards.js | 2 +- ui/js/selectors/app.js | 97 +++++-- ui/js/selectors/content.js | 17 +- ui/js/triggers.js | 8 +- ui/scss/component/_header.scss | 8 +- 42 files changed, 713 insertions(+), 932 deletions(-) delete mode 100644 ui/js/component/drawer/index.jsx delete mode 100644 ui/js/component/drawer/view.jsx create mode 100644 ui/js/component/navMain/index.jsx create mode 100644 ui/js/component/navMain/view.jsx create mode 100644 ui/js/component/navSettings/index.jsx create mode 100644 ui/js/component/navSettings/view.jsx create mode 100644 ui/js/component/navWallet/index.jsx create mode 100644 ui/js/component/navWallet/view.jsx delete mode 100644 ui/js/component/sub-header.js delete mode 100644 ui/js/component/wallet-nav.js create mode 100644 ui/js/component/wunderbar/index.js create mode 100644 ui/js/component/wunderbar/view.jsx create mode 100644 ui/js/page/channel/index.js create mode 100644 ui/js/page/channel/view.jsx rename ui/js/page/{showPage => filePage}/index.js (89%) rename ui/js/page/{showPage => filePage}/view.jsx (69%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54c22c93c..72cb1ce2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ Web UI version numbers should always match the corresponding version of LBRY App * The app is much more responsive switching pages. It no longer reloads the entire page and all assets on each page change. * lbry.js now offers a subscription model for wallet balance similar to file info. * Fixed file info subscribes not being unsubscribed in unmount. - * Fixed drawer not highlighting selected page. * You can now make API calls directly on the lbry module, e.g. lbry.peer_list() * New-style API calls return promises instead of using callbacks * Wherever possible, use outpoints for unique IDs instead of names or SD hashes diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index d2c7679f3..1f384cbd5 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -32,18 +32,6 @@ export function doNavigate(path) { export function doLogoClick() { } -export function doOpenDrawer() { - return { - type: types.OPEN_DRAWER - } -} - -export function doCloseDrawer() { - return { - type: types.CLOSE_DRAWER - } -} - export function doOpenModal(modal) { return { type: types.OPEN_MODAL, @@ -59,6 +47,12 @@ export function doCloseModal() { } } +export function doHistoryBack() { + return { + type: types.HISTORY_BACK + } +} + export function doUpdateDownloadProgress(percent) { return { type: types.UPGRADE_DOWNLOAD_PROGRESSED, @@ -153,12 +147,8 @@ export function doCheckUpgradeAvailable() { return function(dispatch, getState) { const state = getState() - lbry.checkNewVersionAvailable(({isAvailable}) => { - if (!isAvailable) { - return; - } - - lbry.getVersionInfo((versionInfo) => { + lbry.getVersionInfo().then(({remoteVersion, upgradeAvailable}) => { + if (upgradeAvailable) { dispatch({ type: types.UPDATE_VERSION, data: { @@ -171,7 +161,7 @@ export function doCheckUpgradeAvailable() { modal: 'upgrade' } }) - }); + } }); } } diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index fb24a0458..76622b84b 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -121,7 +121,7 @@ export function doFetchPublishedContent() { } } -export function doFetchFeaturedContent() { +export function doFetchFeaturedUris() { return function(dispatch, getState) { const state = getState() @@ -130,11 +130,20 @@ export function doFetchFeaturedContent() { }) const success = ({ Categories, Uris }) => { + + let featuredUris = {} + + Categories.forEach((category) => { + if (Uris[category] && Uris[category].length) { + featuredUris[category] = Uris[category] + } + }) + dispatch({ type: types.FETCH_FEATURED_CONTENT_COMPLETED, data: { categories: Categories, - uris: Uris, + uris: featuredUris, } }) @@ -146,6 +155,13 @@ export function doFetchFeaturedContent() { } const failure = () => { + dispatch({ + type: types.FETCH_FEATURED_CONTENT_COMPLETED, + data: { + categories: [], + uris: {} + } + }) } lbryio.call('discover', 'list', { version: "early-access" } ) diff --git a/ui/js/actions/wallet.js b/ui/js/actions/wallet.js index 79b22d8d6..1faae7a5d 100644 --- a/ui/js/actions/wallet.js +++ b/ui/js/actions/wallet.js @@ -41,6 +41,7 @@ export function doGetNewAddress() { type: types.GET_NEW_ADDRESS_STARTED }) + console.log('do get new address'); lbry.wallet_new_address().then(function(address) { localStorage.setItem('wallet_address', address); dispatch({ diff --git a/ui/js/app.js b/ui/js/app.js index 88ad0eef8..51e097da2 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -14,322 +14,4 @@ const app = { } } global.app = app; -module.exports = app; - -// -// import React from 'react'; -// import {Line} from 'rc-progress'; -// -// import lbry from './lbry.js'; -// import SettingsPage from './page/settings.js'; -// import HelpPage from './page/help.js'; -// import WatchPage from './page/watch.js'; -// import ReportPage from './page/report.js'; -// import StartPage from './page/start.js'; -// import RewardsPage from './page/rewards.js'; -// import RewardPage from './page/reward.js'; -// import WalletPage from './page/wallet.js'; -// import ShowPage from './page/show.js'; -// import PublishPage from './page/publish.js'; -// import SearchPage from './page/search.js'; -// import DiscoverPage from './page/discover.js'; -// import DeveloperPage from './page/developer.js'; -// import lbryuri from './lbryuri.js'; -// import {FileListDownloaded, FileListPublished} from './page/file-list.js'; -// import Header from './component/header.js'; -// import {Modal, ExpandableModal} from './component/modal.js'; -// import {Link} from './component/link'; -// -// -// const {remote, ipcRenderer, shell} = require('electron'); -// const {download} = remote.require('electron-dl'); -// const path = require('path'); -// const app = require('electron').remote.app; -// const fs = remote.require('fs'); -// -// -// var App = React.createClass({ -// _error_key_labels: { -// connectionString: 'API connection string', -// method: 'Method', -// params: 'Parameters', -// code: 'Error code', -// message: 'Error message', -// data: 'Error data', -// }, -// _fullScreenPages: ['watch'], -// _storeHistoryOfNextRender: false, -// -// _upgradeDownloadItem: null, -// _isMounted: false, -// _version: null, -// getUpdateUrl: function() { -// switch (process.platform) { -// case 'darwin': -// return 'https://lbry.io/get/lbry.dmg'; -// case 'linux': -// return 'https://lbry.io/get/lbry.deb'; -// case 'win32': -// return 'https://lbry.io/get/lbry.exe'; -// default: -// throw 'Unknown platform'; -// } -// }, -// // Hard code the filenames as a temporary workaround, because -// // electron-dl throws errors when you try to get the filename -// getUpgradeFilename: function() { -// switch (process.platform) { -// case 'darwin': -// return `LBRY-${this._version}.dmg`; -// case 'linux': -// return `LBRY_${this._version}_amd64.deb`; -// case 'windows': -// return `LBRY.Setup.${this._version}.exe`; -// default: -// throw 'Unknown platform'; -// } -// }, -// getViewingPageAndArgs: function(address) { -// // For now, routes are in format ?page or ?page=args -// let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/); -// return { -// viewingPage: viewingPage, -// pageArgs: pageArgs === undefined ? null : decodeURIComponent(pageArgs) -// }; -// }, -// getInitialState: function() { -// return Object.assign(this.getViewingPageAndArgs(window.location.search), { -// viewingPage: 'discover', -// appUrl: null, -// errorInfo: null, -// modal: null, -// downloadProgress: null, -// downloadComplete: false, -// }); -// }, -// componentWillMount: function() { -// window.addEventListener("popstate", this.onHistoryPop); -// -// document.addEventListener('unhandledError', (event) => { -// this.alertError(event.detail); -// }); -// -// //open links in external browser and skip full redraw on changing page -// document.addEventListener('click', (event) => { -// var target = event.target; -// while (target && target !== document) { -// if (target.matches('a[href^="http"]')) { -// event.preventDefault(); -// shell.openExternal(target.href); -// return; -// } -// if (target.matches('a[href^="?"]')) { -// event.preventDefault(); -// if (this._isMounted) { -// let appUrl = target.getAttribute('href'); -// this._storeHistoryOfNextRender = true; -// this.setState(Object.assign({}, this.getViewingPageAndArgs(appUrl), { appUrl: appUrl })); -// document.body.scrollTop = 0; -// } -// } -// target = target.parentNode; -// } -// }); -// -// if (!sessionStorage.getItem('upgradeSkipped')) { -// lbry.getVersionInfo().then(({remoteVersion, upgradeAvailable}) => { -// if (upgradeAvailable) { -// this._version = remoteVersion; -// this.setState({ -// modal: 'upgrade', -// }); -// } -// }); -// } -// }, -// closeModal: function() { -// this.setState({ -// modal: null, -// }); -// }, -// componentDidMount: function() { -// this._isMounted = true; -// }, -// componentWillUnmount: function() { -// this._isMounted = false; -// window.removeEventListener("popstate", this.onHistoryPop); -// }, -// onHistoryPop: function() { -// this.setState(this.getViewingPageAndArgs(location.search)); -// }, -// onSearch: function(term) { -// this._storeHistoryOfNextRender = true; -// const isShow = term.startsWith('lbry://'); -// this.setState({ -// viewingPage: isShow ? "show" : "search", -// appUrl: (isShow ? "?show=" : "?search=") + encodeURIComponent(term), -// pageArgs: term -// }); -// }, -// onSubmit: function(uri) { -// this._storeHistoryOfNextRender = true; -// this.setState({ -// address: uri, -// appUrl: "?show=" + encodeURIComponent(uri), -// viewingPage: "show", -// pageArgs: uri -// }) -// }, -// handleUpgradeClicked: function() { -// // Make a new directory within temp directory so the filename is guaranteed to be available -// const dir = fs.mkdtempSync(app.getPath('temp') + require('path').sep); -// -// let options = { -// onProgress: (p) => this.setState({downloadProgress: Math.round(p * 100)}), -// directory: dir, -// }; -// download(remote.getCurrentWindow(), this.getUpdateUrl(), options) -// .then(downloadItem => { -// /** -// * TODO: get the download path directly from the download object. It should just be -// * downloadItem.getSavePath(), but the copy on the main process is being garbage collected -// * too soon. -// */ -// -// this._upgradeDownloadItem = downloadItem; -// this._upgradeDownloadPath = path.join(dir, this.getUpgradeFilename()); -// this.setState({ -// downloadComplete: true -// }); -// }); -// this.setState({modal: 'downloading'}); -// }, -// handleStartUpgradeClicked: function() { -// ipcRenderer.send('upgrade', this._upgradeDownloadPath); -// }, -// cancelUpgrade: function() { -// if (this._upgradeDownloadItem) { -// /* -// * Right now the remote reference to the download item gets garbage collected as soon as the -// * the download is over (maybe even earlier), so trying to cancel a finished download may -// * throw an error. -// */ -// try { -// this._upgradeDownloadItem.cancel(); -// } catch (err) { -// // Do nothing -// } -// } -// this.setState({ -// downloadProgress: null, -// downloadComplete: false, -// modal: null, -// }); -// }, -// handleSkipClicked: function() { -// sessionStorage.setItem('upgradeSkipped', true); -// this.setState({ -// modal: null, -// }); -// }, -// alertError: function(error) { -// var errorInfoList = []; -// for (let key of Object.keys(error)) { -// let val = typeof error[key] == 'string' ? error[key] : JSON.stringify(error[key]); -// let label = this._error_key_labels[key]; -// errorInfoList.push(
    • {label}: {val}
    • ); -// } -// -// this.setState({ -// modal: 'error', -// errorInfo:
        {errorInfoList}
      , -// }); -// }, -// getContentAndAddress: function() -// { -// switch(this.state.viewingPage) -// { -// case 'search': -// return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', ]; -// case 'settings': -// return ["Settings", "icon-gear", ]; -// case 'help': -// return ["Help", "icon-question", ]; -// case 'report': -// return ['Report an Issue', 'icon-file', ]; -// case 'downloaded': -// return ["Downloads & Purchases", "icon-folder", ]; -// case 'published': -// return ["Publishes", "icon-folder", ]; -// case 'start': -// return ["Start", "icon-file", ]; -// case 'rewards': -// return ["Rewards", "icon-bank", ]; -// case 'wallet': -// case 'send': -// case 'receive': -// return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", ] -// case 'show': -// return [lbryuri.normalize(this.state.pageArgs), "icon-file", ]; -// case 'publish': -// return ["Publish", "icon-upload", ]; -// case 'developer': -// return ["Developer", "icon-file", ]; -// case 'discover': -// default: -// return ["Home", "icon-home", ]; -// } -// }, -// render: function() { -// let [address, wunderBarIcon, mainContent] = this.getContentAndAddress(); -// -// lbry.setTitle(address); -// -// if (this._storeHistoryOfNextRender) { -// this._storeHistoryOfNextRender = false; -// history.pushState({}, document.title, this.state.appUrl); -// } -// -// return ( -// this._fullScreenPages.includes(this.state.viewingPage) ? -// mainContent : -//
      -//
      -//
      -// {mainContent} -//
      -// -// Your version of LBRY is out of date and may be unreliable or insecure. -// -// -// Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}%` : null} -// -// {this.state.downloadComplete ? ( -//
      -//
      -//

      Click "Begin Upgrade" to start the upgrade process.

      -//

      The app will close, and you will be prompted to install the latest version of LBRY.

      -//

      After the install is complete, please reopen the app.

      -//
      -// ) : null } -//
      -// {this.state.downloadComplete -// ? -// : null} -// -//
      -//
      -// -//

      Error

      -// -//
      -//
      -//

      We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.

      -//
      -//
      -//
      -// ); \ No newline at end of file +module.exports = app; \ No newline at end of file diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index 0920e51d8..95ca69761 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -10,8 +10,6 @@ import { } from 'selectors/app' import { doCheckUpgradeAvailable, - doOpenDrawer, - doCloseDrawer, doOpenModal, doCloseModal, doSearch, @@ -21,15 +19,12 @@ import App from './view' const select = (state) => ({ currentPage: selectCurrentPage(state), modal: selectCurrentModal(state), - drawerOpen: selectDrawerOpen(state), headerLinks: selectHeaderLinks(state), searchTerm: selectSearchTerm(state) }) const perform = (dispatch) => ({ checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), - openDrawer: () => dispatch(doOpenDrawer()), - closeDrawer: () => dispatch(doCloseDrawer()), openModal: () => dispatch(doOpenModal()), closeModal: () => dispatch(doCloseModal()), }) diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 944547044..92cb7d662 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -1,26 +1,13 @@ import React from 'react' - -import lbry from 'lbry.js'; import Router from 'component/router' import Header from 'component/header'; -import {Modal, ExpandableModal} from 'component/modal.js'; import ErrorModal from 'component/errorModal' import DownloadingModal from 'component/downloadingModal' import UpgradeModal from 'component/upgradeModal' -import Link from 'component/link'; import {Line} from 'rc-progress'; -const App = React.createClass({ - // Temporary workaround since electron-dl throws errors when you try to get the filename - getViewingPageAndArgs: function(address) { - // For now, routes are in format ?page or ?page=args - let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/); - return { - viewingPage: viewingPage, - pageArgs: pageArgs === undefined ? null : pageArgs - }; - }, - componentWillMount: function() { +class App extends React.Component { + componentWillMount() { document.addEventListener('unhandledError', (event) => { this.props.alertError(event.detail); }); @@ -28,24 +15,20 @@ const App = React.createClass({ if (!this.props.upgradeSkipped) { this.props.checkUpgradeAvailable() } - }, - render: function() { + } + + render() { const { currentPage, - openDrawer, - closeDrawer, - openModal, - closeModal, modal, - drawerOpen, headerLinks, - search, searchTerm, } = this.props const searchQuery = (currentPage == 'discover' && searchTerm ? searchTerm : '') - return
      -
      + return
      +
      { alert('header search'); }} + onSubmit={() => { alert('header submit'); }} links={headerLinks} />
      @@ -54,6 +37,6 @@ const App = React.createClass({ {modal == 'error' && }
      } -}); +} export default App diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index 2575d2e9c..261b065b1 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -2,7 +2,8 @@ import React from "react"; import lbryio from "../lbryio.js"; import Modal from "./modal.js"; import ModalPage from "./modal-page.js"; -import {Link, RewardLink} from "../component/link"; +import Link from "component/link" +import {RewardLink} from 'component/reward-link'; import {FormRow} from "../component/form.js"; import {CreditAmount, Address} from "../component/common.js"; import {getLocal, getSession, setSession, setLocal} from '../utils.js'; diff --git a/ui/js/component/drawer/index.jsx b/ui/js/component/drawer/index.jsx deleted file mode 100644 index 3c4c524b0..000000000 --- a/ui/js/component/drawer/index.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' -import { - connect -} from 'react-redux' -import Drawer from './view' -import { - doNavigate, - doCloseDrawer, - doLogoClick, -} from 'actions/app' -import { - doUpdateBalance, -} from 'actions/wallet' -import { - selectCurrentPage, -} from 'selectors/app' -import { - selectBalance, -} from 'selectors/wallet' - -const select = (state) => ({ - currentPage: selectCurrentPage(state), - balance: selectBalance(state), -}) - -const perform = { - linkClick: doNavigate, - logoClick: doLogoClick, - closeDrawerClick: doCloseDrawer, - updateBalance: doUpdateBalance, -} - -export default connect(select, perform)(Drawer) diff --git a/ui/js/component/drawer/view.jsx b/ui/js/component/drawer/view.jsx deleted file mode 100644 index da65cd523..000000000 --- a/ui/js/component/drawer/view.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import lbry from 'lbry.js'; -import React from 'react'; -import Link from 'component/link'; - -const DrawerItem = (props) => { - const { - currentPage, - href, - subPages, - badge, - label, - linkClick, - icon, - } = props - const isSelected = ( - currentPage == href.substr(0) || - (subPages && subPages.indexOf(currentPage) != -1) - ) - - return linkClick(href)} className={ 'drawer-item ' + (isSelected ? 'drawer-item-selected' : '') } /> -} - -var drawerImageStyle = { //@TODO: remove this, img should be properly scaled once size is settled - height: '36px' -}; - -class Drawer extends React.Component { - constructor(props) { - super(props) - this._balanceSubscribeId = null - } - - componentDidMount() { - const { updateBalance } = this.props - - this._balanceSubscribeId = lbry.balanceSubscribe((balance) => { - updateBalance(balance) - }); - } - componentWillUnmount() { - if (this._balanceSubscribeId) { - lbry.balanceUnsubscribe(this._balanceSubscribeId) - } - } - - render() { - const { - closeDrawerClick, - logoClick, - balance, - } = this.props - - return() - } -} - -export default Drawer; diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js index 44efdc355..2a10b5e81 100644 --- a/ui/js/component/header/index.js +++ b/ui/js/component/header/index.js @@ -3,31 +3,21 @@ import { connect } from 'react-redux' import { - selectCurrentPage, - selectHeaderLinks, - selectPageTitle, -} from 'selectors/app' + selectBalance +} from 'selectors/wallet' import { doNavigate, + doHistoryBack, } from 'actions/app' -import { - doSearchContent, - doActivateSearch, - doDeactivateSearch, -} from 'actions/search' import Header from './view' const select = (state) => ({ - currentPage: selectCurrentPage(state), - subLinks: selectHeaderLinks(state), - pageTitle: selectPageTitle(state), + balance: lbry.formatCredits(selectBalance(state), 1) }) const perform = (dispatch) => ({ navigate: (path) => dispatch(doNavigate(path)), - search: (query) => dispatch(doSearchContent(query)), - activateSearch: () => dispatch(doActivateSearch()), - deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 50), + back: () => dispatch(doHistoryBack()), }) export default connect(select, perform)(Header) diff --git a/ui/js/component/header/view.jsx b/ui/js/component/header/view.jsx index 92a45cad9..0707b7e10 100644 --- a/ui/js/component/header/view.jsx +++ b/ui/js/component/header/view.jsx @@ -1,193 +1,37 @@ import React from 'react'; -import lbryuri from 'lbryuri.js'; -import {Icon, CreditAmount} from 'component/common.js'; import Link from 'component/link'; +import WunderBar from 'component/wunderbar'; -let Header = React.createClass({ - _balanceSubscribeId: null, - _isMounted: false, - - propTypes: { - onSearch: React.PropTypes.func.isRequired, - onSubmit: React.PropTypes.func.isRequired - }, - - getInitialState: function() { - return { - balance: 0 - }; - }, - componentDidMount: function() { - this._isMounted = true; - this._balanceSubscribeId = lbry.balanceSubscribe((balance) => { - if (this._isMounted) { - this.setState({balance: balance}); - } - }); - }, - componentWillUnmount: function() { - this._isMounted = false; - if (this._balanceSubscribeId) { - lbry.balanceUnsubscribe(this._balanceSubscribeId) - } - }, - render: function() { - return - } -}); - -class WunderBar extends React.PureComponent { - static propTypes = { - onSearch: React.PropTypes.func.isRequired, - onSubmit: React.PropTypes.func.isRequired - } - - constructor(props) { - super(props); - this._userTypingTimer = null; - this._input = null; - this._stateBeforeSearch = null; - this._resetOnNextBlur = true; - this.onChange = this.onChange.bind(this); - this.onFocus = this.onFocus.bind(this); - this.onBlur = this.onBlur.bind(this); - this.onKeyPress = this.onKeyPress.bind(this); - this.onReceiveRef = this.onReceiveRef.bind(this); - this.state = { - address: this.props.address, - icon: this.props.icon - }; - } - - componentWillUnmount() { - if (this.userTypingTimer) { - clearTimeout(this._userTypingTimer); - } - } - - onChange(event) { - - if (this._userTypingTimer) - { - clearTimeout(this._userTypingTimer); - } - - this.setState({ address: event.target.value }) - - let searchTerm = event.target.value; - - this._userTypingTimer = setTimeout(() => { - this._resetOnNextBlur = false; - this.props.onSearch(searchTerm); - }, 800); // 800ms delay, tweak for faster/slower - } +export const Header = (props) => { + const { + balance, + back, + navigate + } = props - componentWillReceiveProps(nextProps) { - if (nextProps.viewingPage !== this.props.viewingPage || nextProps.address != this.props.address) { - this.setState({ address: nextProps.address, icon: nextProps.icon }); - } - } - - onFocus() { - this._stateBeforeSearch = this.state; - let newState = { - icon: "icon-search", - isActive: true - } - - this._focusPending = true; - //below is hacking, improved when we have proper routing - if (!this.state.address.startsWith('lbry://') && this.state.icon !== "icon-search") //onFocus, if they are not on an exact URL or a search page, clear the bar - { - newState.address = ''; - } - this.setState(newState); - } - - onBlur() { - let commonState = {isActive: false}; - if (this._resetOnNextBlur) { - this.setState(Object.assign({}, this._stateBeforeSearch, commonState)); - this._input.value = this.state.address; - } else { - this._resetOnNextBlur = true; - this._stateBeforeSearch = this.state; - this.setState(commonState); - } - } - - componentDidUpdate() { - this._input.value = this.state.address; - if (this._input && this._focusPending) { - this._input.select(); - this._focusPending = false; - } - } - - onKeyPress(event) { - if (event.charCode == 13 && this._input.value) { - - let uri = null, - method = "onSubmit"; - - this._resetOnNextBlur = false; - clearTimeout(this._userTypingTimer); - - try { - uri = lbryuri.normalize(this._input.value); - this.setState({ value: uri }); - } catch (error) { //then it's not a valid URL, so let's search - uri = this._input.value; - method = "onSearch"; - } - - this.props[method](uri); - this._input.blur(); - } - } - - onReceiveRef(ref) { - this._input = ref; - } - - render() { - return ( -
      - {this.state.icon ? : '' } - -
      - ); - } + return } export default Header; diff --git a/ui/js/component/navMain/index.jsx b/ui/js/component/navMain/index.jsx new file mode 100644 index 000000000..2c27c39a5 --- /dev/null +++ b/ui/js/component/navMain/index.jsx @@ -0,0 +1,21 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import { + selectCurrentPage, +} from 'selectors/app' +import { + doNavigate, +} from 'actions/app' +import NavMain from './view' + +const select = (state) => ({ + currentPage: selectCurrentPage(state) +}) + +const perform = (dispatch) => ({ + navigate: (path) => dispatch(doNavigate(path)) +}) + +export default connect(select, perform)(NavMain) diff --git a/ui/js/component/navMain/view.jsx b/ui/js/component/navMain/view.jsx new file mode 100644 index 000000000..f10f07a6b --- /dev/null +++ b/ui/js/component/navMain/view.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +export const NavMain = (props) => { + const { + links, + currentPage, + navigate, + } = props + + return ( + + ) +} + +export default NavMain \ No newline at end of file diff --git a/ui/js/component/navSettings/index.jsx b/ui/js/component/navSettings/index.jsx new file mode 100644 index 000000000..9fb7efb4d --- /dev/null +++ b/ui/js/component/navSettings/index.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import NavSettings from './view' + +export default connect(null, null)(NavSettings) diff --git a/ui/js/component/navSettings/view.jsx b/ui/js/component/navSettings/view.jsx new file mode 100644 index 000000000..099e2acae --- /dev/null +++ b/ui/js/component/navSettings/view.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import NavMain from 'component/navMain' + +export const NavSettings = () => { + return ; +} + +export default NavSettings \ No newline at end of file diff --git a/ui/js/component/navWallet/index.jsx b/ui/js/component/navWallet/index.jsx new file mode 100644 index 000000000..4ace4d388 --- /dev/null +++ b/ui/js/component/navWallet/index.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import NavWallet from './view' + +export default connect(null, null)(NavWallet) diff --git a/ui/js/component/navWallet/view.jsx b/ui/js/component/navWallet/view.jsx new file mode 100644 index 000000000..c076e9c67 --- /dev/null +++ b/ui/js/component/navWallet/view.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import NavMain from 'component/navMain' + +export const NavWallet = () => { + return +} + +export default NavWallet \ No newline at end of file diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index 78246c0b6..f8b4c300b 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -4,13 +4,15 @@ import HelpPage from 'page/help'; import ReportPage from 'page/report.js'; import StartPage from 'page/start.js'; import WalletPage from 'page/wallet'; -import ShowPage from 'page/showPage'; +import FilePage from 'page/filePage'; import PublishPage from 'page/publish'; import DiscoverPage from 'page/discover'; import SplashScreen from 'component/splash.js'; import DeveloperPage from 'page/developer.js'; +import RewardsPage from 'page/rewards.js'; import FileListDownloaded from 'page/fileListDownloaded' import FileListPublished from 'page/fileListPublished' +import ChannelPage from 'page/channel' const route = (page, routesMap) => { const component = routesMap[page] @@ -34,10 +36,12 @@ const Router = (props) => { 'wallet': , 'send': , 'receive': , - 'show': , + 'show': , + 'channel': , 'publish': , 'developer': , 'discover': , + 'rewards': , }) } diff --git a/ui/js/component/sub-header.js b/ui/js/component/sub-header.js deleted file mode 100644 index 061b0e942..000000000 --- a/ui/js/component/sub-header.js +++ /dev/null @@ -1,22 +0,0 @@ -const SubHeader = (props) => { - const { - subLinks, - currentPage, - navigate, - } = props - - const links = [], - viewingUrl = '?' + this.props.viewingPage; - - for(let link of Object.keys(subLinks)) { - links.push( - navigate(link)} key={link} className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected' }> - {subLinks[link]} - - ); - } - - return ( - - ) -} \ No newline at end of file diff --git a/ui/js/component/wallet-nav.js b/ui/js/component/wallet-nav.js deleted file mode 100644 index 51cd9278b..000000000 --- a/ui/js/component/wallet-nav.js +++ /dev/null @@ -1,12 +0,0 @@ -import {SubHeader} from '../component/sub-header.js'; - -export let WalletNav = React.createClass({ - render: function () { - return ; - } -}); \ No newline at end of file diff --git a/ui/js/component/wunderbar/index.js b/ui/js/component/wunderbar/index.js new file mode 100644 index 000000000..85551e637 --- /dev/null +++ b/ui/js/component/wunderbar/index.js @@ -0,0 +1,32 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + selectWunderBarAddress, + selectWunderBarIcon +} from 'selectors/app' +import { + doNavigate, +} from 'actions/app' +import { + doSearchContent, + doActivateSearch, + doDeactivateSearch, +} from 'actions/search' +import Wunderbar from './view' + +const select = (state) => ({ + address: selectWunderBarAddress(state), + icon: selectWunderBarIcon(state) +}) + +const perform = (dispatch) => ({ + // navigate: (path) => dispatch(doNavigate(path)), + onSearch: (query) => dispatch(doSearchContent(query)), + onSubmit: (query) => dispatch(doSearchContent(query)), + // activateSearch: () => dispatch(doActivateSearch()), + // deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 50), +}) + +export default connect(select, perform)(Wunderbar) diff --git a/ui/js/component/wunderbar/view.jsx b/ui/js/component/wunderbar/view.jsx new file mode 100644 index 000000000..821e9bfc0 --- /dev/null +++ b/ui/js/component/wunderbar/view.jsx @@ -0,0 +1,136 @@ +import React from 'react'; +import lbryuri from 'lbryuri.js'; +import {Icon} from 'component/common.js'; + +class WunderBar extends React.PureComponent { + static propTypes = { + onSearch: React.PropTypes.func.isRequired, + onSubmit: React.PropTypes.func.isRequired + } + + constructor(props) { + super(props); + this._userTypingTimer = null; + this._input = null; + this._stateBeforeSearch = null; + this._resetOnNextBlur = true; + this.onChange = this.onChange.bind(this); + this.onFocus = this.onFocus.bind(this); + this.onBlur = this.onBlur.bind(this); + this.onKeyPress = this.onKeyPress.bind(this); + this.onReceiveRef = this.onReceiveRef.bind(this); + this.state = { + address: this.props.address, + icon: this.props.icon + }; + } + + componentWillUnmount() { + if (this.userTypingTimer) { + clearTimeout(this._userTypingTimer); + } + } + + onChange(event) { + + if (this._userTypingTimer) + { + clearTimeout(this._userTypingTimer); + } + + this.setState({ address: event.target.value }) + + let searchTerm = event.target.value; + + this._userTypingTimer = setTimeout(() => { + this._resetOnNextBlur = false; + this.props.onSearch(searchTerm); + }, 800); // 800ms delay, tweak for faster/slower + } + + componentWillReceiveProps(nextProps) { + if (nextProps.viewingPage !== this.props.viewingPage || nextProps.address != this.props.address) { + this.setState({ address: nextProps.address, icon: nextProps.icon }); + } + } + + onFocus() { + this._stateBeforeSearch = this.state; + let newState = { + icon: "icon-search", + isActive: true + } + + this._focusPending = true; + //below is hacking, improved when we have proper routing + if (!this.state.address.startsWith('lbry://') && this.state.icon !== "icon-search") //onFocus, if they are not on an exact URL or a search page, clear the bar + { + newState.address = ''; + } + this.setState(newState); + } + + onBlur() { + let commonState = {isActive: false}; + if (this._resetOnNextBlur) { + this.setState(Object.assign({}, this._stateBeforeSearch, commonState)); + this._input.value = this.state.address; + } else { + this._resetOnNextBlur = true; + this._stateBeforeSearch = this.state; + this.setState(commonState); + } + } + + componentDidUpdate() { + this._input.value = this.state.address; + if (this._input && this._focusPending) { + this._input.select(); + this._focusPending = false; + } + } + + onKeyPress(event) { + if (event.charCode == 13 && this._input.value) { + + let uri = null, + method = "onSubmit"; + + this._resetOnNextBlur = false; + clearTimeout(this._userTypingTimer); + + try { + uri = lbryuri.normalize(this._input.value); + this.setState({ value: uri }); + } catch (error) { //then it's not a valid URL, so let's search + uri = this._input.value; + method = "onSearch"; + } + + this.props[method](uri); + this._input.blur(); + } + } + + onReceiveRef(ref) { + this._input = ref; + } + + render() { + return ( +
      + {this.state.icon ? : '' } + +
      + ); + } +} + +export default WunderBar; diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 80eecab79..14f6c7b88 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -1,9 +1,7 @@ export const NAVIGATE = 'NAVIGATE' export const OPEN_MODAL = 'OPEN_MODAL' export const CLOSE_MODAL = 'CLOSE_MODAL' - -export const OPEN_DRAWER = 'OPEN_DRAWER' -export const CLOSE_DRAWER = 'CLOSE_DRAWER' +export const HISTORY_BACK = 'HISTORY_BACK' export const DAEMON_READY = 'DAEMON_READY' diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 5fd96595a..b5238764e 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -155,10 +155,6 @@ lbry.checkFirstRun = function(callback) { lbry.call('is_first_run', {}, callback); } -lbry.getNewAddress = function(callback) { - lbry.call('wallet_new_address', {}, callback); -} - lbry.getUnusedAddress = function(callback) { lbry.call('wallet_unused_address', {}, callback); } @@ -174,7 +170,7 @@ lbry.getDaemonSettings = function(callback) { lbry.setDaemonSettings = function(settings, callback) { lbry.call('set_settings', settings, callback); } - + lbry.setDaemonSetting = function(setting, value, callback) { var setSettingsArgs = {}; setSettingsArgs[setting] = value; @@ -640,19 +636,19 @@ lbry.claim_list_mine = function(params={}) { }); } +const claimCacheKey = 'resolve_claim_cache'; +lbry._claimCache = getLocal(claimCacheKey, {}); lbry.resolve = function(params={}) { - const claimCacheKey = 'resolve_claim_cache', - claimCache = getSession(claimCacheKey, {}) return new Promise((resolve, reject) => { if (!params.uri) { throw "Resolve has hacked cache on top of it that requires a URI" } - if (params.uri && claimCache[params.uri] !== undefined) { - resolve(claimCache[params.uri]); + if (params.uri && lbry._claimCache[params.uri] !== undefined) { + resolve(lbry._claimCache[params.uri]); } else { lbry.call('resolve', params, function(data) { - claimCache[params.uri] = data; - setSession(claimCacheKey, claimCache) + lbry._claimCache[params.uri] = data; + setLocal(claimCacheKey, lbry._claimCache) resolve(data) }, reject) } @@ -660,20 +656,18 @@ lbry.resolve = function(params={}) { } // Adds caching. +lbry._settingsPromise = null; lbry.settings_get = function(params={}) { - return new Promise((resolve, reject) => { - if (params.allow_cached) { - const cached = getSession('settings'); - if (cached) { - return resolve(cached); - } - } - + if (params.allow_cached && lbry._settingsPromise) { + return lbry._settingsPromise; + } + lbry._settingsPromise = new Promise((resolve, reject) => { lbry.call('settings_get', {}, (settings) => { setSession('settings', settings); resolve(settings); - }); + }, reject); }); + return lbry._settingsPromise; } // lbry.get = function(params={}) { diff --git a/ui/js/page/channel/index.js b/ui/js/page/channel/index.js new file mode 100644 index 000000000..50100b244 --- /dev/null +++ b/ui/js/page/channel/index.js @@ -0,0 +1,17 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + selectCurrentUriTitle, +} from 'selectors/app' +import ChannelPage from './view' + +const select = (state) => ({ + title: selectCurrentUriTitle(state) +}) + +const perform = (dispatch) => ({ +}) + +export default connect(select, perform)(ChannelPage) diff --git a/ui/js/page/channel/view.jsx b/ui/js/page/channel/view.jsx new file mode 100644 index 000000000..587c7237e --- /dev/null +++ b/ui/js/page/channel/view.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const ChannelPage = (props) => { + const { + title + } = props + + return
      +
      +
      +

      {title}

      +
      +
      +

      + This channel page is a stub. +

      +
      +
      +
      +} + +export default ChannelPage; diff --git a/ui/js/page/discover/index.js b/ui/js/page/discover/index.js index bb2d97e49..5a1c14328 100644 --- a/ui/js/page/discover/index.js +++ b/ui/js/page/discover/index.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux' import { - selectFeaturedContentByCategory + selectFeaturedUris } from 'selectors/content' import { doSearchContent, @@ -17,11 +17,7 @@ import { import DiscoverPage from './view' const select = (state) => ({ - featuredContentByCategory: selectFeaturedContentByCategory(state), - isSearching: selectIsSearching(state), - query: selectSearchQuery(state), - results: selectCurrentSearchResults(state), - searchActive: selectSearchActivated(state), + featuredUris: selectFeaturedUris(state), }) const perform = (dispatch) => ({ diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index 5c4c4e511..d476f8780 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -22,44 +22,65 @@ const FeaturedCategory = (props) => {
      } -let DiscoverPage = React.createClass({ - getInitialState: function() { - return { - featuredUris: {}, - failed: false - }; - }, - componentWillMount: function() { - lbryio.call('discover', 'list', { version: "early-access" } ).then(({Categories, Uris}) => { - let featuredUris = {} - Categories.forEach((category) => { - if (Uris[category] && Uris[category].length) { - featuredUris[category] = Uris[category] - } - }) - this.setState({ featuredUris: featuredUris }); - }, () => { - this.setState({ - failed: true - }) - }); - }, - render: function() { - return
      { - this.state.failed ? -
      Failed to load landing content.
      : -
      - { - Object.keys(this.state.featuredUris).map((category) => { - return this.state.featuredUris[category].length ? - : - ''; - }) - } -
      - }
      ; - } -}) +const DiscoverPage = (props) => { + const { + featuredUris + } = props + + return
      { + Object.keys(featuredUris).length === 0 ? +
      Failed to load landing content.
      : +
      + { + Object.keys(featuredUris).map((category) => { + return featuredUris[category].length ? + : + ''; + }) + } +
      + }
      +} + +// +// let DiscoverPage = React.createClass({ +// getInitialState: function() { +// return { +// featuredUris: {}, +// failed: false +// }; +// }, +// componentWillMount: function() { +// lbryio.call('discover', 'list', { version: "early-access" } ).then(({Categories, Uris}) => { +// let featuredUris = {} +// Categories.forEach((category) => { +// if (Uris[category] && Uris[category].length) { +// featuredUris[category] = Uris[category] +// } +// }) +// this.setState({ featuredUris: featuredUris }); +// }, () => { +// this.setState({ +// failed: true +// }) +// }); +// }, +// render: function() { +// return
      { +// this.state.failed ? +//
      Failed to load landing content.
      : +//
      +// { +// Object.keys(this.state.featuredUris).map((category) => { +// return this.state.featuredUris[category].length ? +// : +// ''; +// }) +// } +//
      +// }
      ; +// } +// }) // const DiscoverPage = (props) => { // const { diff --git a/ui/js/page/showPage/index.js b/ui/js/page/filePage/index.js similarity index 89% rename from ui/js/page/showPage/index.js rename to ui/js/page/filePage/index.js index 4ba0ba729..d553517c2 100644 --- a/ui/js/page/showPage/index.js +++ b/ui/js/page/filePage/index.js @@ -17,7 +17,7 @@ import { import { selectCurrentUriCostInfo, } from 'selectors/cost_info' -import ShowPage from './view' +import FilePage from './view' const select = (state) => ({ claim: selectCurrentUriClaim(state), @@ -30,4 +30,4 @@ const select = (state) => ({ const perform = (dispatch) => ({ }) -export default connect(select, perform)(ShowPage) +export default connect(select, perform)(FilePage) diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/filePage/view.jsx similarity index 69% rename from ui/js/page/showPage/view.jsx rename to ui/js/page/filePage/view.jsx index 8e46b619c..6bb9099f6 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/filePage/view.jsx @@ -16,27 +16,20 @@ import UriIndicator from 'component/uriIndicator'; const FormatItem = (props) => { const { contentType, - metadata, metadata: { - thumbnail, author, - title, - description, language, license, - }, - cost, - uri, - outpoint, - costIncludesData, + } } = props + const mediaType = lbry.getMediaType(contentType); return ( - + @@ -52,23 +45,6 @@ const FormatItem = (props) => { ) } -let ChannelPage = React.createClass({ - render: function() { - return
      -
      -
      -

      {this.props.title}

      -
      -
      -

      - This channel page is a stub. -

      -
      -
      -
      - } -}); - let FilePage = React.createClass({ _isMounted: false, @@ -267,8 +243,6 @@ let ShowPage = React.createClass({ } ; - } else if (this.state.claimType == "channel") { - innerContent = } else { let channelUriObj = lbryuri.parse(this._uri) delete channelUriObj.path; @@ -289,4 +263,105 @@ let ShowPage = React.createClass({ } }); -export default ShowPage; +export default FilePage; + +// +// const ShowPage = (props) => { +// const { +// claim, +// navigate, +// claim: { +// txid, +// nout, +// has_signature: hasSignature, +// signature_is_valid: signatureIsValid, +// value, +// value: { +// stream, +// stream: { +// metadata, +// source, +// metadata: { +// title, +// } = {}, +// source: { +// contentType, +// } = {}, +// } = {}, +// } = {}, +// }, +// uri, +// isDownloaded, +// fileInfo, +// costInfo, +// costInfo: { +// cost, +// includesData: costIncludesData, +// } = {}, +// } = props +// +// const outpoint = txid + ':' + nout; +// const uriLookupComplete = !!claim && Object.keys(claim).length +// +// if (props.isFailed) { +// return ( +//
      +//
      +//
      +//

      {uri}

      +//
      +//
      +//

      +// This location is not yet in use. +// { ' ' } +// navigate('publish')} label="Put something here" />. +//

      +//
      +//
      +//
      +// ) +// } +// +// return ( +//
      +//
      +// { contentType && contentType.startsWith('video/') ? +//
      +//
      +//
      +//
      +// {isDownloaded === false +// ? +// : null} +//

      {title}

      +// { uriLookupComplete ? +//
      +//
      +// +//
      +//
      +// +//
      +//
      : '' } +//
      +// { uriLookupComplete ? +//
      +//
      +// {metadata.description} +//
      +//
      +// :
      } +//
      +// { metadata ? +//
      +// +//
      : '' } +//
      +// +//
      +//
      +//
      +// ) +// } \ No newline at end of file diff --git a/ui/js/page/help/view.jsx b/ui/js/page/help/view.jsx index b5fe61787..095435e20 100644 --- a/ui/js/page/help/view.jsx +++ b/ui/js/page/help/view.jsx @@ -2,6 +2,7 @@ import React from 'react'; import lbry from 'lbry.js'; import Link from 'component/link'; +import NavSettings from 'component/navSettings'; import {version as uiVersion} from 'json!../../../package.json'; var HelpPage = React.createClass({ @@ -49,58 +50,71 @@ var HelpPage = React.createClass({ return (
      +
      -

      Read the FAQ

      -

      Our FAQ answers many common questions.

      -

      +
      +

      Read the FAQ

      +
      +
      +

      Our FAQ answers many common questions.

      +

      +
      -

      Get Live Help

      -

      - Live help is available most hours in the #help channel of our Slack chat room. -

      -

      - -

      +
      +

      Get Live Help

      +
      +
      +

      + Live help is available most hours in the #help channel of our Slack chat room. +

      +

      + +

      +
      -

      Report a Bug

      -

      Did you find something wrong?

      -

      -
      Thanks! LBRY is made by its users.
      +

      Report a Bug

      +
      +

      Did you find something wrong?

      +

      +
      Thanks! LBRY is made by its users.
      +
      {!ver ? null : -
      -

      About

      - {ver.lbrynet_update_available || ver.lbryum_update_available ? +
      +

      About

      +
      + {ver.lbrynet_update_available || ver.lbryum_update_available ?

      A newer version of LBRY is available.

      - :

      Your copy of LBRY is up to date.

      - } -
      Content-Type{contentType}Content-Type{mediaType}
      Author{author}
      - - - - - - - - - - - - - - - - - - - - - - -
      daemon (lbrynet){ver.lbrynet_version}
      wallet (lbryum){ver.lbryum_version}
      interface{uiVersion}
      Platform{platform}
      Installation ID{this.state.lbryId}
      -
      + :

      Your copy of LBRY is up to date.

      + } + + + + + + + + + + + + + + + + + + + + + + + +
      daemon (lbrynet){ver.lbrynet_version}
      wallet (lbryum){ver.lbryum_version}
      interface{uiVersion}
      Platform{platform}
      Installation ID{this.state.lbryId}
      +
      + }
      ); diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index 2419ec97d..5b9bb8352 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -1,10 +1,7 @@ import React from 'react'; -import lbry from 'lbry'; import lbryio from 'lbryio'; import {CreditAmount, Icon} from 'component/common.js'; -import rewards from 'rewards'; -import Modal from 'component/modal'; -import {WalletNav} from 'component/wallet-nav ewar'; +import NavWallet from 'component/navWallet'; import {RewardLink} from 'component/reward-link'; const RewardTile = React.createClass({ @@ -36,7 +33,7 @@ const RewardTile = React.createClass({ } }); -var RewardsPage = React.createClass({ +export let RewardsPage = React.createClass({ componentWillMount: function() { this.loadRewards() }, @@ -58,7 +55,7 @@ var RewardsPage = React.createClass({ render: function() { return (
      - +
      {!this.state.userRewards ? (this.state.failed ?
      Failed to load rewards.
      : '') diff --git a/ui/js/page/settings.js b/ui/js/page/settings.js index 09f45a9c5..61a9ae879 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings.js @@ -1,17 +1,8 @@ import React from 'react'; import {FormField, FormRow} from '../component/form.js'; -import {SubHeader} from '../component/sub-header.js'; +import NavSettings from 'component/navSettings'; import lbry from '../lbry.js'; -export let SettingsNav = React.createClass({ - render: function() { - return ; - } -}); - var SettingsPage = React.createClass({ _onSettingSaveSuccess: function() { // This is bad. @@ -100,7 +91,7 @@ var SettingsPage = React.createClass({ */ return (
      - +

      Download Directory

      diff --git a/ui/js/page/wallet/view.jsx b/ui/js/page/wallet/view.jsx index e1c458f68..cebe970ed 100644 --- a/ui/js/page/wallet/view.jsx +++ b/ui/js/page/wallet/view.jsx @@ -2,6 +2,7 @@ import React from 'react'; import lbry from 'lbry.js'; import Link from 'component/link'; import Modal from 'component/modal'; +import NavWallet from 'component/navWallet'; import { FormField, FormRow @@ -247,6 +248,7 @@ const WalletPage = (props) => { return (
      +

      Balance

      diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index 43ae54090..b370ed22c 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -6,7 +6,6 @@ const defaultState = { isLoaded: false, currentPath: 'discover', platform: process.platform, - drawerOpen: sessionStorage.getItem('drawerOpen') || true, upgradeSkipped: sessionStorage.getItem('upgradeSkipped'), daemonReady: false, platform: window.navigator.platform, @@ -84,20 +83,6 @@ reducers[types.CLOSE_MODAL] = function(state, action) { }) } -reducers[types.OPEN_DRAWER] = function(state, action) { - sessionStorage.setItem('drawerOpen', false) - return Object.assign({}, state, { - drawerOpen: true - }) -} - -reducers[types.CLOSE_DRAWER] = function(state, action) { - sessionStorage.setItem('drawerOpen', false) - return Object.assign({}, state, { - drawerOpen: false - }) -} - reducers[types.UPGRADE_DOWNLOAD_PROGRESSED] = function(state, action) { return Object.assign({}, state, { downloadProgress: action.data.percent diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index 6c36daf0c..4f9d54594 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -12,15 +12,15 @@ reducers[types.FETCH_FEATURED_CONTENT_STARTED] = function(state, action) { reducers[types.FETCH_FEATURED_CONTENT_COMPLETED] = function(state, action) { const { - uris + uris, + success } = action.data - const newFeaturedContent = Object.assign({}, state.featuredContent, { - byCategory: uris, - }) + return Object.assign({}, state, { fetchingFeaturedContent: false, - featuredContent: newFeaturedContent + fetchingFeaturedContentFailed: !success, + featuredUris: uris }) } diff --git a/ui/js/rewards.js b/ui/js/rewards.js index 399965db2..840c22c81 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -56,7 +56,7 @@ rewards.claimReward = function (type) { } return new Promise((resolve, reject) => { - lbry.wallet_new_address().then((address) => { + lbry.wallet_unused_address().then((address) => { const params = { reward_type: type, wallet_address: address, diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index e0d1a7a43..29ca4db02 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -1,4 +1,4 @@ -import { createSelector } from 'reselect' +import {createSelector} from 'reselect' export const _selectState = state => state.app || {} @@ -22,35 +22,98 @@ export const selectCurrentUri = createSelector( (path) => { if (path.match(/=/)) { return path.split('=')[1] - } else { + } + else { return undefined } } ) +export const selectCurrentUriTitle = createSelector( + _selectState, + (state) => "fix me" +) + export const selectPageTitle = createSelector( selectCurrentPage, selectCurrentUri, (page, uri) => { - switch(page) - { - case 'discover': - return 'Discover' + switch (page) { + case 'search': + return 'Search' + case 'settings': + return 'Settings' + case 'help': + return 'Help' + case 'report': + return 'Report' case 'wallet': case 'send': case 'receive': case 'rewards': - return 'Wallet' + return page.charAt(0).toUpperCase() + page.slice(1) + case 'show': + return lbryuri.normalize(page) case 'downloaded': - return 'My Files' + return 'Downloads & Purchases' case 'published': - return 'My Files' + return 'Publishes' + case 'start': + return 'Start' case 'publish': return 'Publish' case 'help': return 'Help' + case 'developer': + return 'Developer' + case 'discover': + return 'Home' default: - return 'LBRY'; + return ''; + } + } +) + +export const selectWunderBarAddress = createSelector( + selectPageTitle, + (title) => title +) + +export const selectWunderBarIcon = createSelector( + selectCurrentPage, + selectCurrentUri, + (page, uri) => { + switch (page) { + case 'search': + return 'icon-search' + case 'settings': + return 'icon-gear' + case 'help': + return 'icon-question' + case 'report': + return 'icon-file' + case 'downloaded': + return 'icon-folder' + case 'published': + return 'icon-folder' + case 'start': + return 'icon-file' + case 'rewards': + return 'icon-bank' + case 'wallet': + case 'send': + case 'receive': + return 'icon-bank' + case 'show': + return 'icon-file' + case 'publish': + return 'icon-upload' + case 'developer': + return 'icon-file' + case 'developer': + return 'icon-code' + case 'discover': + return 'icon-home' } } ) @@ -115,24 +178,18 @@ export const selectDownloadComplete = createSelector( (state) => state.upgradeDownloadCompleted ) -export const selectDrawerOpen = createSelector( - _selectState, - (state) => state.drawerOpen -) - export const selectHeaderLinks = createSelector( selectCurrentPage, (page) => { - switch(page) - { + switch (page) { case 'wallet': case 'send': case 'receive': case 'rewards': return { - 'wallet' : 'Overview', - 'send' : 'Send', - 'receive' : 'Receive', + 'wallet': 'Overview', + 'send': 'Send', + 'receive': 'Receive', 'rewards': 'Rewards', }; case 'downloaded': diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index 87b91182d..05e9f2ef9 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -7,26 +7,21 @@ import { export const _selectState = state => state.content || {} -export const selectFeaturedContent = createSelector( +export const selectFeaturedUris = createSelector( _selectState, - (state) => state.featuredContent || {} + (state) => state.featuredUris || {} ) -export const selectFeaturedContentByCategory = createSelector( - selectFeaturedContent, - (featuredContent) => featuredContent.byCategory || {} -) - -export const selectFetchingFeaturedContent = createSelector( +export const selectFetchingFeaturedUris = createSelector( _selectState, (state) => !!state.fetchingFeaturedContent ) -export const shouldFetchFeaturedContent = createSelector( +export const shouldFetchFeaturedUris = createSelector( selectDaemonReady, selectCurrentPage, - selectFetchingFeaturedContent, - selectFeaturedContentByCategory, + selectFetchingFeaturedUris, + selectFeaturedUris, (daemonReady, page, fetching, byCategory) => { if (!daemonReady) return false if (page != 'discover') return false diff --git a/ui/js/triggers.js b/ui/js/triggers.js index 41d0e0763..3d4633061 100644 --- a/ui/js/triggers.js +++ b/ui/js/triggers.js @@ -3,7 +3,7 @@ import { shouldGetReceiveAddress, } from 'selectors/wallet' import { - shouldFetchFeaturedContent, + shouldFetchFeaturedUris, shouldFetchDownloadedContent, shouldFetchPublishedContent, } from 'selectors/content' @@ -21,7 +21,7 @@ import { doGetNewAddress, } from 'actions/wallet' import { - doFetchFeaturedContent, + doFetchFeaturedUris, doFetchDownloadedContent, doFetchPublishedContent, } from 'actions/content' @@ -48,8 +48,8 @@ triggers.push({ }) triggers.push({ - selector: shouldFetchFeaturedContent, - action: doFetchFeaturedContent, + selector: shouldFetchFeaturedUris, + action: doFetchFeaturedUris, }) triggers.push({ diff --git a/ui/scss/component/_header.scss b/ui/scss/component/_header.scss index 0071f01f9..1343ddf86 100644 --- a/ui/scss/component/_header.scss +++ b/ui/scss/component/_header.scss @@ -60,11 +60,9 @@ nav.sub-header { text-transform: uppercase; padding: 0 0 $spacing-vertical; - &.sub-header--constrained { - max-width: $width-page-constrained; - margin-left: auto; - margin-right: auto; - } + max-width: $width-page-constrained; + margin-left: auto; + margin-right: auto; > a { $sub-header-selected-underline-height: 2px; -- 2.43.4 From d7f84caaa4f953cdbf6f91fce797dbeaa5732076 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 5 May 2017 11:28:30 +0700 Subject: [PATCH 49/56] Add webpack build notifications in dev --- ui/package.json | 1 + ui/webpack.dev.config.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/ui/package.json b/ui/package.json index a52366c8c..a8c1bf32c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -54,6 +54,7 @@ "node-sass": "^3.13.0", "webpack": "^1.13.3", "webpack-dev-server": "^2.4.4", + "webpack-notifier": "^1.5.0", "webpack-target-electron-renderer": "^0.4.0" } } diff --git a/ui/webpack.dev.config.js b/ui/webpack.dev.config.js index cb284e233..cc5372370 100644 --- a/ui/webpack.dev.config.js +++ b/ui/webpack.dev.config.js @@ -1,4 +1,6 @@ const path = require('path'); +const WebpackNotifierPlugin = require('webpack-notifier') + const appPath = path.resolve(__dirname, 'js'); const PATHS = { @@ -21,6 +23,9 @@ module.exports = { root: appPath, extensions: ['', '.js', '.jsx', '.css'], }, + plugins: [ + new WebpackNotifierPlugin(), + ], module: { preLoaders: [ { -- 2.43.4 From ac626ab9f9114a0663e7fc8899e27ad0e6a2c338 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 5 May 2017 12:10:37 +0700 Subject: [PATCH 50/56] File page working --- ui/js/component/link/view.js | 7 +- ui/js/page/filePage/view.jsx | 162 ++++++++++++++++------------------- 2 files changed, 79 insertions(+), 90 deletions(-) diff --git a/ui/js/component/link/view.js b/ui/js/component/link/view.js index 7b11e67d8..8d690d555 100644 --- a/ui/js/component/link/view.js +++ b/ui/js/component/link/view.js @@ -13,8 +13,9 @@ const Link = (props) => { button, hidden, disabled, + children, } = props - + const className = (props.className || '') + (!props.className && !props.button ? 'button-text' : '') + // Non-button links get the same look as text buttons (props.button ? ' button-block button-' + props.button + ' button-set-item' : '') + @@ -22,8 +23,8 @@ const Link = (props) => { let content; - if (props.children) { - content = this.props.children + if (children) { + content = children } else { content = ( diff --git a/ui/js/page/filePage/view.jsx b/ui/js/page/filePage/view.jsx index 6bb9099f6..f3b6ac859 100644 --- a/ui/js/page/filePage/view.jsx +++ b/ui/js/page/filePage/view.jsx @@ -45,98 +45,86 @@ const FormatItem = (props) => { ) } -let FilePage = React.createClass({ - _isMounted: false, +const FilePage = (props) => { + const { + claim, + navigate, + claim: { + txid, + nout, + has_signature: hasSignature, + signature_is_valid: signatureIsValid, + value, + value: { + stream, + stream: { + metadata, + source, + metadata: { + title, + } = {}, + source: { + contentType, + } = {}, + } = {}, + } = {}, + }, + uri, + isDownloaded, + fileInfo, + costInfo, + costInfo: { + cost, + includesData: costIncludesData, + } = {}, + } = props - propTypes: { - uri: React.PropTypes.string, - }, + const outpoint = txid + ':' + nout; + const uriLookupComplete = !!claim && Object.keys(claim).length - getInitialState: function() { - return { - cost: null, - costIncludesData: null, - isDownloaded: null, - }; - }, + const channelUriObj = lbryuri.parse(uri) + delete channelUriObj.path; + delete channelUriObj.contentName; + const channelUri = signatureIsValid && hasSignature && channelUriObj.isChannel ? lbryuri.build(channelUriObj, false) : null; + const uriIndicator = - componentWillUnmount: function() { - this._isMounted = false; - }, - - componentWillReceiveProps: function(nextProps) { - if (nextProps.outpoint != this.props.outpoint || nextProps.uri != this.props.uri) { - this.loadCostAndFileState(nextProps.uri, nextProps.outpoint); - } - }, - - componentWillMount: function() { - this._isMounted = true; - this.loadCostAndFileState(this.props.uri, this.props.outpoint); - }, - - loadCostAndFileState: function(uri, outpoint) { - lbry.file_list({outpoint: outpoint}).then((fileInfo) => { - if (this._isMounted) { - this.setState({ - isDownloaded: fileInfo.length > 0, - }); - } - }); - - lbry.getCostInfo(uri).then(({cost, includesData}) => { - if (this._isMounted) { - this.setState({ - cost: cost, - costIncludesData: includesData, - }); - } - }); - }, - - render: function() { - const metadata = this.props.metadata, - title = metadata ? this.props.metadata.title : this.props.uri, - uriIndicator = - - return ( -
      -
      - { this.props.contentType && this.props.contentType.startsWith('video/') ? -
      -
      -
      -
      - {isDownloaded === false - ? - : null} -

      {title}

      -
      - { this.props.channelUri ? - {uriIndicator} : - uriIndicator} -
      -
      -
      -
      -
      - {metadata.description} -
      + return ( +
      +
      + { contentType && contentType.startsWith('video/') ? +
      +
      +
      +
      + {isDownloaded === false + ? + : null} +

      {title}

      +
      + { channelUri ? + {uriIndicator} : + uriIndicator} +
      +
      +
      - { metadata ? -
      - -
      : '' } +
      + {metadata.description} +
      +
      + { metadata ?
      - -
      -
      -
      - ); - } -}); + +
      : '' } +
      + +
      +
      +
      + ) +} let ShowPage = React.createClass({ _uri: null, -- 2.43.4 From 494d885f3196c6c59b6e1aa653a7659d4393d40a Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 5 May 2017 12:28:28 +0700 Subject: [PATCH 51/56] Use SubHeader for all sub navigation --- ui/js/component/navMain/index.jsx | 21 -------------------- ui/js/component/navMain/view.jsx | 22 --------------------- ui/js/component/navSettings/index.jsx | 7 ------- ui/js/component/navSettings/view.jsx | 11 ----------- ui/js/component/navWallet/index.jsx | 7 ------- ui/js/component/navWallet/view.jsx | 13 ------------- ui/js/component/subHeader/index.js | 23 ++++++++++++++++++++++ ui/js/component/subHeader/view.jsx | 28 +++++++++++++++++++++++++++ ui/js/page/help/view.jsx | 4 ++-- ui/js/page/rewards.js | 4 ++-- ui/js/page/wallet/view.jsx | 4 ++-- ui/js/selectors/app.js | 6 ++++++ 12 files changed, 63 insertions(+), 87 deletions(-) delete mode 100644 ui/js/component/navMain/index.jsx delete mode 100644 ui/js/component/navMain/view.jsx delete mode 100644 ui/js/component/navSettings/index.jsx delete mode 100644 ui/js/component/navSettings/view.jsx delete mode 100644 ui/js/component/navWallet/index.jsx delete mode 100644 ui/js/component/navWallet/view.jsx create mode 100644 ui/js/component/subHeader/index.js create mode 100644 ui/js/component/subHeader/view.jsx diff --git a/ui/js/component/navMain/index.jsx b/ui/js/component/navMain/index.jsx deleted file mode 100644 index 2c27c39a5..000000000 --- a/ui/js/component/navMain/index.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react' -import { - connect, -} from 'react-redux' -import { - selectCurrentPage, -} from 'selectors/app' -import { - doNavigate, -} from 'actions/app' -import NavMain from './view' - -const select = (state) => ({ - currentPage: selectCurrentPage(state) -}) - -const perform = (dispatch) => ({ - navigate: (path) => dispatch(doNavigate(path)) -}) - -export default connect(select, perform)(NavMain) diff --git a/ui/js/component/navMain/view.jsx b/ui/js/component/navMain/view.jsx deleted file mode 100644 index f10f07a6b..000000000 --- a/ui/js/component/navMain/view.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -export const NavMain = (props) => { - const { - links, - currentPage, - navigate, - } = props - - return ( - - ) -} - -export default NavMain \ No newline at end of file diff --git a/ui/js/component/navSettings/index.jsx b/ui/js/component/navSettings/index.jsx deleted file mode 100644 index 9fb7efb4d..000000000 --- a/ui/js/component/navSettings/index.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' -import { - connect, -} from 'react-redux' -import NavSettings from './view' - -export default connect(null, null)(NavSettings) diff --git a/ui/js/component/navSettings/view.jsx b/ui/js/component/navSettings/view.jsx deleted file mode 100644 index 099e2acae..000000000 --- a/ui/js/component/navSettings/view.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import NavMain from 'component/navMain' - -export const NavSettings = () => { - return ; -} - -export default NavSettings \ No newline at end of file diff --git a/ui/js/component/navWallet/index.jsx b/ui/js/component/navWallet/index.jsx deleted file mode 100644 index 4ace4d388..000000000 --- a/ui/js/component/navWallet/index.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' -import { - connect, -} from 'react-redux' -import NavWallet from './view' - -export default connect(null, null)(NavWallet) diff --git a/ui/js/component/navWallet/view.jsx b/ui/js/component/navWallet/view.jsx deleted file mode 100644 index c076e9c67..000000000 --- a/ui/js/component/navWallet/view.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import NavMain from 'component/navMain' - -export const NavWallet = () => { - return -} - -export default NavWallet \ No newline at end of file diff --git a/ui/js/component/subHeader/index.js b/ui/js/component/subHeader/index.js new file mode 100644 index 000000000..4878b65c0 --- /dev/null +++ b/ui/js/component/subHeader/index.js @@ -0,0 +1,23 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import { + selectCurrentPage, + selectHeaderLinks, +} from 'selectors/app' +import { + doNavigate, +} from 'actions/app' +import SubHeader from './view' + +const select = (state, props) => ({ + currentPage: selectCurrentPage(state), + subLinks: selectHeaderLinks(state), +}) + +const perform = (dispatch) => ({ + navigate: (path) => dispatch(doNavigate(path)), +}) + +export default connect(select, perform)(SubHeader) diff --git a/ui/js/component/subHeader/view.jsx b/ui/js/component/subHeader/view.jsx new file mode 100644 index 000000000..8daf63867 --- /dev/null +++ b/ui/js/component/subHeader/view.jsx @@ -0,0 +1,28 @@ +import React from 'react' + +const SubHeader = (props) => { + const { + subLinks, + currentPage, + navigate, + modifier, + } = props + + const links = [] + + for(let link of Object.keys(subLinks)) { + links.push( + navigate(link)} key={link} className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected' }> + {subLinks[link]} + + ) + } + + return ( + + ) +} + +export default SubHeader diff --git a/ui/js/page/help/view.jsx b/ui/js/page/help/view.jsx index 095435e20..a6485b238 100644 --- a/ui/js/page/help/view.jsx +++ b/ui/js/page/help/view.jsx @@ -2,7 +2,7 @@ import React from 'react'; import lbry from 'lbry.js'; import Link from 'component/link'; -import NavSettings from 'component/navSettings'; +import SubHeader from 'component/subHeader' import {version as uiVersion} from 'json!../../../package.json'; var HelpPage = React.createClass({ @@ -50,7 +50,7 @@ var HelpPage = React.createClass({ return (
      - +

      Read the FAQ

      diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index 5b9bb8352..6dcdcc4c5 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -1,7 +1,7 @@ import React from 'react'; import lbryio from 'lbryio'; import {CreditAmount, Icon} from 'component/common.js'; -import NavWallet from 'component/navWallet'; +import SubHeader from 'component/subHeader' import {RewardLink} from 'component/reward-link'; const RewardTile = React.createClass({ @@ -55,7 +55,7 @@ export let RewardsPage = React.createClass({ render: function() { return (
      - +
      {!this.state.userRewards ? (this.state.failed ?
      Failed to load rewards.
      : '') diff --git a/ui/js/page/wallet/view.jsx b/ui/js/page/wallet/view.jsx index cebe970ed..c9ef2f71c 100644 --- a/ui/js/page/wallet/view.jsx +++ b/ui/js/page/wallet/view.jsx @@ -2,7 +2,7 @@ import React from 'react'; import lbry from 'lbry.js'; import Link from 'component/link'; import Modal from 'component/modal'; -import NavWallet from 'component/navWallet'; +import SubHeader from 'component/subHeader' import { FormField, FormRow @@ -248,7 +248,7 @@ const WalletPage = (props) => { return (
      - +

      Balance

      diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index 29ca4db02..a104ac17d 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -198,6 +198,12 @@ export const selectHeaderLinks = createSelector( 'downloaded': 'Downloaded', 'published': 'Published', }; + case 'settings': + case 'help': + return { + 'settings': 'Settings', + 'help': 'Help', + } default: return null; } -- 2.43.4 From f2490c902f960b0b7a05f3751ca0de4c3fb91ba0 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 5 May 2017 12:48:15 +0700 Subject: [PATCH 52/56] Missed NavSettings --- ui/js/page/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/js/page/settings.js b/ui/js/page/settings.js index 0f2ba6538..bbd4fe4c3 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings.js @@ -1,6 +1,6 @@ import React from 'react'; import {FormField, FormRow} from '../component/form.js'; -import NavSettings from 'component/navSettings'; +import SubHeader from 'component/subHeader' import lbry from '../lbry.js'; var SettingsPage = React.createClass({ @@ -91,7 +91,7 @@ var SettingsPage = React.createClass({ */ return (
      - +

      Download Directory

      -- 2.43.4 From daabe975e17a624e5505fe0fab477226082a9477 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 5 May 2017 13:13:06 +0700 Subject: [PATCH 53/56] Add showPage again. Not 100% sure if it's needed? --- ui/js/component/router/view.jsx | 4 +- ui/js/page/filePage/view.jsx | 228 +------------------------------- ui/js/page/showPage/index.js | 35 +++++ ui/js/page/showPage/view.jsx | 72 ++++++++++ 4 files changed, 110 insertions(+), 229 deletions(-) create mode 100644 ui/js/page/showPage/index.js create mode 100644 ui/js/page/showPage/view.jsx diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index f8b4c300b..7fc915882 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -4,7 +4,7 @@ import HelpPage from 'page/help'; import ReportPage from 'page/report.js'; import StartPage from 'page/start.js'; import WalletPage from 'page/wallet'; -import FilePage from 'page/filePage'; +import ShowPage from 'page/showPage' import PublishPage from 'page/publish'; import DiscoverPage from 'page/discover'; import SplashScreen from 'component/splash.js'; @@ -36,7 +36,7 @@ const Router = (props) => { 'wallet': , 'send': , 'receive': , - 'show': , + 'show': , 'channel': , 'publish': , 'developer': , diff --git a/ui/js/page/filePage/view.jsx b/ui/js/page/filePage/view.jsx index f3b6ac859..339caa3f1 100644 --- a/ui/js/page/filePage/view.jsx +++ b/ui/js/page/filePage/view.jsx @@ -126,230 +126,4 @@ const FilePage = (props) => { ) } -let ShowPage = React.createClass({ - _uri: null, - _isMounted: false, - - propTypes: { - uri: React.PropTypes.string, - }, - - getInitialState: function() { - return { - outpoint: null, - metadata: null, - contentType: null, - hasSignature: false, - claimType: null, - signatureIsValid: false, - cost: null, - costIncludesData: null, - uriLookupComplete: null, - isFailed: false, - }; - }, - - componentWillUnmount: function() { - this._isMounted = false; - }, - - componentWillReceiveProps: function(nextProps) { - if (nextProps.uri != this.props.uri) { - this.setState(this.getInitialState()); - this.loadUri(nextProps.uri); - } - }, - - componentWillMount: function() { - this._isMounted = true; - this.loadUri(this.props.uri); - }, - - loadUri: function(uri) { - this._uri = lbryuri.normalize(uri); - - lbry.resolve({uri: this._uri}).then((resolveData) => { - const isChannel = resolveData && resolveData.claims_in_channel; - if (!this._isMounted) { - return; - } - if (resolveData) { - let newState = { uriLookupComplete: true } - if (!isChannel) { - let {claim: {txid: txid, nout: nout, has_signature: has_signature, signature_is_valid: signature_is_valid, value: {stream: {metadata: metadata, source: {contentType: contentType}}}}} = resolveData; - - Object.assign(newState, { - claimType: "file", - metadata: metadata, - outpoint: txid + ':' + nout, - hasSignature: has_signature, - signatureIsValid: signature_is_valid, - contentType: contentType - }); - - - lbry.setTitle(metadata.title ? metadata.title : this._uri) - - } else { - let {certificate: {txid: txid, nout: nout, has_signature: has_signature}} = resolveData; - Object.assign(newState, { - claimType: "channel", - outpoint: txid + ':' + nout, - txid: txid, - metadata: { - title:resolveData.certificate.name - } - }); - } - - this.setState(newState); - - } else { - this.setState(Object.assign({}, this.getInitialState(), { - uriLookupComplete: true, - isFailed: true - })); - } - }); - }, - - render: function() { - const metadata = this.state.metadata, - title = metadata ? this.state.metadata.title : this._uri; - - let innerContent = ""; - - if (!this.state.uriLookupComplete || this.state.isFailed) { - innerContent =
      -
      -

      {title}

      -
      -
      - { this.state.uriLookupComplete ? -

      This location is not yet in use. { ' ' }.

      : - - } -
      -
      ; - } else { - let channelUriObj = lbryuri.parse(this._uri) - delete channelUriObj.path; - delete channelUriObj.contentName; - const channelUri = this.state.signatureIsValid && this.state.hasSignature && channelUriObj.isChannel ? lbryuri.build(channelUriObj, false) : null; - innerContent = ; - } - - return
      {innerContent}
      ; - } -}); - -export default FilePage; - -// -// const ShowPage = (props) => { -// const { -// claim, -// navigate, -// claim: { -// txid, -// nout, -// has_signature: hasSignature, -// signature_is_valid: signatureIsValid, -// value, -// value: { -// stream, -// stream: { -// metadata, -// source, -// metadata: { -// title, -// } = {}, -// source: { -// contentType, -// } = {}, -// } = {}, -// } = {}, -// }, -// uri, -// isDownloaded, -// fileInfo, -// costInfo, -// costInfo: { -// cost, -// includesData: costIncludesData, -// } = {}, -// } = props -// -// const outpoint = txid + ':' + nout; -// const uriLookupComplete = !!claim && Object.keys(claim).length -// -// if (props.isFailed) { -// return ( -//
      -//
      -//
      -//

      {uri}

      -//
      -//
      -//

      -// This location is not yet in use. -// { ' ' } -// navigate('publish')} label="Put something here" />. -//

      -//
      -//
      -//
      -// ) -// } -// -// return ( -//
      -//
      -// { contentType && contentType.startsWith('video/') ? -//
      -//
      -//
      -//
      -// {isDownloaded === false -// ? -// : null} -//

      {title}

      -// { uriLookupComplete ? -//
      -//
      -// -//
      -//
      -// -//
      -//
      : '' } -//
      -// { uriLookupComplete ? -//
      -//
      -// {metadata.description} -//
      -//
      -// :
      } -//
      -// { metadata ? -//
      -// -//
      : '' } -//
      -// -//
      -//
      -//
      -// ) -// } \ No newline at end of file +export default FilePage; \ No newline at end of file diff --git a/ui/js/page/showPage/index.js b/ui/js/page/showPage/index.js new file mode 100644 index 000000000..8155da8dd --- /dev/null +++ b/ui/js/page/showPage/index.js @@ -0,0 +1,35 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + selectCurrentUri, +} from 'selectors/app' +import { + selectCurrentUriIsDownloaded, +} from 'selectors/file_info' +import { + selectCurrentUriClaim, +} from 'selectors/claims' +import { + selectCurrentUriFileInfo, +} from 'selectors/file_info' +import { + selectCurrentUriCostInfo, +} from 'selectors/cost_info' +import ShowPage from './view' + +const select = (state) => ({ + claim: selectCurrentUriClaim(state), + uri: selectCurrentUri(state), + isDownloaded: selectCurrentUriIsDownloaded(state), + fileInfo: selectCurrentUriFileInfo(state), + costInfo: selectCurrentUriCostInfo(state), + isFailed: false, + claimType: 'file', +}) + +const perform = (dispatch) => ({ +}) + +export default connect(select, perform)(ShowPage) diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx new file mode 100644 index 000000000..f76a87792 --- /dev/null +++ b/ui/js/page/showPage/view.jsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { + BusyMessage, +} from 'component/common'; +import FilePage from 'page/filePage' + +const ShowPage = (props) => { + const { + claim, + navigate, + claim: { + txid, + nout, + has_signature: hasSignature, + signature_is_valid: signatureIsValid, + value, + value: { + stream, + stream: { + metadata, + source, + metadata: { + title, + } = {}, + source: { + contentType, + } = {}, + } = {}, + } = {}, + }, + uri, + isDownloaded, + fileInfo, + costInfo, + costInfo: { + cost, + includesData: costIncludesData, + } = {}, + isFailed, + claimType, + } = props + + const outpoint = txid + ':' + nout; + const uriLookupComplete = !!claim && Object.keys(claim).length + const pageTitle = metadata ? metadata.title : uri; + + let innerContent = ""; + + if (!uriLookupComplete || isFailed) { + innerContent =
      +
      +

      {pageTitle}

      +
      +
      + { uriLookupComplete ? +

      This location is not yet in use. { ' ' } navigate('publish')} label="Put something here" />.

      : + + } +
      +
      ; + } else if (claimType == "channel") { + innerContent = + } else { + innerContent = + } + + return ( +
      {innerContent}
      + ) +} + +export default ShowPage -- 2.43.4 From 29c53a9bf8654eb2be4ccd4c1a1f4d94730056f3 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 5 May 2017 15:01:16 +0700 Subject: [PATCH 54/56] Search kind of working, but nasty --- ui/js/component/router/view.jsx | 2 + ui/js/component/wunderbar/index.js | 4 +- ui/js/component/wunderbar/view.jsx | 3 + ui/js/page/search.js | 165 ----------------------------- ui/js/page/search/index.js | 30 ++++++ ui/js/page/search/view.jsx | 92 ++++++++++++++++ ui/js/selectors/app.js | 11 +- 7 files changed, 139 insertions(+), 168 deletions(-) delete mode 100644 ui/js/page/search.js create mode 100644 ui/js/page/search/index.js create mode 100644 ui/js/page/search/view.jsx diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index 7fc915882..8b3d66102 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -13,6 +13,7 @@ import RewardsPage from 'page/rewards.js'; import FileListDownloaded from 'page/fileListDownloaded' import FileListPublished from 'page/fileListPublished' import ChannelPage from 'page/channel' +import SearchPage from 'page/search' const route = (page, routesMap) => { const component = routesMap[page] @@ -42,6 +43,7 @@ const Router = (props) => { 'developer': , 'discover': , 'rewards': , + 'search': , }) } diff --git a/ui/js/component/wunderbar/index.js b/ui/js/component/wunderbar/index.js index 85551e637..9860df4f1 100644 --- a/ui/js/component/wunderbar/index.js +++ b/ui/js/component/wunderbar/index.js @@ -25,8 +25,8 @@ const perform = (dispatch) => ({ // navigate: (path) => dispatch(doNavigate(path)), onSearch: (query) => dispatch(doSearchContent(query)), onSubmit: (query) => dispatch(doSearchContent(query)), - // activateSearch: () => dispatch(doActivateSearch()), - // deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 50), + activateSearch: () => dispatch(doActivateSearch()), + deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 50), }) export default connect(select, perform)(Wunderbar) diff --git a/ui/js/component/wunderbar/view.jsx b/ui/js/component/wunderbar/view.jsx index 821e9bfc0..6ff9c6312 100644 --- a/ui/js/component/wunderbar/view.jsx +++ b/ui/js/component/wunderbar/view.jsx @@ -61,6 +61,8 @@ class WunderBar extends React.PureComponent { isActive: true } + this.props.activateSearch() + this._focusPending = true; //below is hacking, improved when we have proper routing if (!this.state.address.startsWith('lbry://') && this.state.icon !== "icon-search") //onFocus, if they are not on an exact URL or a search page, clear the bar @@ -71,6 +73,7 @@ class WunderBar extends React.PureComponent { } onBlur() { + this.props.deactivateSearch() let commonState = {isActive: false}; if (this._resetOnNextBlur) { this.setState(Object.assign({}, this._stateBeforeSearch, commonState)); diff --git a/ui/js/page/search.js b/ui/js/page/search.js deleted file mode 100644 index f51541bcc..000000000 --- a/ui/js/page/search.js +++ /dev/null @@ -1,165 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import lbryio from '../lbryio.js'; -import lbryuri from '../lbryuri.js'; -import lighthouse from '../lighthouse.js'; -import {FileTile, FileTileStream} from '../component/file-tile.js'; -import {Link} from '../component/link'; -import {ToolTip} from '../component/tooltip.js'; -import {BusyMessage} from '../component/common.js'; - -var SearchNoResults = React.createClass({ - render: function() { - return
      - - No one has checked anything in for {this.props.query} yet. - - -
      ; - } -}); - -var SearchResultList = React.createClass({ - render: function() { - var rows = [], - seenNames = {}; //fix this when the search API returns claim IDs - - for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of this.props.results) { - const uri = lbryuri.build({ - channelName: channel_name, - contentName: name, - claimId: channel_id || claim_id, - }); - - rows.push( - - ); - } - return ( -
      {rows}
      - ); - } -}); - -let SearchResults = React.createClass({ - propTypes: { - query: React.PropTypes.string.isRequired - }, - - _isMounted: false, - - search: function(term) { - lighthouse.search(term).then(this.searchCallback); - if (!this.state.searching) { - this.setState({ searching: true }) - } - }, - - componentWillMount: function () { - this._isMounted = true; - this.search(this.props.query); - }, - - componentWillReceiveProps: function (nextProps) { - if (nextProps.query != this.props.query) { - this.search(nextProps.query); - } - }, - - componentWillUnmount: function () { - this._isMounted = false; - }, - - getInitialState: function () { - return { - results: [], - searching: true - }; - }, - - searchCallback: function (results) { - if (this._isMounted) //could have canceled while results were pending, in which case nothing to do - { - this.setState({ - results: results, - searching: false //multiple searches can be out, we're only done if we receive one we actually care about - }); - } - }, - - render: function () { - return this.state.searching ? - : - (this.state.results && this.state.results.length ? - : - ); - } -}); - -let SearchPage = React.createClass({ - - _isMounted: false, - - propTypes: { - query: React.PropTypes.string.isRequired - }, - - isValidUri: function(query) { - try { - lbryuri.parse(query); - return true; - } catch (e) { - return false; - } - }, - - componentWillMount: function() { - this._isMounted = true; - lighthouse.search(this.props.query).then(this.searchCallback); - }, - - componentWillUnmount: function() { - this._isMounted = false; - }, - - getInitialState: function() { - return { - results: [], - searching: true - }; - }, - - searchCallback: function(results) { - if (this._isMounted) //could have canceled while results were pending, in which case nothing to do - { - this.setState({ - results: results, - searching: false //multiple searches can be out, we're only done if we receive one we actually care about - }); - } - }, - - render: function() { - return ( -
      - { this.isValidUri(this.props.query) ? -
      -

      - Exact URL - -

      - -
      : '' } -
      -

      - Search Results for {this.props.query} - -

      - -
      -
      - ); - } -}); - -export default SearchPage; diff --git a/ui/js/page/search/index.js b/ui/js/page/search/index.js new file mode 100644 index 000000000..e307905e1 --- /dev/null +++ b/ui/js/page/search/index.js @@ -0,0 +1,30 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import { + doSearchContent, +} from 'actions/search' +import { + selectIsSearching, + selectSearchQuery, + selectCurrentSearchResults, + selectSearchActivated, +} from 'selectors/search' +import { + doNavigate, +} from 'actions/app' +import SearchPage from './view' + +const select = (state) => ({ + isSearching: selectIsSearching(state), + query: selectSearchQuery(state), + results: selectCurrentSearchResults(state), + searchActive: selectSearchActivated(state), +}) + +const perform = (dispatch) => ({ + navigate: (path) => dispatch(doNavigate(path)), +}) + +export default connect(select, perform)(SearchPage) diff --git a/ui/js/page/search/view.jsx b/ui/js/page/search/view.jsx new file mode 100644 index 000000000..a74db1d0a --- /dev/null +++ b/ui/js/page/search/view.jsx @@ -0,0 +1,92 @@ +import React from 'react'; +import lbry from 'lbry'; +import lbryio from 'lbryio'; +import lbryuri from 'lbryuri'; +import lighthouse from 'lighthouse'; +import FileTile from 'component/fileTile' +import FileTileStream from 'component/fileTileStream' +import Link from 'component/link' +import {ToolTip} from 'component/tooltip.js'; +import {BusyMessage} from 'component/common.js'; + +const SearchNoResults = (props) => { + const { + navigate, + query, + } = props + + return
      + + No one has checked anything in for {query} yet. + navigate('publish')} /> + +
      ; +} + +const SearchResultList = (props) => { + const { + results, + } = props + + const rows = [], + seenNames = {}; //fix this when the search API returns claim IDs + + for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of results) { + const uri = lbryuri.build({ + channelName: channel_name, + contentName: name, + claimId: channel_id || claim_id, + }); + + rows.push( + + ); + } + return ( +
      {rows}
      + ); +} + +const SearchResults = (props) => { + const { + searching, + results, + query, + } = props + + return ( + searching ? + : + (results && results.length) ? + : + + ) +} + +const SearchPage = (props) => { + const isValidUri = (query) => true + const { + query, + } = props + + return ( +
      + { isValidUri(query) ? +
      +

      + Exact URL + +

      + +
      : '' } +
      +

      + Search Results for {query} + +

      + +
      +
      + ) +} +export default SearchPage; diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index a104ac17d..c72786747 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -1,4 +1,8 @@ import {createSelector} from 'reselect' +import { + selectIsSearching, + selectSearchActivated, +} from 'selectors/search' export const _selectState = state => state.app || {} @@ -14,7 +18,12 @@ export const selectCurrentPath = createSelector( export const selectCurrentPage = createSelector( selectCurrentPath, - (path) => path.split('=')[0] + selectSearchActivated, + (path, searchActivated) => { + if (searchActivated) return 'search' + + return path.split('=')[0] + } ) export const selectCurrentUri = createSelector( -- 2.43.4 From 89e91c6d8a37e5c5bc4410ca81bb0690cf788bf3 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 5 May 2017 15:10:51 +0700 Subject: [PATCH 55/56] Add downloaded/published nav and comment out broken reward code --- ui/js/page/fileListDownloaded/view.jsx | 27 +++--- ui/js/page/fileListPublished/view.jsx | 118 ++++++------------------- 2 files changed, 38 insertions(+), 107 deletions(-) diff --git a/ui/js/page/fileListDownloaded/view.jsx b/ui/js/page/fileListDownloaded/view.jsx index cf95e0224..0a0a39b00 100644 --- a/ui/js/page/fileListDownloaded/view.jsx +++ b/ui/js/page/fileListDownloaded/view.jsx @@ -8,6 +8,7 @@ import rewards from 'rewards.js'; import lbryio from 'lbryio.js'; import {BusyMessage, Thumbnail} from 'component/common.js'; import FileList from 'component/fileList' +import SubHeader from 'component/subHeader' class FileListDownloaded extends React.Component { render() { @@ -17,25 +18,21 @@ class FileListDownloaded extends React.Component { navigate, } = this.props + let content if (fetching) { - return ( -
      - -
      - ); + content = } else if (!downloadedContent.length) { - return ( -
      - You haven't downloaded anything from LBRY yet. Go navigate('discover')} label="search for your first download" />! -
      - ); + content = You haven't downloaded anything from LBRY yet. Go navigate('discover')} label="search for your first download" />! } else { - return ( -
      - -
      - ); + content = } + + return ( +
      + + {content} +
      + ) } } diff --git a/ui/js/page/fileListPublished/view.jsx b/ui/js/page/fileListPublished/view.jsx index 56d00003b..fd4470ee1 100644 --- a/ui/js/page/fileListPublished/view.jsx +++ b/ui/js/page/fileListPublished/view.jsx @@ -8,6 +8,7 @@ import rewards from 'rewards.js'; import lbryio from 'lbryio.js'; import {BusyMessage, Thumbnail} from 'component/common.js'; import FileList from 'component/fileList' +import SubHeader from 'component/subHeader' class FileListPublished extends React.Component { componentDidUpdate() { @@ -15,17 +16,20 @@ class FileListPublished extends React.Component { } _requestPublishReward() { - lbryio.call('reward', 'list', {}).then(function(userRewards) { - //already rewarded - if (userRewards.filter(function (reward) { - return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID - }).length) { - return - } - else { - rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {}) - } - }) + // TODO this is throwing an error now + // Error: LBRY internal API is disabled + // + // lbryio.call('reward', 'list', {}).then(function(userRewards) { + // //already rewarded + // if (userRewards.filter(function (reward) { + // return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID + // }).length) { + // return + // } + // else { + // rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {}) + // } + // }) } render() { @@ -35,92 +39,22 @@ class FileListPublished extends React.Component { navigate, } = this.props + let content if (fetching) { - return ( -
      - -
      - ); + content = } else if (!publishedContent.length) { - return ( -
      - You haven't downloaded anything from LBRY yet. Go navigate('discover')} label="search for your first download" />! -
      - ); + content = You haven't downloaded anything from LBRY yet. Go navigate('discover')} label="search for your first download" />! } else { - return ( -
      - -
      - ); + content = } + + return ( +
      + + {content} +
      + ) } } -// const FileListPublished = React.createClass({ -// _isMounted: false, - -// getInitialState: function () { -// return { -// fileInfos: null, -// }; -// }, -// _requestPublishReward: function() { -// lbryio.call('reward', 'list', {}).then(function(userRewards) { -// //already rewarded -// if (userRewards.filter(function (reward) { -// return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID; -// }).length) { -// return; -// } -// else { -// rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {}) -// } -// }); -// }, -// componentDidMount: function () { -// this._isMounted = true; -// this._requestPublishReward(); -// document.title = "Published Files"; - -// lbry.claim_list_mine().then((claimInfos) => { -// if (!this._isMounted) { return; } - -// lbry.file_list().then((fileInfos) => { -// if (!this._isMounted) { return; } - -// const myClaimOutpoints = claimInfos.map(({txid, nout}) => txid + ':' + nout); -// this.setState({ -// fileInfos: fileInfos.filter(({outpoint}) => myClaimOutpoints.includes(outpoint)), -// }); -// }); -// }); -// }, -// componentWillUnmount: function() { -// this._isMounted = false; -// }, -// render: function () { -// if (this.state.fileInfos === null) { -// return ( -//
      -// -//
      -// ); -// } -// else if (!this.state.fileInfos.length) { -// return ( -//
      -// You haven't published anything to LBRY yet. Try ! -//
      -// ); -// } -// else { -// return ( -//
      -// -//
      -// ); -// } -// } -// }); export default FileListPublished -- 2.43.4 From fb3d366fe058847d213f9e889a868877fcbf6159 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 6 May 2017 00:05:33 +0700 Subject: [PATCH 56/56] Fix upgrading and start on history --- ui/js/actions/app.js | 18 ++++++++++++++---- ui/js/component/header/index.js | 1 + ui/js/component/header/view.jsx | 2 +- ui/js/reducers/app.js | 1 - 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 1f384cbd5..b943f5e4b 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -6,6 +6,7 @@ import { selectUpgradeDownloadItem, selectUpgradeFilename, selectPageTitle, + selectCurrentPath, } from 'selectors/app' const {remote, ipcRenderer, shell} = require('electron'); @@ -16,6 +17,11 @@ const fs = remote.require('fs'); export function doNavigate(path) { return function(dispatch, getState) { + const state = getState() + const previousPath = selectCurrentPath(state) + const previousTitle = selectPageTitle(state) + history.pushState(state, previousTitle, previousPath); + dispatch({ type: types.NAVIGATE, data: { @@ -23,8 +29,8 @@ export function doNavigate(path) { } }) - const state = getState() const pageTitle = selectPageTitle(state) + window.document.title = pageTitle } } @@ -48,8 +54,12 @@ export function doCloseModal() { } export function doHistoryBack() { - return { - type: types.HISTORY_BACK + return function(dispatch, getState) { + if (window.history.length > 1) { + window.history.back(); + } else { + dispatch(doNavigate('discover')) + } } } @@ -152,7 +162,7 @@ export function doCheckUpgradeAvailable() { dispatch({ type: types.UPDATE_VERSION, data: { - version: versionInfo.lbrynet_version + version: remoteVersion, } }) dispatch({ diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js index 2a10b5e81..dafdabf32 100644 --- a/ui/js/component/header/index.js +++ b/ui/js/component/header/index.js @@ -1,4 +1,5 @@ import React from 'react' +import lbry from 'lbry' import { connect } from 'react-redux' diff --git a/ui/js/component/header/view.jsx b/ui/js/component/header/view.jsx index 0707b7e10..e5ed26063 100644 --- a/ui/js/component/header/view.jsx +++ b/ui/js/component/header/view.jsx @@ -8,7 +8,7 @@ export const Header = (props) => { back, navigate } = props - + return