Show Blender Foundation videos by default
* Finishes the saved torrents feature! * Torrents load and save correctly. Poster is autogenerated only once. * Torrents can be paused and restarted * Download button indicates state: - White means paused - Pulsating green means downloading - Solid green means finished downloading, only seeding
This commit is contained in:
@@ -64,7 +64,7 @@ function setAspectRatio (aspectRatio, extraSize) {
|
||||
// Display string in dock badging area (OS X)
|
||||
function setBadge (text) {
|
||||
debug('setBadge %s', text)
|
||||
electron.app.dock.setBadge(String(text))
|
||||
if (electron.app.dock) electron.app.dock.setBadge(String(text))
|
||||
}
|
||||
|
||||
// Show progress bar. Valid range is [0, 1]. Remove when < 0; indeterminate when > 1.
|
||||
|
||||
@@ -25,7 +25,7 @@ function createMainWindow (menu) {
|
||||
title: config.APP_NAME,
|
||||
titleBarStyle: 'hidden-inset', // Hide OS chrome, except traffic light buttons (OS X)
|
||||
width: 450,
|
||||
height: 300
|
||||
height: 450
|
||||
})
|
||||
win.loadURL(config.INDEX)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"dependencies": {
|
||||
"airplay-js": "guerrerocarlos/node-airplay-js",
|
||||
"application-config": "^0.2.0",
|
||||
"application-config-path": "^0.1.0",
|
||||
"chromecasts": "^1.8.0",
|
||||
"create-torrent": "^3.22.1",
|
||||
"debug": "^2.2.0",
|
||||
@@ -24,8 +25,7 @@
|
||||
"pretty-bytes": "^3.0.0",
|
||||
"upload-element": "^1.0.1",
|
||||
"virtual-dom": "^2.1.1",
|
||||
"webtorrent": "^0.82.1",
|
||||
"xtend": "^4.0.1"
|
||||
"webtorrent": "^0.82.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron-packager": "^5.0.0",
|
||||
|
||||
@@ -288,6 +288,7 @@ body.drag::before {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: calc(100% - 100px);
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 0 0 4px;
|
||||
}
|
||||
|
||||
@@ -317,6 +318,22 @@ body.drag::before {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.torrent .buttons .download.downloading {
|
||||
animation-name: greenpulse;
|
||||
animation-duration: 0.8s;
|
||||
animation-direction: alternate;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@keyframes greenpulse {
|
||||
0% { color: #ffffff }
|
||||
100% { color: #44dd44 }
|
||||
}
|
||||
|
||||
.torrent .buttons .download.seeding {
|
||||
color: #44dd44;
|
||||
}
|
||||
|
||||
.torrent .buttons .play {
|
||||
padding-top: 10px;
|
||||
background-color: #F44336;
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
/* global URL, Blob */
|
||||
|
||||
console.time('init')
|
||||
var airplay = require('airplay-js')
|
||||
var cfg = require('application-config')('WebTorrent')
|
||||
var cfgDirectory = require('application-config-path')('WebTorrent')
|
||||
var chromecasts = require('chromecasts')()
|
||||
var config = require('../config')
|
||||
var createTorrent = require('create-torrent')
|
||||
var dragDrop = require('drag-drop')
|
||||
var electron = require('electron')
|
||||
var EventEmitter = require('events')
|
||||
var extend = require('xtend')
|
||||
var fs = require('fs')
|
||||
var mainLoop = require('main-loop')
|
||||
var networkAddress = require('network-address')
|
||||
var os = require('os')
|
||||
var path = require('path')
|
||||
var torrentPoster = require('./lib/torrent-poster')
|
||||
var WebTorrent = require('webtorrent')
|
||||
@@ -23,8 +21,6 @@ var patch = require('virtual-dom/patch')
|
||||
|
||||
var App = require('./views/app')
|
||||
|
||||
var HOME = os.homedir()
|
||||
|
||||
// For easy debugging in Developer Tools
|
||||
var state = global.state = require('./state')
|
||||
|
||||
@@ -38,10 +34,6 @@ global.WEBTORRENT_ANNOUNCE = createTorrent.announceList
|
||||
})
|
||||
|
||||
var vdomLoop
|
||||
var defaultSaved = {
|
||||
torrents: [],
|
||||
downloadPath: path.join(HOME, 'Downloads')
|
||||
}
|
||||
|
||||
// All state lives in state.js. `state.saved` is read from and written to a file.
|
||||
// All other state is ephemeral. First we load state.saved then initialize the app.
|
||||
@@ -53,14 +45,12 @@ loadState(init)
|
||||
* the dock icon and drag+drop.
|
||||
*/
|
||||
function init () {
|
||||
document.querySelector('.loading').remove()
|
||||
|
||||
// Connect to the WebTorrent and BitTorrent networks
|
||||
// WebTorrent.app is a hybrid client, as explained here: https://webtorrent.io/faq
|
||||
state.client = new WebTorrent()
|
||||
state.client.on('warning', onWarning)
|
||||
state.client.on('error', onError)
|
||||
state.client.on('torrent', saveTorrentData)
|
||||
resumeTorrents() /* restart everything we were torrenting last time the app ran */
|
||||
|
||||
// The UI is built with virtual-dom, a minimalist library extracted from React
|
||||
// The concepts--one way data flow, a pure function that renders state to a
|
||||
@@ -78,8 +68,8 @@ function init () {
|
||||
// (eg % downloaded) and to keep the cursor in sync when playing a video
|
||||
setInterval(update, 1000)
|
||||
|
||||
// Resume all saved torrents now that state is loaded and vdom is ready
|
||||
resumeAllTorrents()
|
||||
// All state lives in state.js. `state.saved` is read from and written to a
|
||||
// file. All other state is ephemeral. Here we'll load state.saved:
|
||||
window.addEventListener('beforeunload', saveState)
|
||||
|
||||
// listen for messages from the main process
|
||||
@@ -121,6 +111,8 @@ function init () {
|
||||
state.isFocused = false
|
||||
})
|
||||
|
||||
// Done! Ideally we want to get here <100ms after the user clicks the app
|
||||
document.querySelector('.loading').remove() /* TODO: no spinner once fast enough */
|
||||
console.timeEnd('init')
|
||||
}
|
||||
|
||||
@@ -138,24 +130,29 @@ function update () {
|
||||
|
||||
// Events from the UI never modify state directly. Instead they call dispatch()
|
||||
function dispatch (action, ...args) {
|
||||
console.log('dispatch: %s %o', action, args)
|
||||
if (['videoMouseMoved', 'playbackJump'].indexOf(action) < 0) {
|
||||
console.log('dispatch: %s %o', action, args) /* log user interactions, but don't spam */
|
||||
}
|
||||
if (action === 'addTorrent') {
|
||||
addTorrent(args[0] /* torrentId */)
|
||||
addTorrent(args[0] /* torrent */)
|
||||
}
|
||||
if (action === 'seed') {
|
||||
seed(args[0] /* files */)
|
||||
}
|
||||
if (action === 'openPlayer') {
|
||||
openPlayer(args[0] /* torrent */)
|
||||
openPlayer(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'toggleTorrent') {
|
||||
toggleTorrent(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'deleteTorrent') {
|
||||
deleteTorrent(args[0] /* torrent */)
|
||||
deleteTorrent(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'openChromecast') {
|
||||
openChromecast(args[0] /* torrent */)
|
||||
openChromecast(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'openAirplay') {
|
||||
openAirplay(args[0] /* torrent */)
|
||||
openAirplay(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'setDimensions') {
|
||||
setDimensions(args[0] /* dimensions */)
|
||||
@@ -226,14 +223,17 @@ function loadState (callback) {
|
||||
electron.ipcRenderer.send('log', 'loaded state from ' + cfg.filePath)
|
||||
|
||||
// populate defaults if they're not there
|
||||
state.saved = extend(defaultSaved, data)
|
||||
state.saved = Object.assign({}, state.defaultSavedState, data)
|
||||
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
function resumeAllTorrents () {
|
||||
state.saved.torrents.forEach((x) => startTorrenting(x.infoHash))
|
||||
// Starts all torrents that aren't paused on program startup
|
||||
function resumeTorrents () {
|
||||
state.saved.torrents
|
||||
.filter((x) => x.status !== 'paused')
|
||||
.forEach((x) => startTorrenting(x.infoHash))
|
||||
}
|
||||
|
||||
// Write state.saved to the JSON state file
|
||||
@@ -279,57 +279,60 @@ function isNotTorrentFile (file) {
|
||||
return !isTorrentFile(file)
|
||||
}
|
||||
|
||||
// Gets a torrent summary {name, infoHash, status} from state.saved.torrents
|
||||
// Returns undefined if we don't know that infoHash
|
||||
function getTorrentSummary (infoHash) {
|
||||
return state.saved.torrents.find((x) => x.infoHash === infoHash)
|
||||
}
|
||||
|
||||
// Get an active torrent from state.client.torrents
|
||||
// Returns undefined if we are not currently torrenting that infoHash
|
||||
function getTorrent (infoHash) {
|
||||
return state.client.torrents.find((x) => x.infoHash === infoHash)
|
||||
}
|
||||
|
||||
// Adds a torrent to the list, starts downloading/seeding. TorrentID can be a
|
||||
// magnet URI, infohash, or torrent file: https://github.com/feross/webtorrent#clientaddtorrentid-opts-function-ontorrent-torrent-
|
||||
function addTorrent (torrentId) {
|
||||
if (!torrentId) torrentId = 'magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d&dn=sintel.mp4'
|
||||
// Charlie Chaplin: 'magnet:?xt=urn:btih:cddf0459a718523480f7499da5ed1a504cffecb8&dn=charlie%5Fchaplin%5Ffilm%5Ffestival'
|
||||
if (!torrentId) torrentId = 'magnet:?xt=urn:btih:6a02592d2bbc069628cd5ed8a54f88ee06ac0ba5&dn=CosmosLaundromatFirstCycle'
|
||||
|
||||
var torrent = startTorrenting(torrentId)
|
||||
|
||||
// check if torrent is duplicate
|
||||
var exists = state.saved.torrents.find((x) => x.infoHash === torrent.infoHash)
|
||||
if (exists) return window.alert('That torrent is already downloading.')
|
||||
// If torrentId is a torrent file, wait for WebTorrent to finish reading it
|
||||
if (!torrent.infoHash) torrent.on('infoHash', addTorrentToList)
|
||||
else addTorrentToList()
|
||||
|
||||
// save only if infoHash is available
|
||||
if (torrent.infoHash) {
|
||||
function addTorrentToList () {
|
||||
if (getTorrentSummary(torrent.infoHash)) {
|
||||
return // Skip, torrent is already in state.saved
|
||||
}
|
||||
state.saved.torrents.push({
|
||||
status: 'new',
|
||||
name: torrent.name,
|
||||
magnetURI: torrent.magnetURI,
|
||||
infoHash: torrent.infoHash
|
||||
})
|
||||
} else {
|
||||
torrent.on('infoHash', () => saveTorrentData(torrent))
|
||||
saveState()
|
||||
}
|
||||
|
||||
saveState()
|
||||
}
|
||||
|
||||
// add torrent metadata to state once it's available
|
||||
function saveTorrentData (torrent) {
|
||||
var ix = state.saved.torrents.findIndex((x) => x.infoHash === torrent.infoHash)
|
||||
var data = {
|
||||
name: torrent.name,
|
||||
magnetURI: torrent.magnetURI,
|
||||
infoHash: torrent.infoHash,
|
||||
path: torrent.path,
|
||||
xt: torrent.xt,
|
||||
dn: torrent.dn,
|
||||
announce: torrent.announce
|
||||
}
|
||||
|
||||
if (ix === -1) state.saved.torrents.push(data)
|
||||
else state.saved.torrents[ix] = data
|
||||
|
||||
saveState()
|
||||
}
|
||||
|
||||
// Starts downloading and/or seeding a given torrent file or magnet URI
|
||||
function startTorrenting (torrentId) {
|
||||
var torrent = state.client.add(torrentId, {
|
||||
// use downloads folder
|
||||
// Starts downloading and/or seeding a given torrent, torrentSummary or magnet URI
|
||||
function startTorrenting (infoHash) {
|
||||
var torrent = state.client.add(infoHash, {
|
||||
// Use downloads folder
|
||||
path: state.saved.downloadPath
|
||||
})
|
||||
addTorrentEvents(torrent)
|
||||
return torrent
|
||||
}
|
||||
|
||||
// Stops downloading and/or seeding. See startTorrenting
|
||||
function stopTorrenting (infoHash) {
|
||||
var torrent = getTorrent(infoHash)
|
||||
if (torrent) torrent.destroy()
|
||||
}
|
||||
|
||||
// Creates a torrent for a local file and starts seeding it
|
||||
function seed (files) {
|
||||
if (files.length === 0) return
|
||||
@@ -339,33 +342,60 @@ function seed (files) {
|
||||
|
||||
function addTorrentEvents (torrent) {
|
||||
torrent.on('infoHash', update)
|
||||
|
||||
torrent.on('ready', torrentReady)
|
||||
torrent.on('done', torrentDone)
|
||||
|
||||
update()
|
||||
|
||||
function torrentReady () {
|
||||
torrentPoster(torrent, function (err, buf) {
|
||||
if (err) return onWarning(err)
|
||||
torrent.posterURL = URL.createObjectURL(new Blob([ buf ], { type: 'image/png' }))
|
||||
update()
|
||||
})
|
||||
var torrentSummary = getTorrentSummary(torrent.infoHash)
|
||||
torrentSummary.status = 'downloading'
|
||||
torrentSummary.ready = true
|
||||
torrentSummary.name = torrent.name
|
||||
torrentSummary.infoHash = torrent.infoHash
|
||||
|
||||
if (!torrentSummary.posterURL) {
|
||||
generateTorrentPoster(torrent, torrentSummary)
|
||||
}
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
function torrentDone () {
|
||||
var torrentSummary = getTorrentSummary(torrent.infoHash)
|
||||
torrentSummary.status = 'seeding'
|
||||
|
||||
if (!state.isFocused) {
|
||||
state.dock.badge += 1
|
||||
electron.ipcRenderer.send('setBadge', state.dock.badge)
|
||||
}
|
||||
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
function startServer (torrent, cb) {
|
||||
function generateTorrentPoster (torrent, torrentSummary) {
|
||||
torrentPoster(torrent, function (err, buf) {
|
||||
if (err) return onWarning(err)
|
||||
// save it for next time
|
||||
var posterFilePath = path.join(cfgDirectory, torrent.infoHash + '.jpg')
|
||||
fs.writeFile(posterFilePath, buf, function (err) {
|
||||
if (err) return onWarning(err)
|
||||
// show the poster
|
||||
torrentSummary.posterURL = 'file:///' + posterFilePath
|
||||
update()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function startServer (infoHash, cb) {
|
||||
if (state.server) return cb()
|
||||
|
||||
var torrent = getTorrent(infoHash)
|
||||
if (!torrent) torrent = startTorrenting(infoHash)
|
||||
if (torrent.ready) startServerFromReadyTorrent(torrent, cb)
|
||||
else torrent.on('ready', () => startServerFromReadyTorrent(torrent, cb))
|
||||
}
|
||||
|
||||
function startServerFromReadyTorrent (torrent, cb) {
|
||||
// use largest file
|
||||
state.torrentPlaying = torrent.files.reduce(function (a, b) {
|
||||
return a.length > b.length ? a : b
|
||||
@@ -385,15 +415,15 @@ function startServer (torrent, cb) {
|
||||
})
|
||||
}
|
||||
|
||||
function closeServer () {
|
||||
function stopServer () {
|
||||
state.server.server.destroy()
|
||||
state.server = null
|
||||
}
|
||||
|
||||
function openPlayer (torrent) {
|
||||
startServer(torrent, function () {
|
||||
function openPlayer (infoHash) {
|
||||
startServer(infoHash, function () {
|
||||
state.url = '/player'
|
||||
state.title = torrent.name
|
||||
/* TODO: set state.title to the clean name of the torrent */
|
||||
update()
|
||||
})
|
||||
}
|
||||
@@ -405,20 +435,36 @@ function closePlayer () {
|
||||
electron.ipcRenderer.send('toggleFullScreen')
|
||||
}
|
||||
restoreBounds()
|
||||
closeServer()
|
||||
stopServer()
|
||||
update()
|
||||
}
|
||||
|
||||
function deleteTorrent (torrent) {
|
||||
var ix = state.saved.torrents.findIndex((x) => x.infoHash === torrent.infoHash)
|
||||
if (ix > -1) state.saved.torrents.splice(ix, 1)
|
||||
torrent.destroy(saveState)
|
||||
function toggleTorrent (infoHash) {
|
||||
var torrentSummary = getTorrentSummary(infoHash)
|
||||
if (!torrentSummary) return
|
||||
if (torrentSummary.status === 'paused') {
|
||||
torrentSummary.status = 'new'
|
||||
startTorrenting(torrentSummary.infoHash)
|
||||
} else {
|
||||
torrentSummary.status = 'paused'
|
||||
stopTorrenting(torrentSummary.infoHash)
|
||||
}
|
||||
}
|
||||
|
||||
function openChromecast (torrent) {
|
||||
startServer(torrent, function () {
|
||||
function deleteTorrent (infoHash) {
|
||||
var torrent = getTorrent(infoHash)
|
||||
torrent.destroy()
|
||||
|
||||
var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash)
|
||||
if (index > -1) state.saved.torrents.splice(index, 1)
|
||||
saveState()
|
||||
}
|
||||
|
||||
function openChromecast (infoHash) {
|
||||
var torrentSummary = getTorrentSummary(infoHash)
|
||||
startServer(infoHash, function () {
|
||||
state.devices.chromecast.play(state.server.networkURL, {
|
||||
title: config.APP_NAME + ' — ' + torrent.name
|
||||
title: config.APP_NAME + ' — ' + torrentSummary.name
|
||||
})
|
||||
state.devices.chromecast.on('error', function (err) {
|
||||
err.message = 'Chromecast: ' + err.message
|
||||
@@ -428,8 +474,8 @@ function openChromecast (torrent) {
|
||||
})
|
||||
}
|
||||
|
||||
function openAirplay (torrent) {
|
||||
startServer(torrent, function () {
|
||||
function openAirplay (infoHash) {
|
||||
startServer(infoHash, function () {
|
||||
state.devices.airplay.play(state.server.networkURL, 0, function () {
|
||||
// TODO: handle airplay errors
|
||||
})
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
var os = require('os')
|
||||
var path = require('path')
|
||||
|
||||
var config = require('../config')
|
||||
|
||||
module.exports = {
|
||||
@@ -44,5 +47,31 @@ module.exports = {
|
||||
*/
|
||||
saved: {
|
||||
torrents: []
|
||||
},
|
||||
|
||||
/* If the saved state file doesn't exist yet, here's what we use instead */
|
||||
defaultSavedState: {
|
||||
version: 1, /* make sure we can upgrade gracefully later */
|
||||
torrents: [
|
||||
{
|
||||
status: 'paused',
|
||||
infoHash: 'f84b51f0d2c3455ab5dabb6643b4340234cd036e',
|
||||
displayName: 'Big Buck Bunny',
|
||||
posterURL: '../resources/bigBuckBunny.jpg'
|
||||
},
|
||||
{
|
||||
status: 'paused',
|
||||
infoHash: '6a9759bffd5c0af65319979fb7832189f4f3c35d',
|
||||
displayName: 'Sintel',
|
||||
posterURL: '../resources/sintel.jpg'
|
||||
},
|
||||
{
|
||||
status: 'paused',
|
||||
infoHash: '02767050e0be2fd4db9a2ad6c12416ac806ed6ed',
|
||||
displayName: 'Tears of Steel',
|
||||
posterURL: '../resources/tearsOfSteel.jpg'
|
||||
}
|
||||
],
|
||||
downloadPath: path.join(os.homedir(), 'Downloads')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ var hx = hyperx(h)
|
||||
var prettyBytes = require('pretty-bytes')
|
||||
|
||||
function TorrentList (state, dispatch) {
|
||||
var list = state.client.torrents.map((torrent) => renderTorrent(torrent, dispatch))
|
||||
var list = state.saved.torrents.map(
|
||||
(torrentSummary) => renderTorrent(torrentSummary, state, dispatch))
|
||||
if (list.length === 0) list = emptyList()
|
||||
return hx`<div class='torrent-list'>${list}</div>`
|
||||
}
|
||||
@@ -24,76 +25,93 @@ function emptyList () {
|
||||
// Renders a torrent in the torrent list
|
||||
// Includes name, download status, play button, background image
|
||||
// May be expanded for additional info, including the list of files inside
|
||||
function renderTorrent (torrent, dispatch) {
|
||||
function renderTorrent (torrentSummary, state, dispatch) {
|
||||
// Get ephemeral data (like progress %) directly from the WebTorrent handle
|
||||
var torrent = state.client.torrents.find((x) => x.infoHash === torrentSummary.infoHash)
|
||||
|
||||
// Background image: show some nice visuals, like a frame from the movie, if possible
|
||||
var style = {}
|
||||
if (torrent.posterURL) {
|
||||
style['background-image'] = `linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%), url("${torrent.posterURL}")`
|
||||
if (torrentSummary.posterURL) {
|
||||
style['background-image'] = 'linear-gradient(to bottom, ' +
|
||||
'rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%), ' +
|
||||
`url("${torrentSummary.posterURL}")`
|
||||
}
|
||||
|
||||
// Foreground: name of the torrent, basic info like size, play button,
|
||||
// cast buttons if available, and delete
|
||||
return hx`
|
||||
<div class='torrent' style=${style}>
|
||||
${renderTorrentMetadata(torrent)}
|
||||
${renderTorrentButtons(torrent)}
|
||||
${renderTorrentMetadata(torrent, torrentSummary)}
|
||||
${renderTorrentButtons(torrentSummary, dispatch)}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function renderTorrentMetadata () {
|
||||
// Show name, download status, % complete
|
||||
function renderTorrentMetadata (torrent, torrentSummary) {
|
||||
var name = torrentSummary.displayName || torrentSummary.name || 'Loading torrent...'
|
||||
var elements = [hx`
|
||||
<div class='name ellipsis'>${name}</div>
|
||||
`]
|
||||
|
||||
// If a torrent is paused and we only get the torrentSummary
|
||||
// If it's downloading/seeding then we have more information
|
||||
if (torrent) {
|
||||
var progress = Math.floor(100 * torrent.progress)
|
||||
var downloaded = prettyBytes(torrent.downloaded)
|
||||
var total = prettyBytes(torrent.length || 0)
|
||||
|
||||
if (downloaded !== total) downloaded += ` / ${total}`
|
||||
|
||||
return hx`
|
||||
<div class='metadata'>
|
||||
<div class='name ellipsis'>${torrent.name || 'Loading torrent...'}</div>
|
||||
<div class='status ellipsis'>
|
||||
${getFilesLength()}
|
||||
<span>${getPeers()}</span>
|
||||
<span>↓ ${prettyBytes(torrent.downloadSpeed || 0)}/s</span>
|
||||
<span>↑ ${prettyBytes(torrent.uploadSpeed || 0)}/s</span>
|
||||
</div>
|
||||
<div class='status2 ellipsis'>
|
||||
<span class='progress'>${progress}%</span>
|
||||
<span>${downloaded}</span>
|
||||
</div>
|
||||
elements.push(hx`
|
||||
<div class='status ellipsis'>
|
||||
${getFilesLength()}
|
||||
<span>${getPeers()}</span>
|
||||
<span>↓ ${prettyBytes(torrent.downloadSpeed || 0)}/s</span>
|
||||
<span>↑ ${prettyBytes(torrent.uploadSpeed || 0)}/s</span>
|
||||
</div>
|
||||
`
|
||||
|
||||
function getPeers () {
|
||||
var count = torrent.numPeers === 1 ? 'peer' : 'peers'
|
||||
return `${torrent.numPeers} ${count}`
|
||||
}
|
||||
|
||||
function getFilesLength () {
|
||||
if (torrent.ready && torrent.files.length > 1) {
|
||||
return hx`<span class='files'>${torrent.files.length} files</span>`
|
||||
}
|
||||
}
|
||||
`)
|
||||
elements.push(hx`
|
||||
<div class='status2 ellipsis'>
|
||||
<span class='progress'>${progress}%</span>
|
||||
<span>${downloaded}</span>
|
||||
</div>
|
||||
`)
|
||||
}
|
||||
|
||||
function renderTorrentButtons () {
|
||||
return hx`
|
||||
<div class="buttons">
|
||||
<i.btn.icon.download
|
||||
class='${torrent.state}'
|
||||
onclick=${() => dispatch('toggleTorrent', torrent)}>
|
||||
file_download
|
||||
</i>
|
||||
<i.btn.icon.play
|
||||
class='${!torrent.ready ? 'disabled' : ''}'
|
||||
onclick=${() => dispatch('openPlayer', torrent)}>
|
||||
play_arrow
|
||||
</i>
|
||||
<i
|
||||
class='icon delete'
|
||||
onclick=${() => dispatch('deleteTorrent', torrent)}>
|
||||
close
|
||||
</i>
|
||||
</div>
|
||||
`
|
||||
return hx`<div class='metadata'>${elements}</div>`
|
||||
|
||||
function getPeers () {
|
||||
var count = torrent.numPeers === 1 ? 'peer' : 'peers'
|
||||
return `${torrent.numPeers} ${count}`
|
||||
}
|
||||
|
||||
function getFilesLength () {
|
||||
if (torrent.ready && torrent.files.length > 1) {
|
||||
return hx`<span class='files'>${torrent.files.length} files</span>`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Download button toggles between torrenting (DL/seed) and paused
|
||||
// Play button starts streaming the torrent immediately, unpausing if needed
|
||||
function renderTorrentButtons (torrentSummary, dispatch) {
|
||||
var infoHash = torrentSummary.infoHash
|
||||
return hx`
|
||||
<div class="buttons">
|
||||
<i.btn.icon.download
|
||||
class='${torrentSummary.status}'
|
||||
onclick=${() => dispatch('toggleTorrent', infoHash)}>
|
||||
file_download
|
||||
</i>
|
||||
<i.btn.icon.play
|
||||
onclick=${() => dispatch('openPlayer', infoHash)}>
|
||||
play_arrow
|
||||
</i>
|
||||
<i
|
||||
class='icon delete'
|
||||
onclick=${() => dispatch('deleteTorrent', infoHash)}>
|
||||
close
|
||||
</i>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
BIN
resources/bigBuckBunny.jpg
Normal file
BIN
resources/bigBuckBunny.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
BIN
resources/sintel.jpg
Normal file
BIN
resources/sintel.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
BIN
resources/tearsOfSteel.jpg
Normal file
BIN
resources/tearsOfSteel.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Reference in New Issue
Block a user