diff --git a/renderer/index.js b/renderer/index.js index a4d21dd0..a0e12396 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -17,10 +17,10 @@ var diff = require('virtual-dom/diff') var patch = require('virtual-dom/patch') var App = require('./views/app') -var config = require('../config') -var torrentPoster = require('./lib/torrent-poster') -var TorrentPlayer = require('./lib/torrent-player') var Cast = require('./lib/cast') +var config = require('../config') +var TorrentPlayer = require('./lib/torrent-player') +var torrentPoster = require('./lib/torrent-poster') // Electron apps have two processes: a main process (node) runs first and starts // a renderer process (essentially a Chrome window). We're in the renderer process, @@ -48,6 +48,8 @@ loadState(init) * the dock icon and drag+drop. */ function init () { + state.location.go({ url: 'home' }) + // Connect to the WebTorrent and BitTorrent networks // WebTorrent.app is a hybrid client, as explained here: https://webtorrent.io/faq state.client = new WebTorrent() @@ -158,7 +160,7 @@ function updateElectron () { // Events from the UI never modify state directly. Instead they call dispatch() function dispatch (action, ...args) { - if (['videoMouseMoved', 'playbackJump'].indexOf(action) < 0) { + if (['videoMouseMoved', 'playbackJump'].indexOf(action) === -1) { console.log('dispatch: %s %o', action, args) /* log user interactions, but don't spam */ } if (action === 'onOpen') { @@ -174,8 +176,14 @@ function dispatch (action, ...args) { seed(args[0] /* files */) } if (action === 'play') { - // TODO: handle audio. video only for now. - openPlayer(args[0] /* torrentSummary */, args[1] /* index */) + state.location.go({ + url: 'player', + onbeforeload: function (cb) { + // TODO: handle audio. video only for now. + openPlayer(args[0] /* torrentSummary */, args[1] /* index */, cb) + }, + onbeforeunload: closePlayer + }) } if (action === 'openFile') { openFile(args[0] /* torrentSummary */, args[1] /* index */) @@ -205,14 +213,12 @@ function dispatch (action, ...args) { setDimensions(args[0] /* dimensions */) } if (action === 'back') { - // TODO - // window.history.back() - ipcRenderer.send('unblockPowerSave') - closePlayer() + state.location.back() + update() } if (action === 'forward') { - // TODO - // window.history.forward() + state.location.forward() + update() } if (action === 'playPause') { playPause() @@ -567,7 +573,7 @@ function stopServer () { } // Opens the video player -function openPlayer (torrentSummary, index) { +function openPlayer (torrentSummary, index, cb) { var torrent = state.client.get(torrentSummary.infoHash) if (!torrent || !torrent.done) playInterfaceSound(config.SOUND_PLAY) torrentSummary.playStatus = 'requested' @@ -589,9 +595,9 @@ function openPlayer (torrentSummary, index) { if (timedOut) return // otherwise, play the video - state.url = 'player' state.window.title = torrentSummary.name update() + cb() }) } @@ -618,18 +624,20 @@ function openFolder (torrentSummary) { }) } -function closePlayer () { - state.url = 'home' +function closePlayer (cb) { state.window.title = config.APP_NAME update() if (state.window.isFullScreen) { dispatch('toggleFullScreen', false) } - restoreBounds() stopServer() update() + + ipcRenderer.send('unblockPowerSave') + + cb() } function toggleTorrent (torrentSummary) { @@ -652,6 +660,7 @@ function deleteTorrent (torrentSummary) { var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash) if (index > -1) state.saved.torrents.splice(index, 1) saveState() + state.location.clearForward() // prevent user from going forward to a deleted torrent playInterfaceSound(config.SOUND_DELETE) } diff --git a/renderer/lib/location-history.js b/renderer/lib/location-history.js new file mode 100644 index 00000000..09122948 --- /dev/null +++ b/renderer/lib/location-history.js @@ -0,0 +1,61 @@ +module.exports = LocationHistory + +function LocationHistory () { + if (!new.target) return new LocationHistory() + this._history = [] + this._forward = [] +} + +LocationHistory.prototype.go = function (page) { + console.log('go', page) + this.clearForward() + this._go(page) +} + +LocationHistory.prototype._go = function (page) { + if (page.onbeforeload) { + page.onbeforeload((err) => { + if (err) return + this._history.push(page) + }) + } else { + this._history.push(page) + } +} + +LocationHistory.prototype.back = function () { + if (this._history.length <= 1) return + + var page = this._history.pop() + + if (page.onbeforeunload) { + page.onbeforeunload(() => { + this._forward.push(page) + }) + } else { + this._forward.push(page) + } +} + +LocationHistory.prototype.forward = function () { + if (this._forward.length === 0) return + + var page = this._forward.pop() + this._go(page) +} + +LocationHistory.prototype.clearForward = function () { + this._forward = [] +} + +LocationHistory.prototype.current = function () { + return this._history[this._history.length - 1] +} + +LocationHistory.prototype.hasBack = function () { + return this._history.length > 1 +} + +LocationHistory.prototype.hasForward = function () { + return this._forward.length > 0 +} diff --git a/renderer/state.js b/renderer/state.js index 312233ec..200237c5 100644 --- a/renderer/state.js +++ b/renderer/state.js @@ -2,6 +2,7 @@ var os = require('os') var path = require('path') var config = require('../config') +var LocationHistory = require('./lib/location-history') module.exports = { /* @@ -11,9 +12,9 @@ module.exports = { client: null, /* the WebTorrent client */ server: null, /* local WebTorrent-to-HTTP server */ prev: {}, /* used for state diffing in updateElectron() */ - url: 'home', + location: new LocationHistory(), window: { - bounds: null, /* x y width height */ + bounds: null, /* {x, y, width, height } */ isFocused: true, isFullScreen: false, title: config.APP_NAME /* current window title */ diff --git a/renderer/views/app.js b/renderer/views/app.js index 8f9ea424..7de7976d 100644 --- a/renderer/views/app.js +++ b/renderer/views/app.js @@ -17,7 +17,7 @@ function App (state, dispatch) { // * The mouse is over the controls or we're scrubbing (see CSS) // * The video is paused // * The video is playing remotely on Chromecast or Airplay - var hideControls = state.url === 'player' && + var hideControls = state.location.current().url === 'player' && state.video.mouseStationarySince !== 0 && new Date().getTime() - state.video.mouseStationarySince > 2000 && !state.video.isPaused && @@ -25,10 +25,10 @@ function App (state, dispatch) { // Hide the header on Windows/Linux when in the player // On OSX, the header appears as part of the title bar - var hideHeader = process.platform !== 'darwin' && state.url === 'player' + var hideHeader = process.platform !== 'darwin' && state.location.current().url === 'player' var cls = [ - 'view-' + state.url, /* e.g. view-home, view-player */ + 'view-' + state.location.current().url, /* e.g. view-home, view-player */ 'is-' + process.platform /* e.g. is-darwin, is-win32, is-linux */ ] if (state.window.isFullScreen) cls.push('is-fullscreen') @@ -75,9 +75,9 @@ function App (state, dispatch) { } function getView () { - if (state.url === 'home') { + if (state.location.current().url === 'home') { return TorrentList(state, dispatch) - } else if (state.url === 'player') { + } else if (state.location.current().url === 'player') { return Player(state, dispatch) } } diff --git a/renderer/views/header.js b/renderer/views/header.js index 382e99d5..d2dbf1e3 100644 --- a/renderer/views/header.js +++ b/renderer/views/header.js @@ -9,14 +9,14 @@ function Header (state, dispatch) {