1
0
Fork 0
mirror of https://github.com/heyarne/airsonic-ui.git synced 2026-05-07 02:33:39 +02:00

Add rudimentary keyboard shortcuts; closes #41

This commit is contained in:
Arne Schlüter 2019-01-30 18:25:35 +01:00
commit f9290562fa
8 changed files with 109 additions and 41 deletions

96
package-lock.json generated
View file

@ -1352,9 +1352,9 @@
"dev": true "dev": true
}, },
"events": { "events": {
"version": "1.1.1", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==",
"dev": true "dev": true
}, },
"evp_bytestokey": { "evp_bytestokey": {
@ -1766,7 +1766,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -1787,12 +1788,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -1807,17 +1810,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -1934,7 +1940,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -1946,6 +1953,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -1960,6 +1968,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -1967,12 +1976,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -1991,6 +2002,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -2071,7 +2083,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -2083,6 +2096,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -2168,7 +2182,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.1", "version": "5.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -2204,6 +2219,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -2223,6 +2239,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -2266,12 +2283,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.2", "version": "3.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@ -2568,9 +2587,9 @@
} }
}, },
"hash.js": { "hash.js": {
"version": "1.1.5", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"dev": true, "dev": true,
"requires": { "requires": {
"inherits": "^2.0.3", "inherits": "^2.0.3",
@ -3649,9 +3668,9 @@
} }
}, },
"node-libs-browser": { "node-libs-browser": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz",
"integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==",
"dev": true, "dev": true,
"requires": { "requires": {
"assert": "^1.1.1", "assert": "^1.1.1",
@ -3661,7 +3680,7 @@
"constants-browserify": "^1.0.0", "constants-browserify": "^1.0.0",
"crypto-browserify": "^3.11.0", "crypto-browserify": "^3.11.0",
"domain-browser": "^1.1.1", "domain-browser": "^1.1.1",
"events": "^1.0.0", "events": "^3.0.0",
"https-browserify": "^1.0.0", "https-browserify": "^1.0.0",
"os-browserify": "^0.3.0", "os-browserify": "^0.3.0",
"path-browserify": "0.0.0", "path-browserify": "0.0.0",
@ -3675,7 +3694,7 @@
"timers-browserify": "^2.0.4", "timers-browserify": "^2.0.4",
"tty-browserify": "0.0.0", "tty-browserify": "0.0.0",
"url": "^0.11.0", "url": "^0.11.0",
"util": "^0.10.3", "util": "^0.11.0",
"vm-browserify": "0.0.4" "vm-browserify": "0.0.4"
}, },
"dependencies": { "dependencies": {
@ -4077,22 +4096,23 @@
} }
}, },
"pako": { "pako": {
"version": "1.0.6", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz",
"integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==",
"dev": true "dev": true
}, },
"parse-asn1": { "parse-asn1": {
"version": "5.1.1", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.3.tgz",
"integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "integrity": "sha512-VrPoetlz7B/FqjBLD2f5wBVZvsZVLnRUrxVLfRYhGXCODa/NWE4p3Wp+6+aV3ZPL3KM7/OZmxDIwwijD7yuucg==",
"dev": true, "dev": true,
"requires": { "requires": {
"asn1.js": "^4.0.0", "asn1.js": "^4.0.0",
"browserify-aes": "^1.0.0", "browserify-aes": "^1.0.0",
"create-hash": "^1.1.0", "create-hash": "^1.1.0",
"evp_bytestokey": "^1.0.0", "evp_bytestokey": "^1.0.0",
"pbkdf2": "^3.0.3" "pbkdf2": "^3.0.3",
"safe-buffer": "^5.1.1"
} }
}, },
"parse-json": { "parse-json": {
@ -4814,9 +4834,9 @@
} }
}, },
"shadow-cljs": { "shadow-cljs": {
"version": "2.7.6", "version": "2.7.21",
"resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-2.7.6.tgz", "resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-2.7.21.tgz",
"integrity": "sha512-hk9dtt3mLkLQzu2YJG+T2/8YyevRNYtGZTGjTrGCUzjLaqKHJInJELY16vU2W17Kq/u9tCsPV0Y+bbnHRv52uw==", "integrity": "sha512-izl5S11oS+p1i46o481VDFOuT1y1LM2k3j9g3JG04KM7exEr02Q10Sz1m5yETM/MkyDxqFGhZWpMfJmCZrOILw==",
"dev": true, "dev": true,
"requires": { "requires": {
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
@ -5221,9 +5241,9 @@
} }
}, },
"stream-browserify": { "stream-browserify": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
"integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
"dev": true, "dev": true,
"requires": { "requires": {
"inherits": "~2.0.1", "inherits": "~2.0.1",
@ -5680,9 +5700,9 @@
} }
}, },
"util": { "util": {
"version": "0.10.4", "version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"inherits": "2.0.3" "inherits": "2.0.3"

View file

@ -43,6 +43,6 @@
"react-flip-move": "^3.0.3", "react-flip-move": "^3.0.3",
"react-highlight.js": "^1.0.7", "react-highlight.js": "^1.0.7",
"sass": "^1.15.1", "sass": "^1.15.1",
"shadow-cljs": "^2.7.6" "shadow-cljs": "^2.7.21"
} }
} }

