Preferences page rehaul: use React components, UI improvements
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
"application-config": "^1.0.0",
|
"application-config": "^1.0.0",
|
||||||
"bitfield": "^1.0.2",
|
"bitfield": "^1.0.2",
|
||||||
"chromecasts": "^1.8.0",
|
"chromecasts": "^1.8.0",
|
||||||
|
"classnames": "^2.2.5",
|
||||||
"create-torrent": "^3.24.5",
|
"create-torrent": "^3.24.5",
|
||||||
"deep-equal": "^1.0.1",
|
"deep-equal": "^1.0.1",
|
||||||
"dlnacasts": "^0.1.0",
|
"dlnacasts": "^0.1.0",
|
||||||
@@ -47,6 +48,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.11.4",
|
"babel-cli": "^6.11.4",
|
||||||
"babel-plugin-syntax-jsx": "^6.13.0",
|
"babel-plugin-syntax-jsx": "^6.13.0",
|
||||||
|
"babel-plugin-transform-es2015-destructuring": "^6.9.0",
|
||||||
|
"babel-plugin-transform-object-rest-spread": "^6.8.0",
|
||||||
"babel-plugin-transform-react-jsx": "^6.8.0",
|
"babel-plugin-transform-react-jsx": "^6.8.0",
|
||||||
"cross-zip": "^2.0.1",
|
"cross-zip": "^2.0.1",
|
||||||
"electron-osx-sign": "^0.3.0",
|
"electron-osx-sign": "^0.3.0",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"syntax-jsx",
|
"syntax-jsx",
|
||||||
|
"transform-es2015-destructuring",
|
||||||
|
"transform-object-rest-spread",
|
||||||
"transform-react-jsx"
|
"transform-react-jsx"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/renderer/views/Button.js
Normal file
40
src/renderer/views/Button.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const c = require('classnames')
|
||||||
|
const React = require('react')
|
||||||
|
|
||||||
|
class Button extends React.Component {
|
||||||
|
static get propTypes () {
|
||||||
|
return {
|
||||||
|
className: React.PropTypes.string,
|
||||||
|
onClick: React.PropTypes.func,
|
||||||
|
theme: React.PropTypes.oneOf(['light', 'dark']),
|
||||||
|
type: React.PropTypes.oneOf(['default', 'flat', 'raised'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get defaultProps () {
|
||||||
|
return {
|
||||||
|
theme: 'light',
|
||||||
|
type: 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { theme, type, className, ...other } = this.props
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
{...other}
|
||||||
|
className={c(
|
||||||
|
'Button',
|
||||||
|
theme,
|
||||||
|
type,
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Button
|
||||||
70
src/renderer/views/PathSelector.js
Normal file
70
src/renderer/views/PathSelector.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
const c = require('classnames')
|
||||||
|
const electron = require('electron')
|
||||||
|
const React = require('react')
|
||||||
|
|
||||||
|
const remote = electron.remote
|
||||||
|
|
||||||
|
const Button = require('./Button')
|
||||||
|
const TextInput = require('./TextInput')
|
||||||
|
|
||||||
|
class PathSelector extends React.Component {
|
||||||
|
static get propTypes () {
|
||||||
|
return {
|
||||||
|
className: React.PropTypes.string,
|
||||||
|
defaultValue: React.PropTypes.string.isRequired,
|
||||||
|
dialog: React.PropTypes.object,
|
||||||
|
label: React.PropTypes.string.isRequired,
|
||||||
|
onChange: React.PropTypes.func
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
value: props.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleClick = this.handleClick.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick () {
|
||||||
|
var opts = Object.assign({
|
||||||
|
defaultPath: this.state.value,
|
||||||
|
properties: [ 'openFile', 'openDirectory' ],
|
||||||
|
title: this.props.label
|
||||||
|
}, this.props.dialog)
|
||||||
|
|
||||||
|
remote.dialog.showOpenDialog(
|
||||||
|
remote.getCurrentWindow(),
|
||||||
|
opts,
|
||||||
|
(filenames) => {
|
||||||
|
if (!Array.isArray(filenames)) return
|
||||||
|
this.setState({value: filenames[0]})
|
||||||
|
this.props.onChange && this.props.onChange(filenames[0])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className={c('PathSelector', this.props.className)}>
|
||||||
|
<div className='label'>{this.props.label}:</div>
|
||||||
|
<TextInput
|
||||||
|
className='input'
|
||||||
|
disabled
|
||||||
|
value={this.state.value}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className='button'
|
||||||
|
theme='dark'
|
||||||
|
onClick={this.handleClick}
|
||||||
|
>
|
||||||
|
Change…
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PathSelector
|
||||||
166
src/renderer/views/PreferencesPage.js
Normal file
166
src/renderer/views/PreferencesPage.js
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
const React = require('react')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const PathSelector = require('./PathSelector')
|
||||||
|
|
||||||
|
const {dispatch} = require('../lib/dispatcher')
|
||||||
|
|
||||||
|
class PreferencesPage extends React.Component {
|
||||||
|
render () {
|
||||||
|
var state = this.props.state
|
||||||
|
return (
|
||||||
|
<div className='PreferencesPage'>
|
||||||
|
<PreferencesSection title='Downloads'>
|
||||||
|
<Preference>
|
||||||
|
{DownloadPathSelector(state)}
|
||||||
|
</Preference>
|
||||||
|
</PreferencesSection>
|
||||||
|
<PreferencesSection title='Playback'>
|
||||||
|
<Preference>
|
||||||
|
{ExternalPlayerPathSelector(state)}
|
||||||
|
</Preference>
|
||||||
|
</PreferencesSection>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {ExternalPlayerCheckbox(state)}
|
||||||
|
// {DefaultAppCheckbox(state)}
|
||||||
|
|
||||||
|
class PreferencesSection extends React.Component {
|
||||||
|
static get propTypes () {
|
||||||
|
return {
|
||||||
|
title: React.PropTypes.string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className='PreferencesSection'>
|
||||||
|
<h2 className='title'>{this.props.title}</h2>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Preference extends React.Component {
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className='Preference'>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function DownloadPathSelector (state) {
|
||||||
|
return (
|
||||||
|
<PathSelector
|
||||||
|
className='download-path'
|
||||||
|
label='Download location'
|
||||||
|
dialog={{
|
||||||
|
title: 'Select download directory',
|
||||||
|
properties: [ 'openDirectory' ]
|
||||||
|
}}
|
||||||
|
defaultValue={state.unsaved.prefs.downloadPath}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
function handleChange (filePath) {
|
||||||
|
dispatch('updatePreferences', 'downloadPath', filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExternalPlayerPathSelector (state) {
|
||||||
|
return (
|
||||||
|
<PathSelector
|
||||||
|
className='download-path'
|
||||||
|
label='Player app location'
|
||||||
|
dialog={{
|
||||||
|
title: 'Select media player app',
|
||||||
|
properties: [ 'openFile' ]
|
||||||
|
}}
|
||||||
|
defaultValue={state.unsaved.prefs.externalPlayerPath || '<VLC>'}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
function handleChange (filePath) {
|
||||||
|
if (path.extname(filePath) === '.app') {
|
||||||
|
// Get executable in packaged mac app
|
||||||
|
var name = path.basename(filePath, '.app')
|
||||||
|
filePath += '/Contents/MacOS/' + name
|
||||||
|
}
|
||||||
|
dispatch('updatePreferences', 'externalPlayerPath', filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function ExternalPlayerCheckbox (state) {
|
||||||
|
// return renderCheckbox({
|
||||||
|
// label: 'Play in External Player',
|
||||||
|
// description: 'Media will play in external player',
|
||||||
|
// property: 'openExternalPlayer',
|
||||||
|
// value: state.saved.prefs.openExternalPlayer
|
||||||
|
// },
|
||||||
|
// state.unsaved.prefs.openExternalPlayer,
|
||||||
|
// function (value) {
|
||||||
|
// dispatch('updatePreferences', 'openExternalPlayer', value)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function renderCheckbox (definition, value, callback) {
|
||||||
|
// var iconClass = 'icon clickable'
|
||||||
|
// if (value) iconClass += ' enabled'
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <div key='{definition.key}' className='control-group'>
|
||||||
|
// <div className='controls'>
|
||||||
|
// <label className='control-label'>
|
||||||
|
// <div className='preference-title'>{definition.label}</div>
|
||||||
|
// </label>
|
||||||
|
// <div className='controls'>
|
||||||
|
// <label className='clickable' onClick={handleClick}>
|
||||||
|
// <i
|
||||||
|
// className={iconClass}
|
||||||
|
// id='{definition.property}'
|
||||||
|
// >
|
||||||
|
// check_circle
|
||||||
|
// </i>
|
||||||
|
// <span className='checkbox-label'>{definition.description}</span>
|
||||||
|
// </label>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// )
|
||||||
|
// function handleClick () {
|
||||||
|
// callback(!value)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function DefaultAppCheckbox (state) {
|
||||||
|
// var definition = {
|
||||||
|
// key: 'file-handlers',
|
||||||
|
// label: 'Handle Torrent Files'
|
||||||
|
// }
|
||||||
|
// var buttonText = state.unsaved.prefs.isFileHandler
|
||||||
|
// ? 'Remove default app for torrent files'
|
||||||
|
// : 'Make WebTorrent the default app for torrent files'
|
||||||
|
// var controls = [(
|
||||||
|
// <button key='toggle-handlers'
|
||||||
|
// className='btn'
|
||||||
|
// onClick={toggleFileHandlers}>
|
||||||
|
// {buttonText}
|
||||||
|
// </button>
|
||||||
|
// )]
|
||||||
|
// return renderControlGroup(definition, controls)
|
||||||
|
|
||||||
|
// function toggleFileHandlers () {
|
||||||
|
// var isFileHandler = state.unsaved.prefs.isFileHandler
|
||||||
|
// dispatch('updatePreferences', 'isFileHandler', !isFileHandler)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
module.exports = PreferencesPage
|
||||||
34
src/renderer/views/TextInput.js
Normal file
34
src/renderer/views/TextInput.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const c = require('classnames')
|
||||||
|
const React = require('react')
|
||||||
|
|
||||||
|
class TextInput extends React.Component {
|
||||||
|
static get propTypes () {
|
||||||
|
return {
|
||||||
|
theme: React.PropTypes.oneOf('light', 'dark'),
|
||||||
|
className: React.PropTypes.string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get defaultProps () {
|
||||||
|
return {
|
||||||
|
theme: 'light'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { className, theme, ...other } = this.props
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
{...other}
|
||||||
|
className={c(
|
||||||
|
'TextInput',
|
||||||
|
theme,
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TextInput
|
||||||
@@ -6,7 +6,7 @@ const Views = {
|
|||||||
'home': require('./torrent-list'),
|
'home': require('./torrent-list'),
|
||||||
'player': require('./player'),
|
'player': require('./player'),
|
||||||
'create-torrent': require('./create-torrent'),
|
'create-torrent': require('./create-torrent'),
|
||||||
'preferences': require('./preferences')
|
'preferences': require('./PreferencesPage')
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modals = {
|
const Modals = {
|
||||||
|
|||||||
@@ -1,217 +0,0 @@
|
|||||||
const React = require('react')
|
|
||||||
const remote = require('electron').remote
|
|
||||||
const dialog = remote.dialog
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const {dispatch} = require('../lib/dispatcher')
|
|
||||||
|
|
||||||
module.exports = class Preferences extends React.Component {
|
|
||||||
render () {
|
|
||||||
var state = this.props.state
|
|
||||||
return (
|
|
||||||
<div className='preferences'>
|
|
||||||
{renderGeneralSection(state)}
|
|
||||||
{renderPlaybackSection(state)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderGeneralSection (state) {
|
|
||||||
return renderSection({
|
|
||||||
key: 'general',
|
|
||||||
title: 'General',
|
|
||||||
description: '',
|
|
||||||
icon: 'settings'
|
|
||||||
}, [
|
|
||||||
renderDownloadPathSelector(state),
|
|
||||||
renderFileHandlers(state)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPlaybackSection (state) {
|
|
||||||
return renderSection({
|
|
||||||
title: 'Playback',
|
|
||||||
description: '',
|
|
||||||
icon: 'settings'
|
|
||||||
}, [
|
|
||||||
renderOpenExternalPlayerSelector(state),
|
|
||||||
renderExternalPlayerSelector(state)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDownloadPathSelector (state) {
|
|
||||||
return renderFileSelector({
|
|
||||||
key: 'download-path',
|
|
||||||
label: 'Download Path',
|
|
||||||
description: 'Data from torrents will be saved here',
|
|
||||||
property: 'downloadPath',
|
|
||||||
options: {
|
|
||||||
title: 'Select download directory',
|
|
||||||
properties: [ 'openDirectory' ]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
state.unsaved.prefs.downloadPath,
|
|
||||||
function (filePath) {
|
|
||||||
dispatch('updatePreferences', 'downloadPath', filePath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderFileHandlers (state) {
|
|
||||||
var definition = {
|
|
||||||
key: 'file-handlers',
|
|
||||||
label: 'Handle Torrent Files'
|
|
||||||
}
|
|
||||||
var buttonText = state.unsaved.prefs.isFileHandler
|
|
||||||
? 'Remove default app for torrent files'
|
|
||||||
: 'Make WebTorrent the default app for torrent files'
|
|
||||||
var controls = [(
|
|
||||||
<button key='toggle-handlers'
|
|
||||||
className='btn'
|
|
||||||
onClick={toggleFileHandlers}>
|
|
||||||
{buttonText}
|
|
||||||
</button>
|
|
||||||
)]
|
|
||||||
return renderControlGroup(definition, controls)
|
|
||||||
|
|
||||||
function toggleFileHandlers () {
|
|
||||||
var isFileHandler = state.unsaved.prefs.isFileHandler
|
|
||||||
dispatch('updatePreferences', 'isFileHandler', !isFileHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderExternalPlayerSelector (state) {
|
|
||||||
return renderFileSelector({
|
|
||||||
label: 'External Media Player',
|
|
||||||
description: 'Progam that will be used to play media externally',
|
|
||||||
property: 'externalPlayerPath',
|
|
||||||
options: {
|
|
||||||
title: 'Select media player executable',
|
|
||||||
properties: [ 'openFile' ]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
state.unsaved.prefs.externalPlayerPath || '<VLC>',
|
|
||||||
function (filePath) {
|
|
||||||
if (path.extname(filePath) === '.app') {
|
|
||||||
// Get executable in packaged mac app
|
|
||||||
var name = path.basename(filePath, '.app')
|
|
||||||
filePath += '/Contents/MacOS/' + name
|
|
||||||
}
|
|
||||||
dispatch('updatePreferences', 'externalPlayerPath', filePath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderOpenExternalPlayerSelector (state) {
|
|
||||||
return renderCheckbox({
|
|
||||||
key: 'open-external-player',
|
|
||||||
label: 'Play in External Player',
|
|
||||||
description: 'Media will play in external player',
|
|
||||||
property: 'openExternalPlayer',
|
|
||||||
value: state.saved.prefs.openExternalPlayer
|
|
||||||
},
|
|
||||||
state.unsaved.prefs.openExternalPlayer,
|
|
||||||
function (value) {
|
|
||||||
dispatch('updatePreferences', 'openExternalPlayer', value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renders a prefs section.
|
|
||||||
// - definition should be {icon, title, description}
|
|
||||||
// - controls should be an array of vdom elements
|
|
||||||
function renderSection (definition, controls) {
|
|
||||||
var helpElem = !definition.description ? null : (
|
|
||||||
<div key='help' className='help text'>
|
|
||||||
<i className='icon'>help_outline</i>{definition.description}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<section key={definition.key} className='section preferences-panel'>
|
|
||||||
<div className='section-container'>
|
|
||||||
<div key='heading' className='section-heading'>
|
|
||||||
<i className='icon'>{definition.icon}</i>{definition.title}
|
|
||||||
</div>
|
|
||||||
{helpElem}
|
|
||||||
<div key='body' className='section-body'>
|
|
||||||
{controls}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderCheckbox (definition, value, callback) {
|
|
||||||
var iconClass = 'icon clickable'
|
|
||||||
if (value) iconClass += ' enabled'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key='{definition.key}' className='control-group'>
|
|
||||||
<div className='controls'>
|
|
||||||
<label className='control-label'>
|
|
||||||
<div className='preference-title'>{definition.label}</div>
|
|
||||||
</label>
|
|
||||||
<div className='controls'>
|
|
||||||
<label className='clickable' onClick={handleClick}>
|
|
||||||
<i
|
|
||||||
className={iconClass}
|
|
||||||
id='{definition.property}'
|
|
||||||
>
|
|
||||||
check_circle
|
|
||||||
</i>
|
|
||||||
<span className='checkbox-label'>{definition.description}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
function handleClick () {
|
|
||||||
callback(!value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a file chooser
|
|
||||||
// - defition should be {label, description, options}
|
|
||||||
// options are passed to dialog.showOpenDialog
|
|
||||||
// - value should be the current pref, a file or folder path
|
|
||||||
// - callback takes a new file or folder path
|
|
||||||
function renderFileSelector (definition, value, callback) {
|
|
||||||
var controls = [(
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='file-picker-text'
|
|
||||||
key={definition.property}
|
|
||||||
id={definition.property}
|
|
||||||
disabled='disabled'
|
|
||||||
value={value} />
|
|
||||||
), (
|
|
||||||
<button
|
|
||||||
key={definition.property + '-btn'}
|
|
||||||
className='btn'
|
|
||||||
onClick={handleClick}>
|
|
||||||
<i className='icon'>folder_open</i>
|
|
||||||
</button>
|
|
||||||
)]
|
|
||||||
return renderControlGroup(definition, controls)
|
|
||||||
|
|
||||||
function handleClick () {
|
|
||||||
dialog.showOpenDialog(remote.getCurrentWindow(), definition.options, function (filenames) {
|
|
||||||
if (!Array.isArray(filenames)) return
|
|
||||||
callback(filenames[0])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderControlGroup (definition, controls) {
|
|
||||||
return (
|
|
||||||
<div key={definition.key} className='control-group'>
|
|
||||||
<div className='controls'>
|
|
||||||
<label className='control-label'>
|
|
||||||
<div className='preference-title'>{definition.label}</div>
|
|
||||||
<div className='preference-description'>{definition.description}</div>
|
|
||||||
</label>
|
|
||||||
<div className='controls'>
|
|
||||||
{controls}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
76
static/css/components/Button.css
Normal file
76
static/css/components/Button.css
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
.Button {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: none;
|
||||||
|
color: #aaa;
|
||||||
|
cursor: default;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 10px;
|
||||||
|
outline: none;
|
||||||
|
padding: 0;
|
||||||
|
padding: 7px 18px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default button
|
||||||
|
*/
|
||||||
|
|
||||||
|
.Button.default {
|
||||||
|
color: #222;
|
||||||
|
background-color: rgba(153, 153, 153, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button.default.dark {
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button.default:hover,
|
||||||
|
.Button.default:focus { /* Material design: focused */
|
||||||
|
background-color: rgba(153, 153, 153, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button.default:active { /* Material design: pressed */
|
||||||
|
background-color: rgba(153, 153, 153, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flat button
|
||||||
|
*/
|
||||||
|
|
||||||
|
.Button.flat {
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button.flat.dark {
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button.flat:hover,
|
||||||
|
.Button.flat:focus {
|
||||||
|
background-color: rgba(153, 153, 153, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button.flat:active {
|
||||||
|
background-color: rgba(153, 153, 153, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raised button
|
||||||
|
*/
|
||||||
|
|
||||||
|
.Button.raised {
|
||||||
|
background-color: #2196f3;
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button.raised:hover,
|
||||||
|
.Button.raised:focus {
|
||||||
|
background-color: #38a0f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button.raised:active {
|
||||||
|
background-color: #51abf6;
|
||||||
|
}
|
||||||
17
static/css/components/PathSelector.css
Normal file
17
static/css/components/PathSelector.css
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.PathSelector {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PathSelector .label {
|
||||||
|
margin-right: 10px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PathSelector .input {
|
||||||
|
flex: 5 5 200px;
|
||||||
|
}
|
||||||
18
static/css/components/TextInput.css
Normal file
18
static/css/components/TextInput.css
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.TextInput {
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
padding: 3px 6px;
|
||||||
|
width: 200px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TextInput.light {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.TextInput.dark {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
@@ -75,12 +75,12 @@ table {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
background: rgb(40, 40, 40);
|
background: rgb(30, 30, 30);
|
||||||
animation: fadein 0.5s;
|
animation: fadein 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app:not(.is-focused) {
|
.app:not(.is-focused) {
|
||||||
background: rgb(50, 50, 50);
|
background: rgb(40, 40, 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -93,7 +93,7 @@ table {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Material Icons'),
|
src: local('Material Icons'),
|
||||||
local('MaterialIcons-Regular'),
|
local('MaterialIcons-Regular'),
|
||||||
url(MaterialIcons-Regular.woff2) format('woff2');
|
url('../MaterialIcons-Regular.woff2') format('woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@@ -170,7 +170,7 @@ table {
|
|||||||
|
|
||||||
.header {
|
.header {
|
||||||
background: rgb(40, 40, 40);
|
background: rgb(40, 40, 40);
|
||||||
border-bottom: 1px solid rgb(20, 20, 20);
|
border-bottom: 1px solid rgb(30, 30, 30);
|
||||||
height: 38px; /* vertically center OS menu buttons (OS X) */
|
height: 38px; /* vertically center OS menu buttons (OS X) */
|
||||||
padding-top: 7px;
|
padding-top: 7px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -247,7 +247,7 @@ table {
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: overlay;
|
overflow-y: overlay;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin-top: 37px;
|
margin-top: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app.view-player .content {
|
.app.view-player .content {
|
||||||
@@ -369,64 +369,10 @@ i:not(.disabled):hover { /* Show they're clickable without pointer: cursor */
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
button { /* Rectangular text buttons */
|
|
||||||
background: transparent;
|
|
||||||
margin-left: 10px;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #aaa;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.button-flat {
|
|
||||||
color: #222;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
button.button-flat:active { /* Material design: pressed */
|
|
||||||
background-color: rgba(153, 153, 153, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
button.button-raised {
|
|
||||||
background-color: #2196f3;
|
|
||||||
color: #eee;
|
|
||||||
padding: 7px 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.button-raised:hover,
|
|
||||||
button.button-raised:focus {
|
|
||||||
background-color: #38a0f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.button-raised:active {
|
|
||||||
background-color: #51abf6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* OTHER FORM ELEMENT DEFAULTS
|
* OTHER FORM ELEMENT DEFAULTS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
input[type='text'] {
|
|
||||||
background: transparent;
|
|
||||||
width: 300px;
|
|
||||||
padding: 6px;
|
|
||||||
border: 1px solid #bbb;
|
|
||||||
border-radius: 3px;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TORRENT LIST
|
* TORRENT LIST
|
||||||
*/
|
*/
|
||||||
@@ -438,7 +384,7 @@ input[type='text'] {
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
transition: -webkit-filter 0.1s ease-out;
|
transition: -webkit-filter 0.1s ease-out;
|
||||||
position: relative;
|
position: relative;
|
||||||
animation: fadein .4s;
|
animation: fadein 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent,
|
.torrent,
|
||||||
@@ -921,185 +867,6 @@ video::-webkit-media-text-track-container {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Preferences page, based on Atom settings style
|
|
||||||
*/
|
|
||||||
|
|
||||||
.preferences {
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: calc(10/7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .text {
|
|
||||||
color: #a8a8a8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .icon {
|
|
||||||
color: rgba(170, 170, 170, 0.6);
|
|
||||||
font-size: 16px;
|
|
||||||
margin-right: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .icon.enabled {
|
|
||||||
color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .btn {
|
|
||||||
display: inline-block;
|
|
||||||
-webkit-appearance: button;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-color: #cccccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: #9da5b4;
|
|
||||||
text-shadow: none;
|
|
||||||
border: 1px solid #181a1f;
|
|
||||||
background-color: #3d3d3d;
|
|
||||||
white-space: initial;
|
|
||||||
font-size: 0.889em;
|
|
||||||
line-height: 1;
|
|
||||||
padding: 0.5em 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .btn .icon {
|
|
||||||
margin: 0;
|
|
||||||
color: #a8a8a8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .help .icon {
|
|
||||||
vertical-align: sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.preferences .preferences-panel .control-group + .control-group {
|
|
||||||
margin-top: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .section {
|
|
||||||
padding: 20px;
|
|
||||||
border-top: 1px solid #181a1f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .section:first {
|
|
||||||
border-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .section:first-child,
|
|
||||||
.preferences .section:last-child {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .section.section:empty {
|
|
||||||
padding: 0;
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .section-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences section .section-heading,
|
|
||||||
.preferences .section .section-heading {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #dcdcdc;
|
|
||||||
font-size: 1.75em;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .sub-section-heading.icon:before,
|
|
||||||
.preferences .section-heading.icon:before {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .section-heading-count {
|
|
||||||
margin-left: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .section-body {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .sub-section {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .sub-section .sub-section-heading {
|
|
||||||
color: #dcdcdc;
|
|
||||||
font-size: 1.4em;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .preferences-panel label {
|
|
||||||
color: #a8a8a8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .preferences-panel .control-group + .control-group {
|
|
||||||
margin-top: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .preferences-panel .control-group .editor-container {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .preference-title {
|
|
||||||
font-size: 1.2em;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .preference-description {
|
|
||||||
color: rgba(170, 170, 170, 0.6);
|
|
||||||
-webkit-user-select: none;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences input {
|
|
||||||
font-size: 1.1em;
|
|
||||||
line-height: 1.15em;
|
|
||||||
max-height: none;
|
|
||||||
width: 100%;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: #a8a8a8;
|
|
||||||
border: 1px solid #181a1f;
|
|
||||||
background-color: #1b1d23;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences input::-webkit-input-placeholder {
|
|
||||||
color: rgba(170, 170, 170, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .control-group input {
|
|
||||||
margin-top: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .control-group input.file-picker-text {
|
|
||||||
width: calc(100% - 40px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .control-group .checkbox .icon {
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin: 0;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences .checkbox {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-label {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MEDIA OVERLAY / AUDIO DETAILS
|
* MEDIA OVERLAY / AUDIO DETAILS
|
||||||
*/
|
*/
|
||||||
|
|||||||
17
static/css/pages/PreferencesPage.css
Normal file
17
static/css/pages/PreferencesPage.css
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.PreferencesPage {
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PreferencesSection:not(:last-child) {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PreferencesSection .title {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preference:not(:last-child) {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
@@ -3,8 +3,15 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<title>WebTorrent Desktop</title>
|
<title>WebTorrent Desktop</title>
|
||||||
<link rel="stylesheet" href="main.css">
|
|
||||||
|
<link rel="stylesheet" href="css/components/Button.css">
|
||||||
|
<link rel="stylesheet" href="css/components/PathSelector.css">
|
||||||
|
<link rel="stylesheet" href="css/components/TextInput.css">
|
||||||
|
<link rel="stylesheet" href="css/pages/PreferencesPage.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/main.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- React prints a warning if you render to <body> directly -->
|
<!-- React prints a warning if you render to <body> directly -->
|
||||||
|
|||||||
Reference in New Issue
Block a user