Allow sidebar sections to be collapsed
This commit is contained in:
parent
22102eae65
commit
eaecba10c6
@ -108,6 +108,7 @@
|
|||||||
locationChunk: 'Chunk',
|
locationChunk: 'Chunk',
|
||||||
contextMenuCopyLink: 'Copy link to here',
|
contextMenuCopyLink: 'Copy link to here',
|
||||||
contextMenuCenterHere: 'Center here',
|
contextMenuCenterHere: 'Center here',
|
||||||
|
toggleTitle: 'Click to toggle this section',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -95,6 +95,7 @@ function buildMessagesConfig(response: any): LiveAtlasMessageConfig {
|
|||||||
locationChunk: liveAtlasMessages.locationChunk || '',
|
locationChunk: liveAtlasMessages.locationChunk || '',
|
||||||
contextMenuCopyLink: liveAtlasMessages.contextMenuCopyLink || '',
|
contextMenuCopyLink: liveAtlasMessages.contextMenuCopyLink || '',
|
||||||
contextMenuCenterHere: liveAtlasMessages.contextMenuCenterHere || '',
|
contextMenuCenterHere: liveAtlasMessages.contextMenuCenterHere || '',
|
||||||
|
toggleTitle: liveAtlasMessages.toggleTitle || '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,9 +42,9 @@ import FollowTarget from './sidebar/FollowTarget.vue';
|
|||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
import SvgIcon from "@/components/SvgIcon.vue";
|
import SvgIcon from "@/components/SvgIcon.vue";
|
||||||
import {MutationTypes} from "@/store/mutation-types";
|
import {MutationTypes} from "@/store/mutation-types";
|
||||||
import {DynmapUIElement} from "@/dynmap";
|
|
||||||
import "@/assets/icons/players.svg";
|
import "@/assets/icons/players.svg";
|
||||||
import "@/assets/icons/maps.svg";
|
import "@/assets/icons/maps.svg";
|
||||||
|
import {LiveAtlasUIElement} from "@/index";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -67,7 +67,7 @@ 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),
|
||||||
|
|
||||||
toggleElement = (element: DynmapUIElement) => {
|
toggleElement = (element: LiveAtlasUIElement) => {
|
||||||
store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, element);
|
store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, element);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -168,7 +168,18 @@ export default defineComponent({
|
|||||||
|
|
||||||
.section__heading {
|
.section__heading {
|
||||||
font-size: 2rem;
|
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 {
|
.section__content {
|
||||||
@ -191,6 +202,17 @@ export default defineComponent({
|
|||||||
align-self: center;
|
align-self: center;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.section--collapsed {
|
||||||
|
.section__heading {
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
margin-bottom: -1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@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>
|
<template>
|
||||||
<section class="sidebar__section sidebar__section--players">
|
<CollapsibleSection name="players">
|
||||||
<span class="section__heading">{{ heading }} [{{ players.size }}/{{ maxPlayers }}]</span>
|
<template v-slot:heading>{{ heading }} [{{ players.size }}/{{ maxPlayers }}]</template>
|
||||||
<ul class="section__content menu">
|
<template v-slot:default>
|
||||||
<PlayerListItem v-for="[account, player] in players" :key="account" :player="player"></PlayerListItem>
|
<ul class="section__content menu">
|
||||||
<li v-if="!players.size" class="section__skeleton">{{ skeletonPlayers }}</li>
|
<PlayerListItem v-for="[account, player] in players" :key="account" :player="player"></PlayerListItem>
|
||||||
</ul>
|
<li v-if="!players.size" class="section__skeleton">{{ skeletonPlayers }}</li>
|
||||||
</section>
|
</ul>
|
||||||
|
</template>
|
||||||
|
</CollapsibleSection>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import PlayerListItem from "./PlayerListItem.vue";
|
import PlayerListItem from "./PlayerListItem.vue";
|
||||||
import {defineComponent} from "@vue/runtime-core";
|
import {defineComponent} from "@vue/runtime-core";
|
||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
|
import CollapsibleSection from "@/components/sidebar/CollapsibleSection.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
CollapsibleSection,
|
||||||
PlayerListItem
|
PlayerListItem
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -50,7 +54,7 @@ export default defineComponent({
|
|||||||
maxPlayers(): number {
|
maxPlayers(): number {
|
||||||
return useStore().state.configuration.maxPlayers;
|
return useStore().state.configuration.maxPlayers;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -15,22 +15,26 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="sidebar__section" v-if="servers.size > 1">
|
<CollapsibleSection v-if="servers.size > 1" name="servers">
|
||||||
<span class="section__heading">{{ heading }}</span>
|
<template v-slot:heading>{{ heading }}</template>
|
||||||
<ul class="section__content menu">
|
<template v-slot:default>
|
||||||
<ServerListItem :server="server" v-for="[name, server] in servers" :key="name"></ServerListItem>
|
<ul class="section__content menu">
|
||||||
</ul>
|
<ServerListItem :server="server" v-for="[name, server] in servers" :key="name"></ServerListItem>
|
||||||
</section>
|
</ul>
|
||||||
|
</template>
|
||||||
|
</CollapsibleSection>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ServerListItem from './ServerListItem.vue';
|
import ServerListItem from './ServerListItem.vue';
|
||||||
import {defineComponent} from 'vue';
|
import {defineComponent} from 'vue';
|
||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
|
import CollapsibleSection from "@/components/sidebar/CollapsibleSection.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ServerList',
|
name: 'ServerList',
|
||||||
components: {
|
components: {
|
||||||
|
CollapsibleSection,
|
||||||
ServerListItem
|
ServerListItem
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -42,7 +46,6 @@ export default defineComponent({
|
|||||||
servers() {
|
servers() {
|
||||||
return useStore().state.servers;
|
return useStore().state.servers;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -15,23 +15,27 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="sidebar__section">
|
<CollapsibleSection name="maps">
|
||||||
<span class="section__heading">{{ heading }}</span>
|
<template v-slot:heading>{{ heading }}</template>
|
||||||
<ul class="section__content">
|
<template v-slot:default>
|
||||||
<WorldListItem :world="world" v-for="[name, world] in worlds" :key="name"></WorldListItem>
|
<ul class="section__content">
|
||||||
<li v-if="!worlds.size" class="section__skeleton">{{ skeletonWorlds }}</li>
|
<WorldListItem :world="world" v-for="[name, world] in worlds" :key="name"></WorldListItem>
|
||||||
</ul>
|
<li v-if="!worlds.size" class="section__skeleton">{{ skeletonWorlds }}</li>
|
||||||
</section>
|
</ul>
|
||||||
|
</template>
|
||||||
|
</CollapsibleSection>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import WorldListItem from './WorldListItem.vue';
|
import WorldListItem from './WorldListItem.vue';
|
||||||
import {defineComponent} from 'vue';
|
import {defineComponent} from 'vue';
|
||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
|
import CollapsibleSection from "@/components/sidebar/CollapsibleSection.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'WorldList',
|
name: 'WorldList',
|
||||||
components: {
|
components: {
|
||||||
|
CollapsibleSection,
|
||||||
WorldListItem
|
WorldListItem
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -47,11 +51,6 @@ export default defineComponent({
|
|||||||
worlds() {
|
worlds() {
|
||||||
return useStore().state.worlds;
|
return useStore().state.worlds;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
2
src/dynmap.d.ts
vendored
2
src/dynmap.d.ts
vendored
@ -296,5 +296,3 @@ interface DynmapChat {
|
|||||||
source?: string;
|
source?: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DynmapUIElement = 'chat' | 'players' | 'maps' | 'settings';
|
|
6
src/index.d.ts
vendored
6
src/index.d.ts
vendored
@ -63,4 +63,8 @@ interface LiveAtlasMessageConfig {
|
|||||||
locationChunk: string;
|
locationChunk: string;
|
||||||
contextMenuCopyLink: string;
|
contextMenuCopyLink: string;
|
||||||
contextMenuCenterHere: string;
|
contextMenuCenterHere: string;
|
||||||
}
|
toggleTitle: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LiveAtlasUIElement = 'chat' | 'players' | 'maps' | 'settings';
|
||||||
|
export type LiveAtlasSidebarSection = 'servers' | 'players' | 'maps';
|
@ -28,10 +28,10 @@ import {
|
|||||||
DynmapPlayer,
|
DynmapPlayer,
|
||||||
DynmapServerConfig, DynmapTileUpdate,
|
DynmapServerConfig, DynmapTileUpdate,
|
||||||
DynmapWorld,
|
DynmapWorld,
|
||||||
DynmapWorldState, DynmapParsedUrl, DynmapChat, DynmapUIElement
|
DynmapWorldState, DynmapParsedUrl, DynmapChat
|
||||||
} from "@/dynmap";
|
} from "@/dynmap";
|
||||||
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
|
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
|
||||||
import {LiveAtlasMessageConfig, LiveAtlasServerDefinition} from "@/index";
|
import {LiveAtlasMessageConfig, LiveAtlasServerDefinition, LiveAtlasUIElement} from "@/index";
|
||||||
|
|
||||||
export type CurrentMapPayload = {
|
export type CurrentMapPayload = {
|
||||||
worldName: string;
|
worldName: string;
|
||||||
@ -80,8 +80,8 @@ export type Mutations<S = State> = {
|
|||||||
[MutationTypes.CLEAR_PAN_TARGET](state: S, a?: void): void
|
[MutationTypes.CLEAR_PAN_TARGET](state: S, a?: void): void
|
||||||
|
|
||||||
[MutationTypes.SET_SMALL_SCREEN](state: S, payload: boolean): void
|
[MutationTypes.SET_SMALL_SCREEN](state: S, payload: boolean): void
|
||||||
[MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY](state: S, payload: DynmapUIElement): void
|
[MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY](state: S, payload: LiveAtlasUIElement): void
|
||||||
[MutationTypes.SET_UI_ELEMENT_VISIBILITY](state: S, payload: {element: DynmapUIElement, state: boolean}): void
|
[MutationTypes.SET_UI_ELEMENT_VISIBILITY](state: S, payload: {element: LiveAtlasUIElement, state: boolean}): void
|
||||||
|
|
||||||
[MutationTypes.SET_LOGGED_IN](state: S, payload: 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;
|
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);
|
const newState = !state.ui.visibleElements.has(element);
|
||||||
|
|
||||||
if(newState && state.ui.smallScreen) {
|
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);
|
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) {
|
if(payload.state && state.ui.smallScreen) {
|
||||||
state.ui.visibleElements.clear();
|
state.ui.visibleElements.clear();
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,10 @@ import {
|
|||||||
DynmapWorldMap, DynmapMarkerSet, DynmapMarkerSetUpdates,
|
DynmapWorldMap, DynmapMarkerSet, DynmapMarkerSetUpdates,
|
||||||
DynmapPlayer,
|
DynmapPlayer,
|
||||||
DynmapServerConfig, DynmapTileUpdate,
|
DynmapServerConfig, DynmapTileUpdate,
|
||||||
DynmapWorld, DynmapWorldState, Coordinate, DynmapParsedUrl, DynmapChat, DynmapUIElement
|
DynmapWorld, DynmapWorldState, Coordinate, DynmapParsedUrl, DynmapChat
|
||||||
} from "@/dynmap";
|
} from "@/dynmap";
|
||||||
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
|
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
|
||||||
import {LiveAtlasMessageConfig, LiveAtlasServerDefinition} from "@/index";
|
import {LiveAtlasMessageConfig, LiveAtlasServerDefinition, LiveAtlasSidebarSection, LiveAtlasUIElement} from "@/index";
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
version: string;
|
version: string;
|
||||||
@ -63,8 +63,9 @@ export type State = {
|
|||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
smallScreen: boolean;
|
smallScreen: boolean;
|
||||||
visibleElements: Set<DynmapUIElement>;
|
visibleElements: Set<LiveAtlasUIElement>;
|
||||||
previouslyVisibleElements: Set<DynmapUIElement>;
|
previouslyVisibleElements: Set<LiveAtlasUIElement>;
|
||||||
|
collapsedSections: Set<LiveAtlasSidebarSection>;
|
||||||
};
|
};
|
||||||
|
|
||||||
parsedUrl: DynmapParsedUrl;
|
parsedUrl: DynmapParsedUrl;
|
||||||
@ -125,7 +126,8 @@ export const state: State = {
|
|||||||
locationRegion: '',
|
locationRegion: '',
|
||||||
locationChunk: '',
|
locationChunk: '',
|
||||||
contextMenuCopyLink: '',
|
contextMenuCopyLink: '',
|
||||||
contextMenuCenterHere: ''
|
contextMenuCenterHere: '',
|
||||||
|
toggleTitle: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
@ -207,6 +209,7 @@ export const state: State = {
|
|||||||
smallScreen: false,
|
smallScreen: false,
|
||||||
visibleElements:new Set(),
|
visibleElements:new Set(),
|
||||||
previouslyVisibleElements: new Set(),
|
previouslyVisibleElements: new Set(),
|
||||||
|
collapsedSections: new Set(),
|
||||||
},
|
},
|
||||||
|
|
||||||
parsedUrl: {
|
parsedUrl: {
|
||||||
|
Loading…
Reference in New Issue
Block a user