View file

@ -5,6 +5,7 @@
:dependencies :dependencies
[[reagent "0.8.0"] [[reagent "0.8.0"]
[re-frame "0.10.6"] [re-frame "0.10.6"]
[re-pressed "0.3.0"]
[day8.re-frame/http-fx "0.1.6"] [day8.re-frame/http-fx "0.1.6"]
[akiroz.re-frame/storage "0.1.2"] [akiroz.re-frame/storage "0.1.2"]
[funcool/bide "1.6.0"] [funcool/bide "1.6.0"]

View file

@ -0,0 +1,19 @@
(ns airsonic-ui.components.keyboard-shortcuts.config)
;; this keymap has the following structure:
;; [[readable-key readable-description event-vector event-keys]
;; ...]
(def keymap
[["Space" "Toggle play / pause"
[:audio-player/toggle-play-pause]
[{:keyCode 32}]]
["←" "Previous song"
[:audio-player/previous-song]
[{:keyCode 37}]]
["→" "Next song"
[:audio-player/next-song]
[{:keyCode 39}]]
["?" "Show / hide keyboard shortcut help"
[:bulma.modal.events/toggle :keyboard-shortcuts-help]
[{:keyCode 63}]]])

View file

@ -0,0 +1,13 @@
(ns airsonic-ui.components.keyboard-shortcuts.events
(:require [re-frame.core :as rf]
[re-pressed.core :as rp]
[airsonic-ui.components.keyboard-shortcuts.config :as config]))
(rf/reg-event-fx
::init-shortcuts
(fn []
(let [event-keys (map (juxt #(nth % 2) #(nth % 3)) config/keymap)
prevent-default-keys (mapcat last event-keys)]
{:dispatch-n [[::rp/add-keyboard-event-listener "keydown"]
[::rp/set-keydown-rules {:event-keys event-keys
:prevent-default-keys prevent-default-keys}]]})))

View file

@ -0,0 +1,12 @@
(ns airsonic-ui.components.keyboard-shortcuts.views
(:require [bulma.modal.views :as bulma]
[airsonic-ui.components.keyboard-shortcuts.config :as config]))
(defn help-modal []
[bulma/modal-card {:title "Keyboard Shortcuts"
:modal-id :keyboard-shortcuts-help}
[:table.table.is-hoverable.is-fullwidth
[:thead [:tr [:th "Key"] [:th "Function"]]]
[:tbody
(for [[idx [k desc]] (map-indexed vector config/keymap)]
^{:key idx} [:tr [:td>code k] [:td desc]])]]])

View file

@ -11,6 +11,7 @@
[airsonic-ui.api.events] [airsonic-ui.api.events]
[airsonic-ui.api.subs] [airsonic-ui.api.subs]
[airsonic-ui.components.audio-player.events] [airsonic-ui.components.audio-player.events]
[airsonic-ui.components.keyboard-shortcuts.events :as keyboard]
[airsonic-ui.components.library.subs] [airsonic-ui.components.library.subs]
[airsonic-ui.components.search.events] [airsonic-ui.components.search.events]
[airsonic-ui.components.search.subs] [airsonic-ui.components.search.subs]
@ -28,8 +29,8 @@
(reagent/render [views/main-panel] (.getElementById js/document "app"))) (reagent/render [views/main-panel] (.getElementById js/document "app")))
(defn ^:export init [] (defn ^:export init []
(storage/reg-co-fx! :airsonic-ui {:fx :store (storage/reg-co-fx! :airsonic-ui {:fx :store, :cofx :store})
:cofx :store})
(rf/dispatch-sync [::events/initialize-app]) (rf/dispatch-sync [::events/initialize-app])
(rf/dispatch [::keyboard/init-shortcuts])
(dev-setup) (dev-setup)
(mount-root)) (mount-root))

View file

@ -19,6 +19,7 @@
[airsonic-ui.components.bangpow.views :refer [not-found]] [airsonic-ui.components.bangpow.views :refer [not-found]]
[airsonic-ui.components.collection.views :as collection] [airsonic-ui.components.collection.views :as collection]
[airsonic-ui.components.current-queue.views :refer [current-queue]] [airsonic-ui.components.current-queue.views :refer [current-queue]]
[airsonic-ui.components.keyboard-shortcuts.views :as keyboard]
[airsonic-ui.components.library.views :as library] [airsonic-ui.components.library.views :as library]
[airsonic-ui.components.podcast.views :as podcast] [airsonic-ui.components.podcast.views :as podcast]
[airsonic-ui.components.search.views :as search])) [airsonic-ui.components.search.views :as search]))
@ -130,6 +131,7 @@
[route-id :as route] @(subscribe [:routes/current-route])] [route-id :as route] @(subscribe [:routes/current-route])]
[(add-classes :div route-id) [(add-classes :div route-id)
[notification-list notifications] [notification-list notifications]
[keyboard/help-modal]
(if is-booting? (if is-booting?
[:div.app-loading>div.loader] [:div.app-loading>div.loader]
[:div [:div