Allow sidebar sections to be collapsed

This commit is contained in:
James Lyne 2021-05-25 19:26:14 +01:00
parent 22102eae65
commit eaecba10c6
11 changed files with 139 additions and 46 deletions

View File

@ -108,6 +108,7 @@
locationChunk: 'Chunk',
contextMenuCopyLink: 'Copy link to here',
contextMenuCenterHere: 'Center here',
toggleTitle: 'Click to toggle this section',
}
};
</script>

View File

@ -95,6 +95,7 @@ function buildMessagesConfig(response: any): LiveAtlasMessageConfig {
locationChunk: liveAtlasMessages.locationChunk || '',
contextMenuCopyLink: liveAtlasMessages.contextMenuCopyLink || '',
contextMenuCenterHere: liveAtlasMessages.contextMenuCenterHere || '',
toggleTitle: liveAtlasMessages.toggleTitle || '',
}
}

View File

@ -42,9 +42,9 @@ import FollowTarget from './sidebar/FollowTarget.vue';
import {useStore} from "@/store";
import SvgIcon from "@/components/SvgIcon.vue";
import {MutationTypes} from "@/store/mutation-types";
import {DynmapUIElement} from "@/dynmap";
import "@/assets/icons/players.svg";
import "@/assets/icons/maps.svg";
import {LiveAtlasUIElement} from "@/index";
export default defineComponent({
components: {
@ -67,7 +67,7 @@ export default defineComponent({
messageWorlds = computed(() => store.state.messages.worldsHeading),
messagePlayers = computed(() => store.state.messages.playersHeading),
toggleElement = (element: DynmapUIElement) => {
toggleElement = (element: LiveAtlasUIElement) => {
store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, element);
},
@ -168,7 +168,18 @@ export default defineComponent({
.section__heading {
font-size: 2rem;
margin-bottom: 1rem;
cursor: pointer;
user-select: none;
padding: 1.5rem 1.5rem 1rem;
margin: -1.5rem -1.5rem 0;
background-color: transparent;
color: inherit;
text-align: left;
&:hover, &:focus-visible, &.focus-visible, &:active {
background-color: transparent;
color: inherit;
}
}
.section__content {
@ -191,6 +202,17 @@ export default defineComponent({
align-self: center;
margin-top: 1rem;
}
&.section--collapsed {
.section__heading {
padding-bottom: 1.5rem;
margin-bottom: -1.5rem;
}
.section__content {
display: none;
}
}
}
@media (max-width: 480px) {

View File

@ -0,0 +1,58 @@
<template>
<section :class="{'sidebar__section': true, 'section--collapsed': collapsed}">
<button type="button" class="section__heading"
@click.prevent="toggle" @keypress.prevent="handleKeypress" :title="title">
<slot name="heading"></slot>
</button>
<ul class="section__content menu">
<slot></slot>
</ul>
</section>
</template>
<script lang="ts">
import {useStore} from "@/store";
import {LiveAtlasSidebarSection} from "@/index";
import {defineComponent} from "@vue/runtime-core";
export default defineComponent({
name: 'CollapsibleSection',
props: {
name: {
type: Object as () => LiveAtlasSidebarSection,
required: true,
}
},
computed: {
title(): string {
return useStore().state.messages.toggleTitle;
},
collapsed(): boolean {
return useStore().state.ui.collapsedSections.has(this.name);
},
},
methods: {
handleKeypress(e: KeyboardEvent) {
if(e.key !== ' ' && e.key !== 'Enter') {
return;
}
this.toggle();
},
toggle() {
const store = useStore();
if(this.collapsed) {
store.state.ui.collapsedSections.delete(this.name);
} else {
store.state.ui.collapsedSections.add(this.name);
}
}
}
});
</script>

View File

@ -15,22 +15,26 @@
-->
<template>
<section class="sidebar__section sidebar__section--players">
<span class="section__heading">{{ heading }} [{{ players.size }}/{{ maxPlayers }}]</span>
<ul class="section__content menu">
<PlayerListItem v-for="[account, player] in players" :key="account" :player="player"></PlayerListItem>
<li v-if="!players.size" class="section__skeleton">{{ skeletonPlayers }}</li>
</ul>
</section>
<CollapsibleSection name="players">
<template v-slot:heading>{{ heading }} [{{ players.size }}/{{ maxPlayers }}]</template>
<template v-slot:default>
<ul class="section__content menu">
<PlayerListItem v-for="[account, player] in players" :key="account" :player="player"></PlayerListItem>
<li v-if="!players.size" class="section__skeleton">{{ skeletonPlayers }}</li>
</ul>
</template>
</CollapsibleSection>
</template>
<script lang="ts">
import PlayerListItem from "./PlayerListItem.vue";
import {defineComponent} from "@vue/runtime-core";
import {useStore} from "@/store";
import CollapsibleSection from "@/components/sidebar/CollapsibleSection.vue";
export default defineComponent({
components: {
CollapsibleSection,
PlayerListItem
},
@ -50,7 +54,7 @@ export default defineComponent({
maxPlayers(): number {
return useStore().state.configuration.maxPlayers;
}
},
}
});
</script>

View File

@ -15,22 +15,26 @@
-->
<template>
<section class="sidebar__section" v-if="servers.size > 1">
<span class="section__heading">{{ heading }}</span>
<ul class="section__content menu">
<ServerListItem :server="server" v-for="[name, server] in servers" :key="name"></ServerListItem>
</ul>
</section>
<CollapsibleSection v-if="servers.size > 1" name="servers">
<template v-slot:heading>{{ heading }}</template>
<template v-slot:default>
<ul class="section__content menu">
<ServerListItem :server="server" v-for="[name, server] in servers" :key="name"></ServerListItem>
</ul>
</template>
</CollapsibleSection>
</template>
<script lang="ts">
import ServerListItem from './ServerListItem.vue';
import {defineComponent} from 'vue';
import {useStore} from "@/store";
import CollapsibleSection from "@/components/sidebar/CollapsibleSection.vue";
export default defineComponent({
name: 'ServerList',
components: {
CollapsibleSection,
ServerListItem
},
@ -42,7 +46,6 @@ export default defineComponent({
servers() {
return useStore().state.servers;
}
},
}
});
</script>

View File

@ -15,23 +15,27 @@
-->
<template>
<section class="sidebar__section">
<span class="section__heading">{{ heading }}</span>
<ul class="section__content">
<WorldListItem :world="world" v-for="[name, world] in worlds" :key="name"></WorldListItem>
<li v-if="!worlds.size" class="section__skeleton">{{ skeletonWorlds }}</li>
</ul>
</section>
<CollapsibleSection name="maps">
<template v-slot:heading>{{ heading }}</template>
<template v-slot:default>
<ul class="section__content">
<WorldListItem :world="world" v-for="[name, world] in worlds" :key="name"></WorldListItem>
<li v-if="!worlds.size" class="section__skeleton">{{ skeletonWorlds }}</li>
</ul>
</template>
</CollapsibleSection>
</template>
<script lang="ts">
import WorldListItem from './WorldListItem.vue';
import {defineComponent} from 'vue';
import {useStore} from "@/store";
import CollapsibleSection from "@/components/sidebar/CollapsibleSection.vue";
export default defineComponent({
name: 'WorldList',
components: {
CollapsibleSection,
WorldListItem
},
@ -47,11 +51,6 @@ export default defineComponent({
worlds() {
return useStore().state.worlds;
}
},
}
});
</script>
<style scoped>
</style>

2
src/dynmap.d.ts vendored
View File

@ -296,5 +296,3 @@ interface DynmapChat {
source?: string;
timestamp: number;
}
export type DynmapUIElement = 'chat' | 'players' | 'maps' | 'settings';

6
src/index.d.ts vendored
View File

@ -63,4 +63,8 @@ interface LiveAtlasMessageConfig {
locationChunk: string;
contextMenuCopyLink: string;
contextMenuCenterHere: string;
}
toggleTitle: string;
}
export type LiveAtlasUIElement = 'chat' | 'players' | 'maps' | 'settings';
export type LiveAtlasSidebarSection = 'servers' | 'players' | 'maps';

View File

@ -28,10 +28,10 @@ import {
DynmapPlayer,
DynmapServerConfig, DynmapTileUpdate,
DynmapWorld,
DynmapWorldState, DynmapParsedUrl, DynmapChat, DynmapUIElement
DynmapWorldState, DynmapParsedUrl, DynmapChat
} from "@/dynmap";
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
import {LiveAtlasMessageConfig, LiveAtlasServerDefinition} from "@/index";
import {LiveAtlasMessageConfig, LiveAtlasServerDefinition, LiveAtlasUIElement} from "@/index";
export type CurrentMapPayload = {
worldName: string;
@ -80,8 +80,8 @@ export type Mutations<S = State> = {
[MutationTypes.CLEAR_PAN_TARGET](state: S, a?: void): void
[MutationTypes.SET_SMALL_SCREEN](state: S, payload: boolean): void
[MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY](state: S, payload: DynmapUIElement): void
[MutationTypes.SET_UI_ELEMENT_VISIBILITY](state: S, payload: {element: DynmapUIElement, state: boolean}): void
[MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY](state: S, payload: LiveAtlasUIElement): void
[MutationTypes.SET_UI_ELEMENT_VISIBILITY](state: S, payload: {element: LiveAtlasUIElement, state: boolean}): void
[MutationTypes.SET_LOGGED_IN](state: S, payload: boolean): void
}
@ -509,7 +509,7 @@ export const mutations: MutationTree<State> & Mutations = {
state.ui.smallScreen = smallScreen;
},
[MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY](state: State, element: DynmapUIElement): void {
[MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY](state: State, element: LiveAtlasUIElement): void {
const newState = !state.ui.visibleElements.has(element);
if(newState && state.ui.smallScreen) {
@ -520,7 +520,7 @@ export const mutations: MutationTree<State> & Mutations = {
newState ? state.ui.visibleElements.add(element) : state.ui.visibleElements.delete(element);
},
[MutationTypes.SET_UI_ELEMENT_VISIBILITY](state: State, payload: {element: DynmapUIElement, state: boolean}): void {
[MutationTypes.SET_UI_ELEMENT_VISIBILITY](state: State, payload: {element: LiveAtlasUIElement, state: boolean}): void {
if(payload.state && state.ui.smallScreen) {
state.ui.visibleElements.clear();
}

View File

@ -19,10 +19,10 @@ import {
DynmapWorldMap, DynmapMarkerSet, DynmapMarkerSetUpdates,
DynmapPlayer,
DynmapServerConfig, DynmapTileUpdate,
DynmapWorld, DynmapWorldState, Coordinate, DynmapParsedUrl, DynmapChat, DynmapUIElement
DynmapWorld, DynmapWorldState, Coordinate, DynmapParsedUrl, DynmapChat
} from "@/dynmap";
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
import {LiveAtlasMessageConfig, LiveAtlasServerDefinition} from "@/index";
import {LiveAtlasMessageConfig, LiveAtlasServerDefinition, LiveAtlasSidebarSection, LiveAtlasUIElement} from "@/index";
export type State = {
version: string;
@ -63,8 +63,9 @@ export type State = {
ui: {
smallScreen: boolean;
visibleElements: Set<DynmapUIElement>;
previouslyVisibleElements: Set<DynmapUIElement>;
visibleElements: Set<LiveAtlasUIElement>;
previouslyVisibleElements: Set<LiveAtlasUIElement>;
collapsedSections: Set<LiveAtlasSidebarSection>;
};
parsedUrl: DynmapParsedUrl;
@ -125,7 +126,8 @@ export const state: State = {
locationRegion: '',
locationChunk: '',
contextMenuCopyLink: '',
contextMenuCenterHere: ''
contextMenuCenterHere: '',
toggleTitle: '',
},
loggedIn: false,
@ -207,6 +209,7 @@ export const state: State = {
smallScreen: false,
visibleElements:new Set(),
previouslyVisibleElements: new Set(),
collapsedSections: new Set(),
},
parsedUrl: {