Merge Pull Request #110 into develop

master
Eugene E. Kashpureff Jr 8 years ago
commit e51edb3e20
  1. 16
      libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueService.java
  2. 9
      libresonic-main/src/main/java/org/libresonic/player/command/PersonalSettingsCommand.java
  3. 1
      libresonic-main/src/main/java/org/libresonic/player/controller/MultiController.java
  4. 2
      libresonic-main/src/main/java/org/libresonic/player/controller/PersonalSettingsController.java
  5. 5
      libresonic-main/src/main/java/org/libresonic/player/dao/UserDao.java
  6. 6
      libresonic-main/src/main/java/org/libresonic/player/dao/schema/hsql/Schema61.java
  7. 9
      libresonic-main/src/main/java/org/libresonic/player/domain/UserSettings.java
  8. 1
      libresonic-main/src/main/java/org/libresonic/player/service/SettingsService.java
  9. 1
      libresonic-main/src/main/resources/org/libresonic/player/i18n/ResourceBundle_de.properties
  10. 23
      libresonic-main/src/main/resources/org/libresonic/player/i18n/ResourceBundle_en.properties
  11. 1
      libresonic-main/src/main/resources/org/libresonic/player/theme/default_dark.properties
  12. 1
      libresonic-main/src/main/resources/org/libresonic/player/theme/default_light.properties
  13. 37
      libresonic-main/src/main/webapp/WEB-INF/jsp/head.jsp
  14. 2
      libresonic-main/src/main/webapp/WEB-INF/jsp/index.jsp
  15. 57
      libresonic-main/src/main/webapp/WEB-INF/jsp/more.jsp
  16. 7
      libresonic-main/src/main/webapp/WEB-INF/jsp/personalSettings.jsp
  17. 103
      libresonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp
  18. 8
      libresonic-main/src/main/webapp/WEB-INF/jsp/top.jsp
  19. BIN
      libresonic-main/src/main/webapp/icons/default_dark/keyboard.png
  20. BIN
      libresonic-main/src/main/webapp/icons/default_light/keyboard.png
  21. 11
      libresonic-main/src/main/webapp/script/mousetrap-1.6.0.js
  22. 6
      libresonic-main/src/test/java/org/libresonic/player/dao/UserDaoTestCase.java

@ -119,6 +119,22 @@ public class PlayQueueService {
return convert(request, player, true); return convert(request, player, true);
} }
public PlayQueueInfo toggleStartStop() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doToggleStartStop(request, response);
}
public PlayQueueInfo doToggleStartStop(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
if (player.getPlayQueue().getStatus() == PlayQueue.Status.STOPPED) {
player.getPlayQueue().setStatus(PlayQueue.Status.PLAYING);
} else if (player.getPlayQueue().getStatus() == PlayQueue.Status.PLAYING) {
player.getPlayQueue().setStatus(PlayQueue.Status.STOPPED);
}
return convert(request, player, true);
}
public PlayQueueInfo skip(int index) throws Exception { public PlayQueueInfo skip(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse(); HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();

@ -52,6 +52,7 @@ public class PersonalSettingsCommand {
private boolean showArtistInfoEnabled; private boolean showArtistInfoEnabled;
private boolean nowPlayingAllowed; private boolean nowPlayingAllowed;
private boolean autoHidePlayQueue; private boolean autoHidePlayQueue;
private boolean keyboardShortcutsEnabled;
private boolean finalVersionNotificationEnabled; private boolean finalVersionNotificationEnabled;
private boolean betaVersionNotificationEnabled; private boolean betaVersionNotificationEnabled;
private boolean songNotificationEnabled; private boolean songNotificationEnabled;
@ -230,6 +231,14 @@ public class PersonalSettingsCommand {
this.autoHidePlayQueue = autoHidePlayQueue; this.autoHidePlayQueue = autoHidePlayQueue;
} }
public boolean isKeyboardShortcutsEnabled() {
return keyboardShortcutsEnabled;
}
public void setKeyboardShortcutsEnabled(boolean keyboardShortcutsEnabled) {
this.keyboardShortcutsEnabled = keyboardShortcutsEnabled;
}
public boolean isLastFmEnabled() { public boolean isLastFmEnabled() {
return lastFmEnabled; return lastFmEnabled;
} }

@ -238,6 +238,7 @@ public class MultiController extends MultiActionController {
map.put("showRight", userSettings.isShowNowPlayingEnabled() || userSettings.isShowChatEnabled()); map.put("showRight", userSettings.isShowNowPlayingEnabled() || userSettings.isShowChatEnabled());
map.put("autoHidePlayQueue", userSettings.isAutoHidePlayQueue()); map.put("autoHidePlayQueue", userSettings.isAutoHidePlayQueue());
map.put("listReloadDelay", userSettings.getListReloadDelay()); map.put("listReloadDelay", userSettings.getListReloadDelay());
map.put("keyboardShortcutsEnabled", userSettings.isKeyboardShortcutsEnabled());
map.put("showSideBar", userSettings.isShowSideBar()); map.put("showSideBar", userSettings.isShowSideBar());
map.put("brand", settingsService.getBrand()); map.put("brand", settingsService.getBrand());
return new ModelAndView("index", "model", map); return new ModelAndView("index", "model", map);

@ -74,6 +74,7 @@ public class PersonalSettingsController extends SimpleFormController {
command.setSongNotificationEnabled(userSettings.isSongNotificationEnabled()); command.setSongNotificationEnabled(userSettings.isSongNotificationEnabled());
command.setAutoHidePlayQueue(userSettings.isAutoHidePlayQueue()); command.setAutoHidePlayQueue(userSettings.isAutoHidePlayQueue());
command.setListReloadDelay(userSettings.getListReloadDelay()); command.setListReloadDelay(userSettings.getListReloadDelay());
command.setKeyboardShortcutsEnabled(userSettings.isKeyboardShortcutsEnabled());
command.setLastFmEnabled(userSettings.isLastFmEnabled()); command.setLastFmEnabled(userSettings.isLastFmEnabled());
command.setLastFmUsername(userSettings.getLastFmUsername()); command.setLastFmUsername(userSettings.getLastFmUsername());
command.setLastFmPassword(userSettings.getLastFmPassword()); command.setLastFmPassword(userSettings.getLastFmPassword());
@ -137,6 +138,7 @@ public class PersonalSettingsController extends SimpleFormController {
settings.setSongNotificationEnabled(command.isSongNotificationEnabled()); settings.setSongNotificationEnabled(command.isSongNotificationEnabled());
settings.setAutoHidePlayQueue(command.isAutoHidePlayQueue()); settings.setAutoHidePlayQueue(command.isAutoHidePlayQueue());
settings.setListReloadDelay(command.getListReloadDelay()); settings.setListReloadDelay(command.getListReloadDelay());
settings.setKeyboardShortcutsEnabled(command.isKeyboardShortcutsEnabled());
settings.setLastFmEnabled(command.isLastFmEnabled()); settings.setLastFmEnabled(command.isLastFmEnabled());
settings.setLastFmUsername(command.getLastFmUsername()); settings.setLastFmUsername(command.getLastFmUsername());
settings.setSystemAvatarId(getSystemAvatarId(command)); settings.setSystemAvatarId(getSystemAvatarId(command));

@ -44,7 +44,7 @@ public class UserDao extends AbstractDao {
"playlist_year, playlist_bit_rate, playlist_duration, playlist_format, playlist_file_size, " + "playlist_year, playlist_bit_rate, playlist_duration, playlist_format, playlist_file_size, " +
"last_fm_enabled, last_fm_username, last_fm_password, transcode_scheme, show_now_playing, selected_music_folder_id, " + "last_fm_enabled, last_fm_username, last_fm_password, transcode_scheme, show_now_playing, selected_music_folder_id, " +
"party_mode_enabled, now_playing_allowed, avatar_scheme, system_avatar_id, changed, show_chat, show_artist_info, auto_hide_play_queue, " + "party_mode_enabled, now_playing_allowed, avatar_scheme, system_avatar_id, changed, show_chat, show_artist_info, auto_hide_play_queue, " +
"view_as_list, default_album_list, queue_following_songs, show_side_bar, list_reload_delay"; "view_as_list, default_album_list, queue_following_songs, show_side_bar, list_reload_delay, keyboard_shortcuts_enabled";
private static final Integer ROLE_ID_ADMIN = 1; private static final Integer ROLE_ID_ADMIN = 1;
private static final Integer ROLE_ID_DOWNLOAD = 2; private static final Integer ROLE_ID_DOWNLOAD = 2;
@ -188,7 +188,7 @@ public class UserDao extends AbstractDao {
settings.getAvatarScheme().name(), settings.getSystemAvatarId(), settings.getChanged(), settings.getAvatarScheme().name(), settings.getSystemAvatarId(), settings.getChanged(),
settings.isShowChatEnabled(), settings.isShowArtistInfoEnabled(), settings.isAutoHidePlayQueue(), settings.isShowChatEnabled(), settings.isShowArtistInfoEnabled(), settings.isAutoHidePlayQueue(),
settings.isViewAsList(), settings.getDefaultAlbumList().getId(), settings.isQueueFollowingSongs(), settings.isViewAsList(), settings.getDefaultAlbumList().getId(), settings.isQueueFollowingSongs(),
settings.isShowSideBar(), settings.getListReloadDelay()}); settings.isShowSideBar(), settings.getListReloadDelay(), settings.isKeyboardShortcutsEnabled()});
} }
private static String encrypt(String s) { private static String encrypt(String s) {
@ -350,6 +350,7 @@ public class UserDao extends AbstractDao {
settings.setQueueFollowingSongs(rs.getBoolean(col++)); settings.setQueueFollowingSongs(rs.getBoolean(col++));
settings.setShowSideBar(rs.getBoolean(col++)); settings.setShowSideBar(rs.getBoolean(col++));
settings.setListReloadDelay((Integer) rs.getObject(col++)); settings.setListReloadDelay((Integer) rs.getObject(col++));
settings.setKeyboardShortcutsEnabled(rs.getBoolean(col++));
return settings; return settings;
} }

@ -44,5 +44,11 @@ public class Schema61 extends Schema {
template.execute("alter table user_settings add list_reload_delay int default 60 not null"); template.execute("alter table user_settings add list_reload_delay int default 60 not null");
LOG.info("Database column 'user_settings.list_reload_delay' was added successfully."); LOG.info("Database column 'user_settings.list_reload_delay' was added successfully.");
} }
if (!columnExists(template, "keyboard_shortcuts_enabled", "user_settings")) {
LOG.info("Database column 'user_settings.keyboard_shortcuts_enabled' not found. Creating it.");
template.execute("alter table user_settings add keyboard_shortcuts_enabled boolean default false not null");
LOG.info("Database column 'user_settings.keyboard_shortcuts_enabled' was added successfully.");
}
} }
} }

@ -38,6 +38,7 @@ public class UserSettings {
private boolean finalVersionNotificationEnabled; private boolean finalVersionNotificationEnabled;
private boolean betaVersionNotificationEnabled; private boolean betaVersionNotificationEnabled;
private boolean songNotificationEnabled; private boolean songNotificationEnabled;
private boolean keyboardShortcutsEnabled;
private boolean autoHidePlayQueue; private boolean autoHidePlayQueue;
private boolean showSideBar; private boolean showSideBar;
private boolean viewAsList; private boolean viewAsList;
@ -221,6 +222,14 @@ public class UserSettings {
this.autoHidePlayQueue = autoHidePlayQueue; this.autoHidePlayQueue = autoHidePlayQueue;
} }
public boolean isKeyboardShortcutsEnabled() {
return keyboardShortcutsEnabled;
}
public void setKeyboardShortcutsEnabled(boolean keyboardShortcutsEnabled) {
this.keyboardShortcutsEnabled = keyboardShortcutsEnabled;
}
public boolean isShowSideBar() { public boolean isShowSideBar() {
return showSideBar; return showSideBar;
} }

@ -1264,6 +1264,7 @@ public class SettingsService {
settings.setPartyModeEnabled(false); settings.setPartyModeEnabled(false);
settings.setNowPlayingAllowed(true); settings.setNowPlayingAllowed(true);
settings.setAutoHidePlayQueue(true); settings.setAutoHidePlayQueue(true);
settings.setKeyboardShortcutsEnabled(false);
settings.setShowSideBar(true); settings.setShowSideBar(true);
settings.setShowArtistInfoEnabled(true); settings.setShowArtistInfoEnabled(true);
settings.setViewAsList(false); settings.setViewAsList(false);

@ -350,6 +350,7 @@ personalsettings.nowplayingallowed = Zeige anderen was ich h\u00F6re
personalsettings.showchat = Zeige Chat Nachrichten personalsettings.showchat = Zeige Chat Nachrichten
personalsettings.showartistinfo = Zeige Artisten-Info personalsettings.showartistinfo = Zeige Artisten-Info
personalsettings.autohideplayqueue = Playlist automatisch ausblenden personalsettings.autohideplayqueue = Playlist automatisch ausblenden
personalsettings.keyboardshortcutsenabled = Tastaturk\u00FCrzeln aktivieren
personalsettings.finalversionnotification = Informiere mich \u00FCber neue Versionen personalsettings.finalversionnotification = Informiere mich \u00FCber neue Versionen
personalsettings.betaversionnotification = Informiere mich \u00FCber neue Beta Versionen personalsettings.betaversionnotification = Informiere mich \u00FCber neue Beta Versionen
personalsettings.songnotification = Benachrichtigen Sie mich, wenn neue Songs gespielt werden (nicht von allen Browsern unterst\u00FCtzt) personalsettings.songnotification = Benachrichtigen Sie mich, wenn neue Songs gespielt werden (nicht von allen Browsern unterst\u00FCtzt)

@ -248,6 +248,28 @@ more.jamstash.text = <p>Jamstash is an HTML5 player for Libresonic. <a href="{0}
Also available as a <a href="https://chrome.google.com/webstore/detail/jamstash/jccdpflnecheidefpofmlblgebobbloc" target="_blank">Chrome App</a>.</p> Also available as a <a href="https://chrome.google.com/webstore/detail/jamstash/jccdpflnecheidefpofmlblgebobbloc" target="_blank">Chrome App</a>.</p>
more.status.title = Network Status more.status.title = Network Status
more.status.text = <a href="status.view">Monitor</a> the real-time status of all network media streams. more.status.text = <a href="status.view">Monitor</a> the real-time status of all network media streams.
more.keyboard.title = Keyboard Shortcuts
more.keyboard.text = Keyboard shortcuts can be enabled in the <a href="personalSettings.view">user preferences</a> page. Supported shortcuts are below.
more.keyboard.playback = Playback
more.keyboard.navigation = Navigation
more.keyboard.general = General
more.keyboard.playpause = Play or pause music
more.keyboard.previous = Go to the previous song
more.keyboard.next = Go to the next song
more.keyboard.volumedown = Decrease the player volume
more.keyboard.volumeup = Increase the player volume
more.keyboard.home = Go to Home
more.keyboard.playlists = Go to Playlists
more.keyboard.podcasts = Go to Podcasts
more.keyboard.settings = Go to Settings
more.keyboard.starred = Go to Starred
more.keyboard.more = Go to More
more.keyboard.about = Go to About
more.keyboard.search = Search
more.keyboard.sidebar = Toggle left sidebar
more.keyboard.playqueue = Toggle play queue
more.keyboard.shortcuts = Show keyboard shortcuts
more.keyboard.then = then
more.mobile.title = Mobile phone more.mobile.title = Mobile phone
more.mobile.text = <p>You can control {0} from any WAP-enabled mobile phone or PDA.<br> \ more.mobile.text = <p>You can control {0} from any WAP-enabled mobile phone or PDA.<br> \
Simply visit the following URL from your phone: <b>http://yourhostname/wap</b></p> \ Simply visit the following URL from your phone: <b>http://yourhostname/wap</b></p> \
@ -375,6 +397,7 @@ personalsettings.nowplayingallowed = Let others see what I am playing
personalsettings.showchat = Show chat messages personalsettings.showchat = Show chat messages
personalsettings.showartistinfo = Show artist info personalsettings.showartistinfo = Show artist info
personalsettings.autohideplayqueue = Auto-hide play queue personalsettings.autohideplayqueue = Auto-hide play queue
personalsettings.keyboardshortcutsenabled = Enable keyboard shortcuts
personalsettings.finalversionnotification = Notify me about new versions personalsettings.finalversionnotification = Notify me about new versions
personalsettings.betaversionnotification = Notify me about new beta versions personalsettings.betaversionnotification = Notify me about new beta versions
personalsettings.songnotification = Notify me when new songs are played (not supported by all browsers) personalsettings.songnotification = Notify me when new songs are played (not supported by all browsers)

@ -21,6 +21,7 @@ helpImage = icons/default_dark/help.png
helpPopupImage = icons/default_dark/help_small.png helpPopupImage = icons/default_dark/help_small.png
homeImage = icons/default_dark/home.png homeImage = icons/default_dark/home.png
html5Image = icons/default_dark/html5.png html5Image = icons/default_dark/html5.png
keyboardImage = icons/default_dark/keyboard.png
logImage = icons/default_dark/log.png logImage = icons/default_dark/log.png
logoImage = icons/default_dark/logo_light.png logoImage = icons/default_dark/logo_light.png
moreImage = icons/default_dark/more.png moreImage = icons/default_dark/more.png

@ -21,6 +21,7 @@ helpImage = icons/default_light/help.png
helpPopupImage = icons/default_light/help_small.png helpPopupImage = icons/default_light/help_small.png
homeImage = icons/default_light/home.png homeImage = icons/default_light/home.png
html5Image = icons/default_light/html5.png html5Image = icons/default_light/html5.png
keyboardImage = icons/default_light/keyboard.png
logImage = icons/default_light/log.png logImage = icons/default_light/log.png
logoImage = icons/default_light/logo.png logoImage = icons/default_light/logo.png
moreImage = icons/default_light/more.png moreImage = icons/default_light/more.png

@ -10,3 +10,40 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu&subset=latin,cyrillic-ext,greek-ext,greek,latin-ext,cyrillic" type="text/css"/> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu&subset=latin,cyrillic-ext,greek-ext,greek,latin-ext,cyrillic" type="text/css"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:400,300,400italic,500,300italic,500italic,700,700italic,100,100italic" type="text/css"/> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:400,300,400italic,500,300italic,500italic,700,700italic,100,100italic" type="text/css"/>
<title>Libresonic</title> <title>Libresonic</title>
<!-- Import Mousetrap and enable shortcuts if necessary -->
<script>
function isKeyboardShortcutsEnabled() {
if (window === parent.frames.top) {
return ${model.keyboardShortcutsEnabled ? 'true' : 'false'};
} else {
return parent.frames.top.isKeyboardShortcutsEnabled();
}
}
</script>
<!-- Bind shortcuts if enabled -->
<script type="text/javascript" src="<c:url value="/script/mousetrap-1.6.0.js"/>"></script>
<script type="text/javascript">
if (isKeyboardShortcutsEnabled()) {
Mousetrap.bind('space', function() { parent.frames.playQueue.onToggleStartStop(); return false; });
Mousetrap.bind('left', function() { parent.frames.playQueue.onPrevious(); });
Mousetrap.bind('right', function() { parent.frames.playQueue.onNext(); });
Mousetrap.bind('*', function() { parent.frames.playQueue.onStarCurrent(); });
Mousetrap.bind('plus', function() { parent.frames.playQueue.onGainAdd(+5); });
Mousetrap.bind('-', function() { parent.frames.playQueue.onGainAdd(-5); });
Mousetrap.bind('q', function() { parent.frames.playQueue.onTogglePlayQueue(); });
Mousetrap.bind('/', function() { parent.frames.upper.$("#query").focus(); });
Mousetrap.bind('m', function() { parent.frames.upper.toggleLeftFrameVisible(); });
Mousetrap.bind('g h', function() { parent.frames.main.location.href = "home.view?"; });
Mousetrap.bind('g p', function() { parent.frames.main.location.href = "playlists.view?"; });
Mousetrap.bind('g o', function() { parent.frames.main.location.href = "podcastChannels.view?"; });
Mousetrap.bind('g s', function() { parent.frames.main.location.href = "settings.view?"; });
Mousetrap.bind('g t', function() { parent.frames.main.location.href = "starred.view?"; });
Mousetrap.bind('g r', function() { parent.frames.main.location.href = "more.view?"; });
Mousetrap.bind('g a', function() { parent.frames.main.location.href = "help.view?"; });
Mousetrap.bind('?', function() { parent.frames.main.location.href = "more.view#shortcuts"; });
}
</script>

@ -11,7 +11,7 @@
<frameset id="mainFrameset" cols=${model.showSideBar ? "230,*" : "0,*"} border="0" framespacing="0" frameborder="0"> <frameset id="mainFrameset" cols=${model.showSideBar ? "230,*" : "0,*"} border="0" framespacing="0" frameborder="0">
<frame name="left" src="left.view?" marginwidth="0" marginheight="0" class="bgcolor2"> <frame name="left" src="left.view?" marginwidth="0" marginheight="0" class="bgcolor2">
<frameset id="playQueueFrameset" rows=${model.autoHidePlayQueue ? "*,50" : "75%,25%"} border="0" framespacing="0" frameborder="0"> <frameset id="playQueueFrameset" rows=${model.autoHidePlayQueue ? "*,50" : "*,150"} border="0" framespacing="0" frameborder="0">
<frameset cols="*,${model.showRight ? 235 : 0}" border="0" framespacing="0" frameborder="0"> <frameset cols="*,${model.showRight ? 235 : 0}" border="0" framespacing="0" frameborder="0">
<frame name="main" src="nowPlaying.view?" marginwidth="0" marginheight="0" class="bgcolor1"> <frame name="main" src="nowPlaying.view?" marginwidth="0" marginheight="0" class="bgcolor1">
<frame name="right" src="right.view?" class="bgcolor1"> <frame name="right" src="right.view?" class="bgcolor1">

@ -38,6 +38,12 @@
} }
</script> </script>
<style type="text/css">
.more-shortcut {
padding: 0 15px;
}
</style>
</head> </head>
<body class="mainframe bgcolor1" onload="${model.user.uploadRole ? "refreshProgress()" : ""}"> <body class="mainframe bgcolor1" onload="${model.user.uploadRole ? "refreshProgress()" : ""}">
@ -182,9 +188,58 @@
<p class="detail" id="progressText"/> <p class="detail" id="progressText"/>
<div id="progressBar"> <div id="progressBar">
<div id="progressBarContent"/> <div id="progressBarContent"></div>
</div> </div>
</c:if> </c:if>
<a name="shortcuts"></a>
<h2>
<img src="<spring:theme code="keyboardImage"/>" alt=""/>
<span style="vertical-align: middle"><fmt:message key="more.keyboard.title"/></span>
</h2>
<fmt:message key="more.keyboard.text"/>
<table class="indent music" style="width:inherit">
<tr>
<th colspan="2"><fmt:message key="more.keyboard.playback"/></th>
<th colspan="2"><fmt:message key="more.keyboard.navigation"/></th>
<th colspan="2"><fmt:message key="more.keyboard.general"/></th>
</tr>
<tr>
<td class="more-shortcut">Space</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.playpause"/></td>
<td class="more-shortcut">g <fmt:message key="more.keyboard.then"/> h</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.home"/></td>
<td class="more-shortcut">/</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.search"/></td>
</tr>
<tr>
<td class="more-shortcut">&#8592;</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.previous"/></td>
<td class="more-shortcut">g <fmt:message key="more.keyboard.then"/> p</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.playlists"/></td>
<td class="more-shortcut">m</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.sidebar"/></td>
</tr>
<tr>
<td class="more-shortcut">&#8593;</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.next"/></td>
<td class="more-shortcut">g <fmt:message key="more.keyboard.then"/> o</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.podcasts"/></td>
<td class="more-shortcut">q</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.playqueue"/></td>
</tr>
<tr>
<td class="more-shortcut">&ndash;</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.volumedown"/></td>
<td class="more-shortcut">g <fmt:message key="more.keyboard.then"/> s</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.settings"/></td>
<td class="more-shortcut">?</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.shortcuts"/></td>
</tr>
<tr>
<td class="more-shortcut">+</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.volumeup"/></td>
<td class="more-shortcut">g <fmt:message key="more.keyboard.then"/> t</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.starred"/></td>
<td></td><td></td>
</tr>
<tr>
<td></td><td></td>
<td class="more-shortcut">g <fmt:message key="more.keyboard.then"/> r</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.more"/></td>
<td></td><td></td>
</tr>
<tr>
<td></td><td></td>
<td class="more-shortcut">g <fmt:message key="more.keyboard.then"/> a</td><td class="more-shortcut-descr"><fmt:message key="more.keyboard.about"/></td>
<td></td><td></td>
</tr>
</table>
</body></html> </body></html>

@ -179,6 +179,13 @@
</tr> </tr>
</table> </table>
<table class="indent">
<tr>
<td><form:checkbox path="keyboardShortcutsEnabled" id="keyboardShortcutsEnabled" cssClass="checkbox"/></td>
<td><label for="keyboardShortcutsEnabled"><fmt:message key="personalsettings.keyboardshortcutsenabled"/></label></td>
</tr>
</table>
<table class="indent"> <table class="indent">
<tr> <tr>
<td><fmt:message key="personalsettings.listreloaddelay"/></td> <td><fmt:message key="personalsettings.listreloaddelay"/></td>

@ -31,12 +31,13 @@
<body class="bgcolor2 playlistframe" onload="init()"> <body class="bgcolor2 playlistframe" onload="init()">
<span id="dummy-animation-target" style="max-width:50px;display: none"></span> <span id="dummy-animation-target" style="max-width: ${model.autoHide ? 50 : 150}px; display: none"></span>
<script type="text/javascript" language="javascript"> <script type="text/javascript" language="javascript">
var songs = null; var songs = null;
var currentStreamUrl = null; var currentStreamUrl = null;
var repeatEnabled = false; var repeatEnabled = false;
var isVisible = ${model.autoHide ? 'false' : 'true'};
var CastPlayer = new CastPlayer(); var CastPlayer = new CastPlayer();
var ignore = false; var ignore = false;
@ -86,17 +87,30 @@
getPlayQueue(); getPlayQueue();
} }
function initAutoHide() { function onHidePlayQueue() {
$(window).mouseleave(function (event) {
if (event.clientY < 30) {
setFrameHeight(50); setFrameHeight(50);
isVisible = false;
} }
});
$(window).mouseenter(function () { function onShowPlayQueue() {
var height = $("body").height() + 25; var height = $("body").height() + 25;
height = Math.min(height, window.top.innerHeight * 0.8); height = Math.min(height, window.top.innerHeight * 0.8);
setFrameHeight(height); setFrameHeight(height);
isVisible = true;
}
function onTogglePlayQueue() {
if (isVisible) onHidePlayQueue();
else onShowPlayQueue();
}
function initAutoHide() {
$(window).mouseleave(function (event) {
if (event.clientY < 30) onHidePlayQueue();
});
$(window).mouseenter(function () {
onShowPlayQueue();
}); });
} }
@ -155,12 +169,55 @@
playQueueService.clear(playQueueCallback); playQueueService.clear(playQueueCallback);
} }
} }
/**
* Start playing from the current playlist
*/
function onStart() { function onStart() {
if (CastPlayer.castSession) {
CastPlayer.playCast();
} else if (jwplayer()) {
if (jwplayer().getState() == "IDLE") {
skip(0);
} else if (jwplayer().getState() == "PAUSED") {
jwplayer().play(true);
}
} else {
playQueueService.start(playQueueCallback); playQueueService.start(playQueueCallback);
} }
}
/**
* Pause playing
*/
function onStop() { function onStop() {
if (CastPlayer.castSession) {
CastPlayer.pauseCast();
} else if (jwplayer()) {
jwplayer().pause(true);
} else {
playQueueService.stop(playQueueCallback); playQueueService.stop(playQueueCallback);
} }
}
/**
* Toggle play/pause
*
* FIXME: Only works for the Web player for now
*/
function onToggleStartStop() {
if (CastPlayer.castSession) {
var playing = CastPlayer.mediaSession && CastPlayer.mediaSession.playerState == chrome.cast.media.PlayerState.PLAYING;
if (playing) onStop();
else onStart();
} else if (jwplayer()) {
if (jwplayer().getState() == "PLAYING") onStop();
else onStart();
} else {
playQueueService.toggleStartStop(playQueueCallback);
}
}
function onGain(gain) { function onGain(gain) {
playQueueService.setGain(gain); playQueueService.setGain(gain);
} }
@ -172,6 +229,33 @@
var value = parseInt($("#castVolume").slider("option", "value")); var value = parseInt($("#castVolume").slider("option", "value"));
CastPlayer.setCastVolume(value / 100, false); CastPlayer.setCastVolume(value / 100, false);
} }
/**
* Increase or decrease volume by a certain amount
*
* @param amount to add or remove from the current volume
*/
function onGainAdd(gain) {
if (CastPlayer.castSession) {
var volume = parseInt($("#castVolume").slider("option", "value")) + gain;
if (volume > 100) volume = 100;
if (volume < 0) volume = 0;
CastPlayer.setCastVolume(volume / 100, false);
$("#castVolume").slider("option", "value", volume); // Need to update UI
} else if (jwplayer()) {
var volume = parseInt(jwplayer().getVolume()) + gain;
if (volume > 100) volume = 100;
if (volume < 0) volume = 0;
jwplayer().setVolume(volume);
} else {
var volume = parseInt($("#jukeboxVolume").slider("option", "value")) + gain;
if (volume > 100) volume = 100;
if (volume < 0) volume = 0;
onGain(volume / 100);
$("#jukeboxVolume").slider("option", "value", volume); // Need to update UI
}
}
function onSkip(index) { function onSkip(index) {
<c:choose> <c:choose>
<c:when test="${model.player.web}"> <c:when test="${model.player.web}">
@ -188,10 +272,10 @@
if (wrap) { if (wrap) {
index = index % songs.length; index = index % songs.length;
} }
skip(index); onSkip(index);
} }
function onPrevious() { function onPrevious() {
skip(parseInt(getCurrentSongIndex()) - 1); onSkip(parseInt(getCurrentSongIndex()) - 1);
} }
function onPlay(id) { function onPlay(id) {
playQueueService.play(id, playQueueCallback); playQueueService.play(id, playQueueCallback);
@ -235,6 +319,9 @@
function onStar(index) { function onStar(index) {
playQueueService.toggleStar(index, playQueueCallback); playQueueService.toggleStar(index, playQueueCallback);
} }
function onStarCurrent() {
onStar(getCurrentSongIndex());
}
function onRemove(index) { function onRemove(index) {
playQueueService.remove(index, playQueueCallback); playQueueService.remove(index, playQueueCallback);
} }

@ -10,6 +10,7 @@
<script type="text/javascript"> <script type="text/javascript">
var previousQuery = ""; var previousQuery = "";
var instantSearchTimeout; var instantSearchTimeout;
var showSideBar = ${model.showSideBar ? 'true' : 'false'};
function triggerInstantSearch() { function triggerInstantSearch() {
if (instantSearchTimeout) { if (instantSearchTimeout) {
@ -31,6 +32,7 @@
$("#hide-left-frame").show(); $("#hide-left-frame").show();
toggleLeftFrame(230); toggleLeftFrame(230);
multiService.setShowSideBar(true); multiService.setShowSideBar(true);
showSideBar = true;
} }
function hideLeftFrame() { function hideLeftFrame() {
@ -38,6 +40,12 @@
$("#show-left-frame").show(); $("#show-left-frame").show();
toggleLeftFrame(0); toggleLeftFrame(0);
multiService.setShowSideBar(false); multiService.setShowSideBar(false);
showSideBar = false;
}
function toggleLeftFrameVisible() {
if (showSideBar) hideLeftFrame();
else showLeftFrame();
} }
function toggleLeftFrame(width) { function toggleLeftFrame(width) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

@ -0,0 +1,11 @@
/* mousetrap v1.6.0 craig.is/killing/mice */
(function(r,t,g){function u(a,b,h){a.addEventListener?a.addEventListener(b,h,!1):a.attachEvent("on"+b,h)}function y(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return k[a.which]?k[a.which]:p[a.which]?p[a.which]:String.fromCharCode(a.which).toLowerCase()}function D(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function v(a){return"shift"==a||"ctrl"==a||"alt"==a||
"meta"==a}function z(a,b){var h,c,e,g=[];h=a;"+"===h?h=["+"]:(h=h.replace(/\+{2}/g,"+plus"),h=h.split("+"));for(e=0;e<h.length;++e)c=h[e],A[c]&&(c=A[c]),b&&"keypress"!=b&&B[c]&&(c=B[c],g.push("shift")),v(c)&&g.push(c);h=c;e=b;if(!e){if(!n){n={};for(var l in k)95<l&&112>l||k.hasOwnProperty(l)&&(n[k[l]]=l)}e=n[h]?"keydown":"keypress"}"keypress"==e&&g.length&&(e="keydown");return{key:c,modifiers:g,action:e}}function C(a,b){return null===a||a===t?!1:a===b?!0:C(a.parentNode,b)}function c(a){function b(a){a=
a||{};var b=!1,m;for(m in n)a[m]?b=!0:n[m]=0;b||(w=!1)}function h(a,b,m,f,c,h){var g,e,k=[],l=m.type;if(!d._callbacks[a])return[];"keyup"==l&&v(a)&&(b=[a]);for(g=0;g<d._callbacks[a].length;++g)if(e=d._callbacks[a][g],(f||!e.seq||n[e.seq]==e.level)&&l==e.action){var q;(q="keypress"==l&&!m.metaKey&&!m.ctrlKey)||(q=e.modifiers,q=b.sort().join(",")===q.sort().join(","));q&&(q=f&&e.seq==f&&e.level==h,(!f&&e.combo==c||q)&&d._callbacks[a].splice(g,1),k.push(e))}return k}function g(a,b,m,f){d.stopCallback(b,
b.target||b.srcElement,m,f)||!1!==a(b,m)||(b.preventDefault?b.preventDefault():b.returnValue=!1,b.stopPropagation?b.stopPropagation():b.cancelBubble=!0)}function e(a){"number"!==typeof a.which&&(a.which=a.keyCode);var b=y(a);b&&("keyup"==a.type&&x===b?x=!1:d.handleKey(b,D(a),a))}function k(a,c,m,f){function e(c){return function(){w=c;++n[a];clearTimeout(r);r=setTimeout(b,1E3)}}function h(c){g(m,c,a);"keyup"!==f&&(x=y(c));setTimeout(b,10)}for(var d=n[a]=0;d<c.length;++d){var p=d+1===c.length?h:e(f||
z(c[d+1]).action);l(c[d],p,f,a,d)}}function l(a,b,c,f,e){d._directMap[a+":"+c]=b;a=a.replace(/\s+/g," ");var g=a.split(" ");1<g.length?k(a,g,b,c):(c=z(a,c),d._callbacks[c.key]=d._callbacks[c.key]||[],h(c.key,c.modifiers,{type:c.action},f,a,e),d._callbacks[c.key][f?"unshift":"push"]({callback:b,modifiers:c.modifiers,action:c.action,seq:f,level:e,combo:a}))}var d=this;a=a||t;if(!(d instanceof c))return new c(a);d.target=a;d._callbacks={};d._directMap={};var n={},r,x=!1,p=!1,w=!1;d._handleKey=function(a,
c,e){var f=h(a,c,e),d;c={};var k=0,l=!1;for(d=0;d<f.length;++d)f[d].seq&&(k=Math.max(k,f[d].level));for(d=0;d<f.length;++d)f[d].seq?f[d].level==k&&(l=!0,c[f[d].seq]=1,g(f[d].callback,e,f[d].combo,f[d].seq)):l||g(f[d].callback,e,f[d].combo);f="keypress"==e.type&&p;e.type!=w||v(a)||f||b(c);p=l&&"keydown"==e.type};d._bindMultiple=function(a,b,c){for(var d=0;d<a.length;++d)l(a[d],b,c)};u(a,"keypress",e);u(a,"keydown",e);u(a,"keyup",e)}if(r){var k={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",
18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},p={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},B={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},A={option:"alt",command:"meta","return":"enter",
escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},n;for(g=1;20>g;++g)k[111+g]="f"+g;for(g=0;9>=g;++g)k[g+96]=g;c.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};c.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};c.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};c.prototype.reset=function(){this._callbacks={};this._directMap=
{};return this};c.prototype.stopCallback=function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")||C(b,this.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};c.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};c.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(k[b]=a[b]);n=null};c.init=function(){var a=c(t),b;for(b in a)"_"!==b.charAt(0)&&(c[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};
c.init();r.Mousetrap=c;"undefined"!==typeof module&&module.exports&&(module.exports=c);"function"===typeof define&&define.amd&&define(function(){return c})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null);

@ -161,6 +161,8 @@ public class UserDaoTestCase extends DaoTestCaseBase {
assertFalse("Error in getUserSettings().", userSettings.isNowPlayingAllowed()); assertFalse("Error in getUserSettings().", userSettings.isNowPlayingAllowed());
assertSame("Error in getUserSettings().", AvatarScheme.NONE, userSettings.getAvatarScheme()); assertSame("Error in getUserSettings().", AvatarScheme.NONE, userSettings.getAvatarScheme());
assertNull("Error in getUserSettings().", userSettings.getSystemAvatarId()); assertNull("Error in getUserSettings().", userSettings.getSystemAvatarId());
assertEquals("Error in getUserSettings().", 0, userSettings.getListReloadDelay());
assertFalse("Error in getUserSettings().", userSettings.isKeyboardShortcutsEnabled());
UserSettings settings = new UserSettings("sindre"); UserSettings settings = new UserSettings("sindre");
settings.setLocale(Locale.SIMPLIFIED_CHINESE); settings.setLocale(Locale.SIMPLIFIED_CHINESE);
@ -181,6 +183,8 @@ public class UserDaoTestCase extends DaoTestCaseBase {
settings.setAvatarScheme(AvatarScheme.SYSTEM); settings.setAvatarScheme(AvatarScheme.SYSTEM);
settings.setSystemAvatarId(1); settings.setSystemAvatarId(1);
settings.setChanged(new Date(9412L)); settings.setChanged(new Date(9412L));
settings.setListReloadDelay(60);
settings.setKeyboardShortcutsEnabled(true);
userDao.updateUserSettings(settings); userDao.updateUserSettings(settings);
userSettings = userDao.getUserSettings("sindre"); userSettings = userDao.getUserSettings("sindre");
@ -204,6 +208,8 @@ public class UserDaoTestCase extends DaoTestCaseBase {
assertSame("Error in getUserSettings().", AvatarScheme.SYSTEM, userSettings.getAvatarScheme()); assertSame("Error in getUserSettings().", AvatarScheme.SYSTEM, userSettings.getAvatarScheme());
assertEquals("Error in getUserSettings().", 1, userSettings.getSystemAvatarId().intValue()); assertEquals("Error in getUserSettings().", 1, userSettings.getSystemAvatarId().intValue());
assertEquals("Error in getUserSettings().", new Date(9412L), userSettings.getChanged()); assertEquals("Error in getUserSettings().", new Date(9412L), userSettings.getChanged());
assertEquals("Error in getUserSettings().", 60, userSettings.getListReloadDelay());
assertTrue("Error in getUserSettings().", userSettings.isKeyboardShortcutsEnabled());
userDao.deleteUser("sindre"); userDao.deleteUser("sindre");
assertNull("Error in cascading delete.", userDao.getUserSettings("sindre")); assertNull("Error in cascading delete.", userDao.getUserSettings("sindre"));

Loading…
Cancel
Save