Browse Source

feat: added keyboard shortcut handler and shortcuts for stations

Ctrl + Space: pause/resume
Ctrl + Right: skip station
Ctrl + Up : increase volume 10%
Ctrl + Down: decrease volume 10%
Ctrl + Shift + Up : increase volume 1%
Ctrl + Shift + Down: decrease volume 1%
Kristian Vos 5 years ago
parent
commit
e7bded8b13
3 changed files with 156 additions and 7 deletions
  1. 27 7
      frontend/App.vue
  2. 97 0
      frontend/components/Station/Station.vue
  3. 32 0
      frontend/keyboardShortcuts.js

+ 27 - 7
frontend/App.vue

@@ -13,7 +13,6 @@
 
 <script>
 import { mapState, mapActions } from "vuex";
-
 import Toast from "toasters";
 
 import Banned from "./components/pages/Banned.vue";
@@ -22,13 +21,15 @@ import MobileAlert from "./components/Modals/MobileAlert.vue";
 import LoginModal from "./components/Modals/Login.vue";
 import RegisterModal from "./components/Modals/Register.vue";
 import io from "./io";
+import keyboardShortcuts from "./keyboardShortcuts";
 
 export default {
 	replace: false,
 	data() {
 		return {
 			serverDomain: "",
-			socketConnected: true
+			socketConnected: true,
+			keyIsDown: false
 		};
 	},
 	computed: mapState({
@@ -81,13 +82,32 @@ export default {
 	mounted() {
 		document.onkeydown = ev => {
 			const event = ev || window.event;
-			if (
-				event.keyCode === 27 &&
-				Object.keys(this.currentlyActive).length !== 0
-			)
-				this.closeCurrentModal();
+			const { keyCode } = event;
+			const shift = event.shiftKey;
+			const ctrl = event.ctrlKey;
+
+			const identifier = `${keyCode}.${shift}.${ctrl}`;
+
+			if (this.keyIsDown === identifier) return;
+			this.keyIsDown = identifier;
+
+			keyboardShortcuts.handleKeyDown(keyCode, shift, ctrl);
 		};
 
+		document.onkeyup = () => {
+			this.keyIsDown = "";
+		};
+
+		keyboardShortcuts.registerShortcut("closeModal", {
+			keyCode: 27,
+			shift: false,
+			ctrl: false,
+			handler: () => {
+				if (Object.keys(this.currentlyActive).length !== 0)
+					this.closeCurrentModal();
+			}
+		});
+
 		if (localStorage.getItem("github_redirect")) {
 			this.$router.go(localStorage.getItem("github_redirect"));
 			localStorage.removeItem("github_redirect");

+ 97 - 0
frontend/components/Station/Station.vue

@@ -430,6 +430,7 @@ import UserIdToUsername from "../UserIdToUsername.vue";
 import Z404 from "../404.vue";
 
 import io from "../../io";
+import keyboardShortcuts from "../../keyboardShortcuts";
 
 export default {
 	data() {
@@ -487,6 +488,9 @@ export default {
 		isAdminOnly() {
 			return this.loggedIn && this.role === "admin";
 		},
+		isOwnerOrAdmin() {
+			return this.isOwnerOnly() || this.isAdminOnly();
+		},
 		removeFromQueue(songId) {
 			window.socket.emit(
 				"stations.removeFromQueue",
@@ -1060,6 +1064,85 @@ export default {
 						});
 					}
 
+					if (this.isOwnerOrAdmin()) {
+						keyboardShortcuts.registerShortcut(
+							"station.pauseResume",
+							{
+								keyCode: 32,
+								shift: false,
+								ctrl: true,
+								handler: () => {
+									if (this.paused) this.resumeStation();
+									else this.pauseStation();
+								}
+							}
+						);
+
+						keyboardShortcuts.registerShortcut(
+							"station.skipStation",
+							{
+								keyCode: 39,
+								shift: false,
+								ctrl: true,
+								handler: () => {
+									this.skipStation();
+								}
+							}
+						);
+
+						keyboardShortcuts.registerShortcut(
+							"station.lowerVolumeLarge",
+							{
+								keyCode: 40,
+								shift: false,
+								ctrl: true,
+								handler: () => {
+									this.volumeSliderValue -= 1000;
+									this.changeVolume();
+								}
+							}
+						);
+
+						keyboardShortcuts.registerShortcut(
+							"station.lowerVolumeSmall",
+							{
+								keyCode: 40,
+								shift: true,
+								ctrl: true,
+								handler: () => {
+									this.volumeSliderValue -= 100;
+									this.changeVolume();
+								}
+							}
+						);
+
+						keyboardShortcuts.registerShortcut(
+							"station.increaseVolumeLarge",
+							{
+								keyCode: 38,
+								shift: false,
+								ctrl: true,
+								handler: () => {
+									this.volumeSliderValue += 1000;
+									this.changeVolume();
+								}
+							}
+						);
+
+						keyboardShortcuts.registerShortcut(
+							"station.increaseVolumeSmall",
+							{
+								keyCode: 38,
+								shift: true,
+								ctrl: true,
+								handler: () => {
+									this.volumeSliderValue += 100;
+									this.changeVolume();
+								}
+							}
+						);
+					}
+
 					// UNIX client time before ping
 					const beforePing = Date.now();
 					this.socket.emit("apis.ping", pong => {
@@ -1283,6 +1366,20 @@ export default {
 			this.volumeSliderValue = volume * 100;
 		}
 	},
+	beforeDestroy() {
+		const shortcutNames = [
+			"station.pauseResume",
+			"station.skipStation",
+			"station.lowerVolumeLarge",
+			"station.lowerVolumeSmall",
+			"station.increaseVolumeLarge",
+			"station.increaseVolumeSmall"
+		];
+
+		shortcutNames.forEach(shortcutName => {
+			keyboardShortcuts.unregisterShortcut(shortcutName);
+		});
+	},
 	components: {
 		StationHeader,
 		SongQueue: () => import("../Modals/AddSongToQueue.vue"),

+ 32 - 0
frontend/keyboardShortcuts.js

@@ -0,0 +1,32 @@
+const shortcuts = {};
+
+let _shortcuts = [];
+
+const lib = {
+	handleKeyDown(keyCode, shift, ctrl) {
+		_shortcuts.forEach(shortcut => {
+			if (
+				shortcut.keyCode === keyCode &&
+				shortcut.shift === shift &&
+				shortcut.ctrl === ctrl
+			)
+				shortcut.handler();
+		});
+	},
+
+	registerShortcut(name, shortcut) {
+		shortcuts[name] = shortcut;
+		lib.remakeShortcutsArray();
+	},
+
+	unregisterShortcut: name => {
+		delete shortcuts[name];
+		lib.remakeShortcutsArray();
+	},
+
+	remakeShortcutsArray: () => {
+		_shortcuts = Object.keys(shortcuts).map(key => shortcuts[key]);
+	}
+};
+
+export default lib;