Implement back/forward buttons (fix #180)

This commit is contained in:
Feross Aboukhadijeh
2016-03-20 22:54:51 -07:00
parent 42ea368c68
commit ce2a9ceb11
5 changed files with 100 additions and 29 deletions

View File

@@ -17,10 +17,10 @@ var diff = require('virtual-dom/diff')
var patch = require('virtual-dom/patch') var patch = require('virtual-dom/patch')
var App = require('./views/app') 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 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 // 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, // 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. * the dock icon and drag+drop.
*/ */
function init () { function init () {
state.location.go({ url: 'home' })
// Connect to the WebTorrent and BitTorrent networks // Connect to the WebTorrent and BitTorrent networks
// WebTorrent.app is a hybrid client, as explained here: https://webtorrent.io/faq // WebTorrent.app is a hybrid client, as explained here: https://webtorrent.io/faq
state.client = new WebTorrent() state.client = new WebTorrent()
@@ -158,7 +160,7 @@ function updateElectron () {
// Events from the UI never modify state directly. Instead they call dispatch() // Events from the UI never modify state directly. Instead they call dispatch()
function dispatch (action, ...args) { 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 */ console.log('dispatch: %s %o', action, args) /* log user interactions, but don't spam */
} }
if (action === 'onOpen') { if (action === 'onOpen') {
@@ -174,8 +176,14 @@ function dispatch (action, ...args) {
seed(args[0] /* files */) seed(args[0] /* files */)
} }
if (action === 'play') { if (action === 'play') {
// TODO: handle audio. video only for now. state.location.go({
openPlayer(args[0] /* torrentSummary */, args[1] /* index */) 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') { if (action === 'openFile') {
openFile(args[0] /* torrentSummary */, args[1] /* index */) openFile(args[0] /* torrentSummary */, args[1] /* index */)
@@ -205,14 +213,12 @@ function dispatch (action, ...args) {
setDimensions(args[0] /* dimensions */) setDimensions(args[0] /* dimensions */)
} }
if (action === 'back') { if (action === 'back') {
// TODO state.location.back()
// window.history.back() update()
ipcRenderer.send('unblockPowerSave')
closePlayer()
} }
if (action === 'forward') { if (action === 'forward') {
// TODO state.location.forward()
// window.history.forward() update()
} }
if (action === 'playPause') { if (action === 'playPause') {
playPause() playPause()
@@ -567,7 +573,7 @@ function stopServer () {
} }
// Opens the video player // Opens the video player
function openPlayer (torrentSummary, index) { function openPlayer (torrentSummary, index, cb) {
var torrent = state.client.get(torrentSummary.infoHash) var torrent = state.client.get(torrentSummary.infoHash)
if (!torrent || !torrent.done) playInterfaceSound(config.SOUND_PLAY) if (!torrent || !torrent.done) playInterfaceSound(config.SOUND_PLAY)
torrentSummary.playStatus = 'requested' torrentSummary.playStatus = 'requested'
@@ -589,9 +595,9 @@ function openPlayer (torrentSummary, index) {
if (timedOut) return if (timedOut) return
// otherwise, play the video // otherwise, play the video
state.url = 'player'
state.window.title = torrentSummary.name state.window.title = torrentSummary.name
update() update()
cb()
}) })
} }
@@ -618,18 +624,20 @@ function openFolder (torrentSummary) {
}) })
} }
function closePlayer () { function closePlayer (cb) {
state.url = 'home'
state.window.title = config.APP_NAME state.window.title = config.APP_NAME
update() update()
if (state.window.isFullScreen) { if (state.window.isFullScreen) {
dispatch('toggleFullScreen', false) dispatch('toggleFullScreen', false)
} }
restoreBounds() restoreBounds()
stopServer() stopServer()
update() update()
ipcRenderer.send('unblockPowerSave')
cb()
} }
function toggleTorrent (torrentSummary) { function toggleTorrent (torrentSummary) {
@@ -652,6 +660,7 @@ function deleteTorrent (torrentSummary) {
var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash) var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash)
if (index > -1) state.saved.torrents.splice(index, 1) if (index > -1) state.saved.torrents.splice(index, 1)
saveState() saveState()
state.location.clearForward() // prevent user from going forward to a deleted torrent
playInterfaceSound(config.SOUND_DELETE) playInterfaceSound(config.SOUND_DELETE)
} }

View File

@@ -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
}

View File

@@ -2,6 +2,7 @@ var os = require('os')
var path = require('path') var path = require('path')
var config = require('../config') var config = require('../config')
var LocationHistory = require('./lib/location-history')
module.exports = { module.exports = {
/* /*
@@ -11,9 +12,9 @@ module.exports = {
client: null, /* the WebTorrent client */ client: null, /* the WebTorrent client */
server: null, /* local WebTorrent-to-HTTP server */ server: null, /* local WebTorrent-to-HTTP server */
prev: {}, /* used for state diffing in updateElectron() */ prev: {}, /* used for state diffing in updateElectron() */
url: 'home', location: new LocationHistory(),
window: { window: {
bounds: null, /* x y width height */ bounds: null, /* {x, y, width, height } */
isFocused: true, isFocused: true,
isFullScreen: false, isFullScreen: false,
title: config.APP_NAME /* current window title */ title: config.APP_NAME /* current window title */

View File

@@ -17,7 +17,7 @@ function App (state, dispatch) {
// * The mouse is over the controls or we're scrubbing (see CSS) // * The mouse is over the controls or we're scrubbing (see CSS)
// * The video is paused // * The video is paused
// * The video is playing remotely on Chromecast or Airplay // * 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 && state.video.mouseStationarySince !== 0 &&
new Date().getTime() - state.video.mouseStationarySince > 2000 && new Date().getTime() - state.video.mouseStationarySince > 2000 &&
!state.video.isPaused && !state.video.isPaused &&
@@ -25,10 +25,10 @@ function App (state, dispatch) {
// Hide the header on Windows/Linux when in the player // Hide the header on Windows/Linux when in the player
// On OSX, the header appears as part of the title bar // 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 = [ 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 */ 'is-' + process.platform /* e.g. is-darwin, is-win32, is-linux */
] ]
if (state.window.isFullScreen) cls.push('is-fullscreen') if (state.window.isFullScreen) cls.push('is-fullscreen')
@@ -75,9 +75,9 @@ function App (state, dispatch) {
} }
function getView () { function getView () {
if (state.url === 'home') { if (state.location.current().url === 'home') {
return TorrentList(state, dispatch) return TorrentList(state, dispatch)
} else if (state.url === 'player') { } else if (state.location.current().url === 'player') {
return Player(state, dispatch) return Player(state, dispatch)
} }
} }

View File

@@ -9,14 +9,14 @@ function Header (state, dispatch) {
<div class='header'> <div class='header'>
${getTitle()} ${getTitle()}
<div class='nav left'> <div class='nav left'>
<i <i.icon.back
class='icon back' class=${state.location.hasBack() ? '' : 'disabled'}
title='back' title='back'
onclick=${() => dispatch('back')}> onclick=${() => dispatch('back')}>
chevron_left chevron_left
</i> </i>
<i <i.icon.forward
class='icon forward' class=${state.location.hasForward() ? '' : 'disabled'}
title='forward' title='forward'
onclick=${() => dispatch('forward')}> onclick=${() => dispatch('forward')}>
chevron_right chevron_right
@@ -35,7 +35,7 @@ function Header (state, dispatch) {
} }
function getAddButton () { function getAddButton () {
if (state.url !== 'player') { if (state.location.current().url !== 'player') {
return hx` return hx`
<i <i
class='icon add' class='icon add'