diff --git a/changes/changelog.d/611.enhancement b/changes/changelog.d/611.enhancement
new file mode 100644
index 0000000000000000000000000000000000000000..1c6121d67b3c8820d5f7892ddbfa0e1b80a7dff6
--- /dev/null
+++ b/changes/changelog.d/611.enhancement
@@ -0,0 +1 @@
+Documented keyboard shortcuts, list is now available by pressing "h" or in the footer (#611)
diff --git a/front/src/App.vue b/front/src/App.vue
index 946a0621e25c8dc8459a3336bf48bdc703a58243..6a4bd746e5bf6bf8f5fae68033580cf3d2224505 100644
--- a/front/src/App.vue
+++ b/front/src/App.vue
@@ -25,64 +25,17 @@
<service-messages v-if="messages.length > 0" />
<router-view :key="$route.fullPath"></router-view>
<div class="ui fitted divider"></div>
- <div id="footer" class="ui vertical footer segment">
- <div class="ui container">
- <div class="ui stackable equal height stackable grid">
- <div class="three wide column">
- <h4 v-translate class="ui header">Links</h4>
- <div class="ui link list">
- <router-link class="item" to="/about">
- <translate>About this instance</translate>
- </router-link>
- <a href="https://funkwhale.audio" class="item" target="_blank"><translate>Official website</translate></a>
- <a href="https://docs.funkwhale.audio" class="item" target="_blank"><translate>Documentation</translate></a>
- <a href="https://code.eliotberriot.com/funkwhale/funkwhale" class="item" target="_blank">
- <translate :translate-params="{version: version}" v-if="version">Source code (%{version})</translate>
- <translate v-else>Source code</translate>
- </a>
- <a href="https://code.eliotberriot.com/funkwhale/funkwhale/issues" class="item" target="_blank"><translate>Issue tracker</translate></a>
- <a @click="switchInstance" class="item" >
- <translate>Use another instance</translate>
- <template v-if="$store.state.instance.instanceUrl !== '/'">
- <br>
- ({{ $store.state.instance.instanceUrl }})
- </template>
- </a>
- </div>
- </div>
- <div class="ten wide column">
- <h4 v-translate class="ui header">About Funkwhale</h4>
- <p>
- <translate>Funkwhale is a free and open-source project run by volunteers. You can help us improve the platform by reporting bugs, suggesting features and share the project with your friends!</translate>
- </p>
- <p>
- <translate>The funkwhale logo was kindly designed and provided by Francis Gading.</translate>
- </p>
- </div>
- <div class="three wide column">
- <h4 v-translate class="ui header">Options</h4>
- <div class="ui form">
- <div class="ui field">
- <label><translate>Change language</translate></label>
- <select class="ui dropdown" v-model="$language.current">
- <option v-for="(language, key) in $language.available" :value="key">{{ language }}</option>
- </select>
- </div>
- </div>
- <br>
- <a target="_blank" href="https://translate.funkwhale.audio/engage/funkwhale/">
- <translate>Help us translate Funkwhale</translate>
- </a>
- </div>
-
- </div>
- </div>
- </div>
+ <app-footer :version="version" @show:shortcuts-modal="showShortcutsModal = !showShortcutsModal"></app-footer>
<raven
v-if="$store.state.instance.settings.raven.front_enabled.value"
:dsn="$store.state.instance.settings.raven.front_dsn.value">
</raven>
<playlist-modal v-if="$store.state.auth.authenticated"></playlist-modal>
+ <shortcuts-modal @update:show="showShortcutsModal = $event" :show="showShortcutsModal"></shortcuts-modal>
+ <GlobalEvents
+ @keydown.h.exact="showShortcutsModal = !showShortcutsModal"
+ />
+
</template>
</div>
</template>
@@ -92,29 +45,35 @@ import axios from 'axios'
import _ from 'lodash'
import {mapState} from 'vuex'
import { WebSocketBridge } from 'django-channels'
-
+import GlobalEvents from '@/components/utils/global-events'
import translations from '@/translations'
import Sidebar from '@/components/Sidebar'
+import AppFooter from '@/components/Footer'
import Raven from '@/components/Raven'
import ServiceMessages from '@/components/ServiceMessages'
import PlaylistModal from '@/components/playlists/PlaylistModal'
+import ShortcutsModal from '@/components/ShortcutsModal'
export default {
name: 'app',
components: {
Sidebar,
+ AppFooter,
Raven,
PlaylistModal,
+ ShortcutsModal,
+ GlobalEvents,
ServiceMessages
},
data () {
return {
bridge: null,
nodeinfo: null,
- instanceUrl: null
+ instanceUrl: null,
+ showShortcutsModal: false,
}
},
created () {
@@ -410,4 +369,10 @@ button.reset {
-webkit-appearance: none;
text-align: inherit;
}
+
+.ui.table > caption {
+ font-weight: bold;
+ padding: 0.5em;
+ text-align: left;
+}
</style>
diff --git a/front/src/components/ShortcutsModal.vue b/front/src/components/ShortcutsModal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2338658dd7b80a9d67c0e424f22b7b7f827270b1
--- /dev/null
+++ b/front/src/components/ShortcutsModal.vue
@@ -0,0 +1,95 @@
+<template>
+ <modal @update:show="$emit('update:show', $event)" :show="show">
+ <header class="header">
+ <translate>Keyboard shortcuts</translate>
+ </header>
+ <section class="scrolling content">
+ <table
+ class="ui compact collapsing basic fixed single line table"
+ v-for="section in sections"
+ :key="section.title">
+ <caption>{{ section.title }}</caption>
+ <tbody>
+ <tr v-for="shortcut in section.shortcuts" :key="shortcut.summary">
+ <td>{{ shortcut.summary }}</td>
+ <td><span class="ui label">{{ shortcut.key }}</span></td>
+ </tr>
+ </tbody>
+ </table>
+ </section>
+ <footer class="actions">
+ <div class="ui cancel button"><translate>Close</translate></div>
+ </footer>
+ </modal>
+</template>
+
+<script>
+import Modal from '@/components/semantic/Modal'
+
+export default {
+ props: ['show'],
+ components: {
+ Modal,
+ },
+ computed: {
+ sections () {
+ return [
+ {
+ title: this.$gettext('General shortcuts'),
+ shortcuts: [
+ {
+ key: 'h',
+ summary: this.$gettext('Show available keyboard shortcuts')
+ }
+ ]
+ },
+ // space.prevent.exact="togglePlay"
+ // ctrl.left.prevent.exact="previous"
+ // ctrl.right.prevent.exact="next"
+ // ctrl.down.prevent.exact="$store.commit('player/incrementVolume', -0.1)"
+ // ctrl.up.prevent.exact="$store.commit('player/incrementVolume', 0.1)"
+ // l.prevent.exact="$store.commit('player/toggleLooping')"
+ // s.prevent.exact="shuffle"
+
+ {
+ title: this.$gettext('Audio player shortcuts'),
+ shortcuts: [
+ {
+ key: 'space',
+ summary: this.$gettext('Pause/play the current track')
+ },
+ {
+ key: 'ctrl left',
+ summary: this.$gettext('Play previous track')
+ },
+ {
+ key: 'ctrl right',
+ summary: this.$gettext('Play next track')
+ },
+ {
+ key: 'ctrl up',
+ summary: this.$gettext('Increase volume')
+ },
+ {
+ key: 'ctrl down',
+ summary: this.$gettext('Decrease volume')
+ },
+ {
+ key: 'l',
+ summary: this.$gettext('Toggle queue looping')
+ },
+ {
+ key: 's',
+ summary: this.$gettext('Shuffle queue')
+ },
+ ]
+ }
+ ]
+ }
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+</style>