Merge branch 'master' into feature/sort-file-alphanumerically-1185
This commit is contained in:
@@ -12,10 +12,10 @@ const Header = require('../components/header')
|
||||
const TorrentListPage = require('./torrent-list-page')
|
||||
|
||||
const Views = {
|
||||
'home': createGetter(() => TorrentListPage),
|
||||
'player': createGetter(() => require('./player-page')),
|
||||
home: createGetter(() => TorrentListPage),
|
||||
player: createGetter(() => require('./player-page')),
|
||||
'create-torrent': createGetter(() => require('./create-torrent-page')),
|
||||
'preferences': createGetter(() => require('./preferences-page'))
|
||||
preferences: createGetter(() => require('./preferences-page'))
|
||||
}
|
||||
|
||||
const Modals = {
|
||||
@@ -24,7 +24,9 @@ const Modals = {
|
||||
),
|
||||
'remove-torrent-modal': createGetter(() => require('../components/remove-torrent-modal')),
|
||||
'update-available-modal': createGetter(() => require('../components/update-available-modal')),
|
||||
'unsupported-media-modal': createGetter(() => require('../components/unsupported-media-modal'))
|
||||
'unsupported-media-modal': createGetter(() => require('../components/unsupported-media-modal')),
|
||||
'delete-all-torrents-modal':
|
||||
createGetter(() => require('../components/delete-all-torrents-modal'))
|
||||
}
|
||||
|
||||
const fontFamily = process.platform === 'win32'
|
||||
@@ -88,8 +90,10 @@ class App extends React.Component {
|
||||
return (<div key={i} className='error'>{error.message}</div>)
|
||||
})
|
||||
return (
|
||||
<div key='errors'
|
||||
className={'error-popover ' + (hasErrors ? 'visible' : 'hidden')}>
|
||||
<div
|
||||
key='errors'
|
||||
className={'error-popover ' + (hasErrors ? 'visible' : 'hidden')}
|
||||
>
|
||||
<div key='title' className='title'>Error</div>
|
||||
{errorElems}
|
||||
</div>
|
||||
|
||||
@@ -65,9 +65,9 @@ class CreateTorrentPage extends React.Component {
|
||||
}
|
||||
|
||||
// Create React event handlers only once
|
||||
this.setIsPrivate = (_, isPrivate) => this.setState({ isPrivate })
|
||||
this.setComment = (_, comment) => this.setState({ comment })
|
||||
this.setTrackers = (_, trackers) => this.setState({ trackers })
|
||||
this.handleSetIsPrivate = (_, isPrivate) => this.setState({ isPrivate })
|
||||
this.handleSetComment = (_, comment) => this.setState({ comment })
|
||||
this.handleSetTrackers = (_, trackers) => this.setState({ trackers })
|
||||
this.handleSubmit = handleSubmit.bind(this)
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@ class CreateTorrentPage extends React.Component {
|
||||
marginBottom: 10
|
||||
}}
|
||||
hideLabel='Hide advanced settings...'
|
||||
showLabel='Show advanced settings...'>
|
||||
showLabel='Show advanced settings...'
|
||||
>
|
||||
{this.renderAdvanced()}
|
||||
</ShowMore>
|
||||
<div className='float-right'>
|
||||
@@ -104,12 +105,14 @@ class CreateTorrentPage extends React.Component {
|
||||
style={{
|
||||
marginRight: 10
|
||||
}}
|
||||
onClick={dispatcher('cancel')} />
|
||||
onClick={dispatcher('cancel')}
|
||||
/>
|
||||
<RaisedButton
|
||||
className='control create-torrent-button'
|
||||
label='Create Torrent'
|
||||
primary
|
||||
onClick={this.handleSubmit} />
|
||||
onClick={this.handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -143,7 +146,8 @@ class CreateTorrentPage extends React.Component {
|
||||
className='torrent-is-private control'
|
||||
style={{ display: '' }}
|
||||
checked={this.state.isPrivate}
|
||||
onCheck={this.setIsPrivate} />
|
||||
onCheck={this.handleSetIsPrivate}
|
||||
/>
|
||||
</div>
|
||||
<div key='trackers' className='torrent-attribute'>
|
||||
<label>Trackers:</label>
|
||||
@@ -155,7 +159,8 @@ class CreateTorrentPage extends React.Component {
|
||||
rows={2}
|
||||
rowsMax={10}
|
||||
value={this.state.trackers}
|
||||
onChange={this.setTrackers} />
|
||||
onChange={this.handleSetTrackers}
|
||||
/>
|
||||
</div>
|
||||
<div key='comment' className='torrent-attribute'>
|
||||
<label>Comment:</label>
|
||||
@@ -168,7 +173,8 @@ class CreateTorrentPage extends React.Component {
|
||||
rows={2}
|
||||
rowsMax={10}
|
||||
value={this.state.comment}
|
||||
onChange={this.setComment} />
|
||||
onChange={this.handleSetComment}
|
||||
/>
|
||||
</div>
|
||||
<div key='files' className='torrent-attribute'>
|
||||
<label>Files:</label>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const React = require('react')
|
||||
const Bitfield = require('bitfield')
|
||||
const prettyBytes = require('prettier-bytes')
|
||||
const zeroFill = require('zero-fill')
|
||||
|
||||
const TorrentSummary = require('../lib/torrent-summary')
|
||||
const Playlist = require('../lib/playlist')
|
||||
@@ -20,7 +19,8 @@ module.exports = class Player extends React.Component {
|
||||
<div
|
||||
className='player'
|
||||
onWheel={handleVolumeWheel}
|
||||
onMouseMove={dispatcher('mediaMouseMoved')}>
|
||||
onMouseMove={dispatcher('mediaMouseMoved')}
|
||||
>
|
||||
{showVideo ? renderMedia(state) : renderCastScreen(state)}
|
||||
{showControls ? renderPlayerControls(state) : null}
|
||||
</div>
|
||||
@@ -95,6 +95,13 @@ function renderMedia (state) {
|
||||
delete file.selectedSubtitle
|
||||
}
|
||||
|
||||
// Switch to selected audio track
|
||||
const audioTracks = mediaElement.audioTracks || []
|
||||
for (let j = 0; j < audioTracks.length; j++) {
|
||||
const isSelectedTrack = j === state.playing.audioTracks.selectedIndex
|
||||
audioTracks[j].enabled = isSelectedTrack
|
||||
}
|
||||
|
||||
state.playing.volume = mediaElement.volume
|
||||
}
|
||||
|
||||
@@ -107,10 +114,11 @@ function renderMedia (state) {
|
||||
trackTags.push(
|
||||
<track
|
||||
key={i}
|
||||
default={isSelected ? 'default' : ''}
|
||||
default={isSelected}
|
||||
label={track.label}
|
||||
type='subtitles'
|
||||
src={track.buffer} />
|
||||
kind='subtitles'
|
||||
src={track.buffer}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -127,7 +135,7 @@ function renderMedia (state) {
|
||||
onError={dispatcher('mediaError')}
|
||||
onTimeUpdate={dispatcher('mediaTimeUpdate')}
|
||||
onEncrypted={dispatcher('mediaEncrypted')}
|
||||
onCanPlay={onCanPlay}>
|
||||
>
|
||||
{trackTags}
|
||||
</MediaTagName>
|
||||
)
|
||||
@@ -137,21 +145,57 @@ function renderMedia (state) {
|
||||
<div
|
||||
key='letterbox'
|
||||
className='letterbox'
|
||||
onMouseMove={dispatcher('mediaMouseMoved')}>
|
||||
onMouseMove={dispatcher('mediaMouseMoved')}
|
||||
>
|
||||
{mediaTag}
|
||||
{renderOverlay(state)}
|
||||
</div>
|
||||
)
|
||||
|
||||
// As soon as we know the video dimensions, resize the window
|
||||
function onLoadedMetadata (e) {
|
||||
if (state.playing.type !== 'video') return
|
||||
const video = e.target
|
||||
const dimensions = {
|
||||
width: video.videoWidth,
|
||||
height: video.videoHeight
|
||||
const mediaElement = e.target
|
||||
|
||||
// check if we can decode video and audio track
|
||||
if (state.playing.type === 'video') {
|
||||
if (mediaElement.videoTracks.length === 0) {
|
||||
dispatch('mediaError', 'Video codec unsupported')
|
||||
}
|
||||
|
||||
if (mediaElement.audioTracks.length === 0) {
|
||||
dispatch('mediaError', 'Audio codec unsupported')
|
||||
}
|
||||
|
||||
dispatch('mediaSuccess')
|
||||
|
||||
const dimensions = {
|
||||
width: mediaElement.videoWidth,
|
||||
height: mediaElement.videoHeight
|
||||
}
|
||||
|
||||
// As soon as we know the video dimensions, resize the window
|
||||
dispatch('setDimensions', dimensions)
|
||||
|
||||
// set audioTracks
|
||||
const tracks = []
|
||||
for (let i = 0; i < mediaElement.audioTracks.length; i++) {
|
||||
tracks.push({
|
||||
label: mediaElement.audioTracks[i].label || `Track ${i + 1}`,
|
||||
language: mediaElement.audioTracks[i].language
|
||||
})
|
||||
}
|
||||
|
||||
state.playing.audioTracks.tracks = tracks
|
||||
state.playing.audioTracks.selectedIndex = 0
|
||||
}
|
||||
|
||||
// check if we can decode audio track
|
||||
if (state.playing.type === 'audio') {
|
||||
if (mediaElement.audioTracks.length === 0) {
|
||||
dispatch('mediaError', 'Audio codec unsupported')
|
||||
}
|
||||
|
||||
dispatch('mediaSuccess')
|
||||
}
|
||||
dispatch('setDimensions', dimensions)
|
||||
}
|
||||
|
||||
function onEnded (e) {
|
||||
@@ -163,19 +207,6 @@ function renderMedia (state) {
|
||||
if (state.window.isFullScreen) dispatch('toggleFullScreen')
|
||||
}
|
||||
}
|
||||
|
||||
function onCanPlay (e) {
|
||||
const elem = e.target
|
||||
if (state.playing.type === 'video' &&
|
||||
elem.webkitVideoDecodedByteCount === 0) {
|
||||
dispatch('mediaError', 'Video codec unsupported')
|
||||
} else if (elem.webkitAudioDecodedByteCount === 0) {
|
||||
dispatch('mediaError', 'Audio codec unsupported')
|
||||
} else {
|
||||
dispatch('mediaSuccess')
|
||||
elem.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderOverlay (state) {
|
||||
@@ -293,7 +324,7 @@ function renderAudioMetadata (state) {
|
||||
}
|
||||
elems.push((
|
||||
<div key='release' className='audio-release'>
|
||||
<label>Release</label>{ releaseInfo.join(', ') }
|
||||
<label>Release</label>{releaseInfo.join(', ')}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
@@ -301,14 +332,18 @@ function renderAudioMetadata (state) {
|
||||
// Audio metadata: format
|
||||
const format = []
|
||||
fileSummary.audioInfo.format = fileSummary.audioInfo.format || ''
|
||||
if (fileSummary.audioInfo.format.dataformat) {
|
||||
format.push(fileSummary.audioInfo.format.dataformat)
|
||||
if (fileSummary.audioInfo.format.container) {
|
||||
format.push(fileSummary.audioInfo.format.container)
|
||||
}
|
||||
if (fileSummary.audioInfo.format.codec &&
|
||||
fileSummary.audioInfo.format.container !== fileSummary.audioInfo.format.codec) {
|
||||
format.push(fileSummary.audioInfo.format.codec)
|
||||
}
|
||||
if (fileSummary.audioInfo.format.bitrate) {
|
||||
format.push(Math.round(fileSummary.audioInfo.format.bitrate / 1000) + ' kbps') // 128 kbps
|
||||
}
|
||||
if (fileSummary.audioInfo.format.sampleRate) {
|
||||
format.push(Math.round(fileSummary.audioInfo.format.sampleRate / 100) / 10 + ' kHz') // 44.1 kHz
|
||||
format.push(Math.round(fileSummary.audioInfo.format.sampleRate / 100) / 10 + ' kHz')
|
||||
}
|
||||
if (fileSummary.audioInfo.format.bitsPerSample) {
|
||||
format.push(fileSummary.audioInfo.format.bitsPerSample + ' bit')
|
||||
@@ -316,7 +351,7 @@ function renderAudioMetadata (state) {
|
||||
if (format.length > 0) {
|
||||
elems.push((
|
||||
<div key='format' className='audio-format'>
|
||||
<label>Format</label>{ format.join(', ') }
|
||||
<label>Format</label>{format.join(', ')}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
@@ -358,7 +393,7 @@ function renderLoadingSpinner (state) {
|
||||
<div key='loading' className='media-stalled'>
|
||||
<div key='loading-spinner' className='loading-spinner' />
|
||||
<div key='loading-progress' className='loading-status ellipsis'>
|
||||
<span className='progress'>{fileProgress}%</span> downloaded
|
||||
<span><span className='progress'>{fileProgress}%</span> downloaded</span>
|
||||
<span> ↓ {prettyBytes(prog.downloadSpeed || 0)}/s</span>
|
||||
<span> ↑ {prettyBytes(prog.uploadSpeed || 0)}/s</span>
|
||||
</div>
|
||||
@@ -425,6 +460,7 @@ function renderCastOptions (state) {
|
||||
return (
|
||||
<li key={ix} onClick={dispatcher('selectCastDevice', ix)}>
|
||||
<i className='icon'>{isSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
|
||||
{' '}
|
||||
{name}
|
||||
</li>
|
||||
)
|
||||
@@ -464,6 +500,27 @@ function renderSubtitleOptions (state) {
|
||||
)
|
||||
}
|
||||
|
||||
function renderAudioTrackOptions (state) {
|
||||
const audioTracks = state.playing.audioTracks
|
||||
if (!audioTracks.tracks.length || !audioTracks.showMenu) return
|
||||
|
||||
const items = audioTracks.tracks.map(function (track, ix) {
|
||||
const isSelected = state.playing.audioTracks.selectedIndex === ix
|
||||
return (
|
||||
<li key={ix} onClick={dispatcher('selectAudioTrack', ix)}>
|
||||
<i className='icon'>{'radio_button_' + (isSelected ? 'checked' : 'unchecked')}</i>
|
||||
{track.label}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<ul key='audio-track-options' className='options-list'>
|
||||
{items}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
function renderPlayerControls (state) {
|
||||
const positionPercent = 100 * state.playing.currentTime / state.playing.duration
|
||||
const playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 3px)' }
|
||||
@@ -472,6 +529,9 @@ function renderPlayerControls (state) {
|
||||
: state.playing.subtitles.selectedIndex >= 0
|
||||
? 'active'
|
||||
: ''
|
||||
const multiAudioClass = state.playing.audioTracks.tracks.length > 1
|
||||
? 'active'
|
||||
: 'disabled'
|
||||
const prevClass = Playlist.hasPrevious(state) ? '' : 'disabled'
|
||||
const nextClass = Playlist.hasNext(state) ? '' : 'disabled'
|
||||
|
||||
@@ -481,41 +541,47 @@ function renderPlayerControls (state) {
|
||||
<div
|
||||
key='cursor'
|
||||
className='playback-cursor'
|
||||
style={playbackCursorStyle} />
|
||||
style={playbackCursorStyle}
|
||||
/>
|
||||
<div
|
||||
key='scrub-bar'
|
||||
className='scrub-bar'
|
||||
draggable='true'
|
||||
draggable
|
||||
onDragStart={handleDragStart}
|
||||
onClick={handleScrub}
|
||||
onDrag={handleScrub} />
|
||||
onDrag={handleScrub}
|
||||
/>
|
||||
</div>,
|
||||
|
||||
<i
|
||||
key='skip-previous'
|
||||
className={'icon skip-previous float-left ' + prevClass}
|
||||
onClick={dispatcher('previousTrack')}>
|
||||
onClick={dispatcher('previousTrack')}
|
||||
>
|
||||
skip_previous
|
||||
</i>,
|
||||
|
||||
<i
|
||||
key='play'
|
||||
className='icon play-pause float-left'
|
||||
onClick={dispatcher('playPause')}>
|
||||
onClick={dispatcher('playPause')}
|
||||
>
|
||||
{state.playing.isPaused ? 'play_arrow' : 'pause'}
|
||||
</i>,
|
||||
|
||||
<i
|
||||
key='skip-next'
|
||||
className={'icon skip-next float-left ' + nextClass}
|
||||
onClick={dispatcher('nextTrack')}>
|
||||
onClick={dispatcher('nextTrack')}
|
||||
>
|
||||
skip_next
|
||||
</i>,
|
||||
|
||||
<i
|
||||
key='fullscreen'
|
||||
className='icon fullscreen float-right'
|
||||
onClick={dispatcher('toggleFullScreen')}>
|
||||
onClick={dispatcher('toggleFullScreen')}
|
||||
>
|
||||
{state.window.isFullScreen ? 'fullscreen_exit' : 'fullscreen'}
|
||||
</i>
|
||||
]
|
||||
@@ -526,9 +592,18 @@ function renderPlayerControls (state) {
|
||||
<i
|
||||
key='subtitles'
|
||||
className={'icon closed-caption float-right ' + captionsClass}
|
||||
onClick={handleSubtitles}>
|
||||
onClick={handleSubtitles}
|
||||
>
|
||||
closed_caption
|
||||
</i>
|
||||
), (
|
||||
<i
|
||||
key='audio-tracks'
|
||||
className={'icon multi-audio float-right ' + multiAudioClass}
|
||||
onClick={handleAudioTracks}
|
||||
>
|
||||
library_music
|
||||
</i>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -539,9 +614,9 @@ function renderPlayerControls (state) {
|
||||
|
||||
// Add the cast buttons. Icons for each cast type, connected/disconnected:
|
||||
const buttonIcons = {
|
||||
'chromecast': { true: 'cast_connected', false: 'cast' },
|
||||
'airplay': { true: 'airplay', false: 'airplay' },
|
||||
'dlna': { true: 'tv', false: 'tv' }
|
||||
chromecast: { true: 'cast_connected', false: 'cast' },
|
||||
airplay: { true: 'airplay', false: 'airplay' },
|
||||
dlna: { true: 'tv', false: 'tv' }
|
||||
}
|
||||
castTypes.forEach(function (castType) {
|
||||
// Do we show this button (eg. the Chromecast button) at all?
|
||||
@@ -570,7 +645,8 @@ function renderPlayerControls (state) {
|
||||
<i
|
||||
key={castType}
|
||||
className={'icon device float-right ' + buttonClass}
|
||||
onClick={buttonHandler}>
|
||||
onClick={buttonHandler}
|
||||
>
|
||||
{buttonIcon}
|
||||
</i>
|
||||
))
|
||||
@@ -593,7 +669,8 @@ function renderPlayerControls (state) {
|
||||
<div key='volume' className='volume float-left'>
|
||||
<i
|
||||
className='icon volume-icon float-left'
|
||||
onMouseDown={handleVolumeMute}>
|
||||
onMouseDown={handleVolumeMute}
|
||||
>
|
||||
{volumeIcon}
|
||||
</i>
|
||||
<input
|
||||
@@ -601,7 +678,8 @@ function renderPlayerControls (state) {
|
||||
type='range' min='0' max='1' step='0.05'
|
||||
value={volume}
|
||||
onChange={handleVolumeScrub}
|
||||
style={volumeStyle} />
|
||||
style={volumeStyle}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
||||
@@ -624,12 +702,15 @@ function renderPlayerControls (state) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div key='controls' className='controls'
|
||||
<div
|
||||
key='controls' className='controls'
|
||||
onMouseEnter={dispatcher('mediaControlsMouseEnter')}
|
||||
onMouseLeave={dispatcher('mediaControlsMouseLeave')}>
|
||||
onMouseLeave={dispatcher('mediaControlsMouseLeave')}
|
||||
>
|
||||
{elements}
|
||||
{renderCastOptions(state)}
|
||||
{renderSubtitleOptions(state)}
|
||||
{renderAudioTrackOptions(state)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -673,6 +754,10 @@ function renderPlayerControls (state) {
|
||||
dispatch('toggleSubtitlesMenu')
|
||||
}
|
||||
}
|
||||
|
||||
function handleAudioTracks (e) {
|
||||
dispatch('toggleAudioTracksMenu')
|
||||
}
|
||||
}
|
||||
|
||||
// Renders the loading bar. Shows which parts of the torrent are loaded, which
|
||||
@@ -738,10 +823,10 @@ function formatTime (time, total) {
|
||||
const totalMinutes = Math.floor(total / 60)
|
||||
const hours = Math.floor(time / 3600)
|
||||
let minutes = Math.floor(time % 3600 / 60)
|
||||
if (totalMinutes > 9) {
|
||||
minutes = zeroFill(2, minutes)
|
||||
if (totalMinutes > 9 && minutes < 10) {
|
||||
minutes = '0' + minutes
|
||||
}
|
||||
const seconds = zeroFill(2, Math.floor(time % 60))
|
||||
const seconds = `0${Math.floor(time % 60)}`.slice(-2)
|
||||
|
||||
return (totalHours > 0 ? hours + ':' : '') + minutes + ':' + seconds
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const path = require('path')
|
||||
const React = require('react')
|
||||
const PropTypes = require('prop-types')
|
||||
|
||||
@@ -26,6 +25,9 @@ class PreferencesPage extends React.Component {
|
||||
|
||||
this.handleStartupChange =
|
||||
this.handleStartupChange.bind(this)
|
||||
|
||||
this.handleSoundNotificationsChange =
|
||||
this.handleSoundNotificationsChange.bind(this)
|
||||
}
|
||||
|
||||
downloadPathSelector () {
|
||||
@@ -34,11 +36,12 @@ class PreferencesPage extends React.Component {
|
||||
<PathSelector
|
||||
dialog={{
|
||||
title: 'Select download directory',
|
||||
properties: [ 'openDirectory' ]
|
||||
properties: ['openDirectory']
|
||||
}}
|
||||
onChange={this.handleDownloadPathChange}
|
||||
title='Download location'
|
||||
value={this.props.state.saved.prefs.downloadPath} />
|
||||
value={this.props.state.saved.prefs.downloadPath}
|
||||
/>
|
||||
</Preference>
|
||||
)
|
||||
}
|
||||
@@ -53,8 +56,9 @@ class PreferencesPage extends React.Component {
|
||||
<Checkbox
|
||||
className='control'
|
||||
checked={!this.props.state.saved.prefs.openExternalPlayer}
|
||||
label={'Play torrent media files using WebTorrent'}
|
||||
onCheck={this.handleOpenExternalPlayerChange} />
|
||||
label='Play torrent media files using WebTorrent'
|
||||
onCheck={this.handleOpenExternalPlayerChange}
|
||||
/>
|
||||
</Preference>
|
||||
)
|
||||
}
|
||||
@@ -69,7 +73,7 @@ class PreferencesPage extends React.Component {
|
||||
<Checkbox
|
||||
className='control'
|
||||
checked={this.props.state.saved.prefs.highestPlaybackPriority}
|
||||
label={'Highest Playback Priority'}
|
||||
label='Highest Playback Priority'
|
||||
onCheck={this.handleHighestPlaybackPriorityChange}
|
||||
/>
|
||||
<p>Pauses all active torrents to allow playback to use all of the available bandwidth.</p>
|
||||
@@ -95,12 +99,12 @@ class PreferencesPage extends React.Component {
|
||||
<PathSelector
|
||||
dialog={{
|
||||
title: 'Select media player app',
|
||||
properties: [ 'openFile' ]
|
||||
properties: ['openFile']
|
||||
}}
|
||||
displayValue={playerName}
|
||||
onChange={this.handleExternalPlayerPathChange}
|
||||
title='External player'
|
||||
value={playerPath ? path.dirname(playerPath) : null} />
|
||||
value={playerPath}
|
||||
/>
|
||||
</Preference>
|
||||
)
|
||||
}
|
||||
@@ -115,7 +119,7 @@ class PreferencesPage extends React.Component {
|
||||
<Checkbox
|
||||
className='control'
|
||||
checked={this.props.state.saved.prefs.autoAddTorrents}
|
||||
label={'Watch for new .torrent files and add them immediately'}
|
||||
label='Watch for new .torrent files and add them immediately'
|
||||
onCheck={(e, value) => { this.handleAutoAddTorrentsChange(e, value) }}
|
||||
/>
|
||||
</Preference>
|
||||
@@ -133,11 +137,11 @@ class PreferencesPage extends React.Component {
|
||||
dispatch('updatePreferences', 'autoAddTorrents', isChecked)
|
||||
|
||||
if (isChecked) {
|
||||
dispatch('startFolderWatcher', null)
|
||||
dispatch('startFolderWatcher')
|
||||
return
|
||||
}
|
||||
|
||||
dispatch('stopFolderWatcher', null)
|
||||
dispatch('stopFolderWatcher')
|
||||
}
|
||||
|
||||
torrentsFolderPathSelector () {
|
||||
@@ -148,17 +152,17 @@ class PreferencesPage extends React.Component {
|
||||
<PathSelector
|
||||
dialog={{
|
||||
title: 'Select folder to watch for new torrents',
|
||||
properties: [ 'openDirectory' ]
|
||||
properties: ['openDirectory']
|
||||
}}
|
||||
displayValue={torrentsFolderPath || ''}
|
||||
onChange={this.handletorrentsFolderPathChange}
|
||||
onChange={this.handleTorrentsFolderPathChange}
|
||||
title='Folder to watch'
|
||||
value={torrentsFolderPath ? path.dirname(torrentsFolderPath) : null} />
|
||||
value={torrentsFolderPath}
|
||||
/>
|
||||
</Preference>
|
||||
)
|
||||
}
|
||||
|
||||
handletorrentsFolderPathChange (filePath) {
|
||||
handleTorrentsFolderPathChange (filePath) {
|
||||
dispatch('updatePreferences', 'torrentsFolderPath', filePath)
|
||||
}
|
||||
|
||||
@@ -177,7 +181,8 @@ class PreferencesPage extends React.Component {
|
||||
<RaisedButton
|
||||
className='control'
|
||||
onClick={this.handleSetDefaultApp}
|
||||
label='Make WebTorrent the default' />
|
||||
label='Make WebTorrent the default'
|
||||
/>
|
||||
</Preference>
|
||||
)
|
||||
}
|
||||
@@ -186,25 +191,40 @@ class PreferencesPage extends React.Component {
|
||||
dispatch('updatePreferences', 'startup', isChecked)
|
||||
}
|
||||
|
||||
setStartupSection () {
|
||||
setStartupCheckbox () {
|
||||
if (config.IS_PORTABLE) {
|
||||
return
|
||||
}
|
||||
|
||||
return (
|
||||
<PreferencesSection title='Startup'>
|
||||
<Preference>
|
||||
<Checkbox
|
||||
className='control'
|
||||
checked={this.props.state.saved.prefs.startup}
|
||||
label={'Open WebTorrent on startup.'}
|
||||
onCheck={this.handleStartupChange}
|
||||
/>
|
||||
</Preference>
|
||||
</PreferencesSection>
|
||||
<Preference>
|
||||
<Checkbox
|
||||
className='control'
|
||||
checked={this.props.state.saved.prefs.startup}
|
||||
label='Open WebTorrent on startup'
|
||||
onCheck={this.handleStartupChange}
|
||||
/>
|
||||
</Preference>
|
||||
)
|
||||
}
|
||||
|
||||
soundNotificationsCheckbox () {
|
||||
return (
|
||||
<Preference>
|
||||
<Checkbox
|
||||
className='control'
|
||||
checked={this.props.state.saved.prefs.soundNotifications}
|
||||
label='Enable sounds'
|
||||
onCheck={this.handleSoundNotificationsChange}
|
||||
/>
|
||||
</Preference>
|
||||
)
|
||||
}
|
||||
|
||||
handleSoundNotificationsChange (e, isChecked) {
|
||||
dispatch('updatePreferences', 'soundNotifications', isChecked)
|
||||
}
|
||||
|
||||
handleSetDefaultApp () {
|
||||
dispatch('updatePreferences', 'isFileHandler', true)
|
||||
}
|
||||
@@ -230,7 +250,10 @@ class PreferencesPage extends React.Component {
|
||||
<PreferencesSection title='Default torrent app'>
|
||||
{this.setDefaultAppButton()}
|
||||
</PreferencesSection>
|
||||
{this.setStartupSection()}
|
||||
<PreferencesSection title='General'>
|
||||
{this.setStartupCheckbox()}
|
||||
{this.soundNotificationsCheckbox()}
|
||||
</PreferencesSection>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@ module.exports = class TorrentList extends React.Component {
|
||||
style={style}
|
||||
className={classes.join(' ')}
|
||||
onContextMenu={infoHash && dispatcher('openTorrentContextMenu', infoHash)}
|
||||
onClick={infoHash && dispatcher('toggleSelectTorrent', infoHash)}>
|
||||
onClick={infoHash && dispatcher('toggleSelectTorrent', infoHash)}
|
||||
>
|
||||
{this.renderTorrentMetadata(torrentSummary)}
|
||||
{infoHash ? this.renderTorrentButtons(torrentSummary) : null}
|
||||
{isSelected ? this.renderTorrentDetails(torrentSummary) : null}
|
||||
@@ -130,7 +131,8 @@ module.exports = class TorrentList extends React.Component {
|
||||
}}
|
||||
checked={isActive}
|
||||
onClick={stopPropagation}
|
||||
onCheck={dispatcher('toggleTorrent', infoHash)} />
|
||||
onCheck={dispatcher('toggleTorrent', infoHash)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -210,7 +212,9 @@ module.exports = class TorrentList extends React.Component {
|
||||
else if (torrentSummary.progress.progress === 1) status = 'Not seeding'
|
||||
else status = 'Paused'
|
||||
} else if (torrentSummary.status === 'downloading') {
|
||||
status = 'Downloading'
|
||||
if (!torrentSummary.progress) status = ''
|
||||
else if (!torrentSummary.progress.ready) status = 'Verifying'
|
||||
else status = 'Downloading'
|
||||
} else if (torrentSummary.status === 'seeding') {
|
||||
status = 'Seeding'
|
||||
} else { // torrentSummary.status is 'new' or something unexpected
|
||||
@@ -232,8 +236,9 @@ module.exports = class TorrentList extends React.Component {
|
||||
<i
|
||||
key='play-button'
|
||||
title='Start streaming'
|
||||
className={'icon play'}
|
||||
onClick={dispatcher('playFile', infoHash)}>
|
||||
className='icon play'
|
||||
onClick={dispatcher('playFile', infoHash)}
|
||||
>
|
||||
play_circle_outline
|
||||
</i>
|
||||
)
|
||||
@@ -246,7 +251,8 @@ module.exports = class TorrentList extends React.Component {
|
||||
key='delete-button'
|
||||
className='icon delete'
|
||||
title='Remove torrent'
|
||||
onClick={dispatcher('confirmDeleteTorrent', infoHash, false)}>
|
||||
onClick={dispatcher('confirmDeleteTorrent', infoHash, false)}
|
||||
>
|
||||
close
|
||||
</i>
|
||||
</div>
|
||||
@@ -319,7 +325,7 @@ module.exports = class TorrentList extends React.Component {
|
||||
torrentSummary.progress.files[index]) {
|
||||
const fileProg = torrentSummary.progress.files[index]
|
||||
isDone = fileProg.numPiecesPresent === fileProg.numPieces
|
||||
progress = Math.round(100 * fileProg.numPiecesPresent / fileProg.numPieces) + '%'
|
||||
progress = Math.floor(100 * fileProg.numPiecesPresent / fileProg.numPieces) + '%'
|
||||
}
|
||||
|
||||
// Second, for media files where we saved our position, show how far we got
|
||||
@@ -362,8 +368,10 @@ module.exports = class TorrentList extends React.Component {
|
||||
<td className={'col-size ' + rowClass}>
|
||||
{prettyBytes(file.length)}
|
||||
</td>
|
||||
<td className='col-select'
|
||||
onClick={dispatcher('toggleTorrentFile', infoHash, index)}>
|
||||
<td
|
||||
className='col-select'
|
||||
onClick={dispatcher('toggleTorrentFile', infoHash, index)}
|
||||
>
|
||||
<i className='icon deselect-file'>{isSelected ? 'close' : 'add'}</i>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user