Reimplement keyboard shortcuts more generically
This commit is contained in:
parent
0ca26beb8e
commit
789505e2d9
40
src/App.vue
40
src/App.vue
@ -31,7 +31,7 @@ import {ActionTypes} from "@/store/action-types";
|
|||||||
import {parseUrl} from '@/util';
|
import {parseUrl} from '@/util';
|
||||||
import {hideSplash, showSplash, showSplashError} from '@/util/splash';
|
import {hideSplash, showSplash, showSplashError} from '@/util/splash';
|
||||||
import {MutationTypes} from "@/store/mutation-types";
|
import {MutationTypes} from "@/store/mutation-types";
|
||||||
import {LiveAtlasServerDefinition} from "@/index";
|
import {LiveAtlasServerDefinition, LiveAtlasUIElement} from "@/index";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'App',
|
name: 'App',
|
||||||
@ -132,6 +132,34 @@ export default defineComponent({
|
|||||||
|
|
||||||
onResize = () => {
|
onResize = () => {
|
||||||
store.commit(MutationTypes.SET_SMALL_SCREEN, window.innerWidth < 480 || window.innerHeight < 500);
|
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);
|
watch(title, (title) => document.title = title);
|
||||||
@ -169,8 +197,14 @@ export default defineComponent({
|
|||||||
handleUrl();
|
handleUrl();
|
||||||
onResize();
|
onResize();
|
||||||
|
|
||||||
onMounted(() => window.addEventListener('resize', onResize));
|
onMounted(() => {
|
||||||
onUnmounted(() => window.addEventListener('resize', onResize));
|
window.addEventListener('resize', onResize);
|
||||||
|
window.addEventListener('keydown', onKeydown);
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.addEventListener('resize', onResize);
|
||||||
|
window.addEventListener('keydown', onKeydown);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chatBoxEnabled,
|
chatBoxEnabled,
|
||||||
|
@ -15,25 +15,22 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="sidebar" role="none">
|
<section class="sidebar" role="none" ref="sidebar">
|
||||||
<header class="sidebar__buttons">
|
<header class="sidebar__buttons">
|
||||||
<button v-if="mapCount > 1" :class="{'button--maps': true}"
|
<button v-if="mapCount > 1" :class="{'button--maps': true}" @click="toggleMaps" :title="messageWorlds"
|
||||||
@click="toggleMaps" :title="messageWorlds"
|
:aria-label="messageWorlds" :aria-expanded="mapsVisible">
|
||||||
:aria-label="messageWorlds" :aria-expanded="currentlyVisible.has('maps')">
|
|
||||||
<SvgIcon name="maps"></SvgIcon>
|
<SvgIcon name="maps"></SvgIcon>
|
||||||
</button>
|
</button>
|
||||||
<button :class="{'button--players': true}"
|
<button :class="{'button--players': true}" @click="togglePlayers" :title="messagePlayers"
|
||||||
@click="togglePlayers" :title="messagePlayers"
|
:aria-label="messagePlayers" :aria-expanded="playersVisible">
|
||||||
:aria-label="messagePlayers" :aria-expanded="currentlyVisible.has('players')">
|
|
||||||
<SvgIcon name="players"></SvgIcon>
|
<SvgIcon name="players"></SvgIcon>
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<div class="sidebar__content" @keydown="handleSidebarKeydown">
|
<div class="sidebar__content" @keydown="handleSidebarKeydown">
|
||||||
<ServerList v-if="serverCount > 1" v-show="currentlyVisible.has('maps')"></ServerList>
|
<ServerList v-if="serverCount > 1" v-show="mapsVisible"></ServerList>
|
||||||
<WorldList v-if="mapCount > 1" v-show="currentlyVisible.has('maps')"></WorldList>
|
<WorldList v-if="mapCount > 1" v-show="mapsVisible"></WorldList>
|
||||||
<PlayerList id="players" v-if="previouslyVisible.has('players')"
|
<PlayerList id="players" v-if="previouslyVisible.has('players')" v-show="playersVisible"></PlayerList>
|
||||||
v-show="currentlyVisible.has('players')"></PlayerList>
|
<FollowTarget v-if="following" v-show="followVisible" :target="following"></FollowTarget>
|
||||||
<FollowTarget v-if="following" v-show="followActive" :target="following"></FollowTarget>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@ -49,8 +46,9 @@ import SvgIcon from "@/components/SvgIcon.vue";
|
|||||||
import {MutationTypes} from "@/store/mutation-types";
|
import {MutationTypes} from "@/store/mutation-types";
|
||||||
import "@/assets/icons/players.svg";
|
import "@/assets/icons/players.svg";
|
||||||
import "@/assets/icons/maps.svg";
|
import "@/assets/icons/maps.svg";
|
||||||
import {nextTick} from "vue";
|
import {nextTick, ref, watch} from "vue";
|
||||||
import {handleKeyboardEvent} from "@/util/events";
|
import {handleKeyboardEvent} from "@/util/events";
|
||||||
|
import {focus} from "@/util";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -63,6 +61,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore(),
|
const store = useStore(),
|
||||||
|
sidebar = ref<HTMLElement | null>(null),
|
||||||
|
|
||||||
currentlyVisible = computed(() => store.state.ui.visibleElements),
|
currentlyVisible = computed(() => store.state.ui.visibleElements),
|
||||||
previouslyVisible = computed(() => store.state.ui.previouslyVisibleElements),
|
previouslyVisible = computed(() => store.state.ui.previouslyVisibleElements),
|
||||||
smallScreen = computed(() => store.state.ui.smallScreen),
|
smallScreen = computed(() => store.state.ui.smallScreen),
|
||||||
@ -73,78 +73,55 @@ export default defineComponent({
|
|||||||
messageWorlds = computed(() => store.state.messages.worldsHeading),
|
messageWorlds = computed(() => store.state.messages.worldsHeading),
|
||||||
messagePlayers = computed(() => store.state.messages.playersHeading),
|
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
|
//Show following alongside playerlist on small screens
|
||||||
return (!smallScreen.value && following.value)
|
return (!smallScreen.value && following.value)
|
||||||
|| (smallScreen.value && currentlyVisible.value.has('players'));
|
|| (smallScreen.value && playersVisible.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
//Arrow key section navigation
|
||||||
mapCount,
|
const handleSidebarKeydown = (e: KeyboardEvent) => {
|
||||||
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) {
|
|
||||||
if(!e.target || !(e.target as HTMLElement).matches('.section__heading button')) {
|
if(!e.target || !(e.target as HTMLElement).matches('.section__heading button')) {
|
||||||
return;
|
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);
|
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) {
|
const togglePlayers = () => store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, 'players');
|
||||||
heading.focus();
|
const toggleMaps = () => store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, 'maps');
|
||||||
}
|
|
||||||
|
//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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
focusMaps() {
|
});
|
||||||
if(this.currentlyVisible.has('maps')) {
|
|
||||||
const heading = document.querySelector('.section__heading button');
|
|
||||||
|
|
||||||
if(heading) {
|
|
||||||
(heading as HTMLElement).focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -229,3 +229,11 @@ export const getUrlForLocation = (world: DynmapWorld, map: DynmapWorldMap, locat
|
|||||||
|
|
||||||
return `#${world.name};${map.name};${locationString};${zoom}`;
|
return `#${world.name};${map.name};${locationString};${zoom}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const focus = (selector: string) => {
|
||||||
|
const element = document.querySelector(selector);
|
||||||
|
|
||||||
|
if(element) {
|
||||||
|
(element as HTMLElement).focus();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user