Convert Create Torrent modal to page, clean up App
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
"bitfield": "^1.0.2",
|
||||
"chromecasts": "^1.8.0",
|
||||
"concat-stream": "^1.5.1",
|
||||
"create-torrent": "^3.22.1",
|
||||
"create-torrent": "^3.24.5",
|
||||
"deep-equal": "^1.0.1",
|
||||
"dlnacasts": "^0.0.3",
|
||||
"drag-drop": "^2.11.0",
|
||||
|
||||
@@ -117,6 +117,30 @@ table {
|
||||
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
|
||||
*/
|
||||
@@ -260,23 +284,54 @@ table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.create-torrent-modal .torrent-attribute {
|
||||
.create-torrent-page {
|
||||
padding: 10px 25px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.create-torrent-page .torrent-attribute {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.create-torrent-modal .torrent-attribute>* {
|
||||
.create-torrent-page .torrent-attribute>* {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.create-torrent-modal .torrent-attribute label {
|
||||
.create-torrent-page .torrent-attribute label {
|
||||
width: 60px;
|
||||
margin-right: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.create-torrent-modal .torrent-attribute div {
|
||||
font-family: Consolas, monospace;
|
||||
.create-torrent-page .torrent-attribute>div {
|
||||
width: calc(100% - 90px);
|
||||
}
|
||||
|
||||
.create-torrent-page .torrent-attribute div {
|
||||
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;
|
||||
}
|
||||
|
||||
button.button-flat.light {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
button.button-flat:hover,
|
||||
button.button-flat:focus { /* Material design: focused */
|
||||
background-color: rgba(153, 153, 153, 0.2);
|
||||
|
||||
@@ -523,12 +523,7 @@ function onOpen (files) {
|
||||
|
||||
// everything else = seed these files
|
||||
var rest = files.filter(not(isTorrent)).filter(not(isSubtitle))
|
||||
if (rest.length > 0) {
|
||||
state.modal = {
|
||||
id: 'create-torrent-modal',
|
||||
files: rest
|
||||
}
|
||||
}
|
||||
if (rest.length > 0) showCreateTorrent(rest)
|
||||
}
|
||||
|
||||
function isTorrent (file) {
|
||||
@@ -640,34 +635,49 @@ function startTorrentingSummary (torrentSummary) {
|
||||
// Shows the Create Torrent page with options to seed a given file or folder
|
||||
function showCreateTorrent (files) {
|
||||
if (Array.isArray(files)) {
|
||||
state.modal = {
|
||||
id: 'create-torrent-modal',
|
||||
if (state.location.pending() || state.location.current().url !== 'home') return
|
||||
state.location.go({
|
||||
url: 'create-torrent',
|
||||
files: files
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
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) {
|
||||
if (err) return onError(err)
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdir(fileOrFolder, function (err, fileNames) {
|
||||
if (err) return onError(err)
|
||||
// TODO: support nested folders
|
||||
var fileObjs = fileNames.map(function (fileName) {
|
||||
return {
|
||||
name: fileName,
|
||||
path: path.join(fileOrFolder, fileName)
|
||||
}
|
||||
})
|
||||
showCreateTorrent(fileObjs)
|
||||
})
|
||||
} else {
|
||||
showCreateTorrent([{
|
||||
name: path.basename(fileOrFolder),
|
||||
path: fileOrFolder
|
||||
|
||||
// Files: return name, path, and size
|
||||
if (!stat.isDirectory()) {
|
||||
var filePath = fileOrFolder
|
||||
return cb([{
|
||||
name: path.basename(filePath),
|
||||
path: filePath,
|
||||
length: stat.size
|
||||
}])
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,17 @@ var hyperx = require('hyperx')
|
||||
var hx = hyperx(h)
|
||||
|
||||
var Header = require('./header')
|
||||
var Player = require('./player')
|
||||
var TorrentList = require('./torrent-list')
|
||||
var Views = {
|
||||
'home': require('./torrent-list'),
|
||||
'player': require('./player'),
|
||||
'create-torrent': require('./create-torrent-page')
|
||||
}
|
||||
var Modals = {
|
||||
'open-torrent-address-modal': require('./open-torrent-address-modal'),
|
||||
'update-available-modal': require('./update-available-modal'),
|
||||
'create-torrent-modal': require('./create-torrent-modal')
|
||||
'update-available-modal': require('./update-available-modal')
|
||||
}
|
||||
|
||||
function App (state, dispatch) {
|
||||
function App (state) {
|
||||
// Hide player controls while playing video, if the mouse stays still for a while
|
||||
// Never hide the controls when:
|
||||
// * The mouse is over the controls or we're scrubbing (see CSS)
|
||||
@@ -40,47 +42,43 @@ function App (state, dispatch) {
|
||||
|
||||
return hx`
|
||||
<div class='app ${cls.join(' ')}'>
|
||||
${Header(state, dispatch)}
|
||||
${getErrorPopover()}
|
||||
<div class='content'>${getView()}</div>
|
||||
${getModal()}
|
||||
${Header(state)}
|
||||
${getErrorPopover(state)}
|
||||
<div class='content'>${getView(state)}</div>
|
||||
${getModal(state)}
|
||||
</div>
|
||||
`
|
||||
|
||||
function getErrorPopover () {
|
||||
var now = new Date().getTime()
|
||||
var recentErrors = state.errors.filter((x) => now - x.time < 5000)
|
||||
|
||||
var errorElems = recentErrors.map(function (error) {
|
||||
return hx`<div class='error'>${error.message}</div>`
|
||||
})
|
||||
return hx`
|
||||
<div class='error-popover ${recentErrors.length > 0 ? 'visible' : 'hidden'}'>
|
||||
<div class='title'>Error</div>
|
||||
${errorElems}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function getModal () {
|
||||
if (state.modal) {
|
||||
var contents = Modals[state.modal.id](state, dispatch)
|
||||
return hx`
|
||||
<div class='modal'>
|
||||
<div class='modal-background'></div>
|
||||
<div class='modal-content'>
|
||||
${contents}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
function getView () {
|
||||
if (state.location.current().url === 'home') {
|
||||
return TorrentList(state, dispatch)
|
||||
} else if (state.location.current().url === 'player') {
|
||||
return Player(state, dispatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getErrorPopover (state) {
|
||||
var now = new Date().getTime()
|
||||
var recentErrors = state.errors.filter((x) => now - x.time < 5000)
|
||||
|
||||
var errorElems = recentErrors.map(function (error) {
|
||||
return hx`<div class='error'>${error.message}</div>`
|
||||
})
|
||||
return hx`
|
||||
<div class='error-popover ${recentErrors.length > 0 ? 'visible' : 'hidden'}'>
|
||||
<div class='title'>Error</div>
|
||||
${errorElems}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function getModal (state) {
|
||||
if (!state.modal) return
|
||||
var contents = Modals[state.modal.id](state)
|
||||
return hx`
|
||||
<div class='modal'>
|
||||
<div class='modal-background'></div>
|
||||
<div class='modal-content'>
|
||||
${contents}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function getView (state) {
|
||||
var url = state.location.current().url
|
||||
return Views[url](state)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
133
renderer/views/create-torrent-page.js
Normal file
133
renderer/views/create-torrent-page.js
Normal 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)
|
||||
}
|
||||
@@ -95,8 +95,9 @@ function stopTorrenting (infoHash) {
|
||||
|
||||
// Create a new torrent, start seeding
|
||||
function createTorrent (torrentKey, options) {
|
||||
console.log('creating torrent %s', torrentKey, options)
|
||||
var torrent = client.seed(options.files, options)
|
||||
console.log('creating torrent', torrentKey, options)
|
||||
var paths = options.files.map((f) => f.path)
|
||||
var torrent = client.seed(paths, options)
|
||||
torrent.key = torrentKey
|
||||
addTorrentEvents(torrent)
|
||||
ipc.send('wt-new-torrent')
|
||||
|
||||
Reference in New Issue
Block a user