Redux proof of concept
This commit is contained in:
parent
d160710d20
commit
0d3647c709
43 changed files with 1471 additions and 565 deletions
|
@ -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');
|
||||
|
|
|
@ -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": {}
|
||||
}
|
||||
|
|
206
ui/js/actions/app.js
Normal file
206
ui/js/actions/app.js
Normal file
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
651
ui/js/app.js
651
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(<li key={key}><strong>{label}</strong>: <code>{val}</code></li>);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
modal: 'error',
|
||||
errorInfo: <ul className="error-modal__error-list">{errorInfoList}</ul>,
|
||||
});
|
||||
},
|
||||
getContentAndAddress: function()
|
||||
{
|
||||
switch(this.state.viewingPage)
|
||||
{
|
||||
case 'search':
|
||||
return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', <SearchPage query={this.state.pageArgs} />];
|
||||
case 'settings':
|
||||
return ["Settings", "icon-gear", <SettingsPage />];
|
||||
case 'help':
|
||||
return ["Help", "icon-question", <HelpPage />];
|
||||
case 'report':
|
||||
return ['Report an Issue', 'icon-file', <ReportPage />];
|
||||
case 'downloaded':
|
||||
return ["Downloads & Purchases", "icon-folder", <FileListDownloaded />];
|
||||
case 'published':
|
||||
return ["Publishes", "icon-folder", <FileListPublished />];
|
||||
case 'start':
|
||||
return ["Start", "icon-file", <StartPage />];
|
||||
case 'rewards':
|
||||
return ["Rewards", "icon-bank", <RewardsPage />];
|
||||
case 'wallet':
|
||||
case 'send':
|
||||
case 'receive':
|
||||
return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", <WalletPage viewingPage={this.state.viewingPage} />]
|
||||
case 'show':
|
||||
return [lbryuri.normalize(this.state.pageArgs), "icon-file", <ShowPage uri={this.state.pageArgs} />];
|
||||
case 'publish':
|
||||
return ["Publish", "icon-upload", <PublishPage />];
|
||||
case 'developer':
|
||||
return ["Developer", "icon-file", <DeveloperPage />];
|
||||
case 'discover':
|
||||
default:
|
||||
return ["Home", "icon-home", <DiscoverPage />];
|
||||
}
|
||||
},
|
||||
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 :
|
||||
<div id="window">
|
||||
<Header onSearch={this.onSearch} onSubmit={this.onSubmit} address={address} wunderBarIcon={wunderBarIcon} viewingPage={this.state.viewingPage} />
|
||||
<div id="main-content">
|
||||
{mainContent}
|
||||
</div>
|
||||
<Modal isOpen={this.state.modal == 'upgrade'} contentLabel="Update available"
|
||||
type="confirm" confirmButtonLabel="Upgrade" abortButtonLabel="Skip"
|
||||
onConfirmed={this.handleUpgradeClicked} onAborted={this.handleSkipClicked}>
|
||||
Your version of LBRY is out of date and may be unreliable or insecure.
|
||||
</Modal>
|
||||
<Modal isOpen={this.state.modal == 'downloading'} contentLabel="Downloading Update" type="custom">
|
||||
Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}%` : null}
|
||||
<Line percent={this.state.downloadProgress} strokeWidth="4"/>
|
||||
{this.state.downloadComplete ? (
|
||||
<div>
|
||||
<br />
|
||||
<p>Click "Begin Upgrade" to start the upgrade process.</p>
|
||||
<p>The app will close, and you will be prompted to install the latest version of LBRY.</p>
|
||||
<p>After the install is complete, please reopen the app.</p>
|
||||
</div>
|
||||
) : null }
|
||||
<div className="modal__buttons">
|
||||
{this.state.downloadComplete
|
||||
? <Link button="primary" label="Begin Upgrade" className="modal__button" onClick={this.handleStartUpgradeClicked} />
|
||||
: null}
|
||||
<Link button="alt" label="Cancel" className="modal__button" onClick={this.cancelUpgrade} />
|
||||
</div>
|
||||
</Modal>
|
||||
<ExpandableModal isOpen={this.state.modal == 'error'} contentLabel="Error" className="error-modal"
|
||||
overlayClassName="error-modal-overlay" onConfirmed={this.closeModal}
|
||||
extraContent={this.state.errorInfo}>
|
||||
<h3 className="modal__header">Error</h3>
|
||||
|
||||
<div className="error-modal__content">
|
||||
<div><img className="error-modal__warning-symbol" src={lbry.imagePath('warning.png')} /></div>
|
||||
<p>We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.</p>
|
||||
</div>
|
||||
</ExpandableModal>
|
||||
</div>
|
||||
);
|
||||
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(<li key={key}><strong>{label}</strong>: <code>{val}</code></li>);
|
||||
// }
|
||||
//
|
||||
// this.setState({
|
||||
// modal: 'error',
|
||||
// errorInfo: <ul className="error-modal__error-list">{errorInfoList}</ul>,
|
||||
// });
|
||||
// },
|
||||
// getContentAndAddress: function()
|
||||
// {
|
||||
// switch(this.state.viewingPage)
|
||||
// {
|
||||
// case 'search':
|
||||
// return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', <SearchPage query={this.state.pageArgs} />];
|
||||
// case 'settings':
|
||||
// return ["Settings", "icon-gear", <SettingsPage />];
|
||||
// case 'help':
|
||||
// return ["Help", "icon-question", <HelpPage />];
|
||||
// case 'report':
|
||||
// return ['Report an Issue', 'icon-file', <ReportPage />];
|
||||
// case 'downloaded':
|
||||
// return ["Downloads & Purchases", "icon-folder", <FileListDownloaded />];
|
||||
// case 'published':
|
||||
// return ["Publishes", "icon-folder", <FileListPublished />];
|
||||
// case 'start':
|
||||
// return ["Start", "icon-file", <StartPage />];
|
||||
// case 'rewards':
|
||||
// return ["Rewards", "icon-bank", <RewardsPage />];
|
||||
// case 'wallet':
|
||||
// case 'send':
|
||||
// case 'receive':
|
||||
// return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", <WalletPage viewingPage={this.state.viewingPage} />]
|
||||
// case 'show':
|
||||
// return [lbryuri.normalize(this.state.pageArgs), "icon-file", <ShowPage uri={this.state.pageArgs} />];
|
||||
// case 'publish':
|
||||
// return ["Publish", "icon-upload", <PublishPage />];
|
||||
// case 'developer':
|
||||
// return ["Developer", "icon-file", <DeveloperPage />];
|
||||
// case 'discover':
|
||||
// default:
|
||||
// return ["Home", "icon-home", <DiscoverPage />];
|
||||
// }
|
||||
// },
|
||||
// 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 :
|
||||
// <div id="window">
|
||||
// <Header onSearch={this.onSearch} onSubmit={this.onSubmit} address={address} wunderBarIcon={wunderBarIcon} viewingPage={this.state.viewingPage} />
|
||||
// <div id="main-content">
|
||||
// {mainContent}
|
||||
// </div>
|
||||
// <Modal isOpen={this.state.modal == 'upgrade'} contentLabel="Update available"
|
||||
// type="confirm" confirmButtonLabel="Upgrade" abortButtonLabel="Skip"
|
||||
// onConfirmed={this.handleUpgradeClicked} onAborted={this.handleSkipClicked}>
|
||||
// Your version of LBRY is out of date and may be unreliable or insecure.
|
||||
// </Modal>
|
||||
// <Modal isOpen={this.state.modal == 'downloading'} contentLabel="Downloading Update" type="custom">
|
||||
// Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}%` : null}
|
||||
// <Line percent={this.state.downloadProgress} strokeWidth="4"/>
|
||||
// {this.state.downloadComplete ? (
|
||||
// <div>
|
||||
// <br />
|
||||
// <p>Click "Begin Upgrade" to start the upgrade process.</p>
|
||||
// <p>The app will close, and you will be prompted to install the latest version of LBRY.</p>
|
||||
// <p>After the install is complete, please reopen the app.</p>
|
||||
// </div>
|
||||
// ) : null }
|
||||
// <div className="modal__buttons">
|
||||
// {this.state.downloadComplete
|
||||
// ? <Link button="primary" label="Begin Upgrade" className="modal__button" onClick={this.handleStartUpgradeClicked} />
|
||||
// : null}
|
||||
// <Link button="alt" label="Cancel" className="modal__button" onClick={this.cancelUpgrade} />
|
||||
// </div>
|
||||
// </Modal>
|
||||
// <ExpandableModal isOpen={this.state.modal == 'error'} contentLabel="Error" className="error-modal"
|
||||
// overlayClassName="error-modal-overlay" onConfirmed={this.closeModal}
|
||||
// extraContent={this.state.errorInfo}>
|
||||
// <h3 className="modal__header">Error</h3>
|
||||
//
|
||||
// <div className="error-modal__content">
|
||||
// <div><img className="error-modal__warning-symbol" src={lbry.imagePath('warning.png')} /></div>
|
||||
// <p>We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.</p>
|
||||
// </div>
|
||||
// </ExpandableModal>
|
||||
// </div>
|
||||
// );
|
37
ui/js/component/app/index.js
Normal file
37
ui/js/component/app/index.js
Normal file
|
@ -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)
|
65
ui/js/component/app/view.jsx
Normal file
65
ui/js/component/app/view.jsx
Normal file
|
@ -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' ?
|
||||
<Router /> :
|
||||
<div id="window" className={ drawerOpen ? 'drawer-open' : 'drawer-closed' }>
|
||||
<Drawer onCloseDrawer={closeDrawer} viewingPage={currentPage} />
|
||||
<div id="main-content" className={ headerLinks ? 'with-sub-nav' : 'no-sub-nav' }>
|
||||
<Header onOpenDrawer={openDrawer} initialQuery={searchQuery} onSearch={search} links={headerLinks} />
|
||||
<Router />
|
||||
</div>
|
||||
{modal == 'upgrade' && <UpgradeModal />}
|
||||
{modal == 'downloading' && <DownloadingModal />}
|
||||
{modal == 'error' && <ErrorModal />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default App
|
25
ui/js/component/downloadingModal/index.jsx
Normal file
25
ui/js/component/downloadingModal/index.jsx
Normal file
|
@ -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)
|
40
ui/js/component/downloadingModal/view.jsx
Normal file
40
ui/js/component/downloadingModal/view.jsx
Normal file
|
@ -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 (
|
||||
<Modal isOpen={true} contentLabel="Downloading Update" type="custom">
|
||||
Downloading Update{downloadProgress ? `: ${downloadProgress}%` : null}
|
||||
<Line percent={downloadProgress} strokeWidth="4"/>
|
||||
{downloadComplete ? (
|
||||
<div>
|
||||
<br />
|
||||
<p>Click "Begin Upgrade" to start the upgrade process.</p>
|
||||
<p>The app will close, and you will be prompted to install the latest version of LBRY.</p>
|
||||
<p>After the install is complete, please reopen the app.</p>
|
||||
</div>
|
||||
) : null }
|
||||
<div className="modal__buttons">
|
||||
{downloadComplete
|
||||
? <Link button="primary" label="Begin Upgrade" className="modal__button" onClick={startUpgrade} />
|
||||
: null}
|
||||
<Link button="alt" label="Cancel" className="modal__button" onClick={cancelUpgrade} />
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default DownloadingModal
|
29
ui/js/component/drawer/index.jsx
Normal file
29
ui/js/component/drawer/index.jsx
Normal file
|
@ -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)
|
68
ui/js/component/drawer/view.jsx
Normal file
68
ui/js/component/drawer/view.jsx
Normal file
|
@ -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 <Link icon={icon} label={label} onClick={() => 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(<nav id="drawer">
|
||||
<div id="drawer-handle">
|
||||
<Link title="Close" onClick={closeDrawerClick} icon="icon-bars" className="close-drawer-link"/>
|
||||
<a href="discover" onMouseUp={logoClick}><img src={lbry.imagePath("lbry-dark-1600x528.png")} style={drawerImageStyle}/></a>
|
||||
</div>
|
||||
<DrawerItem {...this.props} href='discover' label="Discover" icon="icon-search" />
|
||||
<DrawerItem {...this.props} href='publish' label="Publish" icon="icon-upload" />
|
||||
<DrawerItem {...this.props} href='downloaded' subPages={['published']} label="My Files" icon='icon-cloud-download' />
|
||||
<DrawerItem {...this.props} href="wallet" subPages={['send', 'receive', 'claim', 'referral']} label="My Wallet" badge={lbry.formatCredits(balance) } icon="icon-bank" />
|
||||
<DrawerItem {...this.props} href='settings' label="Settings" icon='icon-gear' />
|
||||
<DrawerItem {...this.props} href='help' label="Help" icon='icon-question-circle' />
|
||||
</nav>)
|
||||
}
|
||||
}
|
||||
|
||||
export default Drawer;
|
23
ui/js/component/errorModal/index.jsx
Normal file
23
ui/js/component/errorModal/index.jsx
Normal file
|
@ -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)
|
50
ui/js/component/errorModal/view.jsx
Normal file
50
ui/js/component/errorModal/view.jsx
Normal file
|
@ -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 = <ul className="error-modal__error-list"></ul>
|
||||
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(<li key={key}><strong>{label}</strong>: <code>{val}</code></li>);
|
||||
}
|
||||
|
||||
return(
|
||||
<ExpandableModal
|
||||
isOpen={modal == 'error'}
|
||||
contentLabel="Error" className="error-modal"
|
||||
overlayClassName="error-modal-overlay"
|
||||
onConfirmed={closeModal}
|
||||
extraContent={errorInfo}
|
||||
>
|
||||
<h3 className="modal__header">Error</h3>
|
||||
|
||||
<div className="error-modal__content">
|
||||
<div><img className="error-modal__warning-symbol" src={lbry.imagePath('warning.png')} /></div>
|
||||
<p>We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.</p>
|
||||
</div>
|
||||
</ExpandableModal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorModal
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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({
|
||||
|
|
7
ui/js/component/link/index.jsx
Normal file
7
ui/js/component/link/index.jsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
connect,
|
||||
} from 'react-redux'
|
||||
import Link from './view'
|
||||
|
||||
export default connect(null, null)(Link)
|
100
ui/js/component/link/view.js
Normal file
100
ui/js/component/link/view.js
Normal file
|
@ -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 = (
|
||||
<span {... 'button' in props ? {className: 'button__content'} : {}}>
|
||||
{'icon' in props ? <Icon icon={icon} fixed={true} /> : null}
|
||||
{label ? <span className="link-label">{label}</span> : null}
|
||||
{'badge' in props ? <span className="badge">{badge}</span> : null}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<a className={className} href={href || 'javascript:;'} title={title}
|
||||
onClick={onClick}
|
||||
{... 'style' in props ? {style: style} : {}}>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
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 = (
|
||||
// <span {... 'button' in this.props ? {className: 'button__content'} : {}}>
|
||||
// {'icon' in this.props ? <Icon icon={this.props.icon} fixed={true} /> : null}
|
||||
// {<span className="link-label">{this.props.label}</span>}
|
||||
// {'badge' in this.props ? <span className="badge">{this.props.badge}</span> : null}
|
||||
// </span>
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <a className={className} href={this.props.href || 'javascript:;'} title={this.props.title}
|
||||
// onClick={this.handleClick} {... 'style' in this.props ? {style: this.props.style} : {}}>
|
||||
// {content}
|
||||
// </a>
|
||||
// );
|
||||
// }
|
||||
// });
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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({
|
||||
|
|
15
ui/js/component/router/index.jsx
Normal file
15
ui/js/component/router/index.jsx
Normal file
|
@ -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);
|
51
ui/js/component/router/view.jsx
Normal file
51
ui/js/component/router/view.jsx
Normal file
|
@ -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': <SettingsPage {...props} />,
|
||||
'help': <HelpPage {...props} />,
|
||||
'watch': <WatchPage {...props} />,
|
||||
'report': <ReportPage {...props} />,
|
||||
'downloaded': <FileListDownloaded {...props} />,
|
||||
'published': <FileListPublished {...props} />,
|
||||
'start': <StartPage {...props} />,
|
||||
'claim': <ClaimCodePage {...props} />,
|
||||
'wallet': <WalletPage {...props} />,
|
||||
'send': <WalletPage {...props} />,
|
||||
'receive': <WalletPage {...props} />,
|
||||
'show': <DetailPage {...props} />,
|
||||
'publish': <PublishPage {...props} />,
|
||||
'developer': <DeveloperPage {...props} />,
|
||||
'discover': <DiscoverPage {...props} />,
|
||||
})
|
||||
}
|
||||
|
||||
export default Router
|
19
ui/js/component/upgradeModal/index.jsx
Normal file
19
ui/js/component/upgradeModal/index.jsx
Normal file
|
@ -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)
|
32
ui/js/component/upgradeModal/view.jsx
Normal file
32
ui/js/component/upgradeModal/view.jsx
Normal file
|
@ -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 (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
contentLabel="Update available"
|
||||
type="confirm"
|
||||
confirmButtonLabel="Upgrade"
|
||||
abortButtonLabel="Skip"
|
||||
onConfirmed={downloadUpgrade}
|
||||
onAborted={skipUpgrade}>
|
||||
Your version of LBRY is out of date and may be unreliable or insecure.
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default UpgradeModal
|
2
ui/js/config/development.js
Normal file
2
ui/js/config/development.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
module.exports = {
|
||||
}
|
21
ui/js/constants/action_types.js
Normal file
21
ui/js/constants/action_types.js
Normal file
|
@ -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'
|
|
@ -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(<div>{ lbryio.enabled ? <AuthOverlay/> : '' }<App /><SnackBar /></div>, canvas)
|
||||
ReactDOM.render(<Provider store={store}><div>{ lbryio.enabled ? <AuthOverlay/> : '' }<App /><SnackBar /></div></Provider>, canvas)
|
||||
}
|
||||
|
||||
if (window.sessionStorage.getItem('loaded') == 'y') {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 ' +
|
||||
|
|
|
@ -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';
|
||||
|
|
7
ui/js/page/help/index.jsx
Normal file
7
ui/js/page/help/index.jsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import HelpPage from './view'
|
||||
|
||||
export default connect(null, null)(HelpPage)
|
|
@ -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() {
|
|
@ -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({
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 (<div>
|
||||
<Link button={ this.props.button ? this.props.button : null }
|
||||
disabled={this.state.loading}
|
||||
label={this.props.label ? this.props.label : ""}
|
||||
className={this.props.className}
|
||||
icon="icon-play"
|
||||
onClick={this.onWatchClick} />
|
||||
<Modal contentLabel="Not enough credits" isOpen={this.state.modal == 'notEnoughCredits'} onConfirmed={this.closeModal}>
|
||||
You don't have enough LBRY credits to pay for this stream.
|
||||
</Modal>
|
||||
<Modal type="confirm" isOpen={this.state.modal == 'affirmPurchase'}
|
||||
contentLabel="Confirm Purchase" onConfirmed={this.play} onAborted={this.closeModal}>
|
||||
Are you sure you'd like to buy <strong>{this.props.metadata.title}</strong> for <strong><FilePrice uri={this.props.uri} metadata={this.props.metadata} label={false} look="plain" /></strong> credits?
|
||||
</Modal>
|
||||
</div>);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
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 (
|
||||
<div className={"video " + this.props.className + (this.state.isPlaying && this.state.readyToPlay ? " video--active" : " video--hidden")}>{
|
||||
this.state.isPlaying ?
|
||||
!this.state.readyToPlay ?
|
||||
<span>this is the world's worst loading screen and we shipped our software with it anyway... <br/><br/>{this.state.loadStatusMessage}</span> :
|
||||
<video controls id="video" ref="video"></video> :
|
||||
<div className="video__cover" style={{backgroundImage: 'url("' + this.props.metadata.thumbnail + '")'}}>
|
||||
<WatchLink className="video__play-button" uri={this.props.uri} metadata={this.props.metadata} outpoint={this.props.outpoint} onGet={this.onGet} icon="icon-play"></WatchLink>
|
||||
</div>
|
||||
}</div>
|
||||
);
|
||||
}
|
||||
})
|
105
ui/js/reducers/app.js
Normal file
105
ui/js/reducers/app.js
Normal file
|
@ -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;
|
||||
}
|
145
ui/js/selectors/app.js
Normal file
145
ui/js/selectors/app.js
Normal file
|
@ -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
|
||||
)
|
37
ui/js/store.js
Normal file
37
ui/js/store.js
Normal file
|
@ -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;
|
|
@ -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": {
|
||||
|
|
|
@ -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' ]
|
||||
|
|
|
@ -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' ]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue