This commit is contained in:
James Lyne 2020-12-31 13:05:54 +00:00
parent 53f5960e52
commit cd22237e33
8 changed files with 250 additions and 31 deletions

View File

@ -17,11 +17,11 @@
<template>
<Map></Map>
<Sidebar></Sidebar>
<Chat v-if="false"></Chat>
<Chat v-if="chatEnabled" v-show="chatEnabled && chatVisible"></Chat>
</template>
<script lang="ts">
import {defineComponent, computed, ref, onMounted, onBeforeUnmount, watch} from 'vue';
import {computed, defineComponent, onBeforeUnmount, onMounted, onUnmounted, ref, watch} from 'vue';
import Map from './components/Map.vue';
import Sidebar from './components/Sidebar.vue';
import Chat from './components/Chat.vue';
@ -44,6 +44,7 @@ export default defineComponent({
title = computed(() => store.state.configuration.title),
currentUrl = computed(() => store.getters.url),
chatEnabled = computed(() => store.state.components.chat),
chatVisible = computed(() => store.state.ui.visibleElements.has('chat')),
updatesEnabled = ref(false),
updateTimeout = ref(0),
configAttempts = ref(0),
@ -101,6 +102,10 @@ export default defineComponent({
store.commit(MutationTypes.SET_PARSED_URL, parsedUrl);
}
},
onResize = () => {
store.commit(MutationTypes.SET_SMALL_SCREEN, window.innerWidth < 480 || window.innerHeight < 500);
};
watch(title, (title) => document.title = title);
@ -110,9 +115,14 @@ export default defineComponent({
onBeforeUnmount(() => stopUpdates());
parseUrl();
onResize();
onMounted(() => window.addEventListener('resize', onResize));
onUnmounted(() => window.addEventListener('resize', onResize));
return {
chatEnabled,
chatVisible,
}
},
});

View File

@ -126,6 +126,7 @@ function buildComponents(response: any): DynmapComponentConfig {
markers: {
showLabels: false,
},
chat: undefined,
playerMarkers: undefined,
coordinatesControl: undefined,
linkControl: false,
@ -188,6 +189,15 @@ function buildComponents(response: any): DynmapComponentConfig {
position: component.position.replace('-', '') || 'topleft',
image: component.logourl || undefined,
});
break;
case "chatbox":
components.chat = {
allowUrlName: component.allowurlname || false,
showPlayerFaces: component.showplayerfaces || false,
messageLifetime: component.messagettl || Infinity,
messageHistory: component.scrollback || Infinity,
}
}
});
@ -444,13 +454,52 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
updates.chat.push({
type: 'chat',
account: entry.account,
message: entry.message,
playerAccount: entry.account,
playerName: entry.playerName ? sanitizer.sanitize(entry.playerName) : "",
message: entry.message ? sanitizer.sanitize(entry.message) : "",
timestamp: entry.timestamp,
channel: entry.channel || undefined,
});
break;
case 'playerjoin':
if(!entry.account || !entry.timestamp) {
dropped.incomplete++;
continue;
}
if(entry.timestamp < lastUpdate) {
dropped.stale++;
continue;
}
updates.chat.push({
type: 'playerjoin',
playerAccount: entry.account,
playerName: entry.playerName ? sanitizer.sanitize(entry.playerName) : "",
timestamp: entry.timestamp || undefined,
});
break;
case 'playerquit':
if(!entry.account || !entry.timestamp) {
dropped.incomplete++;
continue;
}
if(entry.timestamp < lastUpdate) {
dropped.stale++;
continue;
}
updates.chat.push({
type: 'playerleave',
playerAccount: entry.account,
playerName: entry.playerName ? sanitizer.sanitize(entry.playerName) : "",
timestamp: entry.timestamp || undefined,
});
break;
case 'tile':
if(!entry.name || !entry.timestamp) {
dropped.incomplete++;

View File

@ -17,7 +17,7 @@
<template>
<section class="chat">
<ul class="chat__messages">
<li class="message" v-for="message in chat" :key="message.timestamp">{{ message.message || 'aaaa' }}</li>
<ChatMessage v-for="message in chat" :key="message.timestamp" :message="message"></ChatMessage>
</ul>
</section>
</template>
@ -25,8 +25,12 @@
<script lang="ts">
import {defineComponent, computed} from "@vue/runtime-core";
import {useStore} from "@/store";
import ChatMessage from "@/components/chat/ChatMessage.vue";
export default defineComponent({
components: {
ChatMessage
},
setup() {
const store = useStore(),
chat = computed(() => store.state.chat);
@ -39,5 +43,47 @@
</script>
<style lang="scss">
@import '../scss/variables';
@import '../scss/placeholders';
.chat {
@extend %panel;
position: absolute;
bottom: 7rem;
left: 7rem;
width: 50rem;
max-width: calc(100% - 8rem);
max-height: 20rem;
display: flex;
box-sizing: border-box;
.chat__messages {
display: flex;
flex-direction: column-reverse;
list-style: none;
overflow: auto;
margin: 0;
padding: 0;
.message {
font-size: 1.6rem;
& + .message {
margin-bottom: 0.5rem
}
}
}
@media (max-width: 25rem), (max-height: 30rem) {
bottom: 6.5rem;
left: 6.5rem;
max-width: calc(100% - 7rem);
}
@media (max-width: 20rem) {
.chat__messages .message + .message {
margin-bottom: 0.7rem;
}
}
}
</style>

View File

@ -24,7 +24,7 @@
<CoordinatesControl v-if="coordinatesControlEnabled" :leaflet="leaflet"></CoordinatesControl>
<LinkControl v-if="linkControlEnabled" :leaflet="leaflet"></LinkControl>
<ClockControl v-if="clockControlEnabled" :leaflet="leaflet"></ClockControl>
<ChatControl v-if="false" :leaflet="leaflet"></ChatControl>
<ChatControl v-if="chatEnabled" :leaflet="leaflet"></ChatControl>
</div>
</template>

View File

@ -0,0 +1,124 @@
<!--
- Copyright 2020 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.
-->
<template>
<li :class="`message message--${message.type}`">
<img v-if="facesEnabled" width="16" height="16" class="message__face" :src="image" alt="" />
<span v-if="showSender" class="message__sender" v-html="message.playerName"></span>
<span class="message__content" v-html="messageContent"></span>
</li>
</template>
<script lang="ts">
import {defineComponent, ref, onMounted, computed} from "@vue/runtime-core";
import {DynmapChat} from "@/dynmap";
import Util from '@/util';
import {useStore} from "@/store";
const defaultImage = require('@/assets/images/player_face.png');
export default defineComponent({
props: {
message: {
type: Object as () => DynmapChat,
required: true,
}
},
setup(props) {
const store = useStore();
let image = ref(defaultImage),
facesEnabled = computed(() => store.state.components.chat?.showPlayerFaces),
showSender = computed(() => props.message.type === 'chat'),
messageContent = computed(() => {
switch(props.message.type) {
case 'chat':
return props.message.message;
case 'playerjoin':
if(props.message.playerName) {
return store.state.messages.playerJoin
.replace('%playername%', props.message.playerName);
} else {
return store.state.messages.anonymousJoin;
}
case 'playerleave':
if(props.message.playerName) {
return store.state.messages.playerQuit
.replace('%playername%', props.message.playerName);
} else {
return store.state.messages.anonymousQuit;
}
}
})
onMounted(() => {
if(store.state.components.playerMarkers && store.state.components.playerMarkers.showSkinFaces) {
Util.getMinecraftHead(props.message.playerAccount, '16')
.then((result) => image.value = result.src).catch(() => {});
}
});
return {
image,
facesEnabled,
showSender,
messageContent
}
}
})
</script>
<style lang="scss">
.message {
.message__face {
display: inline-block;
vertical-align: baseline;
margin-right: 0.5rem;
}
.message__sender {
margin-right: 0.5rem;
word-wrap: break-word;
&:after {
content: ': ';
}
}
.message__content {
word-wrap: break-word;
}
&.message--join,
&.message--leave {
.message__sender:after {
content: none;
}
}
@media (max-width: 20rem) {
&.message--chat {
.message__sender:after {
content: none;
}
.message__content {
display: block;
color: #eeeeee;
}
}
}
}
</style>

3
src/dynmap.d.ts vendored
View File

@ -298,7 +298,8 @@ interface DynmapParsedUrl {
interface DynmapChat {
type: 'chat' | 'playerjoin' | 'playerleave';
account: string;
playerAccount: string;
playerName: string;
channel?: string;
message?: string;
// source?: string;

View File

@ -16,6 +16,9 @@
import {Control, ControlOptions, DomUtil, Map} from 'leaflet';
import chat from '@/assets/icons/chat.svg';
import {useStore} from "@/store";
import {MutationTypes} from "@/store/mutation-types";
import {watch} from "@vue/runtime-core";
export class ChatControl extends Control {
// @ts-ignore
@ -35,6 +38,16 @@ export class ChatControl extends Control {
<use xlink:href="${chat.url}" />
</svg>`;
chatButton.addEventListener('click', e => {
useStore().commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, 'chat');
e.stopPropagation();
e.preventDefault();
});
watch(useStore().state.ui.visibleElements, (newValue) => {
chatButton.classList.toggle('active', newValue.has('chat'));
});
return chatButton;
}
}

View File

@ -396,31 +396,7 @@ button {
* Chat
*/
.chat {
@extend %panel;
position: absolute;
bottom: 7rem;
left: 7rem;
width: 50rem;
max-width: calc(100% - 8rem);
max-height: 20rem;
display: flex;
box-sizing: border-box;
.chat__messages {
display: flex;
flex-direction: column-reverse;
list-style: none;
overflow: auto;
margin: 0;
padding: 0;
.message {
font-size: 1.6rem;
margin-bottom: 0.5rem
}
}
}
.chatinput {