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
## 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
### Added

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ var app = electron.app
var dialog = require('./dialog')
var dock = require('./dock')
var handlers = require('./handlers')
var log = require('./log')
var menu = require('./menu')
var powerSaveBlocker = require('./power-save-blocker')
@@ -60,14 +61,14 @@ function init () {
*/
ipc.on('onPlayerOpen', function () {
menu.onPlayerOpen()
menu.setPlayerOpen(true)
powerSaveBlocker.enable()
shortcuts.enable()
thumbar.enable()
})
ipc.on('onPlayerClose', function () {
menu.onPlayerClose()
menu.setPlayerOpen(false)
powerSaveBlocker.disable()
shortcuts.disable()
thumbar.disable()
@@ -91,6 +92,14 @@ function init () {
ipc.on('showItemInFolder', (e, ...args) => shell.showItemInFolder(...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
*/
@@ -103,6 +112,7 @@ function init () {
ipc.on('setTitle', (e, ...args) => main.setTitle(...args))
ipc.on('show', () => main.show())
ipc.on('toggleFullScreen', (e, ...args) => main.toggleFullScreen(...args))
ipc.on('setAllowNav', (e, ...args) => menu.setAllowNav(...args))
/**
* VLC

View File

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

View File

@@ -1,8 +1,7 @@
module.exports = {
hasTray,
init,
onWindowBlur,
onWindowFocus
setWindowFocus
}
var electron = require('electron')
@@ -31,12 +30,7 @@ function hasTray () {
return !!tray
}
function onWindowBlur () {
if (!tray) return
updateTrayMenu()
}
function onWindowFocus () {
function setWindowFocus (flag) {
if (!tray) return
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)
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 {
log('setBounds: not setting bounds because of window maximization')
}
@@ -204,13 +206,13 @@ function toggleFullScreen (flag) {
}
function onWindowBlur () {
menu.onWindowBlur()
tray.onWindowBlur()
menu.setWindowFocus(false)
tray.setWindowFocus(false)
}
function onWindowFocus () {
menu.onWindowFocus()
tray.onWindowFocus()
menu.setWindowFocus(true)
tray.setWindowFocus(true)
}
function getIconPath () {

View File

@@ -188,7 +188,7 @@ module.exports = class PlaybackController {
}, 10000) /* give it a few seconds */
if (torrentSummary.status === 'paused') {
dispatch('startTorrentingSummary', torrentSummary)
dispatch('startTorrentingSummary', torrentSummary.torrentKey)
ipcRenderer.once('wt-ready-' + torrentSummary.infoHash,
() => this.openPlayerFromActiveTorrent(torrentSummary, index, timeout, cb))
} else {
@@ -241,12 +241,19 @@ module.exports = class PlaybackController {
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
dispatch('setTitle', torrentSummary.files[state.playing.fileIndex].name)
state.window.title = torrentSummary.files[state.playing.fileIndex].name
this.update()
ipcRenderer.send('onPlayerOpen')
cb()
})
}

View File

@@ -1,5 +1,6 @@
const {dispatch} = require('../lib/dispatcher')
const State = require('../lib/state')
const {dispatch} = require('../lib/dispatcher')
const ipcRenderer = require('electron').ipcRenderer
// Controls the Preferences screen
module.exports = class PrefsController {
@@ -15,11 +16,15 @@ module.exports = class PrefsController {
url: 'preferences',
setup: function (cb) {
// initialize preferences
dispatch('setTitle', 'Preferences')
state.window.title = 'Preferences'
state.unsaved = Object.assign(state.unsaved || {}, {prefs: state.saved.prefs || {}})
ipcRenderer.send('setAllowNav', false)
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
save () {
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.save(state)
dispatch('checkDownloadPath')
}
}

View File

@@ -24,8 +24,8 @@ module.exports = class TorrentListController {
// Use path string instead of W3C File object
torrentId = torrentId.path
}
// 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)) {
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
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
// to the create-torrent screen
if (files.length === 0 || typeof files[0] !== 'string') {
this.state.location.go({
url: 'create-torrent',
files: files
files: files,
setup: (cb) => {
this.state.window.title = 'Create New Torrent'
cb(null)
}
})
return
}
@@ -67,27 +76,29 @@ module.exports = class TorrentListController {
var state = this.state
var torrentKey = state.nextTorrentKey++
ipcRenderer.send('wt-create-torrent', torrentKey, options)
state.location.backToFirst(function () {
state.location.clearForward('create-torrent')
})
state.location.cancel()
}
// Starts downloading and/or seeding a given torrentSummary.
startTorrentingSummary (torrentSummary) {
var s = torrentSummary
// Backward compatibility for config files save before we had torrentKey
if (!s.torrentKey) s.torrentKey = this.state.nextTorrentKey++
startTorrentingSummary (torrentKey) {
var s = TorrentSummary.getByKey(this.state, torrentKey)
if (!s) throw new Error('Missing key: ' + torrentKey)
// Use Downloads folder by default
if (!s.path) s.path = this.state.saved.prefs.downloadPath
ipcRenderer.send('wt-start-torrenting',
s.torrentKey,
TorrentSummary.getTorrentID(s),
s.path,
s.fileModtimes,
s.selections)
fs.stat(TorrentSummary.getFileOrFolder(s), function (err) {
if (err) {
s.error = 'path-missing'
return
}
ipcRenderer.send('wt-start-torrenting',
s.torrentKey,
TorrentSummary.getTorrentID(s),
s.path,
s.fileModtimes,
s.selections)
})
}
// TODO: use torrentKey, not infoHash
@@ -95,7 +106,7 @@ module.exports = class TorrentListController {
var torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
if (torrentSummary.status === 'paused') {
torrentSummary.status = 'new'
this.startTorrentingSummary(torrentSummary)
this.startTorrentingSummary(torrentSummary.torrentKey)
sound.play('ENABLE')
} else {
torrentSummary.status = 'paused'
@@ -271,6 +282,7 @@ function saveTorrentFileAs (torrentSummary) {
]
}
electron.remote.dialog.showSaveDialog(electron.remote.getCurrentWindow(), opts, function (savePath) {
if (!savePath) return // They clicked Cancel
var torrentPath = TorrentSummary.getTorrentPath(torrentSummary)
fs.readFile(torrentPath, function (err, torrentFile) {
if (err) return dispatch('error', err)

View File

@@ -25,6 +25,10 @@ function run (state) {
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
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') {
continue // Don't save whether a torrent is playing / pending
}
if (key === 'error') {
continue // Don't save error states
}
torrent[key] = x[key]
}
return torrent

View File

@@ -7,6 +7,7 @@ const dragDrop = require('drag-drop')
const electron = require('electron')
const React = require('react')
const ReactDOM = require('react-dom')
const fs = require('fs')
const config = require('../config')
const App = require('./views/app')
@@ -77,17 +78,20 @@ function onState (err, _state) {
// Restart everything we were torrenting last time the app ran
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
window.setTimeout(delayedInit, config.DELAYED_INIT)
// Listen for messages from the main process
setupIpc()
// 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'))
// Warn if the download dir is gone, eg b/c an external drive is unplugged
checkDownloadPath()
// OS integrations:
// ...drag and drop files/text to start torrenting or seeding
@@ -175,8 +179,7 @@ const dispatchHandlers = {
'deleteTorrent': (infoHash, deleteData) => controllers.torrentList.deleteTorrent(infoHash, deleteData),
'toggleSelectTorrent': (infoHash) => controllers.torrentList.toggleSelectTorrent(infoHash),
'openTorrentContextMenu': (infoHash) => controllers.torrentList.openTorrentContextMenu(infoHash),
'startTorrentingSummary': (torrentSummary) =>
controllers.torrentList.startTorrentingSummary(torrentSummary),
'startTorrentingSummary': (torrentKey) => controllers.torrentList.startTorrentingSummary(torrentKey),
// Playback
'playFile': (infoHash, index) => controllers.playback.playFile(infoHash, index),
@@ -212,6 +215,7 @@ const dispatchHandlers = {
// Preferences screen
'preferences': () => controllers.prefs.show(),
'updatePreferences': (key, value) => controllers.prefs.update(key, value),
'checkDownloadPath': checkDownloadPath,
// Update (check for new versions on Linux, where there's no auto updater)
'updateAvailable': (version) => controllers.update.updateAvailable(version),
@@ -223,6 +227,7 @@ const dispatchHandlers = {
'escapeBack': escapeBack,
'back': () => state.location.back(),
'forward': () => state.location.forward(),
'cancel': () => state.location.cancel(),
// Controlling the window
'setDimensions': setDimensions,
@@ -312,8 +317,14 @@ function escapeBack () {
// Starts all torrents that aren't paused on program startup
function resumeTorrents () {
state.saved.torrents
.filter((torrentSummary) => torrentSummary.status !== 'paused')
.forEach((torrentSummary) => controllers.torrentList.startTorrentingSummary(torrentSummary))
.map((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
@@ -360,25 +371,25 @@ function setDimensions (dimensions) {
function onOpen (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
}
var subtitles = files.filter(controllers.subtitles.isSubtitle)
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)
controllers.torrentList.showCreateTorrent(files)
} else {
// Drop files onto any other screen: show error
return onError('Please go back to the torrent list before creating a new torrent.')
}
update()
@@ -432,3 +443,14 @@ function onFullscreenChanged (e, isFullScreen) {
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]
return (
<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'>
<ModalContents state={state} />
</div>

View File

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

View File

@@ -73,11 +73,11 @@ module.exports = class CreateTorrentPage extends React.Component {
<div key='advanced' className={'create-torrent-advanced ' + collapsedClass}>
<div key='comment' className='torrent-attribute'>
<label>Comment:</label>
<textarea className='torrent-attribute torrent-comment'></textarea>
<textarea className='torrent-attribute torrent-comment' />
</div>
<div key='trackers' className='torrent-attribute'>
<label>Trackers:</label>
<textarea className='torrent-attribute torrent-trackers' value={trackers}></textarea>
<textarea className='torrent-attribute torrent-trackers' defaultValue={trackers} />
</div>
<div key='private' className='torrent-attribute'>
<label>Private:</label>
@@ -89,7 +89,7 @@ module.exports = class CreateTorrentPage extends React.Component {
</div>
</div>
<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>
</div>
</div>

View File

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

View File

@@ -10,6 +10,7 @@ module.exports = class Preferences extends React.Component {
return (
<div className='preferences'>
{renderGeneralSection(state)}
{renderPlaybackSection(state)}
</div>
)
}
@@ -22,11 +23,36 @@ function renderGeneralSection (state) {
description: '',
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({
key: 'download-path',
label: 'Download Path',
@@ -39,10 +65,33 @@ function renderDownloadDirSelector (state) {
},
state.unsaved.prefs.downloadPath,
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.
// - definition should be {icon, title, description}
// - 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
// - defition should be {label, description, options}
// options are passed to dialog.showOpenDialog
// - value should be the current pref, a file or folder path
// - callback takes a new file or folder path
function renderFileSelector (definition, value, callback) {
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'>
<input type='text' className='file-picker-text'
id={definition.property}
disabled='disabled'
value={value} />
<button className='btn' onClick={handleClick}>
<i className='icon'>folder_open</i>
</button>
</div>
</div>
</div>
)
var controls = [(
<input
type='text'
className='file-picker-text'
key={definition.property}
id={definition.property}
disabled='disabled'
value={value} />
), (
<button
key={definition.property + '-btn'}
className='btn'
onClick={handleClick}>
<i className='icon'>folder_open</i>
</button>
)]
return renderControlGroup(definition, controls)
function handleClick () {
dialog.showOpenDialog(remote.getCurrentWindow(), definition.options, function (filenames) {
if (!Array.isArray(filenames)) return
@@ -100,6 +177,18 @@ function renderFileSelector (definition, value, callback) {
}
}
function setStateValue (property, value) {
dispatch('updatePreferences', property, value)
function renderControlGroup (definition, controls) {
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 {
render () {
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)
)
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 (
<div key='torrent-list' className='torrent-list'>
{torrentRows}
<div key='torrent-placeholder' className='torrent-placeholder'>
<span className='ellipsis'>Drop a torrent file here or paste a magnet link</span>
</div>
{contents}
</div>
)
}
@@ -44,6 +60,7 @@ module.exports = class TorrentList extends React.Component {
if (torrentSummary.playStatus) classes.push(torrentSummary.playStatus)
if (isSelected) classes.push('selected')
if (!infoHash) classes.push('disabled')
if (!torrentSummary.torrentKey) throw new Error('Missing torrentKey')
return (
<div
key={torrentSummary.torrentKey}
@@ -67,8 +84,14 @@ module.exports = class TorrentList extends React.Component {
// If it's downloading/seeding then show progress info
var prog = torrentSummary.progress
if (torrentSummary.status !== 'paused' && prog) {
elements.push((
if (torrentSummary.error) {
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'>
{renderPercentProgress()}
{renderTotalProgress()}
@@ -77,7 +100,7 @@ module.exports = class TorrentList extends React.Component {
{renderUploadSpeed()}
{renderEta()}
</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
var playButton
if (TorrentPlayer.isPlayableTorrentSummary(torrentSummary)) {
var playButton, downloadButton
var noErrors = !torrentSummary.error
if (noErrors && TorrentPlayer.isPlayableTorrentSummary(torrentSummary)) {
playButton = (
<i
key='play-button'
@@ -186,11 +210,8 @@ module.exports = class TorrentList extends React.Component {
</i>
)
}
return (
<div key='buttons' className='buttons'>
{positionElem}
{playButton}
if (noErrors) {
downloadButton = (
<i
key='download-button'
className={'button-round icon download ' + torrentSummary.status}
@@ -198,6 +219,14 @@ module.exports = class TorrentList extends React.Component {
onClick={dispatcher('toggleTorrent', infoHash)}>
{downloadIcon}
</i>
)
}
return (
<div key='buttons' className='buttons'>
{positionElem}
{playButton}
{downloadButton}
<i
key='delete-button'
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
renderTorrentDetails (torrentSummary) {
var filesElement
if (!torrentSummary.files) {
// We don't know what files this torrent contains
var message = torrentSummary.status === 'paused'
? 'Failed to load torrent info. Click the download button to try again...'
: 'Downloading torrent info...'
filesElement = (<div key='files' className='files warning'>{message}</div>)
if (torrentSummary.error || !torrentSummary.files) {
var message = ''
if (torrentSummary.error === 'path-missing') {
// Special case error: this torrent's download dir or file is missing
message = 'Missing path: ' + TorrentSummary.getFileOrFolder(torrentSummary)
} 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 {
// We do know the files. List them and show download stats for each one
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='circle' className='circle'>
<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 key='mask-half' className='mask half'>
<div key='fill' className='fill' style={transformFill}></div>
<div key='fill-fix' className='fill fix' style={transformFix}></div>
<div key='fill' className='fill' style={transformFill} />
<div key='fill-fix' className='fill fix' style={transformFix} />
</div>
</div>
<div key='inset' className='inset'></div>
<div key='inset' className='inset' />
</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>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'>
<button className='button button-flat' onClick={handleCancel}>Skip This Release</button>
<button className='button button-raised' onClick={handleOK}>Show Download Page</button>
<button className='button button-flat' onClick={handleSkip}>Skip This Release</button>
<button className='button button-raised' onClick={handleShow}>Show Download Page</button>
</p>
</div>
)
function handleOK () {
function handleShow () {
electron.shell.openExternal('https://github.com/feross/webtorrent-desktop/releases')
dispatch('exitModal')
}
function handleCancel () {
function handleSkip () {
dispatch('skipVersion', state.modal.version)
dispatch('exitModal')
}

View File

@@ -122,6 +122,10 @@ table {
* UTILITY CLASSES
*/
.clickable {
cursor: pointer;
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
@@ -551,6 +555,19 @@ input[type='text'] {
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
*/
@@ -919,6 +936,10 @@ video::-webkit-media-text-track-container {
margin-right: 0.2em;
}
.preferences .icon.enabled {
color: yellow;
}
.preferences .btn {
display: inline-block;
-webkit-appearance: button;
@@ -1066,6 +1087,14 @@ video::-webkit-media-text-track-container {
vertical-align: text-bottom;
}
.preferences .checkbox {
width: auto;
}
.checkbox-label {
vertical-align: top;
}
/*
* MEDIA OVERLAY / AUDIO DETAILS