Migrate ChatControl to vue

This commit is contained in:
James Lyne 2022-06-23 21:27:35 +01:00
parent 26d5d59b1b
commit c6502e5023
5 changed files with 68 additions and 108 deletions

View File

@ -18,7 +18,6 @@
<Map v-slot="slotProps"> <Map v-slot="slotProps">
<MapUI v-if="slotProps.leaflet" :leaflet="slotProps.leaflet"></MapUI> <MapUI v-if="slotProps.leaflet" :leaflet="slotProps.leaflet"></MapUI>
</Map> </Map>
<ChatBox v-if="chatBoxEnabled" v-show="chatBoxEnabled && chatVisible"></ChatBox>
<LoginModal v-if="loginEnabled" v-show="loginModalVisible" :required="loginRequired"></LoginModal> <LoginModal v-if="loginEnabled" v-show="loginModalVisible" :required="loginRequired"></LoginModal>
<Sidebar></Sidebar> <Sidebar></Sidebar>
<notifications position="bottom center" :speed="250" :max="3" :ignoreDuplicates="true" classes="notification" /> <notifications position="bottom center" :speed="250" :max="3" :ignoreDuplicates="true" classes="notification" />
@ -28,7 +27,6 @@
import {computed, defineComponent, onBeforeUnmount, onMounted, ref, watch} from 'vue'; import {computed, defineComponent, onBeforeUnmount, onMounted, ref, watch} from 'vue';
import Map from './components/Map.vue'; import Map from './components/Map.vue';
import Sidebar from './components/Sidebar.vue'; import Sidebar from './components/Sidebar.vue';
import ChatBox from './components/ChatBox.vue';
import {useStore} from "@/store"; import {useStore} from "@/store";
import {ActionTypes} from "@/store/action-types"; import {ActionTypes} from "@/store/action-types";
import {parseUrl} from '@/util'; import {parseUrl} from '@/util';
@ -46,7 +44,6 @@ export default defineComponent({
MapUI, MapUI,
Map, Map,
Sidebar, Sidebar,
ChatBox,
LoginModal LoginModal
}, },
@ -58,8 +55,6 @@ export default defineComponent({
currentUrl = computed(() => store.getters.url), currentUrl = computed(() => store.getters.url),
currentServer = computed(() => store.state.currentServer), currentServer = computed(() => store.state.currentServer),
configurationHash = computed(() => store.state.configurationHash), configurationHash = computed(() => store.state.configurationHash),
chatBoxEnabled = computed(() => store.state.components.chatBox),
chatVisible = computed(() => store.state.ui.visibleElements.has('chat')),
playerImageUrl = computed(() => store.state.components.players.imageUrl), playerImageUrl = computed(() => store.state.components.players.imageUrl),
loggedIn = computed(() => store.state.loggedIn), //Whether the user is currently logged in loggedIn = computed(() => store.state.loggedIn), //Whether the user is currently logged in
@ -229,8 +224,6 @@ export default defineComponent({
}); });
return { return {
chatBoxEnabled,
chatVisible,
loginEnabled, loginEnabled,
loginRequired, loginRequired,
loginModalVisible loginModalVisible

View File

@ -15,18 +15,18 @@
--> -->
<template> <template>
<section class="chat"> <section class="chatbox">
<ul class="chat__messages" role="log" aria-live="polite" aria-relevant="additions"> <ul class="chatbox__messages" role="log" aria-live="polite" aria-relevant="additions">
<ChatMessage v-for="message in chatMessages" :key="message.timestamp" :message="message"></ChatMessage> <ChatMessage v-for="message in chatMessages" :key="message.timestamp" :message="message"></ChatMessage>
<li v-if="!chatMessages.length" class="message message--skeleton" role="none">{{ messageNoMessages }}</li> <li v-if="!chatMessages.length" class="message message--skeleton" role="none">{{ messageNoMessages }}</li>
</ul> </ul>
<form v-if="sendingEnabled" class="chat__form" @submit.prevent="sendMessage"> <form v-if="sendingEnabled" class="chatbox__form" @submit.prevent="sendMessage">
<div role="alert" v-if="sendingError" class="chat__error">{{ sendingError }}</div> <div role="alert" v-if="sendingError" class="chatbox__error">{{ sendingError }}</div>
<input ref="chatInput" v-model="enteredMessage" class="chat__input" type="text" :maxlength="maxMessageLength" <input ref="chatInput" v-model="enteredMessage" class="chatbox__input" type="text" :maxlength="maxMessageLength"
:placeholder="messagePlaceholder" :disabled="sendingMessage"> :placeholder="messagePlaceholder" :disabled="sendingMessage">
<button class="chat__send" :disabled="!enteredMessage || sendingMessage">{{ messageSend }}</button> <button type="submit" class="chatbox__send" :disabled="!enteredMessage || sendingMessage">{{ messageSend }}</button>
</form> </form>
<button type="button" v-if="loginRequired" class="chat__login" @click="login">{{ messageLogin }}</button> <button type="button" v-if="loginRequired" class="chatbox__login" @click="login">{{ messageLogin }}</button>
</section> </section>
</template> </template>
@ -129,18 +129,12 @@
<style lang="scss"> <style lang="scss">
@import '../scss/placeholders'; @import '../scss/placeholders';
.chat { .chatbox {
@extend %panel; @extend %panel;
position: absolute;
bottom: calc((var(--ui-element-spacing) * 2) + var(--ui-button-size));
left: calc((var(--ui-element-spacing) * 2) + var(--ui-button-size));
width: 50rem;
max-width: calc(100% - 8rem);
max-height: 20rem;
display: flex; display: flex;
box-sizing: border-box; box-sizing: border-box;
.chat__messages { .chatbox__messages {
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
list-style: none; list-style: none;
@ -163,24 +157,24 @@
} }
} }
.chat__form { .chatbox__form {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: stretch; align-items: stretch;
margin: 1.5rem -1.5rem -1.5rem; margin: 1.5rem -1.5rem -1.5rem;
.chat__input { .chatbox__input {
border-bottom-left-radius: var(--border-radius); border-bottom-left-radius: var(--border-radius);
flex-grow: 1; flex-grow: 1;
} }
.chat__send { .chatbox__send {
padding-left: 1rem; padding-left: 1rem;
padding-right: 1rem; padding-right: 1rem;
border-radius: 0 0 var(--border-radius) 0; border-radius: 0 0 var(--border-radius) 0;
} }
.chat__error { .chatbox__error {
background-color: var(--background-error); background-color: var(--background-error);
color: var(--text-emphasis); color: var(--text-emphasis);
font-size: 1.6rem; font-size: 1.6rem;
@ -190,7 +184,7 @@
} }
} }
.chat__login { .chatbox__login {
font-size: 1.6rem; font-size: 1.6rem;
padding: 1.2rem; padding: 1.2rem;
background-color: var(--background-light); background-color: var(--background-light);
@ -206,7 +200,7 @@
} }
@media (max-width: 320px) { @media (max-width: 320px) {
.chat__messages .message + .message { .chatbox__messages .message + .message {
margin-bottom: 0.7rem; margin-bottom: 0.7rem;
} }
} }

View File

@ -27,7 +27,7 @@
<div id="ui__bottom-left" class="ui__section"> <div id="ui__bottom-left" class="ui__section">
<div class="ui__toolbar toolbar--vertical"> <div class="ui__toolbar toolbar--vertical">
<LoginControl v-if="loginEnabled" :leaflet="leaflet"></LoginControl> <LoginControl v-if="loginEnabled" :leaflet="leaflet"></LoginControl>
<ChatControl v-if="chatBoxEnabled" :leaflet="leaflet"></ChatControl> <ChatControl v-if="chatBoxEnabled"></ChatControl>
</div> </div>
<div class="ui__toolbar"> <div class="ui__toolbar">
<LinkControl v-if="linkControlEnabled" :leaflet="leaflet"></LinkControl> <LinkControl v-if="linkControlEnabled" :leaflet="leaflet"></LinkControl>

View File

@ -14,26 +14,48 @@
- limitations under the License. - limitations under the License.
--> -->
<template>
<div class="chat">
<button class="ui__element ui__button" type="button" :title="buttonTitle" :aria-expanded="chatVisible"
@click.prevent.stop="handleClick"
@keydown.right.prevent.stop="handleKeydown">
<SvgIcon name="chat"></SvgIcon>
</button>
<ChatBox v-show="chatVisible"></ChatBox>
</div>
</template>
<script lang="ts"> <script lang="ts">
import {defineComponent, onMounted, onUnmounted} from "@vue/runtime-core"; import {computed, defineComponent} from "@vue/runtime-core";
import {ChatControl} from "@/leaflet/control/ChatControl"; import {useStore} from "@/store";
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap"; import SvgIcon from "@/components/SvgIcon.vue";
import {MutationTypes} from "@/store/mutation-types";
import "@/assets/icons/chat.svg";
import ChatBox from "@/components/ChatBox.vue";
export default defineComponent({ export default defineComponent({
props: { components: {
leaflet: { ChatBox,
type: Object as () => LiveAtlasLeafletMap, SvgIcon,
required: true,
}
}, },
setup(props) { setup() {
const control = new ChatControl({ const store = useStore(),
position: 'topleft', chatVisible = computed(() => store.state.ui.visibleElements.has('chat')),
}); buttonTitle = computed(() => store.state.messages.chatTitle);
onMounted(() => props.leaflet.addControl(control)); const handleClick = () => store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, 'chat'),
onUnmounted(() => props.leaflet.removeControl(control)); handleKeydown = () =>
store.commit(MutationTypes.SET_UI_ELEMENT_VISIBILITY, {element: 'chat', state: true});
return {
buttonTitle,
chatVisible,
handleClick,
handleKeydown
}
}, },
render() { render() {
@ -41,3 +63,19 @@ export default defineComponent({
} }
}) })
</script> </script>
<style lang="scss" scoped>
.chat {
position: relative;
.chatbox {
pointer-events: auto;
position: absolute;
bottom: 0;
width: 50rem;
max-width: calc(100vw - 8rem);
max-height: 20rem;
left: calc(100% + var(--ui-element-spacing));
}
}
</style>

View File

@ -1,65 +0,0 @@
/*
* Copyright 2022 James Lyne
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Control, ControlOptions, DomEvent, DomUtil} from 'leaflet';
import {useStore} from "@/store";
import {MutationTypes} from "@/store/mutation-types";
import {watch} from "@vue/runtime-core";
import "@/assets/icons/chat.svg";
/**
* Leaflet map control providing a chat button which opens the chatbox on click
*/
export class ChatControl extends Control {
declare options: ControlOptions
constructor(options: ControlOptions) {
super(options);
}
onAdd() {
const store = useStore(),
chatButton = DomUtil.create('button',
'leaflet-control-bottom leaflet-control-button leaflet-control-chat') as HTMLButtonElement;
chatButton.type = 'button';
chatButton.title = store.state.messages.chatTitle;
chatButton.innerHTML = `
<svg class="svg-icon">
<use xlink:href="#icon--chat" />
</svg>`;
chatButton.addEventListener('click', e => {
store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, 'chat');
e.stopPropagation();
e.preventDefault();
});
//Open chat on ArrowRight from button
DomEvent.on(chatButton,'keydown', (e: Event) => {
if((e as KeyboardEvent).key === 'ArrowRight') {
store.commit(MutationTypes.SET_UI_ELEMENT_VISIBILITY, {element: 'chat', state: true});
}
});
watch(store.state.ui.visibleElements, (newValue) => {
chatButton.setAttribute('aria-expanded', newValue.has('chat').toString());
});
return chatButton;
}
}