diff --git a/package.json b/package.json index f5cd7848..91499a07 100755 --- a/package.json +++ b/package.json @@ -10,6 +10,11 @@ "babel-loader": "6.2.5", "babel-preset-es2015": "*", "babel-preset-react": "*", + "sass-loader": "*", + "style-loader": "*", + "css-loader": "*", + "url-loader": "*", + "file-loader": "*", "flux": "^2.1.1", "redux": "*", "react": "*", @@ -26,7 +31,6 @@ "jquery": "3.1.1", "mopidy": "^0.5.0", "expose-loader": "*", - "kioc": "1.0.4", "react-fontawesome": "*" }, "scripts": { diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js index 060fbb95..da589852 100755 --- a/src/js/bootstrap.js +++ b/src/js/bootstrap.js @@ -9,7 +9,7 @@ import spotify from './services/spotify/reducer' import thunk from 'redux-thunk' import mopidyMiddleware from './services/mopidy/middleware' import spotifyMiddleware from './services/spotify/middleware' -import localstorageMiddleware from './services/localstorageMiddleware' +import localstorageMiddleware from './services/localstorage/middleware' let reducers = combineReducers({ mopidy, diff --git a/src/js/components/ConfirmationButton.js b/src/js/components/ConfirmationButton.js index 109a2c38..0d7bef12 100755 --- a/src/js/components/ConfirmationButton.js +++ b/src/js/components/ConfirmationButton.js @@ -13,6 +13,10 @@ export default class ConfirmationButton extends React.Component{ this.unconfirmTimer = false; } + componentWillUnmount(){ + clearTimeout( this.unconfirmTimer ); + } + handleClick(e){ if( this.state.confirming ){ this.setState({ confirming: false }); @@ -27,12 +31,14 @@ export default class ConfirmationButton extends React.Component{ } handleMouseLeave(e){ - this.unconfirmTimer = setTimeout( - function(){ - this.setState({ confirming: false }); - }.bind(this), - 1500 - ); + if( this.state.confirming ){ + this.unconfirmTimer = setTimeout( + function(){ + this.setState({ confirming: false }); + }.bind(this), + 1500 + ); + } } render(){ diff --git a/src/js/components/Track.js b/src/js/components/Track.js index daa2e790..cfe8758b 100755 --- a/src/js/components/Track.js +++ b/src/js/components/Track.js @@ -10,12 +10,15 @@ export default class Track extends React.Component{ super(props); } - toggleSelected( e ){ - return this.props.toggleSelected(); + handleClick( e ){ + var target = $(e.target); + if( !target.is('a') && target.closest('a').length <= 0 ){ + this.props.handleClick(e); + } } - playTrack( e ){ - return this.props.playTrack(); + handleDoubleClick( e ){ + return this.props.handleDoubleClick(e); } render(){ @@ -28,11 +31,21 @@ export default class Track extends React.Component{ return (
this.playTrack(e) }> - this.toggleSelected(e) } /> - {this.props.track.name} - - { this.props.track.album ? : null } + onDoubleClick={ (e) => this.handleDoubleClick(e) } + onClick={ (e) => this.handleClick(e) }> + + + {this.props.track.name} + + + + + + { this.props.track.album ? : null } + + + { this.props.track.duration } +
); } diff --git a/src/js/components/TrackList.js b/src/js/components/TrackList.js index ddf2d3be..621cfc88 100755 --- a/src/js/components/TrackList.js +++ b/src/js/components/TrackList.js @@ -12,17 +12,44 @@ class TrackList extends React.Component{ super(props); this.state = { - tracks: this.props.tracks + tracks: this.props.tracks, + lastSelectedTrack: false } } - toggleSelected( index ){ + handleClick( e, index ){ var tracks = this.state.tracks; - tracks[index].selected = !tracks[index].selected; - this.setState({ tracks: tracks }); + + if( e.ctrlKey ){ + + tracks[index].selected = !tracks[index].selected; + + }else if( e.shiftKey ){ + + if( this.state.lastSelectedTrack < index ){ + var start = this.state.lastSelectedTrack; + var end = index; + }else{ + var start = index; + var end = this.state.lastSelectedTrack; + } + + for( var i = start; i <= end; i++ ){ + tracks[i].selected = true; + } + + }else{ + + for( var i = 0; i < tracks.length; i++ ){ + tracks[i].selected = false; + } + tracks[index].selected = !tracks[index].selected; + } + + this.setState({ tracks: tracks, lastSelectedTrack: index }); } - playTrack( index ){ + handleDoubleClick( e, index ){ var tracks = this.state.tracks; this.props.actions.changeTrack( tracks[index].tlid ) } @@ -67,8 +94,8 @@ class TrackList extends React.Component{ return self.playTrack(index)} - toggleSelected={() => self.toggleSelected(index)} /> + handleDoubleClick={(e) => self.handleDoubleClick(e, index)} + handleClick={(e) => self.handleClick(e, index)} /> } ) } diff --git a/src/js/index.js b/src/js/index.js index 7eb82327..b70a9552 100755 --- a/src/js/index.js +++ b/src/js/index.js @@ -10,6 +10,8 @@ import { Router, Route, Link, hashHistory } from 'react-router' import store from './bootstrap.js' +require('../scss/app.scss'); + import App from './views/App' import Album from './views/Album' import Artist from './views/Artist' diff --git a/src/js/services/keyboard/actions.js b/src/js/services/keyboard/actions.js new file mode 100755 index 00000000..19a7212e --- /dev/null +++ b/src/js/services/keyboard/actions.js @@ -0,0 +1,19 @@ + +/** + * Actions and Action Creators + **/ + +export function shiftKey( down ){ + return { + type: 'SHIFT_KEY', + down: down + } +} + +export function ctrlKey( down ){ + return { + type: 'CTRL_KEY', + down: down + } +} + diff --git a/src/js/services/keyboard/reducer.js b/src/js/services/keyboard/reducer.js new file mode 100755 index 00000000..eeb4a944 --- /dev/null +++ b/src/js/services/keyboard/reducer.js @@ -0,0 +1,18 @@ + +export default function reducer(keyboard = {}, action){ + console.log(action); + switch (action.type) { + + case 'SHIFT_KEY': + return Object.assign({}, keyboard, { shift: action.down }); + + case 'CTRL_KEY': + return Object.assign({}, keyboard, { ctrl: action.down }); + + default: + return keyboard + } +} + + + diff --git a/src/js/services/localstorageMiddleware.js b/src/js/services/localstorage/middleware.js similarity index 100% rename from src/js/services/localstorageMiddleware.js rename to src/js/services/localstorage/middleware.js diff --git a/src/scss/app.scss b/src/scss/app.scss index 3dfe8a8f..81b2d372 100755 --- a/src/scss/app.scss +++ b/src/scss/app.scss @@ -1,2 +1,4 @@ @import 'vendor/font-awesome/index'; +@import 'components/track'; + diff --git a/src/scss/components/_track.scss b/src/scss/components/_track.scss new file mode 100755 index 00000000..3f54a363 --- /dev/null +++ b/src/scss/components/_track.scss @@ -0,0 +1,25 @@ + +.track { + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + .name, + .artists, + .album, + .duration { + width: 20%; + min-height: 1px; + float: left; + } + + &::after { + content: ''; + clear: both; + display: block; + } +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index dde65b33..94232e96 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,3 +1,4 @@ + var dev = process.env.NODE_ENV !== "production"; var webpack = require('webpack'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); @@ -17,7 +18,9 @@ var config = { module: { loaders: [ - { test: require.resolve('jquery'), loader: 'expose?jQuery!expose?$' }, + { + test: require.resolve('jquery'), loader: 'expose?jQuery!expose?$' + }, { // loading JSX (aka Babel) into browser-friendly ES6 test: /\.js$/, @@ -37,6 +40,11 @@ var config = { 'css'+(dev? '?sourceMap=true': ''), 'sass'+(dev? '?outputStyle=expanded&sourceMap=true&sourceMapContents=true': '') ]) + }, + { + // load external resources (ie Google fonts) + test: /.(png|woff(2)?|eot|ttf|svg)(\?[a-z0-9=\.]+)?$/, + loader: 'url-loader?limit=100000' } ] },