diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a5f60a198..b24d9073c 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,23 @@ [bumpversion] -current_version = 0.1.1 +current_version = 0.9.0rc6 commit = True tag = True +parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\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} diff --git a/CHANGELOG.md b/CHANGELOG.md index 79341d400..6b0776f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ 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 [LBRY App](https://github.com/lbryio/lbry-app). Web UI version numbers track 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 diff --git a/dist/quit.html b/dist/quit.html new file mode 100644 index 000000000..5bed1d4a9 --- /dev/null +++ b/dist/quit.html @@ -0,0 +1,37 @@ + + + + + LBRY + + + + + + + + + + + + + + + + +
+
+ LBRY +
+

+ Shutting Down + +

+
+
+
+ diff --git a/dist/warning.html b/dist/warning.html new file mode 100644 index 000000000..d82276d94 --- /dev/null +++ b/dist/warning.html @@ -0,0 +1,37 @@ + + + + + LBRY + + + + + + + + + + + + + + + + +
+
+ LBRY +
+

+ The daemon has unexpectedly shutdown. Goodbye. + +

+
+
+
+ diff --git a/js/app.js b/js/app.js index 68c03a648..fdf8b1028 100644 --- a/js/app.js +++ b/js/app.js @@ -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} + // TODO: have color refence css color-primary + + Downloading Update: {this.state.downloadProgress}% Complete + +

Error

diff --git a/js/component/common.js b/js/component/common.js index afb110871..9e163476c 100644 --- a/js/component/common.js +++ b/js/component/common.js @@ -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, diff --git a/js/component/drawer.js b/js/component/drawer.js index c43239345..01b7d0ffc 100644 --- a/js/component/drawer.js +++ b/js/component/drawer.js @@ -38,15 +38,15 @@ var Drawer = React.createClass({ ); } diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 6cc2afddc..d6e79b860 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -121,15 +121,15 @@ export let FileTileStream = React.createClass({
- +
{ !this.props.hidePrice ? : null} - +

- + {title} @@ -196,4 +196,4 @@ export let FileTile = React.createClass({ return ; } -}); \ No newline at end of file +}); diff --git a/js/component/form.js b/js/component/form.js index ae4135a29..1d2b63bf6 100644 --- a/js/component/form.js +++ b/js/component/form.js @@ -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; } diff --git a/js/component/splash.js b/js/component/splash.js index 29cb65234..bba92e288 100644 --- a/js/component/splash.js +++ b/js/component/splash.js @@ -28,6 +28,7 @@ var SplashScreen = React.createClass({ }); lbry.resolveName('one', () => { + window.sessionStorage.setItem('loaded', 'y') this.props.onLoadDone(); }); return; diff --git a/js/main.js b/js/main.js index d5cb1ab57..e03313718 100644 --- a/js/main.js +++ b/js/main.js @@ -13,24 +13,27 @@ var init = function() { } var canvas = document.getElementById('canvas'); - - ReactDOM.render( - { - if (balance <= 0) { - window.location.href = '?claim'; - } else { + if (window.sessionStorage.getItem('loaded') == 'y') { + ReactDOM.render(, canvas) + } else { + ReactDOM.render( + { + if (balance <= 0) { + window.location.href = '?claim'; + } else { + ReactDOM.render(, canvas); + } + }); + } else { ReactDOM.render(, canvas); - } - }); - } else { - ReactDOM.render(, canvas); - } - }}/>, - canvas - ); + } + }}/>, + canvas + ); + } }; init(); diff --git a/js/page/publish.js b/js/page/publish.js index d1d48bc3a..cf2d6f45d 100644 --- a/js/page/publish.js +++ b/js/page/publish.js @@ -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({

Choose File

- - { !this.state.fileInfo ? '' : - (!this.state.tempFileReady ?
- - {!this.state.uploaded ? Importing file into LBRY... : Processing file...} -
- :
File ready for publishing!
) } + { this.state.myClaimExists ?
If you don't choose a file, the file from your existing claim will be used.
: null }
diff --git a/js/page/watch.js b/js/page/watch.js index e9b3a9185..640d43ac2 100644 --- a/js/page/watch.js +++ b/js/page/watch.js @@ -1,8 +1,18 @@ import React from 'react'; +import {Icon} from '../component/common.js'; +import {Link} from '../component/link.js'; import lbry from '../lbry.js'; import LoadScreen from '../component/load_screen.js' +const fs = require('fs'); +const VideoStream = require('videostream'); + + var WatchPage = 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: { name: React.PropTypes.string, }, @@ -12,19 +22,55 @@ var WatchPage = React.createClass({ readyToPlay: false, loadStatusMessage: "Requesting stream", mimeType: null, + controlsShown: false, }; }, componentDidMount: function() { lbry.getStream(this.props.name); this.updateLoadStatus(); }, + handleBackClicked: function() { + history.back(); + }, + 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); + }, + handleMouseOut: function() { + if (this._controlsTimeout) { + clearTimeout(this._controlsTimeout); + } + + if (this.state.controlsShown) { + this.setState({ + controlsShown: false, + }); + } + }, updateLoadStatus: function() { 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 + loadStatusMessage: status.message, }); } setTimeout(() => { this.updateLoadStatus() }, 250); @@ -33,11 +79,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(); } }); }, @@ -45,10 +97,22 @@ var WatchPage = React.createClass({ return ( !this.state.readyToPlay ? - :
-