From 789505e2d9826aea54d2f342ea0f9aadb5fa93b2 Mon Sep 17 00:00:00 2001 From: James Lyne Date: Sat, 29 May 2021 00:08:43 +0100 Subject: [PATCH] Reimplement keyboard shortcuts more generically --- src/App.vue | 40 +++++++++++- src/components/Sidebar.vue | 123 +++++++++++++++---------------------- src/util.ts | 8 +++ 3 files changed, 95 insertions(+), 76 deletions(-) diff --git a/src/App.vue b/src/App.vue index 0fa8b27..7c2b637 100644 --- a/src/App.vue +++ b/src/App.vue @@ -31,7 +31,7 @@ import {ActionTypes} from "@/store/action-types"; import {parseUrl} from '@/util'; import {hideSplash, showSplash, showSplashError} from '@/util/splash'; import {MutationTypes} from "@/store/mutation-types"; -import {LiveAtlasServerDefinition} from "@/index"; +import {LiveAtlasServerDefinition, LiveAtlasUIElement} from "@/index"; export default defineComponent({ name: 'App', @@ -132,6 +132,34 @@ export default defineComponent({ onResize = () => { store.commit(MutationTypes.SET_SMALL_SCREEN, window.innerWidth < 480 || window.innerHeight < 500); + }, + + onKeydown = (e: KeyboardEvent) => { + if(!e.ctrlKey || !e.shiftKey) { + return; + } + + let element: LiveAtlasUIElement; + + switch(e.key) { + case 'M': + element = 'maps'; + break; + case 'C': + element = 'chat'; + break; + case 'P': + element = 'players'; + break; + case 'L': + element = 'layers'; + break; + default: + return; + } + + e.preventDefault(); + store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, element); }; watch(title, (title) => document.title = title); @@ -169,8 +197,14 @@ export default defineComponent({ handleUrl(); onResize(); - onMounted(() => window.addEventListener('resize', onResize)); - onUnmounted(() => window.addEventListener('resize', onResize)); + onMounted(() => { + window.addEventListener('resize', onResize); + window.addEventListener('keydown', onKeydown); + }); + onUnmounted(() => { + window.addEventListener('resize', onResize); + window.addEventListener('keydown', onKeydown); + }); return { chatBoxEnabled, diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index e6d8779..aec7595 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -15,25 +15,22 @@ --> @@ -49,8 +46,9 @@ import SvgIcon from "@/components/SvgIcon.vue"; import {MutationTypes} from "@/store/mutation-types"; import "@/assets/icons/players.svg"; import "@/assets/icons/maps.svg"; -import {nextTick} from "vue"; +import {nextTick, ref, watch} from "vue"; import {handleKeyboardEvent} from "@/util/events"; +import {focus} from "@/util"; export default defineComponent({ components: { @@ -63,6 +61,8 @@ export default defineComponent({ setup() { const store = useStore(), + sidebar = ref(null), + currentlyVisible = computed(() => store.state.ui.visibleElements), previouslyVisible = computed(() => store.state.ui.previouslyVisibleElements), smallScreen = computed(() => store.state.ui.smallScreen), @@ -73,78 +73,55 @@ export default defineComponent({ messageWorlds = computed(() => store.state.messages.worldsHeading), messagePlayers = computed(() => store.state.messages.playersHeading), - followActive = computed(() => { + playersVisible = computed(() => currentlyVisible.value.has('players')), + mapsVisible = computed(() => currentlyVisible.value.has('maps')), + followVisible = computed(() => { //Show following alongside playerlist on small screens return (!smallScreen.value && following.value) - || (smallScreen.value && currentlyVisible.value.has('players')); + || (smallScreen.value && playersVisible.value); }); - return { - mapCount, - serverCount, - currentlyVisible, - previouslyVisible, - followActive, - following, - messageWorlds, - messagePlayers, - } - }, - - mounted() { - window.addEventListener('keydown', this.handleButtonKeydown); - }, - - unmounted() { - window.removeEventListener('keydown', this.handleButtonKeydown); - }, - - methods: { - handleButtonKeydown(e: KeyboardEvent) { - if(e.key === '!' && e.ctrlKey && e.shiftKey) { - e.preventDefault(); - this.toggleMaps(); - } else if(e.key === '"' && e.ctrlKey && e.shiftKey) { - e.preventDefault(); - this.togglePlayers(); - } - }, - handleSidebarKeydown(e: KeyboardEvent) { + //Arrow key section navigation + const handleSidebarKeydown = (e: KeyboardEvent) => { if(!e.target || !(e.target as HTMLElement).matches('.section__heading button')) { return; } - const sectionHeadings: HTMLElement[] = Array.from(this.$el.querySelectorAll('.section__heading button')); + const sectionHeadings: HTMLElement[] = Array.from(sidebar.value!.querySelectorAll('.section__heading button')); handleKeyboardEvent(e, sectionHeadings); - }, - togglePlayers() { - useStore().commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, 'players'); - nextTick(this.focusPlayers); - }, - toggleMaps() { - useStore().commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, 'maps'); - nextTick(this.focusMaps); - }, - focusPlayers() { - if (this.currentlyVisible.has('players')) { - const heading = document.getElementById('players-heading'); + }; - if (heading) { - heading.focus(); - } - } - }, - focusMaps() { - if(this.currentlyVisible.has('maps')) { - const heading = document.querySelector('.section__heading button'); + const togglePlayers = () => store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, 'players'); + const toggleMaps = () => store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, 'maps'); - if(heading) { - (heading as HTMLElement).focus(); - } - } + //Move focus when sidebar sections become visible + const focusMaps = () => focus('.section__heading button'); + const focusPlayers = () => focus('#players-heading'); + + watch(playersVisible, newValue => newValue && nextTick(() => focusPlayers())); + watch(mapsVisible, newValue => newValue && nextTick(() => focusMaps())); + + return { + sidebar, + + mapCount, + serverCount, + following, + + messageWorlds, + messagePlayers, + + previouslyVisible, + playersVisible, + mapsVisible, + followVisible, + + handleSidebarKeydown, + togglePlayers, + toggleMaps } - } -}) + }, +}); diff --git a/src/util.ts b/src/util.ts index 784be00..422e03b 100644 --- a/src/util.ts +++ b/src/util.ts @@ -229,3 +229,11 @@ export const getUrlForLocation = (world: DynmapWorld, map: DynmapWorldMap, locat return `#${world.name};${map.name};${locationString};${zoom}`; } + +export const focus = (selector: string) => { + const element = document.querySelector(selector); + + if(element) { + (element as HTMLElement).focus(); + } +} \ No newline at end of file