Compare commits

..

46 Commits

Author SHA1 Message Date
Feross Aboukhadijeh
70bc32614b 0.11.0 2016-08-19 16:24:20 -07:00
Feross Aboukhadijeh
9bf44d7d7e Merge pull request #784 from feross/plist-2
plist@2
2016-08-20 01:21:27 +02:00
Feross Aboukhadijeh
f48ecb87b2 plist@2
Looks like there are no important changes. They just deleted some
deprecated methods we don't use.
2016-08-19 16:19:05 -07:00
Feross Aboukhadijeh
1765aba681 CHANGELOG 2016-08-19 16:15:25 -07:00
Feross Aboukhadijeh
c6063c759e Merge pull request #783 from feross/standard-v8
update to standard v8
2016-08-19 10:46:13 +02:00
Feross Aboukhadijeh
bb4db2cede update to standard v8
The only thing we have to change is to self-close tags that don't
contain anything. This wasn't even an explicit change in standard. It
was already a rule, but I think it wasn't getting enforced very well
until a bugfix.
2016-08-19 01:44:28 -07:00
DC
7c36898f78 Merge pull request #674 from codealchemist/open-in-vlc-preferences
Added Playback preferences and Play in VLC
2016-08-19 00:49:37 -07:00
Alberto Miranda
23e8cdf216 using key on rendered checkbox to avoid react errors; dispatching updatePreferences to update playInVlc. 2016-08-19 01:10:55 -03:00
Alberto Miranda
5ffd4123a1 fixed merge conflicts 2016-08-19 01:09:43 -03:00
Alberto Miranda
27e3c14f10 merged 2016-08-19 00:28:07 -03:00
Feross Aboukhadijeh
d57bfb825a Merge pull request #776 from feross/dc/missing-path
Check for missing download path
2016-08-15 01:10:05 +02:00
DC
0809e20a6e Check path for each torrent 2016-08-13 22:37:14 -07:00
DC
1ec305162e Check for missing download path
Fixes #646
2016-08-12 20:57:03 -07:00
Feross Aboukhadijeh
45d46d7ae8 show title on 'create new torrent' page 2016-08-12 15:35:56 -07:00
Feross Aboukhadijeh
adb41736d5 Merge pull request #775 from feross/dc/768
Create Torrent: make trackers editable again
2016-08-13 00:08:38 +02:00
DC
09d6fa550a Handle Save Torrent File As... -> Cancel 2016-08-12 09:31:28 -07:00
DC
75cc7383cb Create Torrent: make trackers editable again
Fixes #768
2016-08-12 09:21:12 -07:00
DC
4d48b9e7c1 Fix screen stacking bug
You can no longer open a whole stack of Prefs windows, or Create Torrent windows

Simplifies and fixes behavior when dropping files onto the app or the dock icon. Before, you could use drag-drop to create stacks of Create Torrent windows. Now, you can only create torrents from the home screen.

Fixes #665
2016-08-12 09:03:32 -07:00
Feross Aboukhadijeh
563e1ca0ba Support for instant.io links does not belong in webtorrent core 2016-08-11 00:20:07 -07:00
Feross Aboukhadijeh
0fa3b678b0 Merge pull request #772 from feross/electron-1.3.3
Update Electron to 1.3.3
2016-08-11 06:54:51 +02:00
Feross Aboukhadijeh
8420c65d25 Merge pull request #771 from feross/dc/file-handler
Pref: default torrent file handler
2016-08-11 02:27:54 +02:00
Feross Aboukhadijeh
3232e96f6e Resize the window's content area
Fixes: https://github.com/feross/webtorrent-desktop/issues/565

This was trivial thanks to a new Electron API in 1.3.3
2016-08-10 16:48:32 -07:00
Feross Aboukhadijeh
110e25af73 electron-prebuilt@1.3.3 2016-08-10 16:47:58 -07:00
DC
8233faf518 Pref: default torrent file handler
Before, the app made itself the default torrent file handler automatically, pissing off some of our users. Now, it's not by default, and you can change it in the prefs.
2016-08-10 02:23:08 -07:00
Alberto Miranda
39ae0343fc Merge branch 'master' of https://github.com/feross/webtorrent-desktop 2016-08-04 08:10:31 -03:00
Alberto Miranda
c637878603 removed logging; restored minimist in package.js ignore list. 2016-07-29 23:01:17 -03:00
Alberto Miranda
91e61f6cd4 using icon as checkbox 2016-07-29 22:07:44 -03:00
Alberto Miranda
9f66418073 merged with latest master; using icon as checkbox. 2016-07-29 22:07:25 -03:00
Alberto Miranda
2c3d667675 Merge remote-tracking branch 'feross/master' 2016-07-29 20:49:29 -03:00
Alberto Miranda
dc7ccb3956 Merge remote-tracking branch 'feross/master' 2016-07-20 09:10:55 -03:00
Alberto Miranda
a420936657 Merge branch 'master' into open-in-vlc-preferences 2016-07-19 02:11:13 -03:00
Alberto Miranda
dcab7f72d4 fixed error with minimist not being loaded on build 2016-07-19 02:10:37 -03:00
Alberto Miranda
a695f7c2d7 using this.state 2016-07-17 21:05:24 -03:00
Alberto Miranda
7677bff6d4 merged with latest master 2016-07-17 20:57:06 -03:00
Alberto Miranda
c7626997de merged with latest master 2016-07-17 20:48:25 -03:00
Alberto Miranda
91a1ab4a56 removed unused config property 2016-06-27 08:21:30 -03:00
Alberto Miranda
3e19cdfb0b fixed js style 2016-06-25 17:40:47 -03:00
Alberto Miranda
2043dc2161 added Playback preferences; added Play in VLC preference. 2016-06-25 17:36:50 -03:00
Alberto Miranda
a9e36472c5 fixed js style 2016-06-24 09:38:08 -03:00
Alberto Miranda
4df4f9b2ad fixed js style 2016-06-24 09:34:54 -03:00
Alberto Miranda
4ad55173a5 added missing method to menu.js 2016-06-24 09:34:12 -03:00
Alberto Miranda
b9c82dd6b2 persisting and reloading "Open in VLC" menu item state. 2016-06-24 09:28:28 -03:00
Alberto Miranda
8333f4893f fixed js style 2016-06-24 01:02:33 -03:00
Alberto Miranda
f071965ae8 fixed js style 2016-06-24 00:57:33 -03:00
Alberto Miranda
a4fa9ac666 added open in vlc feature. 2016-06-24 00:46:42 -03:00
Alberto Miranda
939ee555b7 fixed typo in buttonIcons (dnla instead of dlna). 2016-06-23 23:45:54 -03:00
21 changed files with 433 additions and 176 deletions

