Convert Create Torrent modal to page, clean up App

This commit is contained in:
DC
2016-04-21 02:57:43 -07:00
committed by DC
parent 31ef283e7b
commit 1479369db1
7 changed files with 280 additions and 164 deletions

View File

@@ -19,7 +19,7 @@
"bitfield": "^1.0.2", "bitfield": "^1.0.2",
"chromecasts": "^1.8.0", "chromecasts": "^1.8.0",
"concat-stream": "^1.5.1", "concat-stream": "^1.5.1",
"create-torrent": "^3.22.1", "create-torrent": "^3.24.5",
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
"dlnacasts": "^0.0.3", "dlnacasts": "^0.0.3",
"drag-drop": "^2.11.0", "drag-drop": "^2.11.0",

View File

@@ -117,6 +117,30 @@ table {
float: right; float: right;
} }
.expand-collapse {
cursor: pointer;
}
.expand-collapse.expanded::before {
content: '▲'
}
.expand-collapse.collapsed::before {
content: '▼'
}
.expand-collapse::before {
margin-right: 5px;
}
.expand-collapse.collapsed {
display: block;
}
.collapsed {
display: none;
}
/* /*
* HEADER * HEADER
*/ */
@@ -260,23 +284,54 @@ table {
width: 100%; width: 100%;
} }
.create-torrent-modal .torrent-attribute { .create-torrent-page {
padding: 10px 25px;
overflow: hidden;
}
.create-torrent-page .torrent-attribute {
white-space: nowrap; white-space: nowrap;
} }
.create-torrent-modal .torrent-attribute>* { .create-torrent-page .torrent-attribute>* {
display: inline-block; display: inline-block;
} }
.create-torrent-modal .torrent-attribute label { .create-torrent-page .torrent-attribute label {
width: 60px; width: 60px;
margin-right: 10px; margin-right: 10px;
vertical-align: top; vertical-align: top;
} }
.create-torrent-modal .torrent-attribute div { .create-torrent-page .torrent-attribute>div {
font-family: Consolas, monospace; width: calc(100% - 90px);
}
.create-torrent-page .torrent-attribute div {
white-space: nowrap; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.create-torrent-page .torrent-attribute textarea {
width: calc(100% - 80px);
height: 80px;
color: #eee;
background-color: transparent;
line-height: 1.5;
font-size: 14px;
font-family: inherit;
border-radius: 2px;
padding: 4px 6px;
}
.create-torrent-page textarea.torrent-trackers {
height: 200px;
}
.create-torrent-page input.torrent-is-private {
width: initial;
margin: 0;
} }
/* /*
@@ -322,6 +377,10 @@ button.button-flat {
padding: 7px 18px; padding: 7px 18px;
} }
button.button-flat.light {
color: #eee;
}
button.button-flat:hover, button.button-flat:hover,
button.button-flat:focus { /* Material design: focused */ button.button-flat:focus { /* Material design: focused */
background-color: rgba(153, 153, 153, 0.2); background-color: rgba(153, 153, 153, 0.2);

View File

@@ -523,12 +523,7 @@ function onOpen (files) {
// everything else = seed these files // everything else = seed these files
var rest = files.filter(not(isTorrent)).filter(not(isSubtitle)) var rest = files.filter(not(isTorrent)).filter(not(isSubtitle))
if (rest.length > 0) { if (rest.length > 0) showCreateTorrent(rest)
state.modal = {
id: 'create-torrent-modal',
files: rest
}
}
} }
function isTorrent (file) { function isTorrent (file) {
@@ -640,34 +635,49 @@ function startTorrentingSummary (torrentSummary) {
// 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
function showCreateTorrent (files) { function showCreateTorrent (files) {
if (Array.isArray(files)) { if (Array.isArray(files)) {
state.modal = { if (state.location.pending() || state.location.current().url !== 'home') return
id: 'create-torrent-modal', state.location.go({
url: 'create-torrent',
files: files files: files
} })
return return
} }
var fileOrFolder = files var fileOrFolder = files
findFilesRecursive(fileOrFolder, showCreateTorrent)
}
// Recursively finds {name, length, path} for all files in a folder
// Calls `cb` on success, calls `onError` on failure
function findFilesRecursive (fileOrFolder, cb) {
fs.stat(fileOrFolder, function (err, stat) { fs.stat(fileOrFolder, function (err, stat) {
if (err) return onError(err) if (err) return onError(err)
if (stat.isDirectory()) {
fs.readdir(fileOrFolder, function (err, fileNames) { // Files: return name, path, and size
if (err) return onError(err) if (!stat.isDirectory()) {
// TODO: support nested folders var filePath = fileOrFolder
var fileObjs = fileNames.map(function (fileName) { return cb([{
return { name: path.basename(filePath),
name: fileName, path: filePath,
path: path.join(fileOrFolder, fileName) length: stat.size
}
})
showCreateTorrent(fileObjs)
})
} else {
showCreateTorrent([{
name: path.basename(fileOrFolder),
path: fileOrFolder
}]) }])
} }
// Folders: recurse, make a list of all the files
var folderPath = fileOrFolder
fs.readdir(folderPath, function (err, fileNames) {
if (err) return onError(err)
var numComplete = 0
var ret = []
fileNames.forEach(function (fileName) {
findFilesRecursive(path.join(folderPath, fileName), function (fileObjs) {
ret = ret.concat(fileObjs)
if (++numComplete === fileNames.length) {
cb(ret)
}
})
})
})
}) })
} }

View File

@@ -5,15 +5,17 @@ var hyperx = require('hyperx')
var hx = hyperx(h) var hx = hyperx(h)
var Header = require('./header') var Header = require('./header')
var Player = require('./player') var Views = {
var TorrentList = require('./torrent-list') 'home': require('./torrent-list'),
'player': require('./player'),
'create-torrent': require('./create-torrent-page')
}
var Modals = { var Modals = {
'open-torrent-address-modal': require('./open-torrent-address-modal'), 'open-torrent-address-modal': require('./open-torrent-address-modal'),
'update-available-modal': require('./update-available-modal'), 'update-available-modal': require('./update-available-modal')
'create-torrent-modal': require('./create-torrent-modal')
} }
function App (state, dispatch) { function App (state) {
// Hide player controls while playing video, if the mouse stays still for a while // Hide player controls while playing video, if the mouse stays still for a while
// Never hide the controls when: // Never hide the controls when:
// * The mouse is over the controls or we're scrubbing (see CSS) // * The mouse is over the controls or we're scrubbing (see CSS)
@@ -40,14 +42,15 @@ function App (state, dispatch) {
return hx` return hx`
<div class='app ${cls.join(' ')}'> <div class='app ${cls.join(' ')}'>
${Header(state, dispatch)} ${Header(state)}
${getErrorPopover()} ${getErrorPopover(state)}
<div class='content'>${getView()}</div> <div class='content'>${getView(state)}</div>
${getModal()} ${getModal(state)}
</div> </div>
` `
}
function getErrorPopover () { function getErrorPopover (state) {
var now = new Date().getTime() var now = new Date().getTime()
var recentErrors = state.errors.filter((x) => now - x.time < 5000) var recentErrors = state.errors.filter((x) => now - x.time < 5000)
@@ -60,11 +63,11 @@ function App (state, dispatch) {
${errorElems} ${errorElems}
</div> </div>
` `
} }
function getModal () { function getModal (state) {
if (state.modal) { if (!state.modal) return
var contents = Modals[state.modal.id](state, dispatch) var contents = Modals[state.modal.id](state)
return hx` return hx`
<div class='modal'> <div class='modal'>
<div class='modal-background'></div> <div class='modal-background'></div>
@@ -73,14 +76,9 @@ function App (state, dispatch) {
</div> </div>
</div> </div>
` `
} }
}
function getView (state) {
function getView () { var url = state.location.current().url
if (state.location.current().url === 'home') { return Views[url](state)
return TorrentList(state, dispatch)
} else if (state.location.current().url === 'player') {
return Player(state, dispatch)
}
}
} }

View File

@@ -1,85 +0,0 @@
module.exports = UpdateAvailableModal
var h = require('virtual-dom/h')
var hyperx = require('hyperx')
var hx = hyperx(h)
var path = require('path')
var {dispatch} = require('../lib/dispatcher')
function UpdateAvailableModal (state) {
var info = state.modal
// First, extract the base folder that the files are all in
var files = info.files
var pathPrefix = info.folderPath
if (!pathPrefix) {
if (files.length > 0) {
pathPrefix = files.map((x) => x.path).reduce(findCommonPrefix)
if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
pathPrefix = path.dirname(pathPrefix)
}
} else {
pathPrefix = files[0]
}
}
// Then, use the name of the base folder (or sole file, for a single file torrent)
// as the default name. Show all files relative to the base folder.
var defaultName = path.basename(pathPrefix)
var basePath = path.dirname(pathPrefix)
var fileElems = files.map(function (file) {
var relativePath = files.length === 0 ? file.name : path.relative(pathPrefix, file.path)
return hx`<div>${relativePath}</div>`
})
return hx`
<div class='create-torrent-modal'>
<p><strong>Create New Torrent</strong></p>
<p class='torrent-attribute'>
<label>Name:</label>
<div class='torrent-attribute'>${defaultName}</div>
</p>
<p class='torrent-attribute'>
<label>Path:</label>
<div class='torrent-attribute'>${pathPrefix}</div>
</p>
<p class='torrent-attribute'>
<label>Files:</label>
<div>${fileElems}</div>
</p>
<p>
<button class='primary' onclick=${handleOK}>Create Torrent</button>
<button class='cancel' onclick=${handleCancel}>Cancel</button>
</p>
</div>
`
function handleOK () {
var options = {
// TODO: we can't let the user choose their own name if we want WebTorrent
// to use the files in place rather than creating a new folder.
// name: document.querySelector('.torrent-name').value
name: defaultName,
path: basePath,
files: files
}
dispatch('createTorrent', options)
dispatch('exitModal')
}
function handleCancel () {
dispatch('exitModal')
}
}
// Finds the longest common prefix
function findCommonPrefix (a, b) {
for (var i = 0; i < a.length && i < b.length; i++) {
if (a.charCodeAt(i) !== b.charCodeAt(i)) break
}
if (i === a.length) return a
if (i === b.length) return b
return a.substring(0, i)
}

View File

@@ -0,0 +1,133 @@
module.exports = CreateTorrentPage
var h = require('virtual-dom/h')
var hyperx = require('hyperx')
var hx = hyperx(h)
var createTorrent = require('create-torrent')
var path = require('path')
var prettyBytes = require('prettier-bytes')
var {dispatch} = require('../lib/dispatcher')
function CreateTorrentPage (state) {
var info = state.location.current()
// First, extract the base folder that the files are all in
var files = info.files
var pathPrefix = info.folderPath
if (!pathPrefix) {
if (files.length > 0) {
pathPrefix = files.map((x) => x.path).reduce(findCommonPrefix)
if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
pathPrefix = path.dirname(pathPrefix)
}
} else {
pathPrefix = files[0]
}
}
// Sanity check: show the number of files and total size
var numFiles = files.length
var totalBytes = files
.map((f) => f.length)
.reduce((a, b) => a + b, 0)
var torrentInfo = `${numFiles} files, ${prettyBytes(totalBytes)}`
// Then, use the name of the base folder (or sole file, for a single file torrent)
// as the default name. Show all files relative to the base folder.
var defaultName = path.basename(pathPrefix)
var basePath = path.dirname(pathPrefix)
var maxFileElems = 100
var fileElems = files.slice(0, maxFileElems).map(function (file) {
var relativePath = files.length === 0 ? file.name : path.relative(pathPrefix, file.path)
return hx`<div>${relativePath}</div>`
})
if (files.length > maxFileElems) {
fileElems.push(hx`<div>+ ${maxFileElems - files.length} more</div>`)
}
var trackers = createTorrent.announceList.join('\n')
var collapsedClass = info.showAdvanced ? 'expanded' : 'collapsed'
return hx`
<div class='create-torrent-page'>
<h2>Create torrent ${defaultName}</h2>
<p class="torrent-info">
${torrentInfo}
</p>
<p class='torrent-attribute'>
<label>Path:</label>
<div class='torrent-attribute'>${pathPrefix}</div>
</p>
<div class='expand-collapse ${collapsedClass}' onclick=${handleToggleShowAdvanced}>
${info.showAdvanced ? 'Basic' : 'Advanced'}
</div>
<div class="create-torrent-advanced ${collapsedClass}">
<p class='torrent-attribute'>
<label>Comment:</label>
<textarea class='torrent-attribute torrent-comment'></textarea>
</p>
<p class='torrent-attribute'>
<label>Trackers:</label>
<textarea class='torrent-attribute torrent-trackers'>${trackers}</textarea>
</p>
<p class='torrent-attribute'>
<label>Private:</label>
<input type='checkbox' class='torrent-is-private' value='torrent-is-private'>
</p>
<p class='torrent-attribute'>
<label>Files:</label>
<div>${fileElems}</div>
</p>
</div>
<p class="float-right">
<button class='button-flat light' onclick=${handleCancel}>Cancel</button>
<button class='button-raised' onclick=${handleOK}>Create Torrent</button>
</p>
</div>
`
function handleOK () {
var announceList = document.querySelector('.torrent-trackers').value
.split('\n')
.map((s) => s.trim())
.filter((s) => s !== '')
var isPrivate = document.querySelector('.torrent-is-private').checked
var comment = document.querySelector('.torrent-comment').value.trim()
var options = {
// We can't let the user choose their own name if we want WebTorrent
// to use the files in place rather than creating a new folder.
// If we ever want to add support for that:
// name: document.querySelector('.torrent-name').value
name: defaultName,
path: basePath,
files: files,
announce: announceList,
private: isPrivate,
comment: comment
}
dispatch('createTorrent', options)
dispatch('backToList')
}
function handleCancel () {
dispatch('backToList')
}
function handleToggleShowAdvanced () {
// TODO: what's the clean way to handle this?
// Should every button on every screen have its own dispatch()?
info.showAdvanced = !info.showAdvanced
dispatch('update')
}
}
// Finds the longest common prefix
function findCommonPrefix (a, b) {
for (var i = 0; i < a.length && i < b.length; i++) {
if (a.charCodeAt(i) !== b.charCodeAt(i)) break
}
if (i === a.length) return a
if (i === b.length) return b
return a.substring(0, i)
}

View File

@@ -95,8 +95,9 @@ function stopTorrenting (infoHash) {
// Create a new torrent, start seeding // Create a new torrent, start seeding
function createTorrent (torrentKey, options) { function createTorrent (torrentKey, options) {
console.log('creating torrent %s', torrentKey, options) console.log('creating torrent', torrentKey, options)
var torrent = client.seed(options.files, options) var paths = options.files.map((f) => f.path)
var torrent = client.seed(paths, options)
torrent.key = torrentKey torrent.key = torrentKey
addTorrentEvents(torrent) addTorrentEvents(torrent)
ipc.send('wt-new-torrent') ipc.send('wt-new-torrent')