diff --git a/src/index.html b/src/index.html index ecbc0295..f796b4b4 100755 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,7 @@ + diff --git a/src/js/App.js b/src/js/App.js index 5c3845c2..8d34f390 100755 --- a/src/js/App.js +++ b/src/js/App.js @@ -119,7 +119,7 @@ class App extends React.Component{ if (this.props.dragger && this.props.dragger.dragging){ this.props.uiActions.dragCancel() } - if (this.props.modal){';' + if (this.props.modal){ this.props.uiActions.closeModal() } break @@ -180,7 +180,7 @@ class App extends React.Component{ if (this.props.dragger && this.props.dragger.active) className += ' dragging' if (this.props.sidebar_open) className += ' sidebar-open' if (this.props.modal) className += ' modal-open' - if (helpers.isTouchDevice() || this.props.emulate_touch){ + if (helpers.isTouchDevice()){ className += ' touch' } else { className += ' notouch' diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js index 2bee3123..a04e836a 100755 --- a/src/js/bootstrap.js +++ b/src/js/bootstrap.js @@ -28,14 +28,13 @@ var initialState = { mopidy: { connected: false, host: window.location.hostname, - port: 6680, + port: (window.location.port ? window.location.port : '80'), volume: 0, progress: 0, play_state: false }, pusher: { connected: false, - port: 6681, username: 'Anonymous', connections: {}, version: { diff --git a/src/js/components/SpotifyAuthenticationFrame.js b/src/js/components/SpotifyAuthenticationFrame.js index d7c07a50..85f692c8 100755 --- a/src/js/components/SpotifyAuthenticationFrame.js +++ b/src/js/components/SpotifyAuthenticationFrame.js @@ -66,20 +66,18 @@ class SpotifyAuthenticationFrame extends React.Component{ renderAuthorizeButton(){ if( this.state.authorizing ){ return ( - - ); + ) }else if( this.props.authorized ){ return ( - ); + ) }else{ return ( - ); + ) } } @@ -88,9 +86,7 @@ class SpotifyAuthenticationFrame extends React.Component{ if (this.props.refreshing_token){ return ( - ); diff --git a/src/js/components/Track.js b/src/js/components/Track.js index 09477a86..6dde451c 100755 --- a/src/js/components/Track.js +++ b/src/js/components/Track.js @@ -135,8 +135,10 @@ export default class Track extends React.Component{ onMouseUp={ e => this.props.handleMouseUp(e) } onDoubleClick={ e => this.props.handleDoubleClick(e) } onContextMenu={ e => this.handleContextMenu(e) }> - { this.props.track.selected ? : null } - { this.props.track.playing ? : null } + + { this.props.track.selected ? : null } + { this.props.track.playing && !this.props.track.selected ? : null } + { track_columns } ); diff --git a/src/js/components/TrackList.js b/src/js/components/TrackList.js index b5626f3d..d3ebbb3f 100755 --- a/src/js/components/TrackList.js +++ b/src/js/components/TrackList.js @@ -14,18 +14,14 @@ import * as uiActions from '../services/ui/actions' class TrackList extends React.Component{ constructor(props) { - super(props); - - this._touch_threshold = 10 - this._touch_x = null - this._touch_y = null + super(props) this.state = { tracks: [], - last_selected_track: false, - edit_mode: false + last_selected_track: false } + this._touching = false this.handleKeyUp = this.handleKeyUp.bind(this) } @@ -45,6 +41,25 @@ class TrackList extends React.Component{ this.setState({ tracks: this.keyifyTracks(nextProps.tracks) }); } + /** + * Figure out if our click/touch event is valid and we can act accordingly + * + * @param e = event obj + * @return string + **/ + triggerType(e){ + var target = $(e.target) + + // Wide screen, so no worries + if ($(window).width() > 800){ + return 'default' + } else if (target.is('.state-icon') || target.closest('.state-icon').length > 0){ + return 'mobile' + } + + return false + } + handleKeyUp(e){ if( this.selectedTracks().length <= 0 ) return; @@ -61,28 +76,15 @@ class TrackList extends React.Component{ } handleTouchStart(e,index){ - this._touch_x = Math.round(e.changedTouches[0].pageX) - this._touch_y = Math.round(e.changedTouches[0].pageY) + this._touching = true } handleTouchEnd(e,index){ - var pageX = Math.round(e.changedTouches[0].pageX) - var pageY = Math.round(e.changedTouches[0].pageY) - clearTimeout(this._touch_hold_timer) - - // make sure our touch was within the threshold of the touch start - // this helps us differentiate between taps and drags but doesn't consider - // multi-finger touches - if( this.state.edit_mode && - this._touch_x < ( pageX + this._touch_threshold ) && - this._touch_x > ( pageX - this._touch_threshold ) && - this._touch_y < ( pageY + this._touch_threshold ) && - this._touch_y > ( pageY - this._touch_threshold ) ){ - var tracks = this.state.tracks - tracks[index].selected = !tracks[index].selected - this.setState({ tracks: tracks, last_selected_track: index }) - } + this._touching = false + this.handleMouseDown(e,index) + // Prevent any event bubbling. This prevents clicks and mouse events + // from also being fired e.preventDefault() } @@ -92,49 +94,61 @@ class TrackList extends React.Component{ } handleMouseDown(e,index){ - if (this.props.emulate_touch){ - this.handleTouchContextMenu(e,index) - }else{ - if (this.props.context_menu) this.props.uiActions.hideContextMenu() - if (!this.state.tracks[index].selected && !this.isRightClick(e) && !e.ctrlKey) this.toggleTrackSelections(e, index) + if (this.props.context_menu) this.props.uiActions.hideContextMenu() - var selected_tracks = this.selectedTracks() - this.props.uiActions.dragStart( e, this.props.context, this.props.uri, selected_tracks, this.tracksIndexes(selected_tracks) ) + // Regular clicking an un-selected element + // This selects the track before we potentially drag + switch (this.triggerType(e)){ + + case 'mobile': + // simple toggle + var tracks = this.state.tracks + tracks[index].selected = !tracks[index].selected + this.setState({tracks: tracks, last_selected_track: index}) + break + + case 'default': + if (!this.state.tracks[index].selected && !this.isRightClick(e) && !e.ctrlKey){ + this.toggleTrackSelections(e, index) + } + break } + + var selected_tracks = this.selectedTracks() + this.props.uiActions.dragStart( e, this.props.context, this.props.uri, selected_tracks, this.tracksIndexes(selected_tracks) ) } handleMouseUp(e,index){ + if (this.triggerType(e) == 'default'){ + + // right-clicking on an un-highlighted track + if (!this.state.tracks[index].selected && this.isRightClick(e)){ + this.toggleTrackSelections(e, index) - // right-clicking on an un-highlighted track - if( !this.state.tracks[index].selected && this.isRightClick(e) ){ - this.toggleTrackSelections(e, index) + // selected track, regular click + }else if (this.state.tracks[index].selected && !this.isRightClick(e)){ + this.toggleTrackSelections(e, index) - // selected track, regular click - }else if( this.state.tracks[index].selected && !this.isRightClick(e) ){ - this.toggleTrackSelections(e, index) + // ctrl key + } else if(e.ctrlKey){ + this.toggleTrackSelections(e, index) + } - // ctrl key - }else if( e.ctrlKey ){ - this.toggleTrackSelections(e, index) - } - - if( this.props.dragger && this.props.dragger.active ){ - - // if this tracklist handles sorting, handle it - if( typeof(this.props.reorderTracks) !== 'undefined' ){ - var indexes = this.props.dragger.victims_indexes - return this.props.reorderTracks( indexes, index ); + if (this.props.dragger && this.props.dragger.active){ + + // if this tracklist handles sorting, handle it + if (typeof(this.props.reorderTracks) !== 'undefined'){ + var indexes = this.props.dragger.victims_indexes + return this.props.reorderTracks( indexes, index ) + } } } } handleContextMenu(e, native_event = true){ - // touch events fired? we assume the user is primarily touching, - // so for touch devices we disable direct context menus - // hybrid devices will only work with touch OR mouse, not both in this case - if (this._touch_x && this._touch_y && native_event){ - this.setState({edit_mode: true}) + // disable context menu events in mobile view + if (this.triggerType(e) == 'mobile'){ e.preventDefault() return false } @@ -327,9 +341,6 @@ class TrackList extends React.Component{ if (this.props.className){ className += ' '+this.props.className } - if (this.state.edit_mode){ - className += ' edit-mode' - } return (
@@ -342,12 +353,12 @@ class TrackList extends React.Component{ key={track.key} track={track} context={this.props.context} - handleDoubleClick={ e => self.handleDoubleClick(e, index)} - handleMouseUp={ e => self.handleMouseUp(e, index)} - handleMouseDown={ e => self.handleMouseDown(e, index)} - handleTouchStart={ e => self.handleTouchStart(e, index)} - handleTouchEnd={ e => self.handleTouchEnd(e, index)} - handleContextMenu={ e => self.handleContextMenu(e)} /> + handleDoubleClick={e => self.handleDoubleClick(e, index)} + handleMouseUp={e => self.handleMouseUp(e, index)} + handleMouseDown={e => self.handleMouseDown(e, index)} + handleTouchStart={e => self.handleTouchStart(e, index)} + handleTouchEnd={e => self.handleTouchEnd(e, index)} + handleContextMenu={e => self.handleContextMenu(e)} /> } ) } diff --git a/src/js/views/Search.js b/src/js/views/Search.js index f146278a..78cf8447 100755 --- a/src/js/views/Search.js +++ b/src/js/views/Search.js @@ -139,7 +139,7 @@ class Search extends React.Component{ var artists_section = (
-

Artists

+

Artists

@@ -152,7 +152,7 @@ class Search extends React.Component{ var albums_section = (
-

Albums

+

Albums

@@ -165,7 +165,7 @@ class Search extends React.Component{ var playlists_section = (
-

Playlists

+

Playlists

diff --git a/src/scss/components/_context-menu.scss b/src/scss/components/_context-menu.scss index 4c0bf143..f35cbaef 100755 --- a/src/scss/components/_context-menu.scss +++ b/src/scss/components/_context-menu.scss @@ -248,28 +248,32 @@ } } -.list { - .context-menu-trigger { - display: none; - } +.list { + &.track-list { - &.track-list.edit-mode { .context-menu-trigger { - display: block; - position: fixed; - bottom: 50px; - right: 5px; - width: 50px; - height: 50px; - overflow: hidden; - z-index: 97; - border-radius: 50%; - background: $blue; - color: #FFFFFF; - - &:focus, - &:active { - background: darken($blue, 10%); + display: none; + } + + @include responsive( $bp_medium ){ + + .context-menu-trigger { + display: block; + position: fixed; + bottom: 50px; + right: 5px; + width: 50px; + height: 50px; + overflow: hidden; + z-index: 97; + border-radius: 50%; + background: $blue; + color: #FFFFFF; + + &:focus, + &:active { + background: darken($blue, 10%); + } } } } diff --git a/src/scss/components/_grid.scss b/src/scss/components/_grid.scss index 6d4185e5..7cc69e51 100755 --- a/src/scss/components/_grid.scss +++ b/src/scss/components/_grid.scss @@ -42,13 +42,9 @@ &:active, &:focus { - .thumbnail { - .image { - margin: 2%; - padding-bottom: 96%; - width: 96%; - } - } + -moz-transform: scale(0.98); + -webkit-transform: scale(0.98); + transform: scale(0.98); } } diff --git a/src/scss/components/_lists.scss b/src/scss/components/_lists.scss index 79865a35..1259f57d 100755 --- a/src/scss/components/_lists.scss +++ b/src/scss/components/_lists.scss @@ -1,7 +1,7 @@ .list { - .list-item { + @include clearfix; -webkit-touch-callout: none; -webkit-user-select: none; @@ -16,12 +16,29 @@ border-top: 1px solid $faint_grey; border-bottom: 1px solid $faint_grey; margin-bottom: -1px; - @include clearfix; + cursor: pointer; &.selected { background: $yellow !important; } + &.playing { + font-weight: bold; + } + + .state-icon { + position: absolute; + top: 0; + left: 0; + font-size: 10px; + + .fa { + position: absolute; + top: 15px; + left: 15px; + } + } + :root .notouch:not(.dragging) &:not(.header):not(.no-click):hover { background: rgba(150,150,150,0.1); cursor: pointer; @@ -71,35 +88,6 @@ } } - .select-state { - position: absolute; - top: 15px; - left: 15px; - font-size: 10px; - } - - .play-state { - display: none; - position: absolute; - top: 16px; - left: 15px; - font-size: 10px; - } - - &.playing { - font-weight: 600; - .play-state { - display: block; - opacity: 1; - } - } - - &.playing.selected { - .play-state { - display: none; - } - } - :root .dragging &:hover { border-top: 3px solid $blue; margin-top: -3px; @@ -222,13 +210,40 @@ @include responsive( $bp_medium ){ .list-item { - padding: 7px 30px 7px 16px !important; + padding: 7px 30px 7px 48px !important; .source { position: static; float: none; } + .state-icon { + top: 0; + left: 0; + height: 100%; + width: 48px; + + .fa { + top: 19px; + left: 17px; + } + + &:after { + display: block; + content: ''; + border: 1px solid $darkest_grey; + width: 15px; + height: 15px; + position: absolute; + top: 15px; + left: 15px; + } + + .playing { + display: none !important; + } + } + .col { &.name { width: 90% !important; @@ -275,13 +290,6 @@ &.header { display: none; } - - &.track { - .select-state, - .play-state { - display: none !important; - } - } } &.queue-track-list { @@ -300,17 +308,5 @@ } } } - - &.edit-mode { - .list-item { - padding-left: 40px !important; - - &.track { - .select-state { - display: block !important; - } - } - } - } } } \ No newline at end of file diff --git a/src/scss/global/_core.scss b/src/scss/global/_core.scss index 476553d6..17e78a83 100755 --- a/src/scss/global/_core.scss +++ b/src/scss/global/_core.scss @@ -6,6 +6,7 @@ html { color: #000000; font-family: "Archivo Narrow", Helvetica, Arial, sans-serif; font-size: 14px; + touch-action: manipulation; } body { diff --git a/src/scss/global/_forms.scss b/src/scss/global/_forms.scss index 4010cfdf..028771a3 100755 --- a/src/scss/global/_forms.scss +++ b/src/scss/global/_forms.scss @@ -1,4 +1,12 @@ +:focus { + outline: none; +} + +::-moz-focus-inner { + border: 0; +} + button, textarea, input[type="text"], @@ -159,6 +167,36 @@ input[type="submit"] { } } + &.working { + position: relative; + + &:after { + position: absolute; + display: block; + content: ''; + background: $mid_grey; + opacity: 1; + animation: slideloader 1s infinite; + bottom: 0; + left: 0; + right: 0; + height: 4px; + z-index: 1; + } + + &.destructive:after { + background: $red; + } + + &.primary:after { + background: $turquoise; + } + + &.alternative:after { + background: $blue; + } + } + &.context-menu-trigger { border-color: transparent; padding: 6px;