View File

@@ -1,5 +1,26 @@
# WebTorrent Desktop Version History # WebTorrent Desktop Version History
## v0.11.0 - 2016-08-19
### Added
- New Preference to "Set WebTorrent as default handler for torrents and magnet links" (#771)
- New Preference to "Always play in VLC" (#674)
- Check for missing default download path and torrent folders on start up (#776)
### Changed
- Do not automatically set WebTorrent as the default handler for torrents (#771)
- Torrents can only be created from the home screen (#770)
- Update Electron to 1.3.3 (#772)
### Fixed
- Allow modifying the default tracker list on the Create Torrent page (#775)
- Prevent opening multiple stacked Preference windows or Create Torrent windows (#770)
- Windows: Player window auto-resize does not match video aspect ratio (#565)
- Missing page title on Create Torrent page
## v0.10.0 - 2016-08-05 ## v0.10.0 - 2016-08-05
### Added ### Added

View File

@@ -1,7 +1,7 @@
{ {
"name": "webtorrent-desktop", "name": "webtorrent-desktop",
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.", "description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
"version": "0.10.0", "version": "0.11.0",
"author": { "author": {
"name": "WebTorrent, LLC", "name": "WebTorrent, LLC",
"email": "feross@webtorrent.io", "email": "feross@webtorrent.io",
@@ -22,7 +22,7 @@
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
"dlnacasts": "^0.1.0", "dlnacasts": "^0.1.0",
"drag-drop": "^2.12.1", "drag-drop": "^2.12.1",
"electron-prebuilt": "1.3.2", "electron-prebuilt": "1.3.3",
"fs-extra": "^0.30.0", "fs-extra": "^0.30.0",
"hat": "0.0.3", "hat": "0.0.3",
"iso-639-1": "^1.2.1", "iso-639-1": "^1.2.1",
@@ -57,10 +57,10 @@
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"nobin-debian-installer": "^0.0.10", "nobin-debian-installer": "^0.0.10",
"open": "0.0.5", "open": "0.0.5",
"plist": "^1.2.0", "plist": "^2.0.1",
"rimraf": "^2.5.2", "rimraf": "^2.5.2",
"run-series": "^1.1.4", "run-series": "^1.1.4",
"standard": "^7.0.0" "standard": "^8.0.0-beta.5"
}, },
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=4.0.0"

View File

@@ -10,7 +10,6 @@ var config = require('../config')
var crashReporter = require('../crash-reporter') var crashReporter = require('../crash-reporter')
var dialog = require('./dialog') var dialog = require('./dialog')
var dock = require('./dock') var dock = require('./dock')
var handlers = require('./handlers')
var ipc = require('./ipc') var ipc = require('./ipc')
var log = require('./log') var log = require('./log')
var menu = require('./menu') var menu = require('./menu')
@@ -111,7 +110,6 @@ function init () {
function delayedInit () { function delayedInit () {
announcement.init() announcement.init()
dock.init() dock.init()
handlers.install()
tray.init() tray.init()
updater.init() updater.init()
userTasks.init() userTasks.init()

View File

@@ -8,6 +8,7 @@ var app = electron.app
var dialog = require('./dialog') var dialog = require('./dialog')
var dock = require('./dock') var dock = require('./dock')
var handlers = require('./handlers')
var log = require('./log') var log = require('./log')
var menu = require('./menu') var menu = require('./menu')
var powerSaveBlocker = require('./power-save-blocker') var powerSaveBlocker = require('./power-save-blocker')
@@ -60,14 +61,14 @@ function init () {
*/ */
ipc.on('onPlayerOpen', function () { ipc.on('onPlayerOpen', function () {
menu.onPlayerOpen() menu.setPlayerOpen(true)
powerSaveBlocker.enable() powerSaveBlocker.enable()
shortcuts.enable() shortcuts.enable()
thumbar.enable() thumbar.enable()
}) })
ipc.on('onPlayerClose', function () { ipc.on('onPlayerClose', function () {
menu.onPlayerClose() menu.setPlayerOpen(false)
powerSaveBlocker.disable() powerSaveBlocker.disable()
shortcuts.disable() shortcuts.disable()
thumbar.disable() thumbar.disable()
@@ -91,6 +92,14 @@ function init () {
ipc.on('showItemInFolder', (e, ...args) => shell.showItemInFolder(...args)) ipc.on('showItemInFolder', (e, ...args) => shell.showItemInFolder(...args))
ipc.on('moveItemToTrash', (e, ...args) => shell.moveItemToTrash(...args)) ipc.on('moveItemToTrash', (e, ...args) => shell.moveItemToTrash(...args))
/**
* File handlers
*/
ipc.on('setDefaultFileHandler', (e, flag) => {
if (flag) handlers.install()
else handlers.uninstall()
})
/** /**
* Windows: Main * Windows: Main
*/ */
@@ -103,6 +112,7 @@ function init () {
ipc.on('setTitle', (e, ...args) => main.setTitle(...args)) ipc.on('setTitle', (e, ...args) => main.setTitle(...args))
ipc.on('show', () => main.show()) ipc.on('show', () => main.show())
ipc.on('toggleFullScreen', (e, ...args) => main.toggleFullScreen(...args)) ipc.on('toggleFullScreen', (e, ...args) => main.toggleFullScreen(...args))
ipc.on('setAllowNav', (e, ...args) => menu.setAllowNav(...args))
/** /**
* VLC * VLC

View File

@@ -1,11 +1,10 @@
module.exports = { module.exports = {
init, init,
onPlayerClose, setPlayerOpen,
onPlayerOpen, setWindowFocus,
setAllowNav,
onToggleAlwaysOnTop, onToggleAlwaysOnTop,
onToggleFullScreen, onToggleFullScreen
onWindowBlur,
onWindowFocus
} }
var electron = require('electron') var electron = require('electron')
@@ -24,26 +23,28 @@ function init () {
electron.Menu.setApplicationMenu(menu) electron.Menu.setApplicationMenu(menu)
} }
function onPlayerClose () { function setPlayerOpen (flag) {
getMenuItem('Play/Pause').enabled = false getMenuItem('Play/Pause').enabled = flag
getMenuItem('Increase Volume').enabled = false getMenuItem('Increase Volume').enabled = flag
getMenuItem('Decrease Volume').enabled = false getMenuItem('Decrease Volume').enabled = flag
getMenuItem('Step Forward').enabled = false getMenuItem('Step Forward').enabled = flag
getMenuItem('Step Backward').enabled = false getMenuItem('Step Backward').enabled = flag
getMenuItem('Increase Speed').enabled = false getMenuItem('Increase Speed').enabled = flag
getMenuItem('Decrease Speed').enabled = false getMenuItem('Decrease Speed').enabled = flag
getMenuItem('Add Subtitles File...').enabled = false getMenuItem('Add Subtitles File...').enabled = flag
} }
function onPlayerOpen () { function setWindowFocus (flag) {
getMenuItem('Play/Pause').enabled = true getMenuItem('Full Screen').enabled = flag
getMenuItem('Increase Volume').enabled = true getMenuItem('Float on Top').enabled = flag
getMenuItem('Decrease Volume').enabled = true }
getMenuItem('Step Forward').enabled = true
getMenuItem('Step Backward').enabled = true // Disallow opening more screens on top of the current one.
getMenuItem('Increase Speed').enabled = true function setAllowNav (flag) {
getMenuItem('Decrease Speed').enabled = true getMenuItem('Preferences').enabled = flag
getMenuItem('Add Subtitles File...').enabled = true getMenuItem('Create New Torrent...').enabled = flag
var item = getMenuItem('Create New Torrent from File...')
if (item) item.enabled = flag
} }
function onToggleAlwaysOnTop (flag) { function onToggleAlwaysOnTop (flag) {
@@ -54,16 +55,6 @@ function onToggleFullScreen (flag) {
getMenuItem('Full Screen').checked = flag getMenuItem('Full Screen').checked = flag
} }
function onWindowBlur () {
getMenuItem('Full Screen').enabled = false
getMenuItem('Float on Top').enabled = false
}
function onWindowFocus () {
getMenuItem('Full Screen').enabled = true
getMenuItem('Float on Top').enabled = true
}
function getMenuItem (label) { function getMenuItem (label) {
for (var i = 0; i < menu.items.length; i++) { for (var i = 0; i < menu.items.length; i++) {
var menuItem = menu.items[i].submenu.items.find(function (item) { var menuItem = menu.items[i].submenu.items.find(function (item) {
@@ -130,14 +121,6 @@ function getMenuTemplate () {
}, },
{ {
role: 'selectall' role: 'selectall'
},
{
type: 'separator'
},
{
label: 'Preferences',
accelerator: 'CmdOrCtrl+,',
click: () => windows.main.dispatch('preferences')
} }
] ]
}, },
@@ -350,6 +333,17 @@ function getMenuTemplate () {
click: () => dialog.openSeedFile() click: () => dialog.openSeedFile()
}) })
// Edit menu (Windows, Linux)
template[1].submenu.push(
{
type: 'separator'
},
{
label: 'Preferences',
accelerator: 'CmdOrCtrl+,',
click: () => windows.main.dispatch('preferences')
})
// Help menu (Windows, Linux) // Help menu (Windows, Linux)
template[4].submenu.push( template[4].submenu.push(
{ {

View File

@@ -1,8 +1,7 @@
module.exports = { module.exports = {
hasTray, hasTray,
init, init,
onWindowBlur, setWindowFocus
onWindowFocus
} }
var electron = require('electron') var electron = require('electron')
@@ -31,12 +30,7 @@ function hasTray () {
return !!tray return !!tray
} }
function onWindowBlur () { function setWindowFocus (flag) {
if (!tray) return
updateTrayMenu()
}
function onWindowFocus () {
if (!tray) return if (!tray) return
updateTrayMenu() updateTrayMenu()
} }

View File

@@ -141,7 +141,9 @@ function setBounds (bounds, maximize) {
bounds.y = Math.round(scr.bounds.y + scr.bounds.height / 2 - bounds.height / 2) bounds.y = Math.round(scr.bounds.y + scr.bounds.height / 2 - bounds.height / 2)
log('setBounds: centered to ' + JSON.stringify(bounds)) log('setBounds: centered to ' + JSON.stringify(bounds))
} }
main.win.setBounds(bounds, true) // Resize the window's content area (so window border doesn't need to be taken
// into account)
main.win.setContentBounds(bounds, true)
} else { } else {
log('setBounds: not setting bounds because of window maximization') log('setBounds: not setting bounds because of window maximization')
} }
@@ -204,13 +206,13 @@ function toggleFullScreen (flag) {
} }
function onWindowBlur () { function onWindowBlur () {
menu.onWindowBlur() menu.setWindowFocus(false)
tray.onWindowBlur() tray.setWindowFocus(false)
} }
function onWindowFocus () { function onWindowFocus () {
menu.onWindowFocus() menu.setWindowFocus(true)
tray.onWindowFocus() tray.setWindowFocus(true)
} }
function getIconPath () { function getIconPath () {

View File

@@ -188,7 +188,7 @@ module.exports = class PlaybackController {
}, 10000) /* give it a few seconds */ }, 10000) /* give it a few seconds */
if (torrentSummary.status === 'paused') { if (torrentSummary.status === 'paused') {
dispatch('startTorrentingSummary', torrentSummary) dispatch('startTorrentingSummary', torrentSummary.torrentKey)
ipcRenderer.once('wt-ready-' + torrentSummary.infoHash, ipcRenderer.once('wt-ready-' + torrentSummary.infoHash,
() => this.openPlayerFromActiveTorrent(torrentSummary, index, timeout, cb)) () => this.openPlayerFromActiveTorrent(torrentSummary, index, timeout, cb))
} else { } else {
@@ -241,12 +241,19 @@ module.exports = class PlaybackController {
return this.update() return this.update()
} }
// play in VLC if set as default player (Preferences / Playback / Play in VLC)
if (this.state.saved.prefs.playInVlc) {
dispatch('vlcPlay')
this.update()
cb()
return
}
// otherwise, play the video // otherwise, play the video
dispatch('setTitle', torrentSummary.files[state.playing.fileIndex].name) state.window.title = torrentSummary.files[state.playing.fileIndex].name
this.update() this.update()
ipcRenderer.send('onPlayerOpen') ipcRenderer.send('onPlayerOpen')
cb() cb()
}) })
} }

View File

@@ -1,5 +1,6 @@
const {dispatch} = require('../lib/dispatcher')
const State = require('../lib/state') const State = require('../lib/state')
const {dispatch} = require('../lib/dispatcher')
const ipcRenderer = require('electron').ipcRenderer
// Controls the Preferences screen // Controls the Preferences screen
module.exports = class PrefsController { module.exports = class PrefsController {
@@ -15,11 +16,15 @@ module.exports = class PrefsController {
url: 'preferences', url: 'preferences',
setup: function (cb) { setup: function (cb) {
// initialize preferences // initialize preferences
dispatch('setTitle', 'Preferences') state.window.title = 'Preferences'
state.unsaved = Object.assign(state.unsaved || {}, {prefs: state.saved.prefs || {}}) state.unsaved = Object.assign(state.unsaved || {}, {prefs: state.saved.prefs || {}})
ipcRenderer.send('setAllowNav', false)
cb() cb()
}, },
destroy: () => this.save() destroy: () => {
ipcRenderer.send('setAllowNav', true)
this.save()
}
}) })
} }
@@ -41,7 +46,11 @@ module.exports = class PrefsController {
// All unsaved prefs take effect atomically, and are saved to config.json // All unsaved prefs take effect atomically, and are saved to config.json
save () { save () {
var state = this.state var state = this.state
if (state.unsaved.prefs.isFileHandler !== state.saved.prefs.isFileHandler) {
ipcRenderer.send('setDefaultFileHandler', state.unsaved.prefs.isFileHandler)
}
state.saved.prefs = Object.assign(state.saved.prefs || {}, state.unsaved.prefs) state.saved.prefs = Object.assign(state.saved.prefs || {}, state.unsaved.prefs)
State.save(state) State.save(state)
dispatch('checkDownloadPath')
} }
} }

View File

@@ -24,8 +24,8 @@ module.exports = class TorrentListController {
// Use path string instead of W3C File object // Use path string instead of W3C File object
torrentId = torrentId.path torrentId = torrentId.path
} }
// Allow a instant.io link to be pasted // Allow a instant.io link to be pasted
// TODO: remove this once support is added to webtorrent core
if (typeof torrentId === 'string' && instantIoRegex.test(torrentId)) { if (typeof torrentId === 'string' && instantIoRegex.test(torrentId)) {
torrentId = torrentId.slice(torrentId.indexOf('#') + 1) torrentId = torrentId.slice(torrentId.indexOf('#') + 1)
} }
@@ -40,12 +40,21 @@ module.exports = class TorrentListController {
// Shows the Create Torrent page with options to seed a given file or folder // Shows the Create Torrent page with options to seed a given file or folder
showCreateTorrent (files) { showCreateTorrent (files) {
// You can only create torrents from the home screen.
if (this.state.location.url() !== 'home') {
return dispatch('error', 'Please go back to the torrent list before creating a new torrent.')
}
// Files will either be an array of file objects, which we can send directly // Files will either be an array of file objects, which we can send directly
// to the create-torrent screen // to the create-torrent screen
if (files.length === 0 || typeof files[0] !== 'string') { if (files.length === 0 || typeof files[0] !== 'string') {
this.state.location.go({ this.state.location.go({
url: 'create-torrent', url: 'create-torrent',
files: files files: files,
setup: (cb) => {
this.state.window.title = 'Create New Torrent'
cb(null)
}
}) })
return return
} }
@@ -67,27 +76,29 @@ module.exports = class TorrentListController {
var state = this.state var state = this.state
var torrentKey = state.nextTorrentKey++ var torrentKey = state.nextTorrentKey++
ipcRenderer.send('wt-create-torrent', torrentKey, options) ipcRenderer.send('wt-create-torrent', torrentKey, options)
state.location.backToFirst(function () { state.location.cancel()
state.location.clearForward('create-torrent')
})
} }
// Starts downloading and/or seeding a given torrentSummary. // Starts downloading and/or seeding a given torrentSummary.
startTorrentingSummary (torrentSummary) { startTorrentingSummary (torrentKey) {
var s = torrentSummary var s = TorrentSummary.getByKey(this.state, torrentKey)
if (!s) throw new Error('Missing key: ' + torrentKey)
// Backward compatibility for config files save before we had torrentKey
if (!s.torrentKey) s.torrentKey = this.state.nextTorrentKey++
// Use Downloads folder by default // Use Downloads folder by default
if (!s.path) s.path = this.state.saved.prefs.downloadPath if (!s.path) s.path = this.state.saved.prefs.downloadPath
ipcRenderer.send('wt-start-torrenting', fs.stat(TorrentSummary.getFileOrFolder(s), function (err) {
s.torrentKey, if (err) {
TorrentSummary.getTorrentID(s), s.error = 'path-missing'
s.path, return
s.fileModtimes, }
s.selections) ipcRenderer.send('wt-start-torrenting',
s.torrentKey,
TorrentSummary.getTorrentID(s),
s.path,
s.fileModtimes,
s.selections)
})
} }
// TODO: use torrentKey, not infoHash // TODO: use torrentKey, not infoHash
@@ -95,7 +106,7 @@ module.exports = class TorrentListController {
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash) var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
if (torrentSummary.status === 'paused') { if (torrentSummary.status === 'paused') {
torrentSummary.status = 'new' torrentSummary.status = 'new'
this.startTorrentingSummary(torrentSummary) this.startTorrentingSummary(torrentSummary.torrentKey)
sound.play('ENABLE') sound.play('ENABLE')
} else { } else {
torrentSummary.status = 'paused' torrentSummary.status = 'paused'
@@ -271,6 +282,7 @@ function saveTorrentFileAs (torrentSummary) {
] ]
} }
electron.remote.dialog.showSaveDialog(electron.remote.getCurrentWindow(), opts, function (savePath) { electron.remote.dialog.showSaveDialog(electron.remote.getCurrentWindow(), opts, function (savePath) {
if (!savePath) return // They clicked Cancel
var torrentPath = TorrentSummary.getTorrentPath(torrentSummary) var torrentPath = TorrentSummary.getTorrentPath(torrentSummary)
fs.readFile(torrentPath, function (err, torrentFile) { fs.readFile(torrentPath, function (err, torrentFile) {
if (err) return dispatch('error', err) if (err) return dispatch('error', err)

View File

@@ -25,6 +25,10 @@ function run (state) {
migrate_0_7_2(state.saved) migrate_0_7_2(state.saved)
} }
if (semver.lt(version, '0.11.0')) {
migrate_0_11_0(state.saved)
}
// Config is now on the new version // Config is now on the new version
state.saved.version = config.APP_VERSION state.saved.version = config.APP_VERSION
} }
@@ -93,3 +97,10 @@ function migrate_0_7_2 (saved) {
} }
} }
} }
function migrate_0_11_0 (saved) {
if (saved.prefs.isFileHandler === undefined) {
// The app used to make itself the default torrent file handler automatically
saved.prefs.isFileHandler = true
}
}

View File

@@ -200,6 +200,9 @@ function save (state, cb) {
if (key === 'playStatus') { if (key === 'playStatus') {
continue // Don't save whether a torrent is playing / pending continue // Don't save whether a torrent is playing / pending
} }
if (key === 'error') {
continue // Don't save error states
}
torrent[key] = x[key] torrent[key] = x[key]
} }
return torrent return torrent

View File

@@ -7,6 +7,7 @@ const dragDrop = require('drag-drop')
const electron = require('electron') const electron = require('electron')
const React = require('react') const React = require('react')
const ReactDOM = require('react-dom') const ReactDOM = require('react-dom')
const fs = require('fs')
const config = require('../config') const config = require('../config')
const App = require('./views/app') const App = require('./views/app')
@@ -77,17 +78,20 @@ function onState (err, _state) {
// Restart everything we were torrenting last time the app ran // Restart everything we were torrenting last time the app ran
resumeTorrents() resumeTorrents()
// Calling update() updates the UI given the current state
// Do this at least once a second to give every file in every torrentSummary
// a progress bar and to keep the cursor in sync when playing a video
setInterval(update, 1000)
app = ReactDOM.render(<App state={state} />, document.querySelector('#body'))
// Lazy-load other stuff, like the AppleTV module, later to keep startup fast // Lazy-load other stuff, like the AppleTV module, later to keep startup fast
window.setTimeout(delayedInit, config.DELAYED_INIT) window.setTimeout(delayedInit, config.DELAYED_INIT)
// Listen for messages from the main process // Listen for messages from the main process
setupIpc() setupIpc()
// Calling update() updates the UI given the current state // Warn if the download dir is gone, eg b/c an external drive is unplugged
// Do this at least once a second to give every file in every torrentSummary checkDownloadPath()
// a progress bar and to keep the cursor in sync when playing a video
setInterval(update, 1000)
app = ReactDOM.render(<App state={state} />, document.querySelector('#body'))
// OS integrations: // OS integrations:
// ...drag and drop files/text to start torrenting or seeding // ...drag and drop files/text to start torrenting or seeding
@@ -175,8 +179,7 @@ const dispatchHandlers = {
'deleteTorrent': (infoHash, deleteData) => controllers.torrentList.deleteTorrent(infoHash, deleteData), 'deleteTorrent': (infoHash, deleteData) => controllers.torrentList.deleteTorrent(infoHash, deleteData),
'toggleSelectTorrent': (infoHash) => controllers.torrentList.toggleSelectTorrent(infoHash), 'toggleSelectTorrent': (infoHash) => controllers.torrentList.toggleSelectTorrent(infoHash),
'openTorrentContextMenu': (infoHash) => controllers.torrentList.openTorrentContextMenu(infoHash), 'openTorrentContextMenu': (infoHash) => controllers.torrentList.openTorrentContextMenu(infoHash),
'startTorrentingSummary': (torrentSummary) => 'startTorrentingSummary': (torrentKey) => controllers.torrentList.startTorrentingSummary(torrentKey),
controllers.torrentList.startTorrentingSummary(torrentSummary),
// Playback // Playback
'playFile': (infoHash, index) => controllers.playback.playFile(infoHash, index), 'playFile': (infoHash, index) => controllers.playback.playFile(infoHash, index),
@@ -212,6 +215,7 @@ const dispatchHandlers = {
// Preferences screen // Preferences screen
'preferences': () => controllers.prefs.show(), 'preferences': () => controllers.prefs.show(),
'updatePreferences': (key, value) => controllers.prefs.update(key, value), 'updatePreferences': (key, value) => controllers.prefs.update(key, value),
'checkDownloadPath': checkDownloadPath,
// Update (check for new versions on Linux, where there's no auto updater) // Update (check for new versions on Linux, where there's no auto updater)
'updateAvailable': (version) => controllers.update.updateAvailable(version), 'updateAvailable': (version) => controllers.update.updateAvailable(version),
@@ -223,6 +227,7 @@ const dispatchHandlers = {
'escapeBack': escapeBack, 'escapeBack': escapeBack,
'back': () => state.location.back(), 'back': () => state.location.back(),
'forward': () => state.location.forward(), 'forward': () => state.location.forward(),
'cancel': () => state.location.cancel(),
// Controlling the window // Controlling the window
'setDimensions': setDimensions, 'setDimensions': setDimensions,
@@ -312,8 +317,14 @@ function escapeBack () {
// Starts all torrents that aren't paused on program startup // Starts all torrents that aren't paused on program startup
function resumeTorrents () { function resumeTorrents () {
state.saved.torrents state.saved.torrents
.filter((torrentSummary) => torrentSummary.status !== 'paused') .map((torrentSummary) => {
.forEach((torrentSummary) => controllers.torrentList.startTorrentingSummary(torrentSummary)) // Torrent keys are ephemeral, reassigned each time the app runs.
// On startup, give all torrents a key, even the ones that are paused.
torrentSummary.torrentKey = state.nextTorrentKey++
return torrentSummary
})
.filter((s) => s.status !== 'paused')
.forEach((s) => controllers.torrentList.startTorrentingSummary(s.torrentKey))
} }
// Set window dimensions to match video dimensions or fill the screen // Set window dimensions to match video dimensions or fill the screen
@@ -360,25 +371,25 @@ function setDimensions (dimensions) {
function onOpen (files) { function onOpen (files) {
if (!Array.isArray(files)) files = [ files ] if (!Array.isArray(files)) files = [ files ]
if (state.modal) { var url = state.location.url()
var allTorrents = files.every(TorrentPlayer.isTorrent)
var allSubtitles = files.every(controllers.subtitles.isSubtitle)
if (allTorrents) {
// Drop torrents onto the app: go to home screen, add torrents, no matter what
dispatch('backToList')
// All .torrent files? Add them.
files.forEach((file) => controllers.torrentList.addTorrent(file))
} else if (url === 'player' && allSubtitles) {
// Drop subtitles onto a playing video: add subtitles
controllers.subtitles.addSubtitles(files, true)
} else if (url === 'home') {
// Drop files onto home screen: show Create Torrent
state.modal = null state.modal = null
} controllers.torrentList.showCreateTorrent(files)
} else {
var subtitles = files.filter(controllers.subtitles.isSubtitle) // Drop files onto any other screen: show error
return onError('Please go back to the torrent list before creating a new torrent.')
if (state.location.url() === 'home' || subtitles.length === 0) {
if (files.every(TorrentPlayer.isTorrent)) {
if (state.location.url() !== 'home') {
dispatch('backToList')
}
// All .torrent files? Add them.
files.forEach((file) => controllers.torrentList.addTorrent(file))
} else {
// Show the Create Torrent screen. Let's seed those files.
controllers.torrentList.showCreateTorrent(files)
}
} else if (state.location.url() === 'player') {
controllers.subtitles.addSubtitles(subtitles, true)
} }
update() update()
@@ -432,3 +443,14 @@ function onFullscreenChanged (e, isFullScreen) {
update() update()
} }
function checkDownloadPath () {
fs.stat(state.saved.prefs.downloadPath, function (err, stat) {
if (err) {
state.downloadPathStatus = 'missing'
return console.error(err)
}
if (stat.isDirectory()) state.downloadPathStatus = 'ok'
else state.downloadPathStatus = 'missing'
})
}

View File

@@ -81,7 +81,7 @@ module.exports = class App extends React.Component {
var ModalContents = Modals[state.modal.id] var ModalContents = Modals[state.modal.id]
return ( return (
<div key='modal' className='modal'> <div key='modal' className='modal'>
<div key='modal-background' className='modal-background'></div> <div key='modal-background' className='modal-background' />
<div key='modal-content' className='modal-content'> <div key='modal-content' className='modal-content'>
<ModalContents state={state} /> <ModalContents state={state} />
</div> </div>

View File

@@ -16,7 +16,7 @@ module.exports = class CreateTorrentErrorPage extends React.Component {
</p> </p>
</p> </p>
<p className='float-right'> <p className='float-right'>
<button className='button-flat light' onClick={dispatcher('back')}> <button className='button-flat light' onClick={dispatcher('cancel')}>
Cancel Cancel
</button> </button>
</p> </p>

View File

@@ -73,11 +73,11 @@ module.exports = class CreateTorrentPage extends React.Component {
<div key='advanced' className={'create-torrent-advanced ' + collapsedClass}> <div key='advanced' className={'create-torrent-advanced ' + collapsedClass}>
<div key='comment' className='torrent-attribute'> <div key='comment' className='torrent-attribute'>
<label>Comment:</label> <label>Comment:</label>
<textarea className='torrent-attribute torrent-comment'></textarea> <textarea className='torrent-attribute torrent-comment' />
</div> </div>
<div key='trackers' className='torrent-attribute'> <div key='trackers' className='torrent-attribute'>
<label>Trackers:</label> <label>Trackers:</label>
<textarea className='torrent-attribute torrent-trackers' value={trackers}></textarea> <textarea className='torrent-attribute torrent-trackers' defaultValue={trackers} />
</div> </div>
<div key='private' className='torrent-attribute'> <div key='private' className='torrent-attribute'>
<label>Private:</label> <label>Private:</label>
@@ -89,7 +89,7 @@ module.exports = class CreateTorrentPage extends React.Component {
</div> </div>
</div> </div>
<div key='buttons' className='float-right'> <div key='buttons' className='float-right'>
<button key='cancel' className='button-flat light' onClick={dispatcher('back')}>Cancel</button> <button key='cancel' className='button-flat light' onClick={dispatcher('cancel')}>Cancel</button>
<button key='create' className='button-raised' onClick={handleOK}>Create Torrent</button> <button key='create' className='button-raised' onClick={handleOK}>Create Torrent</button>
</div> </div>
</div> </div>

View File

@@ -232,7 +232,7 @@ function renderAudioMetadata (state) {
} }
// Align the title with the other info, if available. Otherwise, center title // Align the title with the other info, if available. Otherwise, center title
var emptyLabel = (<label></label>) var emptyLabel = (<label />)
elems.unshift(( elems.unshift((
<div key='title' className='audio-title'> <div key='title' className='audio-title'>
{elems.length ? emptyLabel : undefined}{title} {elems.length ? emptyLabel : undefined}{title}
@@ -380,16 +380,16 @@ function renderPlayerControls (state) {
<div <div
key='cursor' key='cursor'
className='playback-cursor' className='playback-cursor'
style={playbackCursorStyle}> style={playbackCursorStyle}
</div> />
<div <div
key='scrub-bar' key='scrub-bar'
className='scrub-bar' className='scrub-bar'
draggable='true' draggable='true'
onDragStart={handleDragStart} onDragStart={handleDragStart}
onClick={handleScrub} onClick={handleScrub}
onDrag={handleScrub}> onDrag={handleScrub}
</div> />
</div>, </div>,
<i <i
@@ -593,7 +593,7 @@ function renderLoadingBar (state) {
width: (100 * part.count / fileProg.numPieces) + '%' width: (100 * part.count / fileProg.numPieces) + '%'
} }
return (<div key={i} className='loading-bar-part' style={style}></div>) return (<div key={i} className='loading-bar-part' style={style} />)
}) })
return (<div key='loading-bar' className='loading-bar'>{loadingBarElems}</div>) return (<div key='loading-bar' className='loading-bar'>{loadingBarElems}</div>)
} }

View File

@@ -10,6 +10,7 @@ module.exports = class Preferences extends React.Component {
return ( return (
<div className='preferences'> <div className='preferences'>
{renderGeneralSection(state)} {renderGeneralSection(state)}
{renderPlaybackSection(state)}
</div> </div>
) )
} }
@@ -22,11 +23,36 @@ function renderGeneralSection (state) {
description: '', description: '',
icon: 'settings' icon: 'settings'
}, [ }, [
renderDownloadDirSelector(state) renderDownloadPathSelector(state),
renderFileHandlers(state)
]) ])
} }
function renderDownloadDirSelector (state) { function renderPlaybackSection (state) {
return renderSection({
title: 'Playback',
description: '',
icon: 'settings'
}, [
renderPlayInVlcSelector(state)
])
}
function renderPlayInVlcSelector (state) {
return renderCheckbox({
key: 'play-in-vlc',
label: 'Play in VLC',
description: 'Media will play in VLC',
property: 'playInVlc',
value: state.saved.prefs.playInVlc
},
state.unsaved.prefs.playInVlc,
function (value) {
dispatch('updatePreferences', 'playInVlc', value)
})
}
function renderDownloadPathSelector (state) {
return renderFileSelector({ return renderFileSelector({
key: 'download-path', key: 'download-path',
label: 'Download Path', label: 'Download Path',
@@ -39,10 +65,33 @@ function renderDownloadDirSelector (state) {
}, },
state.unsaved.prefs.downloadPath, state.unsaved.prefs.downloadPath,
function (filePath) { function (filePath) {
setStateValue('downloadPath', filePath) dispatch('updatePreferences', 'downloadPath', filePath)
}) })
} }
function renderFileHandlers (state) {
var definition = {
key: 'file-handlers',
label: 'Handle Torrent Files'
}
var buttonText = state.unsaved.prefs.isFileHandler
? 'Remove default app for torrent files'
: 'Make WebTorrent the default app for torrent files'
var controls = [(
<button key='toggle-handlers'
className='btn'
onClick={toggleFileHandlers}>
{buttonText}
</button>
)]
return renderControlGroup(definition, controls)
function toggleFileHandlers () {
var isFileHandler = state.unsaved.prefs.isFileHandler
dispatch('updatePreferences', 'isFileHandler', !isFileHandler)
}
}
// Renders a prefs section. // Renders a prefs section.
// - definition should be {icon, title, description} // - definition should be {icon, title, description}
// - controls should be an array of vdom elements // - controls should be an array of vdom elements
@@ -67,31 +116,59 @@ function renderSection (definition, controls) {
) )
} }
function renderCheckbox (definition, value, callback) {
var iconClass = 'icon clickable'
if (value) iconClass += ' enabled'
return (
<div key='{definition.key}' className='control-group'>
<div className='controls'>
<label className='control-label'>
<div className='preference-title'>{definition.label}</div>
</label>
<div className='controls'>
<label className='clickable' onClick={handleClick}>
<i
className={iconClass}
id='{definition.property}'
>
check_circle
</i>
<span className='checkbox-label'>{definition.description}</span>
</label>
</div>
</div>
</div>
)
function handleClick () {
callback(!value)
}
}
// Creates a file chooser // Creates a file chooser
// - defition should be {label, description, options} // - defition should be {label, description, options}
// options are passed to dialog.showOpenDialog // options are passed to dialog.showOpenDialog
// - value should be the current pref, a file or folder path // - value should be the current pref, a file or folder path
// - callback takes a new file or folder path // - callback takes a new file or folder path
function renderFileSelector (definition, value, callback) { function renderFileSelector (definition, value, callback) {
return ( var controls = [(
<div key={definition.key} className='control-group'> <input
<div className='controls'> type='text'
<label className='control-label'> className='file-picker-text'
<div className='preference-title'>{definition.label}</div> key={definition.property}
<div className='preference-description'>{definition.description}</div> id={definition.property}
</label> disabled='disabled'
<div className='controls'> value={value} />
<input type='text' className='file-picker-text' ), (
id={definition.property} <button
disabled='disabled' key={definition.property + '-btn'}
value={value} /> className='btn'
<button className='btn' onClick={handleClick}> onClick={handleClick}>
<i className='icon'>folder_open</i> <i className='icon'>folder_open</i>
</button> </button>
</div> )]
</div> return renderControlGroup(definition, controls)
</div>
)
function handleClick () { function handleClick () {
dialog.showOpenDialog(remote.getCurrentWindow(), definition.options, function (filenames) { dialog.showOpenDialog(remote.getCurrentWindow(), definition.options, function (filenames) {
if (!Array.isArray(filenames)) return if (!Array.isArray(filenames)) return
@@ -100,6 +177,18 @@ function renderFileSelector (definition, value, callback) {
} }
} }
function setStateValue (property, value) { function renderControlGroup (definition, controls) {
dispatch('updatePreferences', property, value) return (
<div key={definition.key} className='control-group'>
<div className='controls'>
<label className='control-label'>
<div className='preference-title'>{definition.label}</div>
<div className='preference-description'>{definition.description}</div>
</label>
<div className='controls'>
{controls}
</div>
</div>
</div>
)
} }

View File

@@ -8,16 +8,32 @@ const {dispatcher} = require('../lib/dispatcher')
module.exports = class TorrentList extends React.Component { module.exports = class TorrentList extends React.Component {
render () { render () {
var state = this.props.state var state = this.props.state
var torrentRows = state.saved.torrents.map(
var contents = []
if (state.downloadPathStatus === 'missing') {
contents.push(
<div key='torrent-missing-path'>
<p>Download path missing: {state.saved.prefs.downloadPath}</p>
<p>Check that all drives are connected?</p>
<p>Alternatively, choose a new download path
in <a href='#' onClick={dispatcher('preferences')}>Preferences</a>
</p>
</div>
)
}
var torrentElems = state.saved.torrents.map(
(torrentSummary) => this.renderTorrent(torrentSummary) (torrentSummary) => this.renderTorrent(torrentSummary)
) )
contents.push(...torrentElems)
contents.push(
<div key='torrent-placeholder' className='torrent-placeholder'>
<span className='ellipsis'>Drop a torrent file here or paste a magnet link</span>
</div>
)
return ( return (
<div key='torrent-list' className='torrent-list'> <div key='torrent-list' className='torrent-list'>
{torrentRows} {contents}
<div key='torrent-placeholder' className='torrent-placeholder'>
<span className='ellipsis'>Drop a torrent file here or paste a magnet link</span>
</div>
</div> </div>
) )
} }
@@ -44,6 +60,7 @@ module.exports = class TorrentList extends React.Component {
if (torrentSummary.playStatus) classes.push(torrentSummary.playStatus) if (torrentSummary.playStatus) classes.push(torrentSummary.playStatus)
if (isSelected) classes.push('selected') if (isSelected) classes.push('selected')
if (!infoHash) classes.push('disabled') if (!infoHash) classes.push('disabled')
if (!torrentSummary.torrentKey) throw new Error('Missing torrentKey')
return ( return (
<div <div
key={torrentSummary.torrentKey} key={torrentSummary.torrentKey}
@@ -67,8 +84,14 @@ module.exports = class TorrentList extends React.Component {
// If it's downloading/seeding then show progress info // If it's downloading/seeding then show progress info
var prog = torrentSummary.progress var prog = torrentSummary.progress
if (torrentSummary.status !== 'paused' && prog) { if (torrentSummary.error) {
elements.push(( elements.push(
<div key='progress-info' className='ellipsis'>
{getErrorMessage(torrentSummary)}
</div>
)
} else if (torrentSummary.status !== 'paused' && prog) {
elements.push(
<div key='progress-info' className='ellipsis'> <div key='progress-info' className='ellipsis'>
{renderPercentProgress()} {renderPercentProgress()}
{renderTotalProgress()} {renderTotalProgress()}
@@ -77,7 +100,7 @@ module.exports = class TorrentList extends React.Component {
{renderUploadSpeed()} {renderUploadSpeed()}
{renderEta()} {renderEta()}
</div> </div>
)) )
} }
return (<div key='metadata' className='metadata'>{elements}</div>) return (<div key='metadata' className='metadata'>{elements}</div>)
@@ -174,8 +197,9 @@ module.exports = class TorrentList extends React.Component {
} }
// Only show the play button for torrents that contain playable media // Only show the play button for torrents that contain playable media
var playButton var playButton, downloadButton
if (TorrentPlayer.isPlayableTorrentSummary(torrentSummary)) { var noErrors = !torrentSummary.error
if (noErrors && TorrentPlayer.isPlayableTorrentSummary(torrentSummary)) {
playButton = ( playButton = (
<i <i
key='play-button' key='play-button'
@@ -186,11 +210,8 @@ module.exports = class TorrentList extends React.Component {
</i> </i>
) )
} }
if (noErrors) {
return ( downloadButton = (
<div key='buttons' className='buttons'>
{positionElem}
{playButton}
<i <i
key='download-button' key='download-button'
className={'button-round icon download ' + torrentSummary.status} className={'button-round icon download ' + torrentSummary.status}
@@ -198,6 +219,14 @@ module.exports = class TorrentList extends React.Component {
onClick={dispatcher('toggleTorrent', infoHash)}> onClick={dispatcher('toggleTorrent', infoHash)}>
{downloadIcon} {downloadIcon}
</i> </i>
)
}
return (
<div key='buttons' className='buttons'>
{positionElem}
{playButton}
{downloadButton}
<i <i
key='delete-button' key='delete-button'
className='icon delete' className='icon delete'
@@ -212,12 +241,26 @@ module.exports = class TorrentList extends React.Component {
// Show files, per-file download status and play buttons, and so on // Show files, per-file download status and play buttons, and so on
renderTorrentDetails (torrentSummary) { renderTorrentDetails (torrentSummary) {
var filesElement var filesElement
if (!torrentSummary.files) { if (torrentSummary.error || !torrentSummary.files) {
// We don't know what files this torrent contains var message = ''
var message = torrentSummary.status === 'paused' if (torrentSummary.error === 'path-missing') {
? 'Failed to load torrent info. Click the download button to try again...' // Special case error: this torrent's download dir or file is missing
: 'Downloading torrent info...' message = 'Missing path: ' + TorrentSummary.getFileOrFolder(torrentSummary)
filesElement = (<div key='files' className='files warning'>{message}</div>) } else if (torrentSummary.error) {
// General error for this torrent: just show the message
message = torrentSummary.error.message || torrentSummary.error
} else if (torrentSummary.status === 'paused') {
// No file info, no infohash, and we're not trying to download from the DHT
message = 'Failed to load torrent info. Click the download button to try again...'
} else {
// No file info, no infohash, trying to load from the DHT
message = 'Downloading torrent info...'
}
filesElement = (
<div key='files' className='files warning'>
{message}
</div>
)
} else { } else {
// We do know the files. List them and show download stats for each one // We do know the files. List them and show download stats for each one
var fileRows = torrentSummary.files var fileRows = torrentSummary.files
@@ -316,15 +359,28 @@ module.exports = class TorrentList extends React.Component {
<div key='radial-progress' className={'radial-progress ' + cssClass}> <div key='radial-progress' className={'radial-progress ' + cssClass}>
<div key='circle' className='circle'> <div key='circle' className='circle'>
<div key='mask-full' className='mask full' style={transformFill}> <div key='mask-full' className='mask full' style={transformFill}>
<div key='fill' className='fill' style={transformFill}></div> <div key='fill' className='fill' style={transformFill} />
</div> </div>
<div key='mask-half' className='mask half'> <div key='mask-half' className='mask half'>
<div key='fill' className='fill' style={transformFill}></div> <div key='fill' className='fill' style={transformFill} />
<div key='fill-fix' className='fill fix' style={transformFix}></div> <div key='fill-fix' className='fill fix' style={transformFix} />
</div> </div>
</div> </div>
<div key='inset' className='inset'></div> <div key='inset' className='inset' />
</div> </div>
) )
} }
} }
function getErrorMessage (torrentSummary) {
var err = torrentSummary.error
if (err === 'path-missing') {
return (
<span>
Path missing.<br />
Fix and restart the app, or delete the torrent.
</span>
)
}
return 'Error'
}

View File

@@ -11,18 +11,18 @@ module.exports = class UpdateAvailableModal extends React.Component {
<p><strong>A new version of WebTorrent is available: v{state.modal.version}</strong></p> <p><strong>A new version of WebTorrent is available: v{state.modal.version}</strong></p>
<p>We have an auto-updater for Windows and Mac. We don't have one for Linux yet, so you'll have to download the new version manually.</p> <p>We have an auto-updater for Windows and Mac. We don't have one for Linux yet, so you'll have to download the new version manually.</p>
<p className='float-right'> <p className='float-right'>
<button className='button button-flat' onClick={handleCancel}>Skip This Release</button> <button className='button button-flat' onClick={handleSkip}>Skip This Release</button>
<button className='button button-raised' onClick={handleOK}>Show Download Page</button> <button className='button button-raised' onClick={handleShow}>Show Download Page</button>
</p> </p>
</div> </div>
) )
function handleOK () { function handleShow () {
electron.shell.openExternal('https://github.com/feross/webtorrent-desktop/releases') electron.shell.openExternal('https://github.com/feross/webtorrent-desktop/releases')
dispatch('exitModal') dispatch('exitModal')
} }
function handleCancel () { function handleSkip () {
dispatch('skipVersion', state.modal.version) dispatch('skipVersion', state.modal.version)
dispatch('exitModal') dispatch('exitModal')
} }

View File

@@ -122,6 +122,10 @@ table {
* UTILITY CLASSES * UTILITY CLASSES
*/ */
.clickable {
cursor: pointer;
}
.ellipsis { .ellipsis {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -551,6 +555,19 @@ input[type='text'] {
line-height: 1.5em; line-height: 1.5em;
} }
/*
* TORRENT LIST: ERRORS
*/
.torrent-list p {
margin: 10px 20px;
}
.torrent-list a {
color: #99f;
text-decoration: none;
}
/* /*
* TORRENT LIST: DRAG-DROP TARGET * TORRENT LIST: DRAG-DROP TARGET
*/ */
@@ -919,6 +936,10 @@ video::-webkit-media-text-track-container {
margin-right: 0.2em; margin-right: 0.2em;
} }
.preferences .icon.enabled {
color: yellow;
}
.preferences .btn { .preferences .btn {
display: inline-block; display: inline-block;
-webkit-appearance: button; -webkit-appearance: button;
@@ -1066,6 +1087,14 @@ video::-webkit-media-text-track-container {
vertical-align: text-bottom; vertical-align: text-bottom;
} }
.preferences .checkbox {
width: auto;
}
.checkbox-label {
vertical-align: top;
}
/* /*
* MEDIA OVERLAY / AUDIO DETAILS * MEDIA OVERLAY / AUDIO DETAILS