Convert Create Torrent modal to page, clean up App
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
@@ -62,9 +65,9 @@ function App (state, dispatch) {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
||||||
@@ -74,13 +77,8 @@ function App (state, dispatch) {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function getView () {
|
function getView (state) {
|
||||||
if (state.location.current().url === 'home') {
|
var url = state.location.current().url
|
||||||
return TorrentList(state, dispatch)
|
return Views[url](state)
|
||||||
} else if (state.location.current().url === 'player') {
|
|
||||||
return Player(state, dispatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// 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')
|
||||||
|
|||||||
Reference in New Issue
Block a user