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: ,
+// });
+// },
+// 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' ]
}