Merge pull request #176 from lbryio/electron-2

Electron 2
This commit is contained in:
Job Evers‐Meltzer 2017-02-21 12:59:00 -06:00 committed by GitHub
commit fe954ed17d
18 changed files with 231 additions and 129 deletions

View file

@ -1,5 +1,23 @@
[bumpversion]
current_version = 0.1.1
current_version = 0.9.0rc6
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)((?P<release>[a-z]+)(?P<candidate>\d+))?
serialize =
{major}.{minor}.{patch}{release}{candidate}
{major}.{minor}.{patch}
[bumpversion:file:package.json]
[bumpversion:part:release]
optional_value = production
values =
rc
production
[bumpversion:file:CHANGELOG.md]
search = [Unreleased]
replace = [Unreleased]
\#\# [{new_version}] - {now:%Y-%m-%d}

View file

@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/).
The LBRY Web UI comes bundled as part of [LBRY App](https://github.com/lbryio/lbry-app). Web UI version numbers should always match the corresponding version of LBRY App.
The LBRY Web UI comes bundled as part of [LBRYApp](https://github.com/lbryio/lbry-app).
Web UI version numbers should always match the corresponding version of LBRY App.
## [Unreleased]
### Changed
* Use local file for publishing
* Use local file and html5 for video playback
* Misc changes needed to make UI compatible with electron

37
dist/quit.html vendored Normal file
View file

@ -0,0 +1,37 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>LBRY</title>
<link href='https://fonts.googleapis.com/css?family=Raleway:600,300' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400italic,600italic,600' rel='stylesheet' type='text/css'>
<link href="./css/all.css" rel="stylesheet" type="text/css" media="screen,print" />
<link href="./js/mediaelement/mediaelementplayer.css" rel="stylesheet" type="text/css" />
<link rel="icon" type="image/png" href="./img/fav/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="./img/fav/favicon-194x194.png" sizes="194x194">
<link rel="icon" type="image/png" href="./img/fav/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="./img/fav/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="./img/fav/favicon-16x16.png" sizes="16x16">
<meta name="msapplication-TileColor" content="#155B4A">
<meta name="msapplication-TileImage" content="/img/fav/mstile-144x144.png">
<meta name="theme-color" content="#155B4A">
<style>
body {
background-color: "#155b4a"
}
</style>
</head>
<div id="canvas">
<div class="load-screen" style="color: white; min-height: 100vh; min-width: 100vw; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<img src="./img/lbry-white-485x160.png" alt="LBRY">
<div style="margin-top: 24px; width: 325px; text-align: center;">
<h3>
<span>Shutting Down <span class="busy-indicator"></span>
</span>
</h3>
</div>
</div>
</div>
</html>

37
dist/warning.html vendored Normal file
View file

@ -0,0 +1,37 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>LBRY</title>
<link href='https://fonts.googleapis.com/css?family=Raleway:600,300' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400italic,600italic,600' rel='stylesheet' type='text/css'>
<link href="./css/all.css" rel="stylesheet" type="text/css" media="screen,print" />
<link href="./js/mediaelement/mediaelementplayer.css" rel="stylesheet" type="text/css" />
<link rel="icon" type="image/png" href="./img/fav/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="./img/fav/favicon-194x194.png" sizes="194x194">
<link rel="icon" type="image/png" href="./img/fav/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="./img/fav/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="./img/fav/favicon-16x16.png" sizes="16x16">
<meta name="msapplication-TileColor" content="#155B4A">
<meta name="msapplication-TileImage" content="/img/fav/mstile-144x144.png">
<meta name="theme-color" content="#155B4A">
<style>
body {
background-color: "#155b4a"
}
</style>
</head>
<div id="canvas">
<div class="load-screen" style="color: white; min-height: 100vh; min-width: 100vw; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<img src="./img/lbry-white-485x160.png" alt="LBRY">
<div style="margin-top: 24px; width: 325px; text-align: center;">
<h3>
<span>The daemon has unexpectedly shutdown. Goodbye.
</span>
</h3>
</div>
</div>
</div>
</html>

View file

@ -1,4 +1,6 @@
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';
@ -19,6 +21,11 @@ import Header from './component/header.js';
import Modal from './component/modal.js';
import {Link} from './component/link.js';
const {remote, ipcRenderer} = require('electron');
const {download} = remote.require('electron-dl');
var App = React.createClass({
_error_key_labels: {
connectionString: 'API connection string',
@ -45,6 +52,7 @@ var App = React.createClass({
modal: null,
updateUrl: null,
isOldOSX: null,
downloadProgress: null,
};
},
componentWillMount: function() {
@ -70,6 +78,9 @@ var App = React.createClass({
} else if (versionInfo.os_system == 'Linux') {
var updateUrl = 'https://lbry.io/get/lbry.deb';
} else if (versionInfo.os_system == 'Windows') {
// A little weird, but for electron, the installer is
// actually an exe. Maybe a better url would
// be something like /get/windows ?
var updateUrl = 'https://lbry.io/get/lbry.msi';
} else {
var updateUrl = 'https://lbry.io/get';
@ -97,8 +108,18 @@ var App = React.createClass({
});
},
handleUpgradeClicked: function() {
lbry.stop();
window.location = this.state.updateUrl;
// TODO: create a callback for onProgress and have the UI
// show download progress
// TODO: remove the saveAs popup. Thats just me being lazy and having
// some indication that the download is happening
// TODO: calling lbry.stop() ends up displaying the "daemon
// unexpectedly stopped" page. Have a better way of shutting down
let options = {
onProgress: (p) => this.setState({downloadProgress: Math.round(p * 100)}),
}
download(remote.getCurrentWindow(), this.state.updateUrl, options)
.then(dl => ipcRenderer.send('shutdown'));
this.setState({modal: 'downloading'});
},
handleSkipClicked: function() {
sessionStorage.setItem('upgradeSkipped', true);
@ -211,6 +232,11 @@ var App = React.createClass({
: null}
</Modal>
// TODO: have color refence css color-primary
<Modal isOpen={this.state.modal == 'downloading'} contentLabel="Downloading Update" type="custom">
Downloading Update: {this.state.downloadProgress}% Complete
<Line percent={this.state.downloadProgress} strokeWidth="4"/>
</Modal>
<Modal isOpen={this.state.modal == 'error'} contentLabel="Error" type="custom"
className="error-modal" overlayClassName="error-modal-overlay" >
<h3 className="modal__header">Error</h3>

View file

@ -98,7 +98,7 @@ export let Address = React.createClass({
});
export let Thumbnail = React.createClass({
_defaultImageUri: '/img/default-thumb.svg',
_defaultImageUri: lbry.imagePath('default-thumb.svg'),
_maxLoadTime: 10000,
_isMounted: false,

View file

@ -38,7 +38,7 @@ var Drawer = React.createClass({
<nav id="drawer">
<div id="drawer-handle">
<Link title="Close" onClick={this.props.onCloseDrawer} icon="icon-bars" className="close-drawer-link"/>
<a href="/"><img src="./img/lbry-dark-1600x528.png" style={drawerImageStyle}/></a>
<a href="/"><img src={lbry.imagePath("lbry-dark-1600x528.png")} style={drawerImageStyle}/></a>
</div>
<DrawerItem href='/?discover' viewingPage={this.props.viewingPage} label="Discover" icon="icon-search" />
<DrawerItem href='/?publish' viewingPage={this.props.viewingPage} label="Publish" icon="icon-upload" />

View file

@ -121,15 +121,15 @@ export let FileTileStream = React.createClass({
<section className={ 'file-tile card ' + (obscureNsfw ? 'card-obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<div className={"row-fluid card-content file-tile__row"}>
<div className="span3">
<a href={'/?show=' + this.props.name}><Thumbnail className="file-tile__thumbnail" src={metadata.thumbnail} alt={`Photo for ${title}`} /></a>
<a href={'/?show=' + this.props.name}><Thumbnail className="file-tile__thumbnail" src={metadata.thumbnail} alt={'Photo for ' + (title || this.props.name)} /></a>
</div>
<div className="span9">
{ !this.props.hidePrice
? <FilePrice name={this.props.name} />
: null}
<div className="meta"><a href={'/?show=' + this.props.name}>{'lbry://' + this.props.name}</a></div>
<div className="meta"><a href={'index.html?show=' + this.props.name}>{'lbry://' + this.props.name}</a></div>
<h3 className="file-tile__title">
<a href={'/?show=' + this.props.name}>
<a href={'index.html?show=' + this.props.name}>
<TruncatedText lines={1}>
{title}
</TruncatedText>
@ -196,4 +196,4 @@ export let FileTile = React.createClass({
return <FileTileStream sdHash={this.state.sdHash} metadata={this.state.metadata} {... this.props} />;
}
});
});

View file

@ -56,6 +56,9 @@ var FormField = React.createClass({
getValue: function() {
if (this.props.type == 'checkbox') {
return this.refs.field.checked;
}
else if (this.props.type == 'file') {
return this.refs.field.files[0].path;
} else {
return this.refs.field.value;
}

View file

@ -28,6 +28,7 @@ var SplashScreen = React.createClass({
});
lbry.resolveName('one', () => {
window.sessionStorage.setItem('loaded', 'y')
this.props.onLoadDone();
});
return;

View file

@ -13,24 +13,27 @@ var init = function() {
}
var canvas = document.getElementById('canvas');
ReactDOM.render(
<SplashScreen message="Connecting" onLoadDone={function() {
// Redirect to the claim code page if needed. Find somewhere better for this logic
if (!localStorage.getItem('claimCodeDone') && window.location.search == '' || window.location.search == '?' || window.location.search == 'discover') {
lbry.getBalance((balance) => {
if (balance <= 0) {
window.location.href = '?claim';
} else {
if (window.sessionStorage.getItem('loaded') == 'y') {
ReactDOM.render(<App/>, canvas)
} else {
ReactDOM.render(
<SplashScreen message="Connecting" onLoadDone={function() {
// Redirect to the claim code page if needed. Find somewhere better for this logic
if (!localStorage.getItem('claimCodeDone') && window.location.search == '' || window.location.search == '?' || window.location.search == 'discover') {
lbry.getBalance((balance) => {
if (balance <= 0) {
window.location.href = '?claim';
} else {
ReactDOM.render(<App/>, canvas);
}
});
} else {
ReactDOM.render(<App/>, canvas);
}
});
} else {
ReactDOM.render(<App/>, canvas);
}
}}/>,
canvas
);
}
}}/>,
canvas
);
}
};
init();

View file

@ -45,14 +45,7 @@ var PublishPage = React.createClass({
}
}
let fileProcessing = false;
if (this.state.fileInfo && !this.state.tempFileReady) {
this.refs.file.showAdvice('Your file is still processing.');
this.refs.file.focus();
fileProcessing = true;
}
if (missingFieldFound || fileProcessing) {
if (missingFieldFound) {
this.setState({
submitting: false,
});
@ -89,7 +82,7 @@ var PublishPage = React.createClass({
};
if (this.refs.file.getValue() !== '') {
publishArgs.file_path = this._tempFilePath;
publishArgs.file_path = this.refs.file.getValue();
}
lbry.publish(publishArgs, (message) => {
@ -114,8 +107,6 @@ var PublishPage = React.createClass({
}
},
getInitialState: function() {
this._tempFilePath = null;
return {
rawName: '',
name: '',
@ -127,14 +118,12 @@ var PublishPage = React.createClass({
myClaimValue: 0.0,
myClaimMetadata: null,
myClaimExists: null,
fileInfo: null,
copyrightNotice: '',
otherLicenseDescription: '',
otherLicenseUrl: '',
uploadProgress: 0.0,
uploaded: false,
errorMessage: null,
tempFileReady: false,
submitting: false,
modal: null,
};
@ -236,56 +225,6 @@ var PublishPage = React.createClass({
feeCurrency: event.target.value,
});
},
handleFileChange: function(event) {
event.preventDefault();
var fileInput = event.target;
this._tempFilePath = null;
if (fileInput.files.length == 0) {
// File was removed
this.setState({
fileInfo: null,
uploadProgress: 0.0,
uploaded: false,
tempFileReady: false,
});
} else {
var file = fileInput.files[0];
this.setState({
fileInfo: {
name: file.name,
size: file.size,
},
uploadProgress: 0.0,
uploaded: false,
tempFileReady: false,
});
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (event) => {
this.setState({
uploadProgress: (event.loaded / event.total),
});
});
xhr.upload.addEventListener('load', (event) => {
this.setState({
uploaded: true,
});
});
xhr.addEventListener('load', (event) => {
this._tempFilePath = JSON.parse(xhr.responseText);
this.setState({
tempFileReady: true,
});
})
var formData = new FormData(fileInput.form);
formData.append('file', fileInput.files[0]);
xhr.open('POST', lbry.webUiUri + '/upload', true);
xhr.send(formData);
}
},
handleFeePrefChange: function(feeEnabled) {
this.setState({
isFee: feeEnabled
@ -333,21 +272,6 @@ var PublishPage = React.createClass({
document.title = "Publish";
},
componentDidUpdate: function() {
if (this.state.fileInfo && !this.state.tempFileReady) {
// A file was chosen but the daemon hasn't finished processing it yet, i.e. it's loading, so
// we're displaying a progress bar and need a value for it.
// React can't unset the "value" prop (to show an "indeterminate" bar) after it's already
// been set, so we have to manage it manually.
if (!this.state.uploaded) {
// Still uploading
this.refs.progress.setAttribute('value', this.state.uploadProgress);
} else {
// Fully uploaded and waiting for server to finish processing, so set progress bar to "indeterminite"
this.refs.progress.removeAttribute('value');
}
}
},
// Also getting a type warning here too
render: function() {
@ -370,13 +294,7 @@ var PublishPage = React.createClass({
<section className="card">
<h4>Choose File</h4>
<FormField name="file" ref="file" type="file" onChange={this.handleFileChange} />
{ !this.state.fileInfo ? '' :
(!this.state.tempFileReady ? <div>
<progress ref='progress'></progress>
{!this.state.uploaded ? <span> Importing file into LBRY...</span> : <span> Processing file...</span>}
</div>
: <div>File ready for publishing!</div>) }
<FormField name="file" ref="file" type="file" />
{ this.state.myClaimExists ? <div className="help">If you don't choose a file, the file from your existing claim will be used.</div> : null }
</section>

View file

@ -2,6 +2,10 @@ import React from 'react';
import lbry from '../lbry.js';
import LoadScreen from '../component/load_screen.js'
const fs = require('fs');
const VideoStream = require('videostream');
var WatchPage = React.createClass({
propTypes: {
name: React.PropTypes.string,
@ -22,6 +26,7 @@ var WatchPage = React.createClass({
lbry.getFileStatus(this.props.name, (status) => {
if (!status || !['running', 'stopped'].includes(status.code) || 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
@ -33,11 +38,17 @@ var WatchPage = React.createClass({
readyToPlay: true,
mimeType: status.mime_type,
})
var player = new MediaElementPlayer(this.refs.player, {
mode: 'shim',
plugins: ['flash'],
setDimensions: false,
});
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)
}
}
var elem = this.refs.video;
var videostream = VideoStream(mediaFile, elem);
elem.play();
}
});
},
@ -46,8 +57,7 @@ var WatchPage = React.createClass({
!this.state.readyToPlay
? <LoadScreen message={'Loading video...'} details={this.state.loadStatusMessage} />
: <main className="full-screen">
<video ref="player" width="100%" height="100%">
<source type={(this.state.mimeType == 'audio/m4a' || this.state.mimeType == 'audio/mp4a-latm') ? 'video/mp4' : this.state.mimeType} src={lbry.webUiUri + '/view?name=' + this.props.name} />
<video controls width="100%" height="100%" id="video" ref="video">
</video>
</main>
);

View file

@ -1,6 +1,6 @@
{
"name": "lbry-web-ui",
"version": "1.0.0",
"version": "0.9.0rc6",
"description": "LBRY web ui",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
@ -25,9 +25,11 @@
"clamp-js-main": "^0.11.1",
"mediaelement": "^2.23.4",
"node-sass": "^3.8.0",
"rc-progress": "^2.0.6",
"react": "^15.4.0",
"react-dom": "^15.4.0",
"react-modal": "^1.5.2"
"react-modal": "^1.5.2",
"videostream": "^2.4.2"
},
"devDependencies": {
"babel": "^6.5.2",
@ -45,6 +47,7 @@
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.7.1",
"node-sass": "^3.13.0",
"webpack": "^1.13.3"
"webpack": "^1.13.3",
"webpack-target-electron-renderer": "^0.4.0"
}
}

View file

@ -2,7 +2,6 @@
.load-screen {
color: white;
background-image: url("/img/lbry-bg.png");
background-size: cover;
min-height: 100vh;
min-width: 100vw;

View file

@ -16,7 +16,7 @@ if [ ! -d "$DIR/node_modules" ]; then
fi
# run sass once without --watch to force update. then run with --watch to keep watching
$DIR/node_modules/.bin/node-sass --output $DIR/dist/css --sourcemap=none $DIR/scss/
$DIR/node_modules/.bin/node-sass --output $DIR/dist/css --sourcemap=none --watch $DIR/scss/ &
$DIR/node_modules/.bin/node-sass --output $DIR/../app/dist/css --sourcemap=none $DIR/scss/
$DIR/node_modules/.bin/node-sass --output $DIR/../app/dist/css --sourcemap=none --watch $DIR/scss/ &
node_modules/.bin/webpack --progress --colors --watch
node_modules/.bin/webpack --config webpack.dev.config.js --progress --colors --watch

View file

@ -25,13 +25,14 @@ module.exports = {
loaders: [
{ test: /\.css$/, loader: "style!css" },
{
test: /\.jsx?$/,
loader: 'babel',
test: /\.jsx?$/,
loader: 'babel',
query: {
cacheDirectory: true,
presets:[ 'es2015', 'react', 'stage-2' ]
}
}
]
}
},
target: 'electron-main',
};

41
webpack.dev.config.js Normal file
View file

@ -0,0 +1,41 @@
const path = require('path');
const PATHS = {
app: path.join(__dirname, 'app'),
dist: path.join(__dirname, '..', 'app', 'dist')
};
module.exports = {
entry: ['babel-polyfill', './js/main.js'],
output: {
path: path.join(PATHS.dist, 'js'),
publicPath: '/js/',
filename: "bundle.js",
pathinfo: true
},
debug: true,
cache: true,
devtool: 'eval',
module: {
preLoaders: [
{
test: /\.jsx?$/,
loaders: ['eslint'],
// define an include so we check just the files we need
include: PATHS.app
}
],
loaders: [
{ test: /\.css$/, loader: "style!css" },
{
test: /\.jsx?$/,
loader: 'babel',
query: {
cacheDirectory: true,
presets:[ 'es2015', 'react', 'stage-2' ]
}
}
]
},
target: 'electron-main',
};