diff --git a/README.rst b/README.rst index eca37f59..c061c131 100644 --- a/README.rst +++ b/README.rst @@ -6,10 +6,6 @@ Mopidy-MusicBox-Webclient :target: https://pypi.python.org/pypi/Mopidy-MusicBox-Webclient/ :alt: Latest PyPI version -.. image:: https://img.shields.io/pypi/dm/Mopidy-MusicBox-Webclient.svg?style=flat - :target: https://pypi.python.org/pypi/Mopidy-MusicBox-Webclient/ - :alt: Number of PyPI downloads - .. image:: https://img.shields.io/travis/pimusicbox/mopidy-musicbox-webclient/develop.svg?style=flat :target: https://travis-ci.org/pimusicbox/mopidy-musicbox-webclient :alt: Travis CI build status @@ -38,7 +34,9 @@ Features - Deep integration with, and additional features for, the `Pi MusicBox `_. - Fullscreen mode. -.. image:: https://github.com/pimusicbox/mopidy-musicbox-webclient/raw/develop/screenshots/queue_desktop.png +.. image:: https://github.com/pimusicbox/mopidy-musicbox-webclient/raw/develop/screenshots/overview.png + :width: 1312 + :height: 723 Dependencies ============ @@ -107,6 +105,33 @@ Project resources Changelog ========= +v2.4.0 (2017-03-15) +------------------- + +- Now shows server name/IP address and port number at the bottom of the navigation pane. (Addresses: `#67 `_). +- Add ability to insert a track anywhere in the current queue. (Addresses: `#75 `_). +- Add 'Show Track Info' popup which can be activated from any context menu or by clicking on either the 'info' icon next + to the album cover or the track's title text on the 'Now Playing' pane. The popup includes the URI of the track, which + can be inserted into various lists elsewhere in the player. +- Updated icon set for font-awesome 4.7.0. +- Added 'Refresh' button for refreshing libraries. (Addresses: `#75 `_). + +**Fixes** + +- Only show 'Show Album' or 'Show Artist' options in popup menus if URI's for those resources are available. + (Fixes: `#213 `_). +- Now shows correct hostname information in loader popup. (Fixes: `#209 `_). +- Reset 'Now Playing' info when the last track in the tracklist is deleted. Fixes an issue where info of the last song played would be displayed even after the queue had been cleared. +- Now initializes the GUI properly, even if the user is offline or the Mopidy server cannot be reached. +- Fixed `Alarm Clock `_ detection. +- Unplayable files are shown with a different icon in track lists. +- Show all available track information in the 'Show Track Info...' popup. (Fixes: `#227 `_). +- The last scroll position is now always saved when navigating between pages or browsing the library. + (Fixes: `#73 `_, `#93 `_). +- Playlists will now list tracks even if they are no longer available in the library. (Fixes: `#226 `_). +- Fixed an issue on Safari where the first page to load would be too wide to fit on the screen. +- Refreshing album or artist info pages no longer raises an exception. (Fixes: `#230 `_). + v2.3.0 (2016-05-15) ------------------- diff --git a/mopidy_musicbox_webclient/__init__.py b/mopidy_musicbox_webclient/__init__.py index 10470679..36a3c5b1 100644 --- a/mopidy_musicbox_webclient/__init__.py +++ b/mopidy_musicbox_webclient/__init__.py @@ -4,7 +4,7 @@ from mopidy import config, ext -__version__ = '2.3.0' +__version__ = '2.4.0' class Extension(ext.Extension): diff --git a/mopidy_musicbox_webclient/static/css/webclient.css b/mopidy_musicbox_webclient/static/css/webclient.css index 48d4e5a0..2cafa99f 100644 --- a/mopidy_musicbox_webclient/static/css/webclient.css +++ b/mopidy_musicbox_webclient/static/css/webclient.css @@ -1,6 +1,6 @@ /* * Mopidy Webclient CSS - * (c) Wouter van Wijk 2012-2013 + * (c) Wouter van Wijk 2012-2017 */ /**************************** @@ -118,7 +118,6 @@ /****************** * Track Slider * ******************/ - #trackslider { display: inline; width: 100%; @@ -160,6 +159,20 @@ display: inline; } +div.hostInfo { + width: 100%; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; +} + +span.hostInfo { + font-weight: normal; + font-size: 0.75em; + overflow: hidden; + text-overflow: ellipsis; +} + /******************** * Pages, content * ********************/ @@ -207,6 +220,17 @@ #homerows div i { font-size: 28px; } + +.ui-block-a-min { + float: left !important; + width: initial !important; +} + +.ui-block-b-min { + float:right !important; + width: initial !important; +} + /*************** * listviews * ***************/ @@ -235,6 +259,45 @@ border-bottom: 1px solid #CECECE; } +.info-table { + display: table !important; +} + +.info-table thead { + visibility: collapse; +} + +.info-table th { + border-bottom: none !important; +} + +.info-table tr { + border-bottom: 1px solid #f2f2f2 +} + +.info-table td { + color: #555 !important; + padding: 2px; + padding-right: 14px; + padding-left: 14px; + border: none !important; +} + +.info-table td.label { + font-weight: bold; +} + +.info-table td.label-center { + vertical-align: middle; +} + +.info-table input { + color: #555; + border: none; + font-size: 1em; + width: 100%; +} + .albumdivider h1, .table li h1 { font-size: 120% !important; } @@ -305,15 +368,33 @@ font-size: initial; } +.infoBtn { + top: 0; + width: 90%; + position: absolute; +} + +.infoBtn i { + font-size: 1.33em; + color: #ddd; + background: white; + border-radius: 50%; + height: 1em; + width: 1em; +} + .backnav { background-color: #ccc !important; } +.refreshLibraryBtnDiv { + display: none; +} + /********************** * Now Playing area * **********************/ - #nowPlayingFooter { height: 50px; line-height: 48px; @@ -342,11 +423,17 @@ /************ * Popups * ************/ -#modalalbum a, #modalartist a { +#modalalbum a, #modalartist a, #modalname a { color: #444; text-decoration: none; } +#modalinfo { + position: relative; + display: inline-block; + padding-top: .5em; +} + .popupArtistLi, .popupAlbumLi { display: none @@ -392,6 +479,7 @@ .ui-icon-playAll:after, .ui-icon-play:after, .ui-icon-playNext:after, +.ui-icon-insert:after, .ui-icon-add:after, .ui-icon-addAll:after, .ui-icon-remove:after { @@ -411,6 +499,10 @@ content: '\f149'; } +.ui-icon-insert:after { + content: '\f177'; +} + .ui-icon-add:after { content: '\f196'; } @@ -434,11 +526,17 @@ font-weight: normal; } -.popupDialog { +.popupDialog, +.popupDialog-full-width { padding: 10px; text-align: center; } +.popupDialog-full-width { + padding-left: 0; + padding-right: 0; +} + /*dont hide clear buttons in text input */ .ui-input-clear-hidden { display: block !important; @@ -447,7 +545,6 @@ /**************** * Common use * ****************/ - #playlistspane { margin: 0 !important; } @@ -531,31 +628,40 @@ a { } /*helper*/ - .ui-loader h1 { color: #efefef; } -/* panel workaround to make it responsive wrap push on wide viewports once open */ -@media (min-width: 35em){ - .ui-responsive-panel.ui-page-panel-open .ui-panel-content-fixed-toolbar-open.ui-panel-content-fixed-toolbar-display-push, - .ui-responsive-panel.ui-page-panel-open .ui-panel-content-fixed-toolbar-open.ui-panel-content-fixed-toolbar-display-reveal, - .ui-responsive-panel.ui-page-panel-open .ui-panel-content-wrap-open.ui-panel-content-wrap-display-push, - .ui-responsive-panel.ui-page-panel-open .ui-panel-content-wrap-open.ui-panel-content-wrap-display-reveal { +/*desktop*/ +@media (min-width: 55em) { + /* panel workaround to make it responsive wrap push on wide viewports once open */ + .ui-responsive-panel.ui-page-panel .ui-panel-content-fixed-toolbar-open.ui-panel-content-fixed-toolbar-display-push, + .ui-responsive-panel.ui-page-panel .ui-panel-content-fixed-toolbar-open.ui-panel-content-fixed-toolbar-display-reveal, + .ui-responsive-panel.ui-page-panel .ui-panel-content-wrap-open.ui-panel-content-wrap-display-push, + .ui-responsive-panel.ui-page-panel .ui-panel-content-wrap-open.ui-panel-content-wrap-display-reveal { margin-right: 17em; + width: auto; } - .ui-responsive-panel.ui-page-panel-open .ui-panel-content-fixed-toolbar-open.ui-panel-content-wrap-display-push.ui-panel-content-fixed-toolbar-position-right, - .ui-responsive-panel.ui-page-panel-open .ui-panel-content-fixed-toolbar-open.ui-panel-content-wrap-display-reveal.ui-panel-content-fixed-toolbar-position-right, - .ui-responsive-panel.ui-page-panel-open .ui-panel-content-wrap-open.ui-panel-content-wrap-display-push.ui-panel-content-wrap-position-right, - .ui-responsive-panel.ui-page-panel-open .ui-panel-content-wrap-open.ui-panel-content-wrap-display-reveal.ui-panel-content-wrap-position-right { + .ui-responsive-panel.ui-page-panel .ui-panel-content-fixed-toolbar-open.ui-panel-content-wrap-display-push.ui-panel-content-fixed-toolbar-position-right, + .ui-responsive-panel.ui-page-panel .ui-panel-content-fixed-toolbar-open.ui-panel-content-wrap-display-reveal.ui-panel-content-fixed-toolbar-position-right, + .ui-responsive-panel.ui-page-panel .ui-panel-content-wrap-open.ui-panel-content-wrap-display-push.ui-panel-content-wrap-position-right, + .ui-responsive-panel.ui-page-panel .ui-panel-content-wrap-open.ui-panel-content-wrap-display-reveal.ui-panel-content-wrap-position-right { margin: 0 0 0 17em; } +} +/*tablets and desktop*/ +@media (min-width: 35em) { .ui-responsive-panel .ui-panel-dismiss-display-reveal { display: none; } + + .popupDialog { + min-width: 320px; + } } + /*smartphones*/ @media (max-width: 35em) { #nowPlayingpane { diff --git a/mopidy_musicbox_webclient/static/index.html b/mopidy_musicbox_webclient/static/index.html index db574902..f7b1eed1 100644 --- a/mopidy_musicbox_webclient/static/index.html +++ b/mopidy_musicbox_webclient/static/index.html @@ -16,7 +16,7 @@ - + @@ -36,8 +36,8 @@ - -
+ +
+
  • +
    + {% if hostname == serverIP %} + {{programName}} running on {{ hostname }}:{{ serverPort}} + {% else %} + {{programName}} running on {{ hostname }} at {{ serverIP }}:{{ serverPort}} + {% end %} +
    +
  • @@ -102,14 +111,14 @@

    - Album cover + Album cover
    Close

     

    - Album artist + Album artist
    @@ -151,40 +163,65 @@

    Artists

  • Play
  • +
  • + Add a Track Below +
  • - Remove from Queue + Remove from Queue
  • - Show Album + Show Album
  • - Show Artist + Show Artist
  • Artists

      +
    • + Show Track Info... +
    • +
      +
      +

      Add a Track to the Queue + + + +

      + + +
      +
      +
      +

      Save Current Queue to a Playlist + onkeypress="return controls.checkDefaultButtonClick(event.keyCode, '#popupSave');" type="text"/>

      -
      -
      - +
      @@ -214,9 +251,22 @@

      Artists

      +
      + Close + + + + + + + + +
      +
      + @@ -284,10 +334,12 @@

      System

      - Album cover +
      + Album cover +
      -

      +

      -

      @@ -325,7 +377,16 @@

      Playlists

      -

      Browse

      +
      +
      +

      Browse

      +
      +
      + +
      +
        @@ -334,16 +395,21 @@

        Browse

        -
        +

        Play Queue

        -
        - - +
        +
        + + + +
        @@ -421,7 +487,7 @@

        Streams

        Play a specific stream/track and optionally save it to your favourites. - Add Track Below \'' + trackName + '\'') + } + if (typeof songdata.track.uri !== 'undefined' && songdata.track.uri !== '') { + $('#getPlayingBtn').button('enable') + } else { + $('#getPlayingBtn').button('disable') + } + + $('#select-add').append('') // PLAY_NEXT + $('#select-add').append('') // ADD_THIS_BOTTOM + $('#select-add').trigger('change') + + $('#popupQueue').popup('close') + $('#popupAddTrack').popup('open') + }, + + addTrack: function (trackUri, mopidy) { + var selection = parseInt($('#select-add').val()) + + if (selection === ADD_THIS_BOTTOM) { + controls.addTrackToBottom(trackUri, mopidy) + } else if (selection === PLAY_NEXT) { + controls.insertTrack(trackUri, mopidy) + } else if (selection === INSERT_AT_INDEX) { + var tlid = $('#popupAddTrack').data('tlid') + controls.insertTrack(trackUri, mopidy, tlid) + } else { + throw new Error('Unkown tracklist action selection option: ' + selection) + } + }, + + insertTrack: function (trackUri, mopidy, tlid) { + if (typeof trackUri === 'undefined' || trackUri === '') { + throw new Error('No track URI provided to insert.') + } + + if (typeof tlid !== 'undefined' && tlid !== '') { + mopidy.tracklist.index({tlid: parseInt(tlid)}).then(function (index) { + controls.playTracks(INSERT_AT_INDEX, mopidy, trackUri, CURRENT_PLAYLIST_TABLE, index) + }) + } else { + // No tlid provided, insert after current track. + controls.playTracks(PLAY_NEXT, mopidy, trackUri, CURRENT_PLAYLIST_TABLE) + } + $('#popupAddTrack').popup('close') + return false + }, + + addTrackToBottom: function (trackUri, mopidy) { + if (typeof trackUri === 'undefined' || trackUri === '') { + throw new Error('No track URI provided to add.') + } + + controls.playTracks(ADD_THIS_BOTTOM, mopidy, trackUri, CURRENT_PLAYLIST_TABLE) + $('#popupAddTrack').popup('close') + return false + }, + showSavePopup: function () { mopidy.tracklist.getTracks().then(function (tracks) { if (tracks.length > 0) { @@ -238,6 +338,134 @@ }) }, + showInfoPopup: function (uri, popupId, mopidy) { + showLoading(true) + var trackUri = uri || $(popupId).data('track') + if (popupId && popupId.length > 0) { + $(popupId).popup('close') + } + $('#popupShowInfo tbody').empty() + + mopidy.library.lookup({'uris': [trackUri]}).then(function (resultDict) { + var uri = Object.keys(resultDict)[0] + var track = resultDict[uri][0] + var html = '' + var rowTemplate = '{label}:{text}' + var row = {'label': '', 'text': ''} + + row.label = 'Name' + if (track.name) { + row.text = track.name + } else { + row.text = '(Not available)' + } + html += stringFromTemplate(rowTemplate, row) + + row.label = 'Album' + if (track.album && track.album.name) { + row.text = track.album.name + } else { + row.text = '(Not available)' + } + html += stringFromTemplate(rowTemplate, row) + + var artists = artistsToString(track.artists) + // Fallback to album artists. + if (artists.length === 0 && track.album && track.album.artists) { + artists = artistsToString(track.album.artists) + } + + if (artists.length > 0) { + row.label = 'Artist' + if (track.artists && track.artists.length > 1 || track.album && track.album.artists && track.album.artists.length > 1) { + row.label += 's' + } + row.text = artists + html += stringFromTemplate(rowTemplate, row) + } + + var composers = artistsToString(track.composers) + if (composers.length > 0) { + row.label = 'Composer' + if (track.composers.length > 1) { + row.label += 's' + } + row.text = composers + html += stringFromTemplate(rowTemplate, row) + } + + var performers = artistsToString(track.performers) + if (performers.length > 0) { + row.label = 'Performer' + if (track.performers.length > 1) { + row.label += 's' + } + row.text = performers + html += stringFromTemplate(rowTemplate, row) + } + + if (track.genre) { + row = {'label': 'Genre', 'text': track.genre} + html += stringFromTemplate(rowTemplate, row) + } + + if (track.track_no) { + row = {'label': 'Track #', 'text': track.track_no} + html += stringFromTemplate(rowTemplate, row) + } + + if (track.disc_no) { + row = {'label': 'Disc #', 'text': track.disc_no} + html += stringFromTemplate(rowTemplate, row) + } + + if (track.date) { + row = {'label': 'Date', 'text': new Date(track.date).toLocaleString()} + html += stringFromTemplate(rowTemplate, row) + } + + if (track.length) { + row = {'label': 'Length', 'text': timeFromSeconds(track.length / 1000)} + html += stringFromTemplate(rowTemplate, row) + } + + if (track.bitrate) { + row = {'label': 'Bitrate', 'text': track.bitrate} + html += stringFromTemplate(rowTemplate, row) + } + + if (track.comment) { + row = {'label': 'Comment', 'text': track.comment} + html += stringFromTemplate(rowTemplate, row) + } + + if (track.musicbrainz_id) { + row = {'label': 'MusicBrainz ID', 'text': track.musicbrainz_id} + html += stringFromTemplate(rowTemplate, row) + } + + if (track.last_modified) { + row = {'label': 'Modified', 'text': track.last_modified} + html += stringFromTemplate(rowTemplate, row) + } + + rowTemplate = '{label}:' + row = {'label': 'URI', 'text': uri} + html += stringFromTemplate(rowTemplate, row) + + $('#popupShowInfo tbody').append(html) + + showLoading(false) + $('#popupShowInfo').popup('open') + if (!isMobile) { + // Set focus and select URI text on desktop systems (don't want the keyboard to pop up automatically on mobile devices) + $('#popupShowInfo #uri-input').focus() + $('#popupShowInfo #uri-input').select() + } + }, console.error) + return false + }, + refreshPlaylists: function () { mopidy.playlists.refresh().then(function () { playlists = {} @@ -247,6 +475,14 @@ return false }, + refreshLibrary: function () { + var uri = $('#refreshLibraryBtn').data('url') + mopidy.library.refresh({'uri': uri}).then(function () { + library.getBrowseDir(uri) + }) + return false + }, + /** *********** * Buttons * *************/ @@ -448,8 +684,8 @@ return false }, - getCurrentlyPlaying: function () { - $('#streamuriinput').val(songdata.track.uri) + getCurrentlyPlaying: function (uriInput, nameInput) { + $('#' + uriInput).val(songdata.track.uri) var name = songdata.track.name if (songdata.track.artists) { var artistStr = artistsToString(songdata.track.artists) @@ -457,7 +693,7 @@ name = artistStr + ' - ' + name } } - $('#streamnameinput').val(name) + $('#' + nameInput).val(name) return true }, @@ -652,7 +888,6 @@ window.history.back() }, 10000) } - } return controls })) diff --git a/mopidy_musicbox_webclient/static/js/custom_scripting.js b/mopidy_musicbox_webclient/static/js/custom_scripting.js index f8b6ca15..bcb97614 100644 --- a/mopidy_musicbox_webclient/static/js/custom_scripting.js +++ b/mopidy_musicbox_webclient/static/js/custom_scripting.js @@ -20,6 +20,17 @@ $(document).bind('mobileinit', configureJQueryMobile) + // Extension: timeout to detect end of scrolling action. + $.fn.scrollEnd = function (callback, timeout) { + $(this).scroll(function () { + var $this = $(this) + if ($this.data('scrollTimeout')) { + clearTimeout($this.data('scrollTimeout')) + } + $this.data('scrollTimeout', setTimeout(callback, timeout)) + }) + } + return configureJQueryMobile })) diff --git a/mopidy_musicbox_webclient/static/js/functionsvars.js b/mopidy_musicbox_webclient/static/js/functionsvars.js index a2d7dfcb..21d57a72 100644 --- a/mopidy_musicbox_webclient/static/js/functionsvars.js +++ b/mopidy_musicbox_webclient/static/js/functionsvars.js @@ -28,8 +28,7 @@ var artiststext = '' var songname = '' var songdata = {'track': {}, 'tlid': -1} -var playlisttracksScroll -var playlistslistScroll +var pageScrollPos = {} var STREAMS_PLAYLIST_NAME = '[Radio Streams]' var STREAMS_PLAYLIST_SCHEME = 'm3u' @@ -42,14 +41,13 @@ var customTracklists = [] // TODO: Refactor into one shared cache var browseStack = [] -var ua = navigator.userAgent +var ua = navigator.userAgent || navigator.vendor || window.opera var isMobileSafari = /Mac/.test(ua) && /Mobile/.test(ua) -var isMobileWebkit = /WebKit/.test(ua) && /Mobile/.test(ua) -var isMobile = /Mobile/.test(ua) -var isWebkit = /WebKit/.test(ua) +var isMobile = isMobileAll() // constants -PROGRAM_NAME = 'MusicBox' +PROGRAM_NAME = $(document.body).data('program-name') +HOSTNAME = $(document.body).data('hostname') ARTIST_TABLE = '#artiststable' ALBUM_TABLE = '#albumstable' BROWSE_TABLE = '#browsetable' @@ -68,6 +66,7 @@ ADD_THIS_BOTTOM = 2 ADD_ALL_BOTTOM = 3 PLAY_ALL = 4 DYNAMIC = 5 +INSERT_AT_INDEX = 6 // the first part of Mopidy extensions which serve radio streams var radioExtensionsList = ['somafm', 'tunein', 'dirble', 'audioaddict'] @@ -75,8 +74,9 @@ var radioExtensionsList = ['somafm', 'tunein', 'dirble', 'audioaddict'] var uriClassList = [ ['spotify', 'fa-spotify'], ['spotifytunigo', 'fa-spotify'], + ['spotifyweb', 'fa-spotify'], ['local', 'fa-file-sound-o'], - ['file', 'fa-folder-o'], + ['file', 'fa-file-sound-o'], ['m3u', 'fa-file-sound-o'], ['podcast', 'fa-rss-square'], ['podcast+file', 'fa-rss-square'], @@ -102,7 +102,8 @@ var uriClassList = [ var uriHumanList = [ ['spotify', 'Spotify'], ['spotifytunigo', 'Spotify browse'], - ['local', 'Local files'], + ['spotifyweb', 'Spotify browse'], + ['local', 'Local media'], ['m3u', 'Local playlists'], ['podcast', 'Podcasts'], ['podcast+itunes', 'iTunes Store: Podcasts'], @@ -130,10 +131,37 @@ var searchBlacklist = [ 'yt' ] +// List of known audio file extensions +// TODO: consider querying GStreamer for supported audio formats - see:https://discuss.mopidy.com/t/supported-codecs-file-formats/473 +var audioExt = [ + 'aa', 'aax', // Audible.com + 'aac', // Advanced Audio Coding format + 'aiff', // Apple + 'au', // Sun Microsystems + 'flac', // Free Lossless Audio Codec + 'gsm', + 'iklax', + 'ivs', + 'm4a', + 'm4b', + 'm4p', + 'mp3', + 'mpc', // Musepack + 'ogg', 'oga', 'mogg', // Ogg-Vorbis + 'opus', // Internet Engineering Task Force (IETF) + 'ra', 'rm', // RealAudio + 'raw', + 'tta', // True Audio + 'vox', + 'wav', + 'wma', // Microsoft + 'wv', + 'webm' // HTML5 video +] + function scrollToTop () { - var divtop = 0 $('body,html').animate({ - scrollTop: divtop + scrollTop: 0 }, 250) } @@ -144,6 +172,14 @@ function scrollToTracklist () { }, 250) } +function isMobileAll () { + // Checks for known mobile and tablet devices - see http://stackoverflow.com/questions/11381673/detecting-a-mobile-browser + var regexpMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i + var regexpTablet = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i + var uaString = ua.substr(0, 4) + return isMobileSafari || regexpMobile.test(uaString) || regexpTablet.test(uaString) +} + // A hack to find the name of the first artist of a playlist. this is not yet returned by mopidy // does not work wel with multiple artists of course function getArtist (pl) { @@ -168,12 +204,14 @@ function getAlbum (pl) { function artistsToString (artists, max) { var result = '' max = max || 3 - for (var i = 0; i < artists.length && i < max; i++) { - if (artists[i].name) { - if (i > 0) { - result += ', ' + if (artists && artists.length > 0) { + for (var i = 0; i < artists.length && i < max; i++) { + if (artists[i].name) { + if (i > 0) { + result += ', ' + } + result += artists[i].name } - result += artists[i].name } } return result @@ -204,13 +242,9 @@ function renderSongLi (previousTrack, track, nextTrack, uri, tlid, target, curre var onClick = '' var html = '' track.name = validateTrackName(track, currentIndex) - // Leave out unplayable items - if (track.name.substring(0, 12) === '[unplayable]') { - return html - } // Streams if (track.length === -1) { - html += '

      • ' + track.name + ' [Stream]

      • ' + html += '
      • ' + track.name + ' [Stream]

      • ' return html } @@ -221,11 +255,13 @@ function renderSongLi (previousTrack, track, nextTrack, uri, tlid, target, curre onClick = 'return controls.playTracks(\'\', mopidy, \'' + track.uri + '\', \'' + uri + '\');' } - html += - '
      • ' + - '' + - '' + - '

        ' + track.name + '

        ' + html += '
      • ' + if (isPlayable(track)) { + // Show popup icon for audio files or 'tracks' of other scheme types + html += '' + + '' + } + html += '

        ' + track.name + '

        ' if (listLength === 1 || (!hasSameAlbum(previousTrack, track) && !hasSameAlbum(track, nextTrack))) { html += renderSongLiAlbumInfo(track) @@ -271,9 +307,9 @@ function renderSongLiDivider (previousTrack, track, nextTrack, target) { if (!hasSameAlbum(previousTrack, track) && hasSameAlbum(track, nextTrack)) { // Large divider with album cover. html += - '
      • ' + + '
      • ' + '' + - '

        ' + track.album.name + '

        ' + + '

        ' + track.album.name + '

        ' + renderSongLiTrackArtists(track) + '

      • ' // Retrieve album covers images.setAlbumImage(track.uri, getjQueryID(target + '-cover', track.uri, true), mopidy, 'small') @@ -281,7 +317,7 @@ function renderSongLiDivider (previousTrack, track, nextTrack, target) { // Small divider html += '
      •  
      • ' } - if (typeof target !== 'undefined' && target.length > 0) { + if (html.length > 0 && typeof target !== 'undefined' && target.length > 0) { target = getjQueryID(target, track.uri, true) $(target).before(html) } @@ -357,40 +393,6 @@ function resultsToTables (results, target, uri, onClickBack, backIsOptional) { updatePlayIcons(songdata.track.uri, songdata.tlid, controls.getIconForAction()) } -// process updated playlist to gui -function playlisttotable (pl, target, uri) { - var tmp = '' - $(target).html('') - var targetmin = target.substr(1) - var child = '' - for (var i = 0; i < pl.length; i++) { - if (pl[i]) { - popupData[pl[i].uri] = pl[i] - child = '
      • ' - child += '

        ' + pl[i].name + 'h1>' - child += '

        ' - child += '' + timeFromSeconds(pl[i].length / 1000) + '' - for (var j = 0; j < pl[i].artists.length; j++) { - if (pl[i].artists[j]) { - child += pl[i].artists[j].name - child += (j === pl[i].artists.length - 1) ? '' : ' / ' - // stop after 3 - if (j > 2) { - child += '...' - break - } - } - } - child += ' / ' + pl[i].album.name + '

        ' - child += '

      • ' - tmp += child - } - } - - $(target).html(tmp) - $(target).attr('data', uri) -} - function getPlaylistTracks (uri) { if (playlists[uri] && playlists[uri].tracks) { return Mopidy.when(playlists[uri].tracks) @@ -456,7 +458,7 @@ function showLoading (on) { if (on) { $('body').css('cursor', 'progress') $.mobile.loading('show', { - text: 'Loading data from ' + PROGRAM_NAME + '. Please wait...', + text: 'Loading data from ' + PROGRAM_NAME + ' on ' + HOSTNAME + '. Please wait...', textVisible: true, theme: 'a' }) @@ -469,7 +471,7 @@ function showLoading (on) { function showOffline (on) { if (on) { $.mobile.loading('show', { - text: 'Trying to reach ' + PROGRAM_NAME + '. Please wait...', + text: 'Trying to reach ' + PROGRAM_NAME + ' on ' + HOSTNAME + '. Please wait...', textVisible: true, theme: 'a' }) @@ -479,9 +481,9 @@ function showOffline (on) { } // from http://dzone.com/snippets/validate-url-regexp -function validUri (str) { +function validUri (uri) { var regexp = /^(mms|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/ - return regexp.test(str) + return regexp.test(uri) } function validServiceUri (str) { @@ -492,18 +494,52 @@ function getScheme (uri) { return uri.split(':')[0].toLowerCase() } +function isPlayable (track) { + if (typeof track.type === 'undefined' || track.type === 'track') { + if (track.uri && getScheme(track.uri) === 'file') { + var ext = track.uri.split('.').pop().toLowerCase() + if ($.inArray(ext, audioExt) === -1) { + // Files must have the correct extension + return false + } + } + return true + } + return false +} + function isStreamUri (uri) { - var a = validUri(uri) - var b = radioExtensionsList.indexOf(getScheme(uri)) >= 0 - return a || b + return validUri(uri) || radioExtensionsList.indexOf(getScheme(uri)) >= 0 } -function getMediaClass (uri) { - var scheme = getScheme(uri) - for (var i = 0; i < uriClassList.length; i++) { - if (scheme === uriClassList[i][0]) { - return 'fa ' + uriClassList[i][1] +function getMediaClass (track) { + var defaultIcon = 'fa-file-sound-o' + var type = track.type + if (typeof type === 'undefined' || type === 'track') { + if (!isPlayable(track)) { + return 'fa fa-file-o' // Unplayable file + } else if (isStreamUri(track.uri)) { + return 'fa fa-rss' // Stream + } + } else if (type === 'directory') { + return 'fa fa-folder-o' + } else if (type === 'album') { + // return 'fa fa-bullseye' // Album + defaultIcon = 'fa-folder-o' + } else if (type === 'artist') { + // return 'fa fa-user-circle-o' // Artist + defaultIcon = 'fa-folder-o' + } else if (type === 'playlist') { + // return 'fa fa-star' // Playlist + } + if (track.uri) { + var scheme = getScheme(track.uri) + for (var i = 0; i < uriClassList.length; i++) { + if (scheme === uriClassList[i][0]) { + return 'fa ' + uriClassList[i][1] + } } + return 'fa ' + defaultIcon } return '' } @@ -544,6 +580,13 @@ function isSpotifyStarredPlaylist (playlist) { return (starredRegex.test(playlist.uri) && playlist.name === 'Starred') } +// Returns a string where {x} in template is replaced by tokens[x]. +function stringFromTemplate (template, tokens) { + return template.replace(/{[^}]+}/g, function (match) { + return tokens[match.slice(1, -1)] + }) +} + /** * Converts a URI to a jQuery-safe identifier. jQuery identifiers need to be * unique per page and cannot contain special characters. diff --git a/mopidy_musicbox_webclient/static/js/gui.js b/mopidy_musicbox_webclient/static/js/gui.js index fea794c6..ee1d2422 100644 --- a/mopidy_musicbox_webclient/static/js/gui.js +++ b/mopidy_musicbox_webclient/static/js/gui.js @@ -20,63 +20,30 @@ function resetSong () { } function resizeMb () { + if ($(window).width() < 880) { + $('#panel').panel('close') + } else { + $('#panel').panel('open') + } + $('#infoname').html(songdata.track.name) $('#infoartist').html(artiststext) - if ($(window).width() <= 960) { -// $('#playlisttracksdiv').hide(); -// $('#playlistslistdiv').show(); - } else { + if ($(window).width() > 960) { $('#playlisttracksdiv').show() $('#playlistslistdiv').show() } -// //set height of playlist scrollers -/* if ($(window).width() > 960) { - $('#playlisttracksdiv').show(); - $('#playlistslistdiv').show(); - $('.scroll').removeClass('height').removeClass('width'); - $('#playlistspane').removeClass('height').removeClass('width'); - } else { - if ( $('#playlisttracksdiv').is(':visible') == $('#playlistslistdiv').is(':visible')) { - $('#playlisttracksdiv').hide(); - $('#playlistslistdiv').show(); - $('.scroll').addClass('height', '99%').addClass('width', '99%'); - $('#playlistspane').addClass('height', '99%').addClass('width', '99%'); - } - } - - if ($('#playlisttracksdiv').is(':visible') && !$('#playlisttracksback').is(':visible') ) { - $('.scroll').height($(window).height() - 96); - //jqm added something which it shouldnt (at least in this case) I guess - // $('#playlistspane').removeClass('height').height($(window).height() - 110); - $('.scroll').removeClass('height').removeClass('width'); - $('#playlistspane').removeClass('height').removeClass('width'); - $('#playlisttracksdiv').show(); - $('#playlistslistdiv').show(); - } else { - $('.scroll').addClass('height', '99%').addClass('width', '99%'); - $('#playlistspane').addClass('height', '99%').addClass('width', '99%'); - $('#playlisttracksdiv').show(); - $('#playlistslistdiv').show(); - } - - if (isMobileWebkit && ($(window).width() > 480)) { - playlistslistScroll.refresh(); - playlisttracksScroll.refresh(); - } -*/ } -function setSongTitle (title, refresh_ui) { - songdata.track.name = title - $('#modalname').html(title) +function setSongTitle (track, refresh_ui) { + songdata.track.name = track.name + $('#modalname').html('' + track.name + '') if (refresh_ui) { resizeMb() } } function setSongInfo (data) { -// console.log(data, songdata); if (!data) { return } if (data.tlid === songdata.tlid) { return } if (!data.track.name || data.track.name === '') { @@ -99,7 +66,7 @@ function setSongInfo (data) { songdata = data - setSongTitle(data.track.name, false) + setSongTitle(data.track, false) songlength = Infinity if (!data.track.length || data.track.length === 0) { @@ -117,7 +84,7 @@ function setSongInfo (data) { if (data.track.artists) { for (var j = 0; j < data.track.artists.length; j++) { - artistshtml += '' + data.track.artists[j].name + '' + artistshtml += '' + data.track.artists[j].name + '' artiststext += data.track.artists[j].name if (j !== data.track.artists.length - 1) { artistshtml += ', ' @@ -127,11 +94,17 @@ function setSongInfo (data) { arttmp = artistshtml } if (data.track.album && data.track.album.name) { - $('#modalalbum').html('' + data.track.album.name + '') + $('#modalalbum').html('' + data.track.album.name + '') } else { $('#modalalbum').html('') } images.setAlbumImage(data.track.uri, '#infocover, #albumCoverImg', mopidy) + if (data.track.uri) { + // Add 'Show Info' icon to album image + $('#modalinfo').append( + '' + + '') + } $('#modalartist').html(arttmp) @@ -148,19 +121,12 @@ function setSongInfo (data) { /** **************** * display popups * ******************/ -function closePopups () { - $('#popupTracks').popup('close') - $('#artistpopup').popup('close') - $('#coverpopup').popup('close') - $('#popupQueue').popup('close') -} - function popupTracks (e, listuri, trackuri, tlid) { if (!e) { e = window.event } $('.popupTrackName').html(popupData[trackuri].name) - if (popupData[trackuri].album && popupData[trackuri].album.name) { + if (popupData[trackuri].album && popupData[trackuri].album.name && popupData[trackuri].album.uri) { $('.popupAlbumName').html(popupData[trackuri].album.name) $('.popupAlbumLi').show() } else { @@ -168,39 +134,42 @@ function popupTracks (e, listuri, trackuri, tlid) { } var child = '' + $('.popupArtistsLi').hide() + $('.popupArtistsDiv').hide() if (popupData[trackuri].artists) { - if (popupData[trackuri].artists.length === 1) { - child = 'Show Artist' + if (popupData[trackuri].artists.length === 1 && popupData[trackuri].artists[0].uri) { + child = 'Show Artist' $('.popupArtistName').html(popupData[trackuri].artists[0].name) - $('.popupArtistHref').attr('onclick', 'library.showArtist("' + popupData[trackuri].artists[0].uri + '");') + $('.popupArtistHref').attr('onclick', 'library.showArtist(\'' + popupData[trackuri].artists[0].uri + '\', mopidy);') $('.popupArtistsDiv').hide() $('.popupArtistsLi').show() } else { + var isValidArtistURI = false for (var j = 0; j < popupData[trackuri].artists.length; j++) { - child += '
      • ' + popupData[trackuri].artists[j].name + '
      • ' + if (popupData[trackuri].artists[j].uri) { + isValidArtistURI = true + child += '
      • ' + popupData[trackuri].artists[j].name + '
      • ' + } + } + if (isValidArtistURI) { + $('.popupArtistsLv').html(child).show() + $('.popupArtistsDiv').show() + // this makes the viewport of the window resize somehow + $('.popupArtistsLv').listview('refresh') } - $('.popupArtistsLi').hide() - $('.popupArtistsLv').html(child).show() - $('.popupArtistsDiv').show() - // this makes the viewport of the window resize somehow - $('.popupArtistsLv').listview('refresh') } - } else { - $('.popupArtistsDiv').hide() - $('.popupArtistsLi').hide() } var hash = document.location.hash.split('?') var divid = hash[0].substr(1) var popupName = '' if (divid === 'current') { - $('.addqueue').hide() popupName = '#popupQueue' } else { - $('.addqueue').show() popupName = '#popupTracks' } + // Set playlist, trackUri, and tlid of clicked item. if (typeof tlid !== 'undefined' && tlid !== '') { $(popupName).data('list', listuri).data('track', trackuri).data('tlid', tlid).popup('open', { x: e.pageX, @@ -213,12 +182,17 @@ function popupTracks (e, listuri, trackuri, tlid) { }) } + $(popupName).one('popupafterclose', function (event, ui) { + // Ensure that popup attributes are reset when the popup is closed. + $(this).removeData('list').removeData('track').removeData('tlid') + }) + return false } function showAlbumPopup (popupId) { uri = $(popupId).data('track') - library.showAlbum(popupData[uri].album.uri) + library.showAlbum(popupData[uri].album.uri, mopidy) } /** ******************** @@ -252,6 +226,11 @@ function initSocketevents () { controls.setPlayState(true) }) + mopidy.on('event:trackPlaybackResumed', function (data) { + setSongInfo(data.tl_track) + controls.setPlayState(true) + }) + mopidy.on('event:playlistsLoaded', function (data) { showLoading(true) library.getPlaylists() @@ -293,6 +272,12 @@ function initSocketevents () { mopidy.on('event:tracklistChanged', function (data) { library.getCurrentPlaylist() + mopidy.tracklist.getTracks().then(function (tracks) { + if (tracks.length === 0) { + // Last track in queue was deleted, reset UI. + resetSong() + } + }) }) mopidy.on('event:seeked', function (data) { @@ -303,14 +288,11 @@ function initSocketevents () { }) mopidy.on('event:streamTitleChanged', function (data) { - setSongTitle(data.title, true) + // Update all track info. + mopidy.playback.getCurrentTlTrack().then(processCurrenttrack, console.error) }) } -$(document).bind('pageinit', function () { - resizeMb() -}) - /** ************ * gui stuff * **************/ @@ -387,66 +369,58 @@ function locationHashChanged () { var hash = document.location.hash.split('?') // remove # var divid = hash[0].substr(1) + var uri = hash[1] + setHeadline(divid) - var uri = hash[1] - $('.mainNav a').removeClass('ui-state-active ui-state-persist ui-btn-active') - // i don't know why some li elements have those classes, but they do, so we need to remove them - $('.mainNav li').removeClass('ui-state-active ui-state-persist ui-btn-active') - if ($(window).width() < 560) { + if ($(window).width() < 880) { $('#panel').panel('close') } - $('.pane').hide() - $('#' + divid + 'pane').show() + $('.mainNav a').removeClass($.mobile.activeBtnClass) + // i don't know why some li elements have those classes, but they do, so we need to remove them + $('.mainNav li').removeClass($.mobile.activeBtnClass) + $('#nav' + divid + ' a').addClass($.mobile.activeBtnClass) // Update navigation pane + + $('.pane').hide() // Hide all pages + $('#' + divid + 'pane').show() // Switch to active pane + + if (divid === 'browse' && browseStack.length > 0) { + window.scrollTo(0, browseStack[browseStack.length - 1].scrollPos || 0) // Restore scroll position - browsing library. + } else if (typeof pageScrollPos[divid] !== 'undefined') { // Restore scroll position - pages + window.scrollTo(0, pageScrollPos[divid]) + } switch (divid) { - case 'home': - $('#navhome a').addClass('ui-state-active ui-state-persist ui-btn-active') - break - case 'nowPlaying': - $('#navnowPlaying a').addClass('ui-state-active ui-state-persist ui-btn-active') - break - case 'current': - $('#navcurrent a').addClass('ui-state-active ui-state-persist ui-btn-active') - break - case 'playlists': - $('#navplaylists a').addClass('ui-state-active ui-state-persist ui-btn-active') - break - case 'browse': - $('#navbrowse a').addClass('ui-state-active ui-state-persist ui-btn-active') + case 'nowPlaying': // Show 'now playing' footer + $('#normalFooter').hide() + $('#nowPlayingFooter').show() break case 'search': - $('#navsearch a').addClass($.mobile.activeBtnClass) $('#searchinput').focus() break - case 'stream': - $('#navstream a').addClass('ui-state-active ui-state-persist ui-btn-active') - break case 'artists': if (uri !== '') { - library.showArtist(uri) + if (mopidy) { + library.showArtist(uri, mopidy) + } else { + showOffline(true) // Page refreshed - wait for mopidy object to be initialized. + } } break case 'albums': if (uri !== '') { - library.showAlbum(uri) + if (mopidy) { + library.showAlbum(uri, mopidy) + } else { + showOffline(true) // Page refreshed - wait for mopidy object to be initialized. + } } break - } - - // switch the footer - switch (divid) { - case 'nowPlaying': - $('#normalFooter').hide() - $('#nowPlayingFooter').show() - break - default: + default: // Default footer $('#normalFooter').show() $('#nowPlayingFooter').hide() } - // Set the page title based on the hash. - document.title = PROGRAM_NAME return false } @@ -454,6 +428,7 @@ function locationHashChanged () { * initialize software * ***********************/ $(document).ready(function (event) { + showOffline(true) // check for websockets if (!window.WebSocket) { switchContent('playlists') @@ -465,40 +440,23 @@ $(document).ready(function (event) { $('.ui-panel-dismiss').on('tap', function () { $('#panel').panel('close') }) // end of workaround - $(window).hashchange() - - // Connect to server - var websocketUrl = $(document.body).data('websocket-url') - if (websocketUrl) { - try { - mopidy = new Mopidy({ - webSocketUrl: websocketUrl, - callingConvention: 'by-position-or-by-name' - }) - } catch (e) { - showOffline(true) - } - } else { - try { - mopidy = new Mopidy({callingConvention: 'by-position-or-by-name'}) - } catch (e) { - showOffline(true) - } - } - - // initialize events - initSocketevents() - - syncedProgressTimer = new SyncedProgressTimer(8, mopidy) - - resetSong() - + window.onhashchange = locationHashChanged if (location.hash.length < 2) { switchContent('home') } + $(window).hashchange() + + // Remember scroll position for each page and browsed folder + $(window).scrollEnd(function () { + var divid = document.location.hash.split('?')[0].substr(1) + if (divid === 'browse' && browseStack.length > 0) { + browseStack[browseStack.length - 1].scrollPos = window.pageYOffset + } else { + pageScrollPos[divid] = window.pageYOffset + } + }, 250) initgui = false - window.onhashchange = locationHashChanged // only show backbutton if in UIWebview if (window.navigator.standalone) { @@ -507,10 +465,6 @@ $(document).ready(function (event) { $('#btback').hide() } - $(window).resize(function () { - resizeMb() - }) - // navigation temporary, rewrite this! $('#songinfo').click(function () { return switchContent('nowPlaying') @@ -571,12 +525,6 @@ $(document).ready(function (event) { } }) - if ($(window).width() < 980) { - $('#panel').panel('close') - } else { - $('#panel').panel('open') - } - $.event.special.swipe.horizontalDistanceThreshold = 125 // (default: 30px) Swipe horizontal displacement must be more than this. $.event.special.swipe.verticalDistanceThreshold = 50 // (default: 75px) Swipe vertical displacement must be less than this. $.event.special.swipe.durationThreshold = 500 @@ -611,6 +559,21 @@ $(document).ready(function (event) { $('#volumeslider').on('slidestart', function () { volumeSliding = true }) $('#volumeslider').on('slidestop', function () { volumeSliding = false }) $('#volumeslider').on('change', function () { controls.doVolume($(this).val()) }) + + $(window).resize(resizeMb).resize() + + // Connect to server + var websocketUrl = $(document.body).data('websocket-url') + var connectOptions = {callingConvention: 'by-position-or-by-name'} + if (websocketUrl) { + connectOptions['webSocketUrl'] = websocketUrl + } + + mopidy = new Mopidy(connectOptions) + // initialize events + initSocketevents() + syncedProgressTimer = new SyncedProgressTimer(8, mopidy) + resetSong() }) function updatePlayIcons (uri, tlid, popupMenuIcon) { diff --git a/mopidy_musicbox_webclient/static/js/library.js b/mopidy_musicbox_webclient/static/js/library.js index 682174f8..2a27753b 100644 --- a/mopidy_musicbox_webclient/static/js/library.js +++ b/mopidy_musicbox_webclient/static/js/library.js @@ -126,35 +126,28 @@ $('#searchtracks').show() } - // Returns a string where {x} in template is replaced by tokens[x]. - function theme (template, tokens) { - return template.replace(/{[^}]+}/g, function (match) { - return tokens[match.slice(1, -1)] - }) - } - - // 'Show more' pattern - var showMorePattern = '
      • Show {count} more
      • ' + // 'Show more' template + var showMoreTemplate = '
      • Show {count} more
      • ' // Artist results var child = '' - var pattern = '
      • {name}
      • ' + var template = '
      • {name}
      • ' var tokens for (i = 0; i < results.artists.length; i++) { tokens = { 'id': results.artists[i].uri, 'name': results.artists[i].name, - 'class': getMediaClass(results.artists[i].uri) + 'class': getMediaClass(results.artists[i]) } // Add 'Show all' item after a certain number of hits. if (i === 4 && results.artists.length > 5) { - child += theme(showMorePattern, {'count': results.artists.length - i}) - pattern = pattern.replace('
      • ', '
      • ') + child += stringFromTemplate(showMoreTemplate, {'count': results.artists.length - i}) + template = template.replace('
      • ', '
      • ') } - child += theme(pattern, tokens) + child += stringFromTemplate(template, tokens) } // Inject list items, refresh listview and hide superfluous items. @@ -162,10 +155,10 @@ // Album results child = '' - pattern = '
      • ' - pattern += '
        {albumName}
        ' - pattern += '

        {artistName}

        ' - pattern += '
      • ' + template = '
      • ' + template += '
        {albumName}
        ' + template += '

        {artistName}

        ' + template += '
      • ' for (i = 0; i < results.albums.length; i++) { tokens = { @@ -173,7 +166,7 @@ 'albumName': results.albums[i].name, 'artistName': '', 'albumYear': results.albums[i].date, - 'class': getMediaClass(results.albums[i].uri) + 'class': getMediaClass(results.albums[i]) } if (results.albums[i].artists) { for (j = 0; j < results.albums[i].artists.length; j++) { @@ -187,11 +180,11 @@ } // Add 'Show all' item after a certain number of hits. if (i === 4 && results.albums.length > 5) { - child += theme(showMorePattern, {'count': results.albums.length - i}) - pattern = pattern.replace('
      • ', '
      • ') + child += stringFromTemplate(showMoreTemplate, {'count': results.albums.length - i}) + template = template.replace('
      • ', '
      • ') } - child += theme(pattern, tokens) + child += stringFromTemplate(template, tokens) } // Inject list items, refresh listview and hide superfluous items. $(SEARCH_ALBUM_TABLE).html(child).listview('refresh').find('.overflow').hide() @@ -215,14 +208,25 @@ showLoading(true) if (!rootdir) { browseStack.pop() - rootdir = browseStack[browseStack.length - 1] - } else { - browseStack.push(rootdir) - } - if (!rootdir) { - rootdir = null + if (browseStack.length > 0) { + rootdir = browseStack[browseStack.length - 1].uri // Navigated one level up + } else { + rootdir = null // Navigated to top of library + } + } else if (browseStack.length === 0 || rootdir !== browseStack[browseStack.length - 1].uri) { + browseStack.push({'uri': rootdir, 'scrollPos': 0}) // Navigated one level down } - mopidy.library.browse({'uri': rootdir}).then(processBrowseDir, console.error) + mopidy.library.browse({'uri': rootdir}).then(function (resultArr) { + processBrowseDir(resultArr) + if (rootdir === null) { + $('.refreshLibraryBtnDiv').hide() // Mopidy does not support refreshing list of backends. + } else { + $('.refreshLibraryBtnDiv').show() + $('#refreshLibraryBtn').data('url', rootdir) + $('#refreshLibraryBtn').off('click') + $('#refreshLibraryBtn').one('click', controls.refreshLibrary) + } + }, console.error) }, getCurrentPlaylist: function () { @@ -265,7 +269,7 @@ return false }, - showArtist: function (nwuri) { + showArtist: function (nwuri, mopidy) { $('#popupQueue').popup('close') $('#popupTracks').popup('close') $('#controlsmodal').popup('close') @@ -284,7 +288,7 @@ return false }, - showAlbum: function (uri) { + showAlbum: function (uri, mopidy) { $('#popupQueue').popup('close') $('#popupTracks').popup('close') $('#controlsmodal').popup('close') diff --git a/mopidy_musicbox_webclient/static/js/process_ws.js b/mopidy_musicbox_webclient/static/js/process_ws.js index a6bfe406..a0f7e668 100644 --- a/mopidy_musicbox_webclient/static/js/process_ws.js +++ b/mopidy_musicbox_webclient/static/js/process_ws.js @@ -90,8 +90,10 @@ function processBrowseDir (resultArr) { var length = 0 || resultArr.length customTracklists[BROWSE_TABLE] = [] var html = '' + var i - for (var i = 0, index = 0; i < resultArr.length; i++) { + // Render list of tracks + for (i = 0, index = 0; i < resultArr.length; i++) { if (resultArr[i].type === 'track') { previousRef = ref || undefined nextRef = i < resultArr.length - 1 ? resultArr[i + 1] : undefined @@ -105,41 +107,39 @@ function processBrowseDir (resultArr) { index++ } else { - var iconClass = '' - if (browseStack.length > 0) { - iconClass = 'fa fa-folder-o' - } else { - iconClass = getMediaClass(resultArr[i].uri) - } html += '
      • ' + - '

        ' + resultArr[i].name + '

      • ' + '

        ' + resultArr[i].name + '

        ' } } $(BROWSE_TABLE).append(html) + if (browseStack.length > 0) { + window.scrollTo(0, browseStack[browseStack.length - 1].scrollPos || 0) // Restore scroll position + } updatePlayIcons(songdata.track.uri, songdata.tlid, controls.getIconForAction()) + // Look up track details and add album headers if (uris.length > 0) { mopidy.library.lookup({'uris': uris}).then(function (resultDict) { // Break into albums and put in tables var track, previousTrack, nextTrack, uri - $.each(resultArr, function (i, ref) { - if (ref.type === 'track') { + for (i = 0, index = 0; i < resultArr.length; i++) { + if (resultArr[i].type === 'track') { previousTrack = track || undefined if (i < resultArr.length - 1 && resultDict[resultArr[i + 1].uri]) { nextTrack = resultDict[resultArr[i + 1].uri][0] } else { nextTrack = undefined } - track = resultDict[ref.uri][0] + track = resultDict[resultArr[i].uri][0] popupData[track.uri] = track // Need full track info in popups in order to display albums and artists. if (uris.length === 1 || (previousTrack && !hasSameAlbum(previousTrack, track) && !hasSameAlbum(track, nextTrack))) { renderSongLiAlbumInfo(track, BROWSE_TABLE) } renderSongLiDivider(previousTrack, track, nextTrack, BROWSE_TABLE) } - }) + } showLoading(false) }, console.error) } else { @@ -166,7 +166,7 @@ function processGetPlaylists (resultArr) { } else if (isFavouritesPlaylist(resultArr[i])) { favourites = li_html + '♥ Musicbox Favourites' } else { - tmp = tmp + li_html + ' ' + resultArr[i].name + '' + tmp = tmp + li_html + ' ' + resultArr[i].name + '' } } // Prepend the user's Spotify "Starred" playlist and favourites to the results. (like Spotify official client). @@ -192,9 +192,11 @@ function processPlaylistItems (resultDict) { return mopidy.library.lookup({'uris': trackUris}).then(function (tracks) { // Transform from dict to list and cache result var newplaylisturi = resultDict.uri + var track playlists[newplaylisturi] = {'uri': newplaylisturi, 'tracks': []} for (i = 0; i < trackUris.length; i++) { - playlists[newplaylisturi].tracks.push(tracks[trackUris[i]][0]) + track = tracks[trackUris[i]][0] || resultDict.items[i] // Fall back to using track Ref if lookup failed. + playlists[newplaylisturi].tracks.push(track) } showLoading(false) return playlists[newplaylisturi].tracks diff --git a/mopidy_musicbox_webclient/static/mb.appcache b/mopidy_musicbox_webclient/static/mb.appcache index 529ba4a7..52a48d8d 100644 --- a/mopidy_musicbox_webclient/static/mb.appcache +++ b/mopidy_musicbox_webclient/static/mb.appcache @@ -1,6 +1,6 @@ CACHE MANIFEST -# 2016-05-15:v1 +# 2017-02-26:v1 NETWORK: * @@ -48,6 +48,7 @@ vendors/font_awesome/less/list.less vendors/font_awesome/less/mixins.less vendors/font_awesome/less/path.less vendors/font_awesome/less/rotated-flipped.less +vendors/font_awesome/less/screen-reader.less vendors/font_awesome/less/stacked.less vendors/font_awesome/less/variables.less vendors/font_awesome/scss/_animated.scss @@ -60,6 +61,7 @@ vendors/font_awesome/scss/_list.scss vendors/font_awesome/scss/_mixins.scss vendors/font_awesome/scss/_path.scss vendors/font_awesome/scss/_rotated-flipped.scss +vendors/font_awesome/scss/_screen-reader.scss vendors/font_awesome/scss/_stacked.scss vendors/font_awesome/scss/_variables.scss vendors/font_awesome/scss/font-awesome.scss diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/css/font-awesome.css b/mopidy_musicbox_webclient/static/vendors/font_awesome/css/font-awesome.css index b2a5fe2f..ee906a81 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/css/font-awesome.css +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/css/font-awesome.css @@ -1,13 +1,13 @@ /*! - * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ /* FONT PATH * -------------------------- */ @font-face { font-family: 'FontAwesome'; - src: url('../fonts/fontawesome-webfont.eot?v=4.5.0'); - src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg'); + src: url('../fonts/fontawesome-webfont.eot?v=4.7.0'); + src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); font-weight: normal; font-style: normal; } @@ -118,31 +118,31 @@ } } .fa-rotate-90 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); } .fa-rotate-180 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); transform: rotate(180deg); } .fa-rotate-270 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; -webkit-transform: rotate(270deg); -ms-transform: rotate(270deg); transform: rotate(270deg); } .fa-flip-horizontal { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; -webkit-transform: scale(-1, 1); -ms-transform: scale(-1, 1); transform: scale(-1, 1); } .fa-flip-vertical { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; -webkit-transform: scale(1, -1); -ms-transform: scale(1, -1); transform: scale(1, -1); @@ -1383,7 +1383,7 @@ .fa-digg:before { content: "\f1a6"; } -.fa-pied-piper:before { +.fa-pied-piper-pp:before { content: "\f1a7"; } .fa-pied-piper-alt:before { @@ -1509,6 +1509,7 @@ content: "\f1ce"; } .fa-ra:before, +.fa-resistance:before, .fa-rebel:before { content: "\f1d0"; } @@ -1831,6 +1832,7 @@ content: "\f23e"; } .fa-battery-4:before, +.fa-battery:before, .fa-battery-full:before { content: "\f240"; } @@ -2084,3 +2086,252 @@ .fa-percent:before { content: "\f295"; } +.fa-gitlab:before { + content: "\f296"; +} +.fa-wpbeginner:before { + content: "\f297"; +} +.fa-wpforms:before { + content: "\f298"; +} +.fa-envira:before { + content: "\f299"; +} +.fa-universal-access:before { + content: "\f29a"; +} +.fa-wheelchair-alt:before { + content: "\f29b"; +} +.fa-question-circle-o:before { + content: "\f29c"; +} +.fa-blind:before { + content: "\f29d"; +} +.fa-audio-description:before { + content: "\f29e"; +} +.fa-volume-control-phone:before { + content: "\f2a0"; +} +.fa-braille:before { + content: "\f2a1"; +} +.fa-assistive-listening-systems:before { + content: "\f2a2"; +} +.fa-asl-interpreting:before, +.fa-american-sign-language-interpreting:before { + content: "\f2a3"; +} +.fa-deafness:before, +.fa-hard-of-hearing:before, +.fa-deaf:before { + content: "\f2a4"; +} +.fa-glide:before { + content: "\f2a5"; +} +.fa-glide-g:before { + content: "\f2a6"; +} +.fa-signing:before, +.fa-sign-language:before { + content: "\f2a7"; +} +.fa-low-vision:before { + content: "\f2a8"; +} +.fa-viadeo:before { + content: "\f2a9"; +} +.fa-viadeo-square:before { + content: "\f2aa"; +} +.fa-snapchat:before { + content: "\f2ab"; +} +.fa-snapchat-ghost:before { + content: "\f2ac"; +} +.fa-snapchat-square:before { + content: "\f2ad"; +} +.fa-pied-piper:before { + content: "\f2ae"; +} +.fa-first-order:before { + content: "\f2b0"; +} +.fa-yoast:before { + content: "\f2b1"; +} +.fa-themeisle:before { + content: "\f2b2"; +} +.fa-google-plus-circle:before, +.fa-google-plus-official:before { + content: "\f2b3"; +} +.fa-fa:before, +.fa-font-awesome:before { + content: "\f2b4"; +} +.fa-handshake-o:before { + content: "\f2b5"; +} +.fa-envelope-open:before { + content: "\f2b6"; +} +.fa-envelope-open-o:before { + content: "\f2b7"; +} +.fa-linode:before { + content: "\f2b8"; +} +.fa-address-book:before { + content: "\f2b9"; +} +.fa-address-book-o:before { + content: "\f2ba"; +} +.fa-vcard:before, +.fa-address-card:before { + content: "\f2bb"; +} +.fa-vcard-o:before, +.fa-address-card-o:before { + content: "\f2bc"; +} +.fa-user-circle:before { + content: "\f2bd"; +} +.fa-user-circle-o:before { + content: "\f2be"; +} +.fa-user-o:before { + content: "\f2c0"; +} +.fa-id-badge:before { + content: "\f2c1"; +} +.fa-drivers-license:before, +.fa-id-card:before { + content: "\f2c2"; +} +.fa-drivers-license-o:before, +.fa-id-card-o:before { + content: "\f2c3"; +} +.fa-quora:before { + content: "\f2c4"; +} +.fa-free-code-camp:before { + content: "\f2c5"; +} +.fa-telegram:before { + content: "\f2c6"; +} +.fa-thermometer-4:before, +.fa-thermometer:before, +.fa-thermometer-full:before { + content: "\f2c7"; +} +.fa-thermometer-3:before, +.fa-thermometer-three-quarters:before { + content: "\f2c8"; +} +.fa-thermometer-2:before, +.fa-thermometer-half:before { + content: "\f2c9"; +} +.fa-thermometer-1:before, +.fa-thermometer-quarter:before { + content: "\f2ca"; +} +.fa-thermometer-0:before, +.fa-thermometer-empty:before { + content: "\f2cb"; +} +.fa-shower:before { + content: "\f2cc"; +} +.fa-bathtub:before, +.fa-s15:before, +.fa-bath:before { + content: "\f2cd"; +} +.fa-podcast:before { + content: "\f2ce"; +} +.fa-window-maximize:before { + content: "\f2d0"; +} +.fa-window-minimize:before { + content: "\f2d1"; +} +.fa-window-restore:before { + content: "\f2d2"; +} +.fa-times-rectangle:before, +.fa-window-close:before { + content: "\f2d3"; +} +.fa-times-rectangle-o:before, +.fa-window-close-o:before { + content: "\f2d4"; +} +.fa-bandcamp:before { + content: "\f2d5"; +} +.fa-grav:before { + content: "\f2d6"; +} +.fa-etsy:before { + content: "\f2d7"; +} +.fa-imdb:before { + content: "\f2d8"; +} +.fa-ravelry:before { + content: "\f2d9"; +} +.fa-eercast:before { + content: "\f2da"; +} +.fa-microchip:before { + content: "\f2db"; +} +.fa-snowflake-o:before { + content: "\f2dc"; +} +.fa-superpowers:before { + content: "\f2dd"; +} +.fa-wpexplorer:before { + content: "\f2de"; +} +.fa-meetup:before { + content: "\f2e0"; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/css/font-awesome.min.css b/mopidy_musicbox_webclient/static/vendors/font_awesome/css/font-awesome.min.css index d0603cb4..540440ce 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/css/font-awesome.min.css +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/css/font-awesome.min.css @@ -1,4 +1,4 @@ /*! - * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"} + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/FontAwesome.otf b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/FontAwesome.otf index 3ed7f8b4..401ec0f3 100644 Binary files a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/FontAwesome.otf and b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/FontAwesome.otf differ diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.eot b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.eot index 9b6afaed..e9f60ca9 100644 Binary files a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.eot and b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.eot differ diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.svg b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.svg index d05688e9..855c845e 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.svg +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.svg @@ -1,655 +1,2671 @@ - - + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.ttf b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.ttf index 26dea795..35acda2f 100644 Binary files a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.ttf and b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.ttf differ diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.woff b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.woff index dc35ce3c..400014a4 100644 Binary files a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.woff and b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.woff differ diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.woff2 b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.woff2 index 500e5172..4d13fc60 100644 Binary files a/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.woff2 and b/mopidy_musicbox_webclient/static/vendors/font_awesome/fonts/fontawesome-webfont.woff2 differ diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/font-awesome.less b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/font-awesome.less index c35d3eeb..c3677def 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/font-awesome.less +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/font-awesome.less @@ -1,5 +1,5 @@ /*! - * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ @@ -15,3 +15,4 @@ @import "rotated-flipped.less"; @import "stacked.less"; @import "icons.less"; +@import "screen-reader.less"; diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/icons.less b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/icons.less index ca60abd7..159d6004 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/icons.less +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/icons.less @@ -438,7 +438,7 @@ .@{fa-css-prefix}-stumbleupon:before { content: @fa-var-stumbleupon; } .@{fa-css-prefix}-delicious:before { content: @fa-var-delicious; } .@{fa-css-prefix}-digg:before { content: @fa-var-digg; } -.@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; } +.@{fa-css-prefix}-pied-piper-pp:before { content: @fa-var-pied-piper-pp; } .@{fa-css-prefix}-pied-piper-alt:before { content: @fa-var-pied-piper-alt; } .@{fa-css-prefix}-drupal:before { content: @fa-var-drupal; } .@{fa-css-prefix}-joomla:before { content: @fa-var-joomla; } @@ -488,6 +488,7 @@ .@{fa-css-prefix}-life-ring:before { content: @fa-var-life-ring; } .@{fa-css-prefix}-circle-o-notch:before { content: @fa-var-circle-o-notch; } .@{fa-css-prefix}-ra:before, +.@{fa-css-prefix}-resistance:before, .@{fa-css-prefix}-rebel:before { content: @fa-var-rebel; } .@{fa-css-prefix}-ge:before, .@{fa-css-prefix}-empire:before { content: @fa-var-empire; } @@ -604,6 +605,7 @@ .@{fa-css-prefix}-opencart:before { content: @fa-var-opencart; } .@{fa-css-prefix}-expeditedssl:before { content: @fa-var-expeditedssl; } .@{fa-css-prefix}-battery-4:before, +.@{fa-css-prefix}-battery:before, .@{fa-css-prefix}-battery-full:before { content: @fa-var-battery-full; } .@{fa-css-prefix}-battery-3:before, .@{fa-css-prefix}-battery-three-quarters:before { content: @fa-var-battery-three-quarters; } @@ -695,3 +697,93 @@ .@{fa-css-prefix}-bluetooth:before { content: @fa-var-bluetooth; } .@{fa-css-prefix}-bluetooth-b:before { content: @fa-var-bluetooth-b; } .@{fa-css-prefix}-percent:before { content: @fa-var-percent; } +.@{fa-css-prefix}-gitlab:before { content: @fa-var-gitlab; } +.@{fa-css-prefix}-wpbeginner:before { content: @fa-var-wpbeginner; } +.@{fa-css-prefix}-wpforms:before { content: @fa-var-wpforms; } +.@{fa-css-prefix}-envira:before { content: @fa-var-envira; } +.@{fa-css-prefix}-universal-access:before { content: @fa-var-universal-access; } +.@{fa-css-prefix}-wheelchair-alt:before { content: @fa-var-wheelchair-alt; } +.@{fa-css-prefix}-question-circle-o:before { content: @fa-var-question-circle-o; } +.@{fa-css-prefix}-blind:before { content: @fa-var-blind; } +.@{fa-css-prefix}-audio-description:before { content: @fa-var-audio-description; } +.@{fa-css-prefix}-volume-control-phone:before { content: @fa-var-volume-control-phone; } +.@{fa-css-prefix}-braille:before { content: @fa-var-braille; } +.@{fa-css-prefix}-assistive-listening-systems:before { content: @fa-var-assistive-listening-systems; } +.@{fa-css-prefix}-asl-interpreting:before, +.@{fa-css-prefix}-american-sign-language-interpreting:before { content: @fa-var-american-sign-language-interpreting; } +.@{fa-css-prefix}-deafness:before, +.@{fa-css-prefix}-hard-of-hearing:before, +.@{fa-css-prefix}-deaf:before { content: @fa-var-deaf; } +.@{fa-css-prefix}-glide:before { content: @fa-var-glide; } +.@{fa-css-prefix}-glide-g:before { content: @fa-var-glide-g; } +.@{fa-css-prefix}-signing:before, +.@{fa-css-prefix}-sign-language:before { content: @fa-var-sign-language; } +.@{fa-css-prefix}-low-vision:before { content: @fa-var-low-vision; } +.@{fa-css-prefix}-viadeo:before { content: @fa-var-viadeo; } +.@{fa-css-prefix}-viadeo-square:before { content: @fa-var-viadeo-square; } +.@{fa-css-prefix}-snapchat:before { content: @fa-var-snapchat; } +.@{fa-css-prefix}-snapchat-ghost:before { content: @fa-var-snapchat-ghost; } +.@{fa-css-prefix}-snapchat-square:before { content: @fa-var-snapchat-square; } +.@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; } +.@{fa-css-prefix}-first-order:before { content: @fa-var-first-order; } +.@{fa-css-prefix}-yoast:before { content: @fa-var-yoast; } +.@{fa-css-prefix}-themeisle:before { content: @fa-var-themeisle; } +.@{fa-css-prefix}-google-plus-circle:before, +.@{fa-css-prefix}-google-plus-official:before { content: @fa-var-google-plus-official; } +.@{fa-css-prefix}-fa:before, +.@{fa-css-prefix}-font-awesome:before { content: @fa-var-font-awesome; } +.@{fa-css-prefix}-handshake-o:before { content: @fa-var-handshake-o; } +.@{fa-css-prefix}-envelope-open:before { content: @fa-var-envelope-open; } +.@{fa-css-prefix}-envelope-open-o:before { content: @fa-var-envelope-open-o; } +.@{fa-css-prefix}-linode:before { content: @fa-var-linode; } +.@{fa-css-prefix}-address-book:before { content: @fa-var-address-book; } +.@{fa-css-prefix}-address-book-o:before { content: @fa-var-address-book-o; } +.@{fa-css-prefix}-vcard:before, +.@{fa-css-prefix}-address-card:before { content: @fa-var-address-card; } +.@{fa-css-prefix}-vcard-o:before, +.@{fa-css-prefix}-address-card-o:before { content: @fa-var-address-card-o; } +.@{fa-css-prefix}-user-circle:before { content: @fa-var-user-circle; } +.@{fa-css-prefix}-user-circle-o:before { content: @fa-var-user-circle-o; } +.@{fa-css-prefix}-user-o:before { content: @fa-var-user-o; } +.@{fa-css-prefix}-id-badge:before { content: @fa-var-id-badge; } +.@{fa-css-prefix}-drivers-license:before, +.@{fa-css-prefix}-id-card:before { content: @fa-var-id-card; } +.@{fa-css-prefix}-drivers-license-o:before, +.@{fa-css-prefix}-id-card-o:before { content: @fa-var-id-card-o; } +.@{fa-css-prefix}-quora:before { content: @fa-var-quora; } +.@{fa-css-prefix}-free-code-camp:before { content: @fa-var-free-code-camp; } +.@{fa-css-prefix}-telegram:before { content: @fa-var-telegram; } +.@{fa-css-prefix}-thermometer-4:before, +.@{fa-css-prefix}-thermometer:before, +.@{fa-css-prefix}-thermometer-full:before { content: @fa-var-thermometer-full; } +.@{fa-css-prefix}-thermometer-3:before, +.@{fa-css-prefix}-thermometer-three-quarters:before { content: @fa-var-thermometer-three-quarters; } +.@{fa-css-prefix}-thermometer-2:before, +.@{fa-css-prefix}-thermometer-half:before { content: @fa-var-thermometer-half; } +.@{fa-css-prefix}-thermometer-1:before, +.@{fa-css-prefix}-thermometer-quarter:before { content: @fa-var-thermometer-quarter; } +.@{fa-css-prefix}-thermometer-0:before, +.@{fa-css-prefix}-thermometer-empty:before { content: @fa-var-thermometer-empty; } +.@{fa-css-prefix}-shower:before { content: @fa-var-shower; } +.@{fa-css-prefix}-bathtub:before, +.@{fa-css-prefix}-s15:before, +.@{fa-css-prefix}-bath:before { content: @fa-var-bath; } +.@{fa-css-prefix}-podcast:before { content: @fa-var-podcast; } +.@{fa-css-prefix}-window-maximize:before { content: @fa-var-window-maximize; } +.@{fa-css-prefix}-window-minimize:before { content: @fa-var-window-minimize; } +.@{fa-css-prefix}-window-restore:before { content: @fa-var-window-restore; } +.@{fa-css-prefix}-times-rectangle:before, +.@{fa-css-prefix}-window-close:before { content: @fa-var-window-close; } +.@{fa-css-prefix}-times-rectangle-o:before, +.@{fa-css-prefix}-window-close-o:before { content: @fa-var-window-close-o; } +.@{fa-css-prefix}-bandcamp:before { content: @fa-var-bandcamp; } +.@{fa-css-prefix}-grav:before { content: @fa-var-grav; } +.@{fa-css-prefix}-etsy:before { content: @fa-var-etsy; } +.@{fa-css-prefix}-imdb:before { content: @fa-var-imdb; } +.@{fa-css-prefix}-ravelry:before { content: @fa-var-ravelry; } +.@{fa-css-prefix}-eercast:before { content: @fa-var-eercast; } +.@{fa-css-prefix}-microchip:before { content: @fa-var-microchip; } +.@{fa-css-prefix}-snowflake-o:before { content: @fa-var-snowflake-o; } +.@{fa-css-prefix}-superpowers:before { content: @fa-var-superpowers; } +.@{fa-css-prefix}-wpexplorer:before { content: @fa-var-wpexplorer; } +.@{fa-css-prefix}-meetup:before { content: @fa-var-meetup; } diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/mixins.less b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/mixins.less index d5a43a14..beef231d 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/mixins.less +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/mixins.less @@ -12,15 +12,49 @@ } .fa-icon-rotate(@degrees, @rotation) { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; -webkit-transform: rotate(@degrees); -ms-transform: rotate(@degrees); transform: rotate(@degrees); } .fa-icon-flip(@horiz, @vert, @rotation) { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; -webkit-transform: scale(@horiz, @vert); -ms-transform: scale(@horiz, @vert); transform: scale(@horiz, @vert); } + + +// Only display content to screen readers. A la Bootstrap 4. +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +.sr-only() { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +.sr-only-focusable() { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/path.less b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/path.less index 9211e665..835be41f 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/path.less +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/path.less @@ -9,7 +9,7 @@ url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); -// src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts + // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts font-weight: normal; font-style: normal; } diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/screen-reader.less b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/screen-reader.less new file mode 100644 index 00000000..11c18819 --- /dev/null +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/screen-reader.less @@ -0,0 +1,5 @@ +// Screen Readers +// ------------------------- + +.sr-only { .sr-only(); } +.sr-only-focusable { .sr-only-focusable(); } diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/variables.less b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/variables.less index 37c4b80a..7ddbbc01 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/less/variables.less +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/less/variables.less @@ -4,14 +4,18 @@ @fa-font-path: "../fonts"; @fa-font-size-base: 14px; @fa-line-height-base: 1; -//@fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.5.0/fonts"; // for referencing Bootstrap CDN font files directly +//@fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts"; // for referencing Bootstrap CDN font files directly @fa-css-prefix: fa; -@fa-version: "4.5.0"; +@fa-version: "4.7.0"; @fa-border-color: #eee; @fa-inverse: #fff; @fa-li-width: (30em / 14); @fa-var-500px: "\f26e"; +@fa-var-address-book: "\f2b9"; +@fa-var-address-book-o: "\f2ba"; +@fa-var-address-card: "\f2bb"; +@fa-var-address-card-o: "\f2bc"; @fa-var-adjust: "\f042"; @fa-var-adn: "\f170"; @fa-var-align-center: "\f037"; @@ -20,6 +24,7 @@ @fa-var-align-right: "\f038"; @fa-var-amazon: "\f270"; @fa-var-ambulance: "\f0f9"; +@fa-var-american-sign-language-interpreting: "\f2a3"; @fa-var-anchor: "\f13d"; @fa-var-android: "\f17b"; @fa-var-angellist: "\f209"; @@ -50,17 +55,24 @@ @fa-var-arrows-alt: "\f0b2"; @fa-var-arrows-h: "\f07e"; @fa-var-arrows-v: "\f07d"; +@fa-var-asl-interpreting: "\f2a3"; +@fa-var-assistive-listening-systems: "\f2a2"; @fa-var-asterisk: "\f069"; @fa-var-at: "\f1fa"; +@fa-var-audio-description: "\f29e"; @fa-var-automobile: "\f1b9"; @fa-var-backward: "\f04a"; @fa-var-balance-scale: "\f24e"; @fa-var-ban: "\f05e"; +@fa-var-bandcamp: "\f2d5"; @fa-var-bank: "\f19c"; @fa-var-bar-chart: "\f080"; @fa-var-bar-chart-o: "\f080"; @fa-var-barcode: "\f02a"; @fa-var-bars: "\f0c9"; +@fa-var-bath: "\f2cd"; +@fa-var-bathtub: "\f2cd"; +@fa-var-battery: "\f240"; @fa-var-battery-0: "\f244"; @fa-var-battery-1: "\f243"; @fa-var-battery-2: "\f242"; @@ -86,6 +98,7 @@ @fa-var-bitbucket-square: "\f172"; @fa-var-bitcoin: "\f15a"; @fa-var-black-tie: "\f27e"; +@fa-var-blind: "\f29d"; @fa-var-bluetooth: "\f293"; @fa-var-bluetooth-b: "\f294"; @fa-var-bold: "\f032"; @@ -94,6 +107,7 @@ @fa-var-book: "\f02d"; @fa-var-bookmark: "\f02e"; @fa-var-bookmark-o: "\f097"; +@fa-var-braille: "\f2a1"; @fa-var-briefcase: "\f0b1"; @fa-var-btc: "\f15a"; @fa-var-bug: "\f188"; @@ -196,6 +210,8 @@ @fa-var-dashboard: "\f0e4"; @fa-var-dashcube: "\f210"; @fa-var-database: "\f1c0"; +@fa-var-deaf: "\f2a4"; +@fa-var-deafness: "\f2a4"; @fa-var-dedent: "\f03b"; @fa-var-delicious: "\f1a5"; @fa-var-desktop: "\f108"; @@ -206,18 +222,25 @@ @fa-var-dot-circle-o: "\f192"; @fa-var-download: "\f019"; @fa-var-dribbble: "\f17d"; +@fa-var-drivers-license: "\f2c2"; +@fa-var-drivers-license-o: "\f2c3"; @fa-var-dropbox: "\f16b"; @fa-var-drupal: "\f1a9"; @fa-var-edge: "\f282"; @fa-var-edit: "\f044"; +@fa-var-eercast: "\f2da"; @fa-var-eject: "\f052"; @fa-var-ellipsis-h: "\f141"; @fa-var-ellipsis-v: "\f142"; @fa-var-empire: "\f1d1"; @fa-var-envelope: "\f0e0"; @fa-var-envelope-o: "\f003"; +@fa-var-envelope-open: "\f2b6"; +@fa-var-envelope-open-o: "\f2b7"; @fa-var-envelope-square: "\f199"; +@fa-var-envira: "\f299"; @fa-var-eraser: "\f12d"; +@fa-var-etsy: "\f2d7"; @fa-var-eur: "\f153"; @fa-var-euro: "\f153"; @fa-var-exchange: "\f0ec"; @@ -231,6 +254,7 @@ @fa-var-eye: "\f06e"; @fa-var-eye-slash: "\f070"; @fa-var-eyedropper: "\f1fb"; +@fa-var-fa: "\f2b4"; @fa-var-facebook: "\f09a"; @fa-var-facebook-f: "\f09a"; @fa-var-facebook-official: "\f230"; @@ -265,6 +289,7 @@ @fa-var-fire: "\f06d"; @fa-var-fire-extinguisher: "\f134"; @fa-var-firefox: "\f269"; +@fa-var-first-order: "\f2b0"; @fa-var-flag: "\f024"; @fa-var-flag-checkered: "\f11e"; @fa-var-flag-o: "\f11d"; @@ -277,11 +302,13 @@ @fa-var-folder-open: "\f07c"; @fa-var-folder-open-o: "\f115"; @fa-var-font: "\f031"; +@fa-var-font-awesome: "\f2b4"; @fa-var-fonticons: "\f280"; @fa-var-fort-awesome: "\f286"; @fa-var-forumbee: "\f211"; @fa-var-forward: "\f04e"; @fa-var-foursquare: "\f180"; +@fa-var-free-code-camp: "\f2c5"; @fa-var-frown-o: "\f119"; @fa-var-futbol-o: "\f1e3"; @fa-var-gamepad: "\f11b"; @@ -300,15 +327,21 @@ @fa-var-github: "\f09b"; @fa-var-github-alt: "\f113"; @fa-var-github-square: "\f092"; +@fa-var-gitlab: "\f296"; @fa-var-gittip: "\f184"; @fa-var-glass: "\f000"; +@fa-var-glide: "\f2a5"; +@fa-var-glide-g: "\f2a6"; @fa-var-globe: "\f0ac"; @fa-var-google: "\f1a0"; @fa-var-google-plus: "\f0d5"; +@fa-var-google-plus-circle: "\f2b3"; +@fa-var-google-plus-official: "\f2b3"; @fa-var-google-plus-square: "\f0d4"; @fa-var-google-wallet: "\f1ee"; @fa-var-graduation-cap: "\f19d"; @fa-var-gratipay: "\f184"; +@fa-var-grav: "\f2d6"; @fa-var-group: "\f0c0"; @fa-var-h-square: "\f0fd"; @fa-var-hacker-news: "\f1d4"; @@ -325,6 +358,8 @@ @fa-var-hand-scissors-o: "\f257"; @fa-var-hand-spock-o: "\f259"; @fa-var-hand-stop-o: "\f256"; +@fa-var-handshake-o: "\f2b5"; +@fa-var-hard-of-hearing: "\f2a4"; @fa-var-hashtag: "\f292"; @fa-var-hdd-o: "\f0a0"; @fa-var-header: "\f1dc"; @@ -347,8 +382,12 @@ @fa-var-houzz: "\f27c"; @fa-var-html5: "\f13b"; @fa-var-i-cursor: "\f246"; +@fa-var-id-badge: "\f2c1"; +@fa-var-id-card: "\f2c2"; +@fa-var-id-card-o: "\f2c3"; @fa-var-ils: "\f20b"; @fa-var-image: "\f03e"; +@fa-var-imdb: "\f2d8"; @fa-var-inbox: "\f01c"; @fa-var-indent: "\f03c"; @fa-var-industry: "\f275"; @@ -386,6 +425,7 @@ @fa-var-link: "\f0c1"; @fa-var-linkedin: "\f0e1"; @fa-var-linkedin-square: "\f08c"; +@fa-var-linode: "\f2b8"; @fa-var-linux: "\f17c"; @fa-var-list: "\f03a"; @fa-var-list-alt: "\f022"; @@ -397,6 +437,7 @@ @fa-var-long-arrow-left: "\f177"; @fa-var-long-arrow-right: "\f178"; @fa-var-long-arrow-up: "\f176"; +@fa-var-low-vision: "\f2a8"; @fa-var-magic: "\f0d0"; @fa-var-magnet: "\f076"; @fa-var-mail-forward: "\f064"; @@ -417,8 +458,10 @@ @fa-var-meanpath: "\f20c"; @fa-var-medium: "\f23a"; @fa-var-medkit: "\f0fa"; +@fa-var-meetup: "\f2e0"; @fa-var-meh-o: "\f11a"; @fa-var-mercury: "\f223"; +@fa-var-microchip: "\f2db"; @fa-var-microphone: "\f130"; @fa-var-microphone-slash: "\f131"; @fa-var-minus: "\f068"; @@ -468,8 +511,9 @@ @fa-var-photo: "\f03e"; @fa-var-picture-o: "\f03e"; @fa-var-pie-chart: "\f200"; -@fa-var-pied-piper: "\f1a7"; +@fa-var-pied-piper: "\f2ae"; @fa-var-pied-piper-alt: "\f1a8"; +@fa-var-pied-piper-pp: "\f1a7"; @fa-var-pinterest: "\f0d2"; @fa-var-pinterest-p: "\f231"; @fa-var-pinterest-square: "\f0d3"; @@ -482,6 +526,7 @@ @fa-var-plus-circle: "\f055"; @fa-var-plus-square: "\f0fe"; @fa-var-plus-square-o: "\f196"; +@fa-var-podcast: "\f2ce"; @fa-var-power-off: "\f011"; @fa-var-print: "\f02f"; @fa-var-product-hunt: "\f288"; @@ -490,10 +535,13 @@ @fa-var-qrcode: "\f029"; @fa-var-question: "\f128"; @fa-var-question-circle: "\f059"; +@fa-var-question-circle-o: "\f29c"; +@fa-var-quora: "\f2c4"; @fa-var-quote-left: "\f10d"; @fa-var-quote-right: "\f10e"; @fa-var-ra: "\f1d0"; @fa-var-random: "\f074"; +@fa-var-ravelry: "\f2d9"; @fa-var-rebel: "\f1d0"; @fa-var-recycle: "\f1b8"; @fa-var-reddit: "\f1a1"; @@ -507,6 +555,7 @@ @fa-var-repeat: "\f01e"; @fa-var-reply: "\f112"; @fa-var-reply-all: "\f122"; +@fa-var-resistance: "\f1d0"; @fa-var-retweet: "\f079"; @fa-var-rmb: "\f157"; @fa-var-road: "\f018"; @@ -519,6 +568,7 @@ @fa-var-rub: "\f158"; @fa-var-ruble: "\f158"; @fa-var-rupee: "\f156"; +@fa-var-s15: "\f2cd"; @fa-var-safari: "\f267"; @fa-var-save: "\f0c7"; @fa-var-scissors: "\f0c4"; @@ -543,9 +593,12 @@ @fa-var-shopping-bag: "\f290"; @fa-var-shopping-basket: "\f291"; @fa-var-shopping-cart: "\f07a"; +@fa-var-shower: "\f2cc"; @fa-var-sign-in: "\f090"; +@fa-var-sign-language: "\f2a7"; @fa-var-sign-out: "\f08b"; @fa-var-signal: "\f012"; +@fa-var-signing: "\f2a7"; @fa-var-simplybuilt: "\f215"; @fa-var-sitemap: "\f0e8"; @fa-var-skyatlas: "\f216"; @@ -554,6 +607,10 @@ @fa-var-sliders: "\f1de"; @fa-var-slideshare: "\f1e7"; @fa-var-smile-o: "\f118"; +@fa-var-snapchat: "\f2ab"; +@fa-var-snapchat-ghost: "\f2ac"; +@fa-var-snapchat-square: "\f2ad"; +@fa-var-snowflake-o: "\f2dc"; @fa-var-soccer-ball-o: "\f1e3"; @fa-var-sort: "\f0dc"; @fa-var-sort-alpha-asc: "\f15d"; @@ -599,6 +656,7 @@ @fa-var-subway: "\f239"; @fa-var-suitcase: "\f0f2"; @fa-var-sun-o: "\f185"; +@fa-var-superpowers: "\f2dd"; @fa-var-superscript: "\f12b"; @fa-var-support: "\f1cd"; @fa-var-table: "\f0ce"; @@ -608,6 +666,7 @@ @fa-var-tags: "\f02c"; @fa-var-tasks: "\f0ae"; @fa-var-taxi: "\f1ba"; +@fa-var-telegram: "\f2c6"; @fa-var-television: "\f26c"; @fa-var-tencent-weibo: "\f1d5"; @fa-var-terminal: "\f120"; @@ -616,6 +675,18 @@ @fa-var-th: "\f00a"; @fa-var-th-large: "\f009"; @fa-var-th-list: "\f00b"; +@fa-var-themeisle: "\f2b2"; +@fa-var-thermometer: "\f2c7"; +@fa-var-thermometer-0: "\f2cb"; +@fa-var-thermometer-1: "\f2ca"; +@fa-var-thermometer-2: "\f2c9"; +@fa-var-thermometer-3: "\f2c8"; +@fa-var-thermometer-4: "\f2c7"; +@fa-var-thermometer-empty: "\f2cb"; +@fa-var-thermometer-full: "\f2c7"; +@fa-var-thermometer-half: "\f2c9"; +@fa-var-thermometer-quarter: "\f2ca"; +@fa-var-thermometer-three-quarters: "\f2c8"; @fa-var-thumb-tack: "\f08d"; @fa-var-thumbs-down: "\f165"; @fa-var-thumbs-o-down: "\f088"; @@ -625,6 +696,8 @@ @fa-var-times: "\f00d"; @fa-var-times-circle: "\f057"; @fa-var-times-circle-o: "\f05c"; +@fa-var-times-rectangle: "\f2d3"; +@fa-var-times-rectangle-o: "\f2d4"; @fa-var-tint: "\f043"; @fa-var-toggle-down: "\f150"; @fa-var-toggle-left: "\f191"; @@ -655,6 +728,7 @@ @fa-var-umbrella: "\f0e9"; @fa-var-underline: "\f0cd"; @fa-var-undo: "\f0e2"; +@fa-var-universal-access: "\f29a"; @fa-var-university: "\f19c"; @fa-var-unlink: "\f127"; @fa-var-unlock: "\f09c"; @@ -664,20 +738,28 @@ @fa-var-usb: "\f287"; @fa-var-usd: "\f155"; @fa-var-user: "\f007"; +@fa-var-user-circle: "\f2bd"; +@fa-var-user-circle-o: "\f2be"; @fa-var-user-md: "\f0f0"; +@fa-var-user-o: "\f2c0"; @fa-var-user-plus: "\f234"; @fa-var-user-secret: "\f21b"; @fa-var-user-times: "\f235"; @fa-var-users: "\f0c0"; +@fa-var-vcard: "\f2bb"; +@fa-var-vcard-o: "\f2bc"; @fa-var-venus: "\f221"; @fa-var-venus-double: "\f226"; @fa-var-venus-mars: "\f228"; @fa-var-viacoin: "\f237"; +@fa-var-viadeo: "\f2a9"; +@fa-var-viadeo-square: "\f2aa"; @fa-var-video-camera: "\f03d"; @fa-var-vimeo: "\f27d"; @fa-var-vimeo-square: "\f194"; @fa-var-vine: "\f1ca"; @fa-var-vk: "\f189"; +@fa-var-volume-control-phone: "\f2a0"; @fa-var-volume-down: "\f027"; @fa-var-volume-off: "\f026"; @fa-var-volume-up: "\f028"; @@ -687,11 +769,20 @@ @fa-var-weixin: "\f1d7"; @fa-var-whatsapp: "\f232"; @fa-var-wheelchair: "\f193"; +@fa-var-wheelchair-alt: "\f29b"; @fa-var-wifi: "\f1eb"; @fa-var-wikipedia-w: "\f266"; +@fa-var-window-close: "\f2d3"; +@fa-var-window-close-o: "\f2d4"; +@fa-var-window-maximize: "\f2d0"; +@fa-var-window-minimize: "\f2d1"; +@fa-var-window-restore: "\f2d2"; @fa-var-windows: "\f17a"; @fa-var-won: "\f159"; @fa-var-wordpress: "\f19a"; +@fa-var-wpbeginner: "\f297"; +@fa-var-wpexplorer: "\f2de"; +@fa-var-wpforms: "\f298"; @fa-var-wrench: "\f0ad"; @fa-var-xing: "\f168"; @fa-var-xing-square: "\f169"; @@ -702,6 +793,7 @@ @fa-var-yc-square: "\f1d4"; @fa-var-yelp: "\f1e9"; @fa-var-yen: "\f157"; +@fa-var-yoast: "\f2b1"; @fa-var-youtube: "\f167"; @fa-var-youtube-play: "\f16a"; @fa-var-youtube-square: "\f166"; diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_icons.scss b/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_icons.scss index 6f937598..e63e702c 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_icons.scss +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_icons.scss @@ -438,7 +438,7 @@ .#{$fa-css-prefix}-stumbleupon:before { content: $fa-var-stumbleupon; } .#{$fa-css-prefix}-delicious:before { content: $fa-var-delicious; } .#{$fa-css-prefix}-digg:before { content: $fa-var-digg; } -.#{$fa-css-prefix}-pied-piper:before { content: $fa-var-pied-piper; } +.#{$fa-css-prefix}-pied-piper-pp:before { content: $fa-var-pied-piper-pp; } .#{$fa-css-prefix}-pied-piper-alt:before { content: $fa-var-pied-piper-alt; } .#{$fa-css-prefix}-drupal:before { content: $fa-var-drupal; } .#{$fa-css-prefix}-joomla:before { content: $fa-var-joomla; } @@ -488,6 +488,7 @@ .#{$fa-css-prefix}-life-ring:before { content: $fa-var-life-ring; } .#{$fa-css-prefix}-circle-o-notch:before { content: $fa-var-circle-o-notch; } .#{$fa-css-prefix}-ra:before, +.#{$fa-css-prefix}-resistance:before, .#{$fa-css-prefix}-rebel:before { content: $fa-var-rebel; } .#{$fa-css-prefix}-ge:before, .#{$fa-css-prefix}-empire:before { content: $fa-var-empire; } @@ -604,6 +605,7 @@ .#{$fa-css-prefix}-opencart:before { content: $fa-var-opencart; } .#{$fa-css-prefix}-expeditedssl:before { content: $fa-var-expeditedssl; } .#{$fa-css-prefix}-battery-4:before, +.#{$fa-css-prefix}-battery:before, .#{$fa-css-prefix}-battery-full:before { content: $fa-var-battery-full; } .#{$fa-css-prefix}-battery-3:before, .#{$fa-css-prefix}-battery-three-quarters:before { content: $fa-var-battery-three-quarters; } @@ -695,3 +697,93 @@ .#{$fa-css-prefix}-bluetooth:before { content: $fa-var-bluetooth; } .#{$fa-css-prefix}-bluetooth-b:before { content: $fa-var-bluetooth-b; } .#{$fa-css-prefix}-percent:before { content: $fa-var-percent; } +.#{$fa-css-prefix}-gitlab:before { content: $fa-var-gitlab; } +.#{$fa-css-prefix}-wpbeginner:before { content: $fa-var-wpbeginner; } +.#{$fa-css-prefix}-wpforms:before { content: $fa-var-wpforms; } +.#{$fa-css-prefix}-envira:before { content: $fa-var-envira; } +.#{$fa-css-prefix}-universal-access:before { content: $fa-var-universal-access; } +.#{$fa-css-prefix}-wheelchair-alt:before { content: $fa-var-wheelchair-alt; } +.#{$fa-css-prefix}-question-circle-o:before { content: $fa-var-question-circle-o; } +.#{$fa-css-prefix}-blind:before { content: $fa-var-blind; } +.#{$fa-css-prefix}-audio-description:before { content: $fa-var-audio-description; } +.#{$fa-css-prefix}-volume-control-phone:before { content: $fa-var-volume-control-phone; } +.#{$fa-css-prefix}-braille:before { content: $fa-var-braille; } +.#{$fa-css-prefix}-assistive-listening-systems:before { content: $fa-var-assistive-listening-systems; } +.#{$fa-css-prefix}-asl-interpreting:before, +.#{$fa-css-prefix}-american-sign-language-interpreting:before { content: $fa-var-american-sign-language-interpreting; } +.#{$fa-css-prefix}-deafness:before, +.#{$fa-css-prefix}-hard-of-hearing:before, +.#{$fa-css-prefix}-deaf:before { content: $fa-var-deaf; } +.#{$fa-css-prefix}-glide:before { content: $fa-var-glide; } +.#{$fa-css-prefix}-glide-g:before { content: $fa-var-glide-g; } +.#{$fa-css-prefix}-signing:before, +.#{$fa-css-prefix}-sign-language:before { content: $fa-var-sign-language; } +.#{$fa-css-prefix}-low-vision:before { content: $fa-var-low-vision; } +.#{$fa-css-prefix}-viadeo:before { content: $fa-var-viadeo; } +.#{$fa-css-prefix}-viadeo-square:before { content: $fa-var-viadeo-square; } +.#{$fa-css-prefix}-snapchat:before { content: $fa-var-snapchat; } +.#{$fa-css-prefix}-snapchat-ghost:before { content: $fa-var-snapchat-ghost; } +.#{$fa-css-prefix}-snapchat-square:before { content: $fa-var-snapchat-square; } +.#{$fa-css-prefix}-pied-piper:before { content: $fa-var-pied-piper; } +.#{$fa-css-prefix}-first-order:before { content: $fa-var-first-order; } +.#{$fa-css-prefix}-yoast:before { content: $fa-var-yoast; } +.#{$fa-css-prefix}-themeisle:before { content: $fa-var-themeisle; } +.#{$fa-css-prefix}-google-plus-circle:before, +.#{$fa-css-prefix}-google-plus-official:before { content: $fa-var-google-plus-official; } +.#{$fa-css-prefix}-fa:before, +.#{$fa-css-prefix}-font-awesome:before { content: $fa-var-font-awesome; } +.#{$fa-css-prefix}-handshake-o:before { content: $fa-var-handshake-o; } +.#{$fa-css-prefix}-envelope-open:before { content: $fa-var-envelope-open; } +.#{$fa-css-prefix}-envelope-open-o:before { content: $fa-var-envelope-open-o; } +.#{$fa-css-prefix}-linode:before { content: $fa-var-linode; } +.#{$fa-css-prefix}-address-book:before { content: $fa-var-address-book; } +.#{$fa-css-prefix}-address-book-o:before { content: $fa-var-address-book-o; } +.#{$fa-css-prefix}-vcard:before, +.#{$fa-css-prefix}-address-card:before { content: $fa-var-address-card; } +.#{$fa-css-prefix}-vcard-o:before, +.#{$fa-css-prefix}-address-card-o:before { content: $fa-var-address-card-o; } +.#{$fa-css-prefix}-user-circle:before { content: $fa-var-user-circle; } +.#{$fa-css-prefix}-user-circle-o:before { content: $fa-var-user-circle-o; } +.#{$fa-css-prefix}-user-o:before { content: $fa-var-user-o; } +.#{$fa-css-prefix}-id-badge:before { content: $fa-var-id-badge; } +.#{$fa-css-prefix}-drivers-license:before, +.#{$fa-css-prefix}-id-card:before { content: $fa-var-id-card; } +.#{$fa-css-prefix}-drivers-license-o:before, +.#{$fa-css-prefix}-id-card-o:before { content: $fa-var-id-card-o; } +.#{$fa-css-prefix}-quora:before { content: $fa-var-quora; } +.#{$fa-css-prefix}-free-code-camp:before { content: $fa-var-free-code-camp; } +.#{$fa-css-prefix}-telegram:before { content: $fa-var-telegram; } +.#{$fa-css-prefix}-thermometer-4:before, +.#{$fa-css-prefix}-thermometer:before, +.#{$fa-css-prefix}-thermometer-full:before { content: $fa-var-thermometer-full; } +.#{$fa-css-prefix}-thermometer-3:before, +.#{$fa-css-prefix}-thermometer-three-quarters:before { content: $fa-var-thermometer-three-quarters; } +.#{$fa-css-prefix}-thermometer-2:before, +.#{$fa-css-prefix}-thermometer-half:before { content: $fa-var-thermometer-half; } +.#{$fa-css-prefix}-thermometer-1:before, +.#{$fa-css-prefix}-thermometer-quarter:before { content: $fa-var-thermometer-quarter; } +.#{$fa-css-prefix}-thermometer-0:before, +.#{$fa-css-prefix}-thermometer-empty:before { content: $fa-var-thermometer-empty; } +.#{$fa-css-prefix}-shower:before { content: $fa-var-shower; } +.#{$fa-css-prefix}-bathtub:before, +.#{$fa-css-prefix}-s15:before, +.#{$fa-css-prefix}-bath:before { content: $fa-var-bath; } +.#{$fa-css-prefix}-podcast:before { content: $fa-var-podcast; } +.#{$fa-css-prefix}-window-maximize:before { content: $fa-var-window-maximize; } +.#{$fa-css-prefix}-window-minimize:before { content: $fa-var-window-minimize; } +.#{$fa-css-prefix}-window-restore:before { content: $fa-var-window-restore; } +.#{$fa-css-prefix}-times-rectangle:before, +.#{$fa-css-prefix}-window-close:before { content: $fa-var-window-close; } +.#{$fa-css-prefix}-times-rectangle-o:before, +.#{$fa-css-prefix}-window-close-o:before { content: $fa-var-window-close-o; } +.#{$fa-css-prefix}-bandcamp:before { content: $fa-var-bandcamp; } +.#{$fa-css-prefix}-grav:before { content: $fa-var-grav; } +.#{$fa-css-prefix}-etsy:before { content: $fa-var-etsy; } +.#{$fa-css-prefix}-imdb:before { content: $fa-var-imdb; } +.#{$fa-css-prefix}-ravelry:before { content: $fa-var-ravelry; } +.#{$fa-css-prefix}-eercast:before { content: $fa-var-eercast; } +.#{$fa-css-prefix}-microchip:before { content: $fa-var-microchip; } +.#{$fa-css-prefix}-snowflake-o:before { content: $fa-var-snowflake-o; } +.#{$fa-css-prefix}-superpowers:before { content: $fa-var-superpowers; } +.#{$fa-css-prefix}-wpexplorer:before { content: $fa-var-wpexplorer; } +.#{$fa-css-prefix}-meetup:before { content: $fa-var-meetup; } diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_mixins.scss b/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_mixins.scss index f96719b6..c3bbd574 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_mixins.scss +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_mixins.scss @@ -12,15 +12,49 @@ } @mixin fa-icon-rotate($degrees, $rotation) { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; -webkit-transform: rotate($degrees); -ms-transform: rotate($degrees); transform: rotate($degrees); } @mixin fa-icon-flip($horiz, $vert, $rotation) { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; -webkit-transform: scale($horiz, $vert); -ms-transform: scale($horiz, $vert); transform: scale($horiz, $vert); } + + +// Only display content to screen readers. A la Bootstrap 4. +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +@mixin sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +@mixin sr-only-focusable { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_screen-reader.scss b/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_screen-reader.scss new file mode 100644 index 00000000..637426f0 --- /dev/null +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_screen-reader.scss @@ -0,0 +1,5 @@ +// Screen Readers +// ------------------------- + +.sr-only { @include sr-only(); } +.sr-only-focusable { @include sr-only-focusable(); } diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_variables.scss b/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_variables.scss index 0a471102..498fc4a0 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_variables.scss +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/_variables.scss @@ -4,14 +4,18 @@ $fa-font-path: "../fonts" !default; $fa-font-size-base: 14px !default; $fa-line-height-base: 1 !default; -//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.5.0/fonts" !default; // for referencing Bootstrap CDN font files directly +//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts" !default; // for referencing Bootstrap CDN font files directly $fa-css-prefix: fa !default; -$fa-version: "4.5.0" !default; +$fa-version: "4.7.0" !default; $fa-border-color: #eee !default; $fa-inverse: #fff !default; $fa-li-width: (30em / 14) !default; $fa-var-500px: "\f26e"; +$fa-var-address-book: "\f2b9"; +$fa-var-address-book-o: "\f2ba"; +$fa-var-address-card: "\f2bb"; +$fa-var-address-card-o: "\f2bc"; $fa-var-adjust: "\f042"; $fa-var-adn: "\f170"; $fa-var-align-center: "\f037"; @@ -20,6 +24,7 @@ $fa-var-align-left: "\f036"; $fa-var-align-right: "\f038"; $fa-var-amazon: "\f270"; $fa-var-ambulance: "\f0f9"; +$fa-var-american-sign-language-interpreting: "\f2a3"; $fa-var-anchor: "\f13d"; $fa-var-android: "\f17b"; $fa-var-angellist: "\f209"; @@ -50,17 +55,24 @@ $fa-var-arrows: "\f047"; $fa-var-arrows-alt: "\f0b2"; $fa-var-arrows-h: "\f07e"; $fa-var-arrows-v: "\f07d"; +$fa-var-asl-interpreting: "\f2a3"; +$fa-var-assistive-listening-systems: "\f2a2"; $fa-var-asterisk: "\f069"; $fa-var-at: "\f1fa"; +$fa-var-audio-description: "\f29e"; $fa-var-automobile: "\f1b9"; $fa-var-backward: "\f04a"; $fa-var-balance-scale: "\f24e"; $fa-var-ban: "\f05e"; +$fa-var-bandcamp: "\f2d5"; $fa-var-bank: "\f19c"; $fa-var-bar-chart: "\f080"; $fa-var-bar-chart-o: "\f080"; $fa-var-barcode: "\f02a"; $fa-var-bars: "\f0c9"; +$fa-var-bath: "\f2cd"; +$fa-var-bathtub: "\f2cd"; +$fa-var-battery: "\f240"; $fa-var-battery-0: "\f244"; $fa-var-battery-1: "\f243"; $fa-var-battery-2: "\f242"; @@ -86,6 +98,7 @@ $fa-var-bitbucket: "\f171"; $fa-var-bitbucket-square: "\f172"; $fa-var-bitcoin: "\f15a"; $fa-var-black-tie: "\f27e"; +$fa-var-blind: "\f29d"; $fa-var-bluetooth: "\f293"; $fa-var-bluetooth-b: "\f294"; $fa-var-bold: "\f032"; @@ -94,6 +107,7 @@ $fa-var-bomb: "\f1e2"; $fa-var-book: "\f02d"; $fa-var-bookmark: "\f02e"; $fa-var-bookmark-o: "\f097"; +$fa-var-braille: "\f2a1"; $fa-var-briefcase: "\f0b1"; $fa-var-btc: "\f15a"; $fa-var-bug: "\f188"; @@ -196,6 +210,8 @@ $fa-var-cutlery: "\f0f5"; $fa-var-dashboard: "\f0e4"; $fa-var-dashcube: "\f210"; $fa-var-database: "\f1c0"; +$fa-var-deaf: "\f2a4"; +$fa-var-deafness: "\f2a4"; $fa-var-dedent: "\f03b"; $fa-var-delicious: "\f1a5"; $fa-var-desktop: "\f108"; @@ -206,18 +222,25 @@ $fa-var-dollar: "\f155"; $fa-var-dot-circle-o: "\f192"; $fa-var-download: "\f019"; $fa-var-dribbble: "\f17d"; +$fa-var-drivers-license: "\f2c2"; +$fa-var-drivers-license-o: "\f2c3"; $fa-var-dropbox: "\f16b"; $fa-var-drupal: "\f1a9"; $fa-var-edge: "\f282"; $fa-var-edit: "\f044"; +$fa-var-eercast: "\f2da"; $fa-var-eject: "\f052"; $fa-var-ellipsis-h: "\f141"; $fa-var-ellipsis-v: "\f142"; $fa-var-empire: "\f1d1"; $fa-var-envelope: "\f0e0"; $fa-var-envelope-o: "\f003"; +$fa-var-envelope-open: "\f2b6"; +$fa-var-envelope-open-o: "\f2b7"; $fa-var-envelope-square: "\f199"; +$fa-var-envira: "\f299"; $fa-var-eraser: "\f12d"; +$fa-var-etsy: "\f2d7"; $fa-var-eur: "\f153"; $fa-var-euro: "\f153"; $fa-var-exchange: "\f0ec"; @@ -231,6 +254,7 @@ $fa-var-external-link-square: "\f14c"; $fa-var-eye: "\f06e"; $fa-var-eye-slash: "\f070"; $fa-var-eyedropper: "\f1fb"; +$fa-var-fa: "\f2b4"; $fa-var-facebook: "\f09a"; $fa-var-facebook-f: "\f09a"; $fa-var-facebook-official: "\f230"; @@ -265,6 +289,7 @@ $fa-var-filter: "\f0b0"; $fa-var-fire: "\f06d"; $fa-var-fire-extinguisher: "\f134"; $fa-var-firefox: "\f269"; +$fa-var-first-order: "\f2b0"; $fa-var-flag: "\f024"; $fa-var-flag-checkered: "\f11e"; $fa-var-flag-o: "\f11d"; @@ -277,11 +302,13 @@ $fa-var-folder-o: "\f114"; $fa-var-folder-open: "\f07c"; $fa-var-folder-open-o: "\f115"; $fa-var-font: "\f031"; +$fa-var-font-awesome: "\f2b4"; $fa-var-fonticons: "\f280"; $fa-var-fort-awesome: "\f286"; $fa-var-forumbee: "\f211"; $fa-var-forward: "\f04e"; $fa-var-foursquare: "\f180"; +$fa-var-free-code-camp: "\f2c5"; $fa-var-frown-o: "\f119"; $fa-var-futbol-o: "\f1e3"; $fa-var-gamepad: "\f11b"; @@ -300,15 +327,21 @@ $fa-var-git-square: "\f1d2"; $fa-var-github: "\f09b"; $fa-var-github-alt: "\f113"; $fa-var-github-square: "\f092"; +$fa-var-gitlab: "\f296"; $fa-var-gittip: "\f184"; $fa-var-glass: "\f000"; +$fa-var-glide: "\f2a5"; +$fa-var-glide-g: "\f2a6"; $fa-var-globe: "\f0ac"; $fa-var-google: "\f1a0"; $fa-var-google-plus: "\f0d5"; +$fa-var-google-plus-circle: "\f2b3"; +$fa-var-google-plus-official: "\f2b3"; $fa-var-google-plus-square: "\f0d4"; $fa-var-google-wallet: "\f1ee"; $fa-var-graduation-cap: "\f19d"; $fa-var-gratipay: "\f184"; +$fa-var-grav: "\f2d6"; $fa-var-group: "\f0c0"; $fa-var-h-square: "\f0fd"; $fa-var-hacker-news: "\f1d4"; @@ -325,6 +358,8 @@ $fa-var-hand-rock-o: "\f255"; $fa-var-hand-scissors-o: "\f257"; $fa-var-hand-spock-o: "\f259"; $fa-var-hand-stop-o: "\f256"; +$fa-var-handshake-o: "\f2b5"; +$fa-var-hard-of-hearing: "\f2a4"; $fa-var-hashtag: "\f292"; $fa-var-hdd-o: "\f0a0"; $fa-var-header: "\f1dc"; @@ -347,8 +382,12 @@ $fa-var-hourglass-start: "\f251"; $fa-var-houzz: "\f27c"; $fa-var-html5: "\f13b"; $fa-var-i-cursor: "\f246"; +$fa-var-id-badge: "\f2c1"; +$fa-var-id-card: "\f2c2"; +$fa-var-id-card-o: "\f2c3"; $fa-var-ils: "\f20b"; $fa-var-image: "\f03e"; +$fa-var-imdb: "\f2d8"; $fa-var-inbox: "\f01c"; $fa-var-indent: "\f03c"; $fa-var-industry: "\f275"; @@ -386,6 +425,7 @@ $fa-var-line-chart: "\f201"; $fa-var-link: "\f0c1"; $fa-var-linkedin: "\f0e1"; $fa-var-linkedin-square: "\f08c"; +$fa-var-linode: "\f2b8"; $fa-var-linux: "\f17c"; $fa-var-list: "\f03a"; $fa-var-list-alt: "\f022"; @@ -397,6 +437,7 @@ $fa-var-long-arrow-down: "\f175"; $fa-var-long-arrow-left: "\f177"; $fa-var-long-arrow-right: "\f178"; $fa-var-long-arrow-up: "\f176"; +$fa-var-low-vision: "\f2a8"; $fa-var-magic: "\f0d0"; $fa-var-magnet: "\f076"; $fa-var-mail-forward: "\f064"; @@ -417,8 +458,10 @@ $fa-var-maxcdn: "\f136"; $fa-var-meanpath: "\f20c"; $fa-var-medium: "\f23a"; $fa-var-medkit: "\f0fa"; +$fa-var-meetup: "\f2e0"; $fa-var-meh-o: "\f11a"; $fa-var-mercury: "\f223"; +$fa-var-microchip: "\f2db"; $fa-var-microphone: "\f130"; $fa-var-microphone-slash: "\f131"; $fa-var-minus: "\f068"; @@ -468,8 +511,9 @@ $fa-var-phone-square: "\f098"; $fa-var-photo: "\f03e"; $fa-var-picture-o: "\f03e"; $fa-var-pie-chart: "\f200"; -$fa-var-pied-piper: "\f1a7"; +$fa-var-pied-piper: "\f2ae"; $fa-var-pied-piper-alt: "\f1a8"; +$fa-var-pied-piper-pp: "\f1a7"; $fa-var-pinterest: "\f0d2"; $fa-var-pinterest-p: "\f231"; $fa-var-pinterest-square: "\f0d3"; @@ -482,6 +526,7 @@ $fa-var-plus: "\f067"; $fa-var-plus-circle: "\f055"; $fa-var-plus-square: "\f0fe"; $fa-var-plus-square-o: "\f196"; +$fa-var-podcast: "\f2ce"; $fa-var-power-off: "\f011"; $fa-var-print: "\f02f"; $fa-var-product-hunt: "\f288"; @@ -490,10 +535,13 @@ $fa-var-qq: "\f1d6"; $fa-var-qrcode: "\f029"; $fa-var-question: "\f128"; $fa-var-question-circle: "\f059"; +$fa-var-question-circle-o: "\f29c"; +$fa-var-quora: "\f2c4"; $fa-var-quote-left: "\f10d"; $fa-var-quote-right: "\f10e"; $fa-var-ra: "\f1d0"; $fa-var-random: "\f074"; +$fa-var-ravelry: "\f2d9"; $fa-var-rebel: "\f1d0"; $fa-var-recycle: "\f1b8"; $fa-var-reddit: "\f1a1"; @@ -507,6 +555,7 @@ $fa-var-reorder: "\f0c9"; $fa-var-repeat: "\f01e"; $fa-var-reply: "\f112"; $fa-var-reply-all: "\f122"; +$fa-var-resistance: "\f1d0"; $fa-var-retweet: "\f079"; $fa-var-rmb: "\f157"; $fa-var-road: "\f018"; @@ -519,6 +568,7 @@ $fa-var-rss-square: "\f143"; $fa-var-rub: "\f158"; $fa-var-ruble: "\f158"; $fa-var-rupee: "\f156"; +$fa-var-s15: "\f2cd"; $fa-var-safari: "\f267"; $fa-var-save: "\f0c7"; $fa-var-scissors: "\f0c4"; @@ -543,9 +593,12 @@ $fa-var-shirtsinbulk: "\f214"; $fa-var-shopping-bag: "\f290"; $fa-var-shopping-basket: "\f291"; $fa-var-shopping-cart: "\f07a"; +$fa-var-shower: "\f2cc"; $fa-var-sign-in: "\f090"; +$fa-var-sign-language: "\f2a7"; $fa-var-sign-out: "\f08b"; $fa-var-signal: "\f012"; +$fa-var-signing: "\f2a7"; $fa-var-simplybuilt: "\f215"; $fa-var-sitemap: "\f0e8"; $fa-var-skyatlas: "\f216"; @@ -554,6 +607,10 @@ $fa-var-slack: "\f198"; $fa-var-sliders: "\f1de"; $fa-var-slideshare: "\f1e7"; $fa-var-smile-o: "\f118"; +$fa-var-snapchat: "\f2ab"; +$fa-var-snapchat-ghost: "\f2ac"; +$fa-var-snapchat-square: "\f2ad"; +$fa-var-snowflake-o: "\f2dc"; $fa-var-soccer-ball-o: "\f1e3"; $fa-var-sort: "\f0dc"; $fa-var-sort-alpha-asc: "\f15d"; @@ -599,6 +656,7 @@ $fa-var-subscript: "\f12c"; $fa-var-subway: "\f239"; $fa-var-suitcase: "\f0f2"; $fa-var-sun-o: "\f185"; +$fa-var-superpowers: "\f2dd"; $fa-var-superscript: "\f12b"; $fa-var-support: "\f1cd"; $fa-var-table: "\f0ce"; @@ -608,6 +666,7 @@ $fa-var-tag: "\f02b"; $fa-var-tags: "\f02c"; $fa-var-tasks: "\f0ae"; $fa-var-taxi: "\f1ba"; +$fa-var-telegram: "\f2c6"; $fa-var-television: "\f26c"; $fa-var-tencent-weibo: "\f1d5"; $fa-var-terminal: "\f120"; @@ -616,6 +675,18 @@ $fa-var-text-width: "\f035"; $fa-var-th: "\f00a"; $fa-var-th-large: "\f009"; $fa-var-th-list: "\f00b"; +$fa-var-themeisle: "\f2b2"; +$fa-var-thermometer: "\f2c7"; +$fa-var-thermometer-0: "\f2cb"; +$fa-var-thermometer-1: "\f2ca"; +$fa-var-thermometer-2: "\f2c9"; +$fa-var-thermometer-3: "\f2c8"; +$fa-var-thermometer-4: "\f2c7"; +$fa-var-thermometer-empty: "\f2cb"; +$fa-var-thermometer-full: "\f2c7"; +$fa-var-thermometer-half: "\f2c9"; +$fa-var-thermometer-quarter: "\f2ca"; +$fa-var-thermometer-three-quarters: "\f2c8"; $fa-var-thumb-tack: "\f08d"; $fa-var-thumbs-down: "\f165"; $fa-var-thumbs-o-down: "\f088"; @@ -625,6 +696,8 @@ $fa-var-ticket: "\f145"; $fa-var-times: "\f00d"; $fa-var-times-circle: "\f057"; $fa-var-times-circle-o: "\f05c"; +$fa-var-times-rectangle: "\f2d3"; +$fa-var-times-rectangle-o: "\f2d4"; $fa-var-tint: "\f043"; $fa-var-toggle-down: "\f150"; $fa-var-toggle-left: "\f191"; @@ -655,6 +728,7 @@ $fa-var-twitter-square: "\f081"; $fa-var-umbrella: "\f0e9"; $fa-var-underline: "\f0cd"; $fa-var-undo: "\f0e2"; +$fa-var-universal-access: "\f29a"; $fa-var-university: "\f19c"; $fa-var-unlink: "\f127"; $fa-var-unlock: "\f09c"; @@ -664,20 +738,28 @@ $fa-var-upload: "\f093"; $fa-var-usb: "\f287"; $fa-var-usd: "\f155"; $fa-var-user: "\f007"; +$fa-var-user-circle: "\f2bd"; +$fa-var-user-circle-o: "\f2be"; $fa-var-user-md: "\f0f0"; +$fa-var-user-o: "\f2c0"; $fa-var-user-plus: "\f234"; $fa-var-user-secret: "\f21b"; $fa-var-user-times: "\f235"; $fa-var-users: "\f0c0"; +$fa-var-vcard: "\f2bb"; +$fa-var-vcard-o: "\f2bc"; $fa-var-venus: "\f221"; $fa-var-venus-double: "\f226"; $fa-var-venus-mars: "\f228"; $fa-var-viacoin: "\f237"; +$fa-var-viadeo: "\f2a9"; +$fa-var-viadeo-square: "\f2aa"; $fa-var-video-camera: "\f03d"; $fa-var-vimeo: "\f27d"; $fa-var-vimeo-square: "\f194"; $fa-var-vine: "\f1ca"; $fa-var-vk: "\f189"; +$fa-var-volume-control-phone: "\f2a0"; $fa-var-volume-down: "\f027"; $fa-var-volume-off: "\f026"; $fa-var-volume-up: "\f028"; @@ -687,11 +769,20 @@ $fa-var-weibo: "\f18a"; $fa-var-weixin: "\f1d7"; $fa-var-whatsapp: "\f232"; $fa-var-wheelchair: "\f193"; +$fa-var-wheelchair-alt: "\f29b"; $fa-var-wifi: "\f1eb"; $fa-var-wikipedia-w: "\f266"; +$fa-var-window-close: "\f2d3"; +$fa-var-window-close-o: "\f2d4"; +$fa-var-window-maximize: "\f2d0"; +$fa-var-window-minimize: "\f2d1"; +$fa-var-window-restore: "\f2d2"; $fa-var-windows: "\f17a"; $fa-var-won: "\f159"; $fa-var-wordpress: "\f19a"; +$fa-var-wpbeginner: "\f297"; +$fa-var-wpexplorer: "\f2de"; +$fa-var-wpforms: "\f298"; $fa-var-wrench: "\f0ad"; $fa-var-xing: "\f168"; $fa-var-xing-square: "\f169"; @@ -702,6 +793,7 @@ $fa-var-yc: "\f23b"; $fa-var-yc-square: "\f1d4"; $fa-var-yelp: "\f1e9"; $fa-var-yen: "\f157"; +$fa-var-yoast: "\f2b1"; $fa-var-youtube: "\f167"; $fa-var-youtube-play: "\f16a"; $fa-var-youtube-square: "\f166"; diff --git a/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/font-awesome.scss b/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/font-awesome.scss index f4668a53..f1c83aaa 100644 --- a/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/font-awesome.scss +++ b/mopidy_musicbox_webclient/static/vendors/font_awesome/scss/font-awesome.scss @@ -1,5 +1,5 @@ /*! - * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ @@ -15,3 +15,4 @@ @import "rotated-flipped"; @import "stacked"; @import "icons"; +@import "screen-reader"; diff --git a/mopidy_musicbox_webclient/web.py b/mopidy_musicbox_webclient/web.py index 3ee687ba..72f62fd5 100644 --- a/mopidy_musicbox_webclient/web.py +++ b/mopidy_musicbox_webclient/web.py @@ -3,7 +3,9 @@ import json import logging +import socket import string +import urlparse import tornado.web @@ -33,23 +35,34 @@ def initialize(self, config, path): webclient = mmw.Webclient(config) + if webclient.is_music_box(): + program_name = 'MusicBox' + else: + program_name = 'Mopidy' + + url = urlparse.urlparse('%s://%s' % (self.request.protocol, self.request.host)) + port = url.port or 80 + self.__dict = { 'isMusicBox': json.dumps(webclient.is_music_box()), 'websocketUrl': webclient.get_websocket_url(self.request), 'hasAlarmClock': json.dumps(webclient.has_alarm_clock()), - 'onTrackClick': webclient.get_default_click_action() + 'onTrackClick': webclient.get_default_click_action(), + 'programName': program_name, + 'hostname': url.hostname, + 'serverIP': socket.gethostbyname(url.hostname), + 'serverPort': port + } self.__path = path - self.__title = string.Template('MusicBox on $hostname') + self.__title = string.Template('{} on $hostname'.format(program_name)) def get(self, path): return self.render(path, title=self.get_title(), **self.__dict) def get_title(self): - hostname, sep, port = self.request.host.rpartition(':') - if not sep or not port.isdigit(): - hostname, port = self.request.host, '80' - return self.__title.safe_substitute(hostname=hostname, port=port) + url = urlparse.urlparse('%s://%s' % (self.request.protocol, self.request.host)) + return self.__title.safe_substitute(hostname=url.hostname) def get_template_path(self): return self.__path diff --git a/mopidy_musicbox_webclient/webclient.py b/mopidy_musicbox_webclient/webclient.py index e4c6e795..91754294 100644 --- a/mopidy_musicbox_webclient/webclient.py +++ b/mopidy_musicbox_webclient/webclient.py @@ -26,21 +26,21 @@ def get_websocket_url(self, request): if host or port: if not host: host = request.host.partition(':')[0] - logger.warning('Musicbox websocket_host not specified, ' + logger.warning('Mopidy websocket_host not specified, ' 'using %s', host) elif not port: port = self.config['http']['port'] - logger.warning('Musicbox websocket_port not specified, ' + logger.warning('Mopidy websocket_port not specified, ' 'using %s', port) protocol = 'ws' if request.protocol == 'https': protocol = 'wss' - ws_url = "%s://%s:%d/mopidy/ws" % (protocol, host, port) + ws_url = '%s://%s:%d/mopidy/ws' % (protocol, host, port) return ws_url def has_alarm_clock(self): - return self.ext_config.get('alarmclock', {}).get('enabled', False) + return self.config.get('alarmclock', {}).get('enabled', False) def is_music_box(self): return self.ext_config.get('musicbox', False) diff --git a/screenshots/mobile_overview.png b/screenshots/mobile_overview.png new file mode 100644 index 00000000..5c5ab672 Binary files /dev/null and b/screenshots/mobile_overview.png differ diff --git a/screenshots/overview.png b/screenshots/overview.png new file mode 100644 index 00000000..370a6845 Binary files /dev/null and b/screenshots/overview.png differ diff --git a/screenshots/queue_desktop.png b/screenshots/queue_desktop.png index 5113a03a..f117ec11 100644 Binary files a/screenshots/queue_desktop.png and b/screenshots/queue_desktop.png differ diff --git a/setup.py b/setup.py index 286077f6..d56c4368 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ def get_version(filename): setup( name='Mopidy-MusicBox-Webclient', version=get_version('mopidy_musicbox_webclient/__init__.py'), - url='https://github.com/woutervanwijk/mopidy-musicbox-webclient', + url='https://github.com/pimusicbox/mopidy-musicbox-webclient', license='Apache License, Version 2.0', author='Wouter van Wijk', author_email='woutervanwijk@gmail.com', diff --git a/tests/js/dummy_tracklist.js b/tests/js/dummy_tracklist.js index 0401dba6..0919f84f 100644 --- a/tests/js/dummy_tracklist.js +++ b/tests/js/dummy_tracklist.js @@ -31,10 +31,13 @@ throw new Error('DummyTracklist.add does not support deprecated "tracks" and "uri" parameters.') } + var position = params.at_position // Add tracks to end of tracklist if no position is provided - params.at_position = params.at_position || this._tlTracks.length + if (typeof position === 'undefined') { + position = Math.max(0, this._tlTracks.length) + } + var tlTrack - var tlTracks = [] for (var i = 0; i < params.uris.length; i++) { tlTrack = { tlid: this._nextTlid++, @@ -42,11 +45,10 @@ uri: params.uris[i] } } - tlTracks.push(tlTrack) - this._tlTracks.splice(params.at_position + i, 0, tlTrack) + this._tlTracks.splice(position++, 0, tlTrack) } - return $.when(tlTracks) + return $.when(this._tlTracks) } /* Clears the tracklist */ @@ -54,6 +56,21 @@ this._tlTracks = [] } + /* Remove the matching tracks from the tracklist */ + DummyTracklist.prototype.remove = function (criteria) { + this.filter(criteria).then( function (matches) { + for (var i = 0; i < matches.length; i++) { + for (var j = 0; j < this._tlTracks.length; j++) { + if (this._tlTracks[j].track.uri === matches[i].track.uri) { + this._tlTracks.splice(j, 1) + } + } + } + }.bind(this)) + + return $.when(this._tlTracks) + } + /** * Retuns a list containing tlTracks that contain the provided * criteria.uri or has ID criteria.tlid. @@ -89,11 +106,11 @@ /* Retuns index of the currently 'playing' track. */ DummyTracklist.prototype.index = function (params) { if (!params) { - if (this._tlTracks.length > 1) { - // Always just assume that the second track is playing - return $.when(1) - } else { + if (this._tlTracks.length > 0) { + // Always just assume that the first track is playing return $.when(0) + } else { + return $.when(null) } } for (var i = 0; i < this._tlTracks.length; i++) { @@ -101,7 +118,17 @@ return $.when(i) } } - return $.when(0) + return $.when(null) + } + + /* Returns the tracks in the tracklist */ + DummyTracklist.prototype.get_tl_tracks = function () { + return $.when(this._tlTracks) + } + + /* Returns the length of the tracklist */ + DummyTracklist.prototype.get_length = function () { + return $.when(this._tlTracks.length) } return DummyTracklist diff --git a/tests/js/test_controls.js b/tests/js/test_controls.js index 23a6825a..b181f61c 100644 --- a/tests/js/test_controls.js +++ b/tests/js/test_controls.js @@ -13,7 +13,7 @@ describe('controls', function () { var mopidy var div_element var QUEUE_TRACKS = [ // Simulate an existing queue with three tracks loaded. - {uri: 'track:tlTrackMock1'}, + {uri: 'track:tlTrackMock1'}, // <-- Currently playing track {uri: 'track:tlTrackMock2'}, {uri: 'track:tlTrackMock3'} ] @@ -48,11 +48,8 @@ describe('controls', function () { mopidy.tracklist.clear() clearSpy.reset() mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)}) - }) - - afterEach(function () { - mopidy.playback.play.reset() addSpy.reset() + mopidy.playback.play.reset() }) after(function () { @@ -62,62 +59,62 @@ describe('controls', function () { describe('#playTracks()', function () { it('PLAY_ALL should clear tracklist first before populating with tracks', function () { - customTracklists[BROWSE_TABLE] = NEW_TRACKS - controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[0].uri, BROWSE_TABLE) + customTracklists[CURRENT_PLAYLIST_TABLE] = NEW_TRACKS + controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[0].uri, CURRENT_PLAYLIST_TABLE) assert(clearSpy.called) }) it('should not clear tracklist for events other than PLAY_ALL', function () { - customTracklists[BROWSE_TABLE] = NEW_TRACKS - controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri, BROWSE_TABLE) + customTracklists[CURRENT_PLAYLIST_TABLE] = NEW_TRACKS + controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri, CURRENT_PLAYLIST_TABLE) assert(clearSpy.notCalled) }) it('should raise exception if trackUri parameter is not provided and "track" data attribute is empty', function () { assert.throw(function () { controls.playTracks('', mopidy) }, Error) - controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[0].uri, BROWSE_TABLE) + controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[0].uri, CURRENT_PLAYLIST_TABLE) assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[0].tlid})) }) it('should raise exception if playListUri parameter is not provided and "track" data attribute is empty', function () { assert.throw(function () { controls.playTracks('', mopidy, NEW_TRACKS[0].uri) }, Error) - controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[0].uri, BROWSE_TABLE) + controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[0].uri, CURRENT_PLAYLIST_TABLE) assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[0].tlid})) }) it('should raise exception if unknown tracklist action is provided', function () { var getTrackURIsForActionStub = sinon.stub(controls, '_getTrackURIsForAction') // Stub to bypass earlier exception - assert.throw(function () { controls.playTracks('99', mopidy, NEW_TRACKS[0].uri, BROWSE_TABLE) }, Error) + assert.throw(function () { controls.playTracks('99', mopidy, NEW_TRACKS[0].uri, CURRENT_PLAYLIST_TABLE) }, Error) getTrackURIsForActionStub.restore() }) it('should use "track" and "list" data attributes as fallback if parameters are not provided', function () { $('#popupTracks').data('track', 'track:trackMock1') // Simulate 'track:trackMock1' being clicked. - $('#popupTracks').data('list', BROWSE_TABLE) - customTracklists[BROWSE_TABLE] = NEW_TRACKS + $('#popupTracks').data('list', CURRENT_PLAYLIST_TABLE) + customTracklists[CURRENT_PLAYLIST_TABLE] = NEW_TRACKS controls.playTracks(PLAY_ALL, mopidy) assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[0].tlid})) }) it('PLAY_NOW, PLAY_NEXT, and ADD_THIS_BOTTOM should only add one track to the tracklist', function () { - controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri) - assert(addSpy.calledWithMatch({at_position: 2, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NOW did not add correct track') - addSpy.reset() + controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri, CURRENT_PLAYLIST_TABLE) + assert(addSpy.calledWithMatch({at_position: 1, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NOW did not add correct track') mopidy.tracklist.clear() mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)}) - - controls.playTracks(PLAY_NEXT, mopidy, NEW_TRACKS[0].uri) - assert(addSpy.calledWithMatch({at_position: 2, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NEXT did not add correct track') addSpy.reset() + controls.playTracks(PLAY_NEXT, mopidy, NEW_TRACKS[0].uri, CURRENT_PLAYLIST_TABLE) + assert(addSpy.calledWithMatch({at_position: 1, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NEXT did not add correct track') + mopidy.tracklist.clear() mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)}) + addSpy.reset() - controls.playTracks(ADD_THIS_BOTTOM, mopidy, NEW_TRACKS[0].uri) + controls.playTracks(ADD_THIS_BOTTOM, mopidy, NEW_TRACKS[0].uri, CURRENT_PLAYLIST_TABLE) assert(addSpy.calledWithMatch({uris: [NEW_TRACKS[0].uri]}), 'ADD_THIS_BOTTOM did not add correct track') }) @@ -133,21 +130,36 @@ describe('controls', function () { assert(addSpy.calledWithMatch({uris: getUris(NEW_TRACKS)}), 'ADD_ALL_BOTTOM did not add correct tracks') }) - it('PLAY_NOW and PLAY_NEXT should insert track after currently playing track', function () { + it('PLAY_NEXT should insert track after currently playing track by default', function () { + controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri) + assert(addSpy.calledWithMatch({at_position: 1, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NEXT did not insert track at correct position') + }) + + it('PLAY_NEXT should insert track after reference track index, if provided', function () { + controls.playTracks(PLAY_NEXT, mopidy, NEW_TRACKS[0].uri, '', 0) + assert(addSpy.calledWithMatch({at_position: 1, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NEXT did not insert track at correct position') + }) + + it('PLAY_NEXT should insert track even if queue is empty', function () { + mopidy.tracklist.clear() + controls.playTracks(PLAY_NEXT, mopidy, NEW_TRACKS[0].uri) + assert(addSpy.calledWithMatch({at_position: 0, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NEXT did not insert track at correct position') + }) + + it('PLAY_NOW should always insert track at current index', function () { controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri) - assert(addSpy.calledWithMatch({at_position: 2, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NOW did not insert track at correct position') + assert(addSpy.calledWithMatch({at_position: 1, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NOW did not insert track at correct position') addSpy.reset() mopidy.tracklist.clear() - mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)}) - controls.playTracks(PLAY_NEXT, mopidy, NEW_TRACKS[0].uri) - assert(addSpy.calledWithMatch({at_position: 2, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NEXT did not insert track at correct position') + controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri) + assert(addSpy.calledWithMatch({at_position: 0, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NOW did not insert track at correct position') }) it('only PLAY_NOW and PLAY_ALL should trigger playback', function () { - controls.playTracks(PLAY_NOW, mopidy, 2) - assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[2].tlid}), 'PLAY_NOW did not start playback of correct track') + controls.playTracks(PLAY_NOW, mopidy) + assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[0].tlid}), 'PLAY_NOW did not start playback of correct track') mopidy.playback.play.reset() mopidy.tracklist.clear() @@ -182,12 +194,12 @@ describe('controls', function () { it('should store last action in cookie if on-track-click mode is set to "DYNAMIC"', function () { $(document.body).data('on-track-click', 'DYNAMIC') var cookieStub = sinon.stub($, 'cookie') - controls.playTracks(PLAY_NOW, mopidy, 2) + controls.playTracks(PLAY_NOW, mopidy) assert(cookieStub.calledWithMatch('onTrackClick', PLAY_NOW, {expires: 365})) cookieStub.reset() $(document.body).data('on-track-click', 'PLAY_NOW') - controls.playTracks(PLAY_NOW, mopidy, 2) + controls.playTracks(PLAY_NOW, mopidy) assert(cookieStub.notCalled) cookieStub.restore() }) @@ -245,9 +257,9 @@ describe('controls', function () { }) it('should get tracks from "playlistUri" for PLAY_ALL, and ADD_ALL_BOTTOM', function () { - customTracklists[BROWSE_TABLE] = NEW_TRACKS + customTracklists[CURRENT_PLAYLIST_TABLE] = NEW_TRACKS - var tracks = controls._getTrackURIsForAction(PLAY_ALL, NEW_TRACKS[0], BROWSE_TABLE) + var tracks = controls._getTrackURIsForAction(PLAY_ALL, NEW_TRACKS[0], CURRENT_PLAYLIST_TABLE) assert.equal(tracks.length, NEW_TRACKS.length) for (var i = 0; i < tracks.length; i++) { assert.equal(tracks[i], NEW_TRACKS[i].uri) @@ -262,4 +274,144 @@ describe('controls', function () { assert.equal(controls._getTrackURIsForAction('0', 'mockUri')[0], 'mockUri') }) }) + + describe('#insertTrack()', function () { + it('should raise exception if no uri is provided', function () { + assert.throw(function () { controls.insertTrack() }, Error) + }) + + it('should insert track after currently playing track by default', function () { + var tracklistLength = QUEUE_TRACKS.length + var insertUri = NEW_TRACKS[0].uri + + controls.insertTrack(insertUri, mopidy) + + mopidy.tracklist.get_length().then(function (length) { + assert.equal(length, tracklistLength + 1) + }) + + mopidy.tracklist.index().then(function (index) { + mopidy.tracklist.get_tl_tracks().then(function (tlTracks) { + assert.equal(tlTracks[index + 1].track.uri, insertUri) + }) + }) + }) + + it('should insert track at provided index', function () { + var tracklistLength = QUEUE_TRACKS.length + var insertUri = NEW_TRACKS[0].uri + + mopidy.tracklist.get_tl_tracks().then(function (tlTracks) { + controls.insertTrack(insertUri, mopidy, tlTracks[1].tlid) + }) + + mopidy.tracklist.get_length().then(function (length) { + assert.equal(length, tracklistLength + 1) + }) + + mopidy.tracklist.get_tl_tracks().then(function (tlTracks) { + assert.equal(tlTracks[2].track.uri, insertUri) + }) + }) + }) + + describe('#addTrackToBottom()', function () { + it('should raise exception if no uri is provided', function () { + assert.throw(function () { controls.addTrackToBottom() }, Error) + }) + + it('should add track at bottom of tracklist', function () { + var tracklistLength = QUEUE_TRACKS.length + var insertUri = NEW_TRACKS[0].uri + + controls.addTrackToBottom(insertUri, mopidy) + + mopidy.tracklist.get_length().then(function (length) { + assert.equal(length, tracklistLength + 1) + }) + + mopidy.tracklist.get_tl_tracks().then(function (tlTracks) { + assert.equal(tlTracks[tlTracks.length - 1].track.uri, insertUri) + }) + }) + }) + + describe('#removeTrack()', function () { + it('should remove track', function () { + var tracklistLength = QUEUE_TRACKS.length + var deleteUri = QUEUE_TRACKS[1].uri + + mopidy.tracklist.get_tl_tracks().then(function (tlTracks) { + controls.removeTrack(tlTracks[1].tlid, mopidy) + }) + + mopidy.tracklist.get_length().then(function (length) { + assert.equal(length, tracklistLength - 1) + }) + + mopidy.tracklist.get_tl_tracks().then(function (tlTracks) { + var found = false + for (var i = 0; i < tlTracks.length; i++) { + if (tlTracks[i].track.uri === deleteUri) { + found = true + } + } + assert(!found) + }) + }) + }) + + describe('#showInfoPopup()', function () { + var track + var popup = $('
        ') + + before(function () { + track = { + 'uri': QUEUE_TRACKS[0].uri, + 'length': 61000, + 'artists': [ + { + 'uri': 'artistUri1', + 'name': 'nameMock1' + }, { + 'uri': 'artistUri2', + 'name': 'nameMock2' + } + ] + } + var library = { + lookup: sinon.stub() + } + mopidy.library = library + mopidy.library.lookup.returns($.when({'track:tlTrackMock1': [track]})) + + $(document.body).append(popup) + $('#popupShowInfo').data(track, track.uri) // Simulate selection from context menu + $('#popupShowInfo').popup() // Initialize popup + }) + + afterEach(function () { + mopidy.library.lookup.reset() + }) + + it('should default track name', function () { + controls.showInfoPopup('', '#popupShowInfo', mopidy) + assert.equal($('td:contains("Name:")').siblings('td').text(), '(Not available)') + }) + + it('should default album name', function () { + controls.showInfoPopup('', '#popupShowInfo', mopidy) + assert.equal($('td:contains("Album:")').siblings('td').text(), '(Not available)') + }) + + it('should add leading zero if seconds length < 10', function () { + controls.showInfoPopup('', '#popupShowInfo', mopidy) + assert.equal($('td:contains("Length:")').siblings('td').text(), '1:01') + }) + + it('should show plural for artist name', function () { + controls.showInfoPopup('', '#popupShowInfo', mopidy) + assert.isOk($('td:contains("Artists:")')) + }) + }) }) diff --git a/tests/test_web.py b/tests/test_web.py index a77484cd..e50d64ea 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -46,13 +46,13 @@ def test_redirect_handler(self): response.headers['Location'].endswith('index.html') -class IndexHandlerTest(BaseTest): +class IndexHandlerTestMusicBox(BaseTest): def test_index_handler(self): response = self.fetch('/index.html', method='GET') assert response.code == 200 - def test_get_title(self): + def test_get_title_musicbox(self): response = self.fetch('/index.html', method='GET') body = tornado.escape.to_unicode(response.body) @@ -65,3 +65,32 @@ def test_initialize_sets_dictionary_objects(self): assert 'data-is-musicbox="true"' in body assert 'data-has-alarmclock="false"' in body assert 'data-websocket-url=""' in body + assert 'data-on-track-click="' in body + assert 'data-program-name="' in body + assert 'data-hostname="' in body + + +class IndexHandlerTestMopidy(BaseTest): + + def get_app(self): + extension = Extension() + self.config = config.Proxy({'musicbox_webclient': { + 'enabled': True, + 'musicbox': False, + 'websocket_host': '', + 'websocket_port': '', + } + }) + return tornado.web.Application(extension.factory(self.config, mock.Mock())) + + def test_initialize_sets_dictionary_objects(self): + response = self.fetch('/index.html', method='GET') + body = tornado.escape.to_unicode(response.body) + + assert 'data-is-musicbox="false"' in body + + def test_get_title_mopidy(self): + response = self.fetch('/index.html', method='GET') + body = tornado.escape.to_unicode(response.body) + + assert 'Mopidy on localhost' in body diff --git a/tests/test_webclient.py b/tests/test_webclient.py index e15acaa8..677627da 100644 --- a/tests/test_webclient.py +++ b/tests/test_webclient.py @@ -20,7 +20,10 @@ def setUp(self): 'musicbox': False, 'websocket_host': 'host_mock', 'websocket_port': 999, - } + }, + 'alarmclock': { + 'enabled': True, + } }) self.ext = Extension() @@ -72,7 +75,7 @@ def test_get_websocket_url_uses_http_port(self): assert self.mmw.get_websocket_url(request_mock) == 'wss://127.0.0.1:999/mopidy/ws' def test_has_alarmclock(self): - assert not self.mmw.has_alarm_clock() + assert self.mmw.has_alarm_clock() def test_is_musicbox(self): assert not self.mmw.is_music_box()