Keyboard selecting of tracks, now native

This commit is contained in:
James Barnsley
2016-10-17 22:00:29 +13:00
parent 97026753a0
commit 6a07ebc8f6
12 changed files with 149 additions and 25 deletions

View File

@ -10,6 +10,11 @@
"babel-loader": "6.2.5", "babel-loader": "6.2.5",
"babel-preset-es2015": "*", "babel-preset-es2015": "*",
"babel-preset-react": "*", "babel-preset-react": "*",
"sass-loader": "*",
"style-loader": "*",
"css-loader": "*",
"url-loader": "*",
"file-loader": "*",
"flux": "^2.1.1", "flux": "^2.1.1",
"redux": "*", "redux": "*",
"react": "*", "react": "*",
@ -26,7 +31,6 @@
"jquery": "3.1.1", "jquery": "3.1.1",
"mopidy": "^0.5.0", "mopidy": "^0.5.0",
"expose-loader": "*", "expose-loader": "*",
"kioc": "1.0.4",
"react-fontawesome": "*" "react-fontawesome": "*"
}, },
"scripts": { "scripts": {

2
src/js/bootstrap.js vendored
View File

@ -9,7 +9,7 @@ import spotify from './services/spotify/reducer'
import thunk from 'redux-thunk' import thunk from 'redux-thunk'
import mopidyMiddleware from './services/mopidy/middleware' import mopidyMiddleware from './services/mopidy/middleware'
import spotifyMiddleware from './services/spotify/middleware' import spotifyMiddleware from './services/spotify/middleware'
import localstorageMiddleware from './services/localstorageMiddleware' import localstorageMiddleware from './services/localstorage/middleware'
let reducers = combineReducers({ let reducers = combineReducers({
mopidy, mopidy,

View File

@ -13,6 +13,10 @@ export default class ConfirmationButton extends React.Component{
this.unconfirmTimer = false; this.unconfirmTimer = false;
} }
componentWillUnmount(){
clearTimeout( this.unconfirmTimer );
}
handleClick(e){ handleClick(e){
if( this.state.confirming ){ if( this.state.confirming ){
this.setState({ confirming: false }); this.setState({ confirming: false });
@ -27,12 +31,14 @@ export default class ConfirmationButton extends React.Component{
} }
handleMouseLeave(e){ handleMouseLeave(e){
this.unconfirmTimer = setTimeout( if( this.state.confirming ){
function(){ this.unconfirmTimer = setTimeout(
this.setState({ confirming: false }); function(){
}.bind(this), this.setState({ confirming: false });
1500 }.bind(this),
); 1500
);
}
} }
render(){ render(){

View File

@ -10,12 +10,15 @@ export default class Track extends React.Component{
super(props); super(props);
} }
toggleSelected( e ){ handleClick( e ){
return this.props.toggleSelected(); var target = $(e.target);
if( !target.is('a') && target.closest('a').length <= 0 ){
this.props.handleClick(e);
}
} }
playTrack( e ){ handleDoubleClick( e ){
return this.props.playTrack(); return this.props.handleDoubleClick(e);
} }
render(){ render(){
@ -28,11 +31,21 @@ export default class Track extends React.Component{
return ( return (
<div <div
className="track" className="track"
onDoubleClick={ (e) => this.playTrack(e) }> onDoubleClick={ (e) => this.handleDoubleClick(e) }
<FontAwesome name={selectedIcon} fixedWidth onClick={ (e) => this.toggleSelected(e) } /> onClick={ (e) => this.handleClick(e) }>
{this.props.track.name} <FontAwesome name={selectedIcon} fixedWidth />
<ArtistList artists={this.props.track.artists} /> <span className="name">
{ this.props.track.album ? <AlbumLink album={this.props.track.album} /> : null } {this.props.track.name}
</span>
<span className="artists">
<ArtistList artists={this.props.track.artists} />
</span>
<span className="album">
{ this.props.track.album ? <AlbumLink album={this.props.track.album} /> : null }
</span>
<span className="duration">
{ this.props.track.duration }
</span>
</div> </div>
); );
} }

View File

@ -12,17 +12,44 @@ class TrackList extends React.Component{
super(props); super(props);
this.state = { this.state = {
tracks: this.props.tracks tracks: this.props.tracks,
lastSelectedTrack: false
} }
} }
toggleSelected( index ){ handleClick( e, index ){
var tracks = this.state.tracks; 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; var tracks = this.state.tracks;
this.props.actions.changeTrack( tracks[index].tlid ) this.props.actions.changeTrack( tracks[index].tlid )
} }
@ -67,8 +94,8 @@ class TrackList extends React.Component{
return <Track return <Track
key={index+'_'+track.uri} key={index+'_'+track.uri}
track={track} track={track}
playTrack={() => self.playTrack(index)} handleDoubleClick={(e) => self.handleDoubleClick(e, index)}
toggleSelected={() => self.toggleSelected(index)} /> handleClick={(e) => self.handleClick(e, index)} />
} }
) )
} }

View File

@ -10,6 +10,8 @@ import { Router, Route, Link, hashHistory } from 'react-router'
import store from './bootstrap.js' import store from './bootstrap.js'
require('../scss/app.scss');
import App from './views/App' import App from './views/App'
import Album from './views/Album' import Album from './views/Album'
import Artist from './views/Artist' import Artist from './views/Artist'

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -1,2 +1,4 @@
@import 'vendor/font-awesome/index'; @import 'vendor/font-awesome/index';
@import 'components/track';

25
src/scss/components/_track.scss Executable file
View File

@ -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;
}
}

View File

@ -1,3 +1,4 @@
var dev = process.env.NODE_ENV !== "production"; var dev = process.env.NODE_ENV !== "production";
var webpack = require('webpack'); var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin');
@ -17,7 +18,9 @@ var config = {
module: { module: {
loaders: [ 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 // loading JSX (aka Babel) into browser-friendly ES6
test: /\.js$/, test: /\.js$/,
@ -37,6 +40,11 @@ var config = {
'css'+(dev? '?sourceMap=true': ''), 'css'+(dev? '?sourceMap=true': ''),
'sass'+(dev? '?outputStyle=expanded&sourceMap=true&sourceMapContents=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'
} }
] ]
}, },