Allow sidebar sections to be collapsed
This commit is contained in:
parent
22102eae65
commit
eaecba10c6
@ -108,6 +108,7 @@
|
||||
locationChunk: 'Chunk',
|
||||
contextMenuCopyLink: 'Copy link to here',
|
||||
contextMenuCenterHere: 'Center here',
|
||||
toggleTitle: 'Click to toggle this section',
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -95,6 +95,7 @@ function buildMessagesConfig(response: any): LiveAtlasMessageConfig {
|
||||
locationChunk: liveAtlasMessages.locationChunk || '',
|
||||
contextMenuCopyLink: liveAtlasMessages.contextMenuCopyLink || '',
|
||||
contextMenuCenterHere: liveAtlasMessages.contextMenuCenterHere || '',
|
||||
toggleTitle: liveAtlasMessages.toggleTitle || '',
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
58
src/components/sidebar/CollapsibleSection.vue
Normal file
58
src/components/sidebar/CollapsibleSection.vue
Normal 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>
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
2
src/dynmap.d.ts
vendored
@ -296,5 +296,3 @@ interface DynmapChat {
|
||||
source?: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export type DynmapUIElement = 'chat' | 'players' | 'maps' | 'settings';
|
4
src/index.d.ts
vendored
4
src/index.d.ts
vendored
@ -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';
|
@ -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();
|
||||
}
|
||||
|
@ -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: {
|
||||
|
Loading…
Reference in New Issue
Block a user