diff --git a/bin/check-deps.js b/bin/check-deps.js
index f4a2dafc..1423f73c 100755
--- a/bin/check-deps.js
+++ b/bin/check-deps.js
@@ -46,11 +46,14 @@ var BUILT_IN_ELECTRON_MODULES = [ 'electron' ]
var BUILT_IN_DEPS = [].concat(BUILT_IN_NODE_MODULES, BUILT_IN_ELECTRON_MODULES)
var EXECUTABLE_DEPS = [
- 'gh-release',
- 'standard',
'babel-cli',
'babel-plugin-syntax-jsx',
- 'babel-plugin-transform-react-jsx'
+ 'babel-plugin-transform-es2015-destructuring',
+ 'babel-plugin-transform-object-rest-spread',
+ 'babel-plugin-transform-react-jsx',
+ 'gh-release',
+ 'nodemon',
+ 'standard'
]
main()
diff --git a/package.json b/package.json
index 1e99e687..f9448c9a 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,6 @@
"application-config": "^1.0.0",
"bitfield": "^1.0.2",
"chromecasts": "^1.8.0",
- "classnames": "^2.2.5",
"create-torrent": "^3.24.5",
"deep-equal": "^1.0.1",
"dlnacasts": "^0.1.0",
@@ -29,12 +28,14 @@
"iso-639-1": "^1.2.1",
"languagedetect": "^1.1.1",
"location-history": "^1.0.0",
+ "material-ui": "^0.15.4",
"musicmetadata": "^2.0.2",
"network-address": "^1.1.0",
"parse-torrent": "^5.7.3",
"prettier-bytes": "^1.0.1",
"react": "^15.2.1",
"react-dom": "^15.2.1",
+ "react-tap-event-plugin": "^1.0.0",
"run-parallel": "^1.1.6",
"semver": "^5.1.0",
"simple-concat": "^1.0.0",
diff --git a/src/renderer/main.js b/src/renderer/main.js
index 54e27385..5659c382 100644
--- a/src/renderer/main.js
+++ b/src/renderer/main.js
@@ -5,9 +5,12 @@ crashReporter.init()
const dragDrop = require('drag-drop')
const electron = require('electron')
+const fs = require('fs')
const React = require('react')
const ReactDOM = require('react-dom')
-const fs = require('fs')
+
+// Required by Material UI -- adds `onTouchTap` event
+require('react-tap-event-plugin')()
const config = require('../config')
const App = require('./views/app')
diff --git a/src/renderer/views/Button.js b/src/renderer/views/Button.js
deleted file mode 100644
index a8aa09d3..00000000
--- a/src/renderer/views/Button.js
+++ /dev/null
@@ -1,40 +0,0 @@
-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 (
-
- )
- }
-}
-
-module.exports = Button
diff --git a/src/renderer/views/PathSelector.js b/src/renderer/views/PathSelector.js
index d5013a97..5069eed1 100644
--- a/src/renderer/views/PathSelector.js
+++ b/src/renderer/views/PathSelector.js
@@ -1,38 +1,33 @@
-const c = require('classnames')
const electron = require('electron')
const React = require('react')
const remote = electron.remote
-const Button = require('./Button')
-const TextInput = require('./TextInput')
+const RaisedButton = require('material-ui/RaisedButton').default
+const TextField = require('material-ui/TextField').default
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
+ displayValue: React.PropTypes.string,
+ id: React.PropTypes.string,
+ onChange: React.PropTypes.func,
+ title: React.PropTypes.string.isRequired,
+ value: React.PropTypes.string.isRequired
}
}
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
+ defaultPath: this.props.value,
+ properties: [ 'openFile', 'openDirectory' ]
}, this.props.dialog)
remote.dialog.showOpenDialog(
@@ -40,28 +35,52 @@ class PathSelector extends React.Component {
opts,
(filenames) => {
if (!Array.isArray(filenames)) return
- this.setState({value: filenames[0]})
this.props.onChange && this.props.onChange(filenames[0])
}
)
}
render () {
+ const id = this.props.title.replace(' ', '-').toLowerCase()
return (
-
-
{this.props.label}:
-
-
)
}
diff --git a/src/renderer/views/PreferencesPage.js b/src/renderer/views/PreferencesPage.js
index 5e338523..2ba3532d 100644
--- a/src/renderer/views/PreferencesPage.js
+++ b/src/renderer/views/PreferencesPage.js
@@ -1,33 +1,142 @@
const React = require('react')
const path = require('path')
-const PathSelector = require('./PathSelector')
+const Checkbox = require('material-ui/Checkbox').default
+const colors = require('material-ui/styles/colors')
+const RaisedButton = require('material-ui/RaisedButton').default
+const PathSelector = require('./PathSelector')
const {dispatch} = require('../lib/dispatcher')
class PreferencesPage extends React.Component {
- render () {
- var state = this.props.state
+ constructor () {
+ super()
+
+ this.handleDownloadPathChange =
+ this.handleDownloadPathChange.bind(this)
+
+ this.handleOpenExternalPlayerChange =
+ this.handleOpenExternalPlayerChange.bind(this)
+
+ this.handleExternalPlayerPathChange =
+ this.handleExternalPlayerPathChange.bind(this)
+ }
+
+ downloadPathSelector () {
return (
-
+
+
+
+ )
+ }
+
+ handleDownloadPathChange (filePath) {
+ dispatch('updatePreferences', 'downloadPath', filePath)
+ }
+
+ openExternalPlayerCheckbox () {
+ return (
+
+
+
+ )
+ }
+
+ handleOpenExternalPlayerChange (e, isChecked) {
+ dispatch('updatePreferences', 'openExternalPlayer', !isChecked)
+ }
+
+ externalPlayerPathSelector () {
+ const playerName = path.basename(
+ this.props.state.unsaved.prefs.externalPlayerPath || 'VLC'
+ )
+
+ const description = this.props.state.unsaved.prefs.openExternalPlayer
+ ? `Torrent media files will always play in ${playerName}.`
+ : `Torrent media files will play in ${playerName} if WebTorrent cannot ` +
+ 'play them.'
+
+ return (
+
+ {description}
+
+
+ )
+ }
+
+ handleExternalPlayerPathChange (filePath) {
+ if (path.extname(filePath) === '.app') {
+ // Mac: Use executable in packaged .app bundle
+ filePath += '/Contents/MacOS/' + path.basename(filePath, '.app')
+ }
+ dispatch('updatePreferences', 'externalPlayerPath', filePath)
+ }
+
+ setDefaultAppButton () {
+ return (
+
+ WebTorrent is not currently the default torrent app.
+
+
+ )
+ }
+
+ handleSetDefaultApp () {
+ window.alert('TODO')
+ // var isFileHandler = state.unsaved.prefs.isFileHandler
+ // dispatch('updatePreferences', 'isFileHandler', !isFileHandler)
+ }
+
+ render () {
+ return (
+
-
- {DownloadPathSelector(state)}
-
+ {this.downloadPathSelector()}
-
- {ExternalPlayerPathSelector(state)}
-
+ {this.openExternalPlayerCheckbox()}
+ {this.externalPlayerPathSelector()}
+
+
+ {this.setDefaultAppButton()}
)
}
}
-// {ExternalPlayerCheckbox(state)}
-// {DefaultAppCheckbox(state)}
-
class PreferencesSection extends React.Component {
static get propTypes () {
return {
@@ -37,8 +146,18 @@ class PreferencesSection extends React.Component {
render () {
return (
-
-
{this.props.title}
+
+
{this.props.title}
{this.props.children}
)
@@ -48,119 +167,15 @@ class PreferencesSection extends React.Component {
class Preference extends React.Component {
render () {
return (
-
+
{this.props.children}
)
}
}
-function DownloadPathSelector (state) {
- return (
-
- )
-
- function handleChange (filePath) {
- dispatch('updatePreferences', 'downloadPath', filePath)
- }
-}
-
-function ExternalPlayerPathSelector (state) {
- return (
-
'}
- 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 (
-//
-//
-//
-//
-//
-//
-//
-//
-// )
-// 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 = [(
-//
-// )]
-// return renderControlGroup(definition, controls)
-
-// function toggleFileHandlers () {
-// var isFileHandler = state.unsaved.prefs.isFileHandler
-// dispatch('updatePreferences', 'isFileHandler', !isFileHandler)
-// }
-// }
-
module.exports = PreferencesPage
diff --git a/src/renderer/views/TextInput.js b/src/renderer/views/TextInput.js
deleted file mode 100644
index 24e26252..00000000
--- a/src/renderer/views/TextInput.js
+++ /dev/null
@@ -1,34 +0,0 @@
-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 (
-
- )
- }
-}
-
-module.exports = TextInput
diff --git a/src/renderer/views/app.js b/src/renderer/views/app.js
index f6f3677a..94d4f687 100644
--- a/src/renderer/views/app.js
+++ b/src/renderer/views/app.js
@@ -1,5 +1,9 @@
const React = require('react')
+const darkBaseTheme = require('material-ui/styles/baseThemes/darkBaseTheme').default
+const getMuiTheme = require('material-ui/styles/getMuiTheme').default
+const MuiThemeProvider = require('material-ui/styles/MuiThemeProvider').default
+
const Header = require('./header')
const Views = {
@@ -16,8 +20,11 @@ const Modals = {
'unsupported-media-modal': require('./unsupported-media-modal')
}
-module.exports = class App extends React.Component {
+var muiTheme = getMuiTheme(Object.assign(darkBaseTheme, {
+ fontFamily: 'BlinkMacSystemFont, \'Helvetica Neue\', Helvetica, sans-serif'
+}))
+module.exports = class App extends React.Component {
constructor (props) {
super(props)
this.state = props.state
@@ -47,12 +54,14 @@ module.exports = class App extends React.Component {
if (hideControls) cls.push('hide-video-controls')
var vdom = (
-
-
- {this.getErrorPopover()}
-
{this.getView()}
- {this.getModal()}
-
+
+
+
+ {this.getErrorPopover()}
+
{this.getView()}
+ {this.getModal()}
+
+
)
return vdom
diff --git a/static/css/components/Button.css b/static/css/components/Button.css
deleted file mode 100644
index b05108ef..00000000
--- a/static/css/components/Button.css
+++ /dev/null
@@ -1,76 +0,0 @@
-.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;
-}
diff --git a/static/css/components/PathSelector.css b/static/css/components/PathSelector.css
deleted file mode 100644
index 7ca86d68..00000000
--- a/static/css/components/PathSelector.css
+++ /dev/null
@@ -1,17 +0,0 @@
-.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;
-}
diff --git a/static/css/components/TextInput.css b/static/css/components/TextInput.css
deleted file mode 100644
index 7289a314..00000000
--- a/static/css/components/TextInput.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.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);
-}
diff --git a/static/css/pages/PreferencesPage.css b/static/css/pages/PreferencesPage.css
deleted file mode 100644
index 28405ba7..00000000
--- a/static/css/pages/PreferencesPage.css
+++ /dev/null
@@ -1,17 +0,0 @@
-.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;
-}
diff --git a/static/css/main.css b/static/main.css
similarity index 97%
rename from static/css/main.css
rename to static/main.css
index 8e97f5e3..36959f3b 100644
--- a/static/css/main.css
+++ b/static/main.css
@@ -20,7 +20,7 @@ body {
}
.app {
- color: #FFF;
+ color: #FAFAFA; /* grey50 */
font-family: BlinkMacSystemFont, 'Helvetica Neue', Helvetica, sans-serif;
font-size: 14px;
line-height: 1.5em;
@@ -91,9 +91,7 @@ table {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
- src: local('Material Icons'),
- local('MaterialIcons-Regular'),
- url('../MaterialIcons-Regular.woff2') format('woff2');
+ src: url('MaterialIcons-Regular.woff2') format('woff2');
}
.icon {
@@ -1025,3 +1023,19 @@ video::-webkit-media-text-track-container {
height: 32px;
margin: 4px 0 0 4px;
}
+
+/**
+ * Use this class on Material UI components to get correct native app behavior:
+ *
+ * - Dragging the button should NOT drag the entire app window
+ * - The cursor should be default, not a pointer (hand) like on the web
+ */
+
+.control {
+ -webkit-app-region: no-drag;
+ cursor: default !important;
+}
+
+.control * {
+ cursor: default !important;
+}
diff --git a/static/main.html b/static/main.html
index 9399e248..702d5f3e 100644
--- a/static/main.html
+++ b/static/main.html
@@ -3,22 +3,11 @@
-
WebTorrent Desktop
-
-
-
-
-
-
-
+
-
-
-
+