Chat
This commit is contained in:
parent
53f5960e52
commit
cd22237e33
14
src/App.vue
14
src/App.vue
@ -17,11 +17,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<Map></Map>
|
<Map></Map>
|
||||||
<Sidebar></Sidebar>
|
<Sidebar></Sidebar>
|
||||||
<Chat v-if="false"></Chat>
|
<Chat v-if="chatEnabled" v-show="chatEnabled && chatVisible"></Chat>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 Map from './components/Map.vue';
|
||||||
import Sidebar from './components/Sidebar.vue';
|
import Sidebar from './components/Sidebar.vue';
|
||||||
import Chat from './components/Chat.vue';
|
import Chat from './components/Chat.vue';
|
||||||
@ -44,6 +44,7 @@ export default defineComponent({
|
|||||||
title = computed(() => store.state.configuration.title),
|
title = computed(() => store.state.configuration.title),
|
||||||
currentUrl = computed(() => store.getters.url),
|
currentUrl = computed(() => store.getters.url),
|
||||||
chatEnabled = computed(() => store.state.components.chat),
|
chatEnabled = computed(() => store.state.components.chat),
|
||||||
|
chatVisible = computed(() => store.state.ui.visibleElements.has('chat')),
|
||||||
updatesEnabled = ref(false),
|
updatesEnabled = ref(false),
|
||||||
updateTimeout = ref(0),
|
updateTimeout = ref(0),
|
||||||
configAttempts = ref(0),
|
configAttempts = ref(0),
|
||||||
@ -101,6 +102,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
store.commit(MutationTypes.SET_PARSED_URL, parsedUrl);
|
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);
|
watch(title, (title) => document.title = title);
|
||||||
@ -110,9 +115,14 @@ export default defineComponent({
|
|||||||
onBeforeUnmount(() => stopUpdates());
|
onBeforeUnmount(() => stopUpdates());
|
||||||
|
|
||||||
parseUrl();
|
parseUrl();
|
||||||
|
onResize();
|
||||||
|
|
||||||
|
onMounted(() => window.addEventListener('resize', onResize));
|
||||||
|
onUnmounted(() => window.addEventListener('resize', onResize));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chatEnabled,
|
chatEnabled,
|
||||||
|
chatVisible,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
53
src/api.ts
53
src/api.ts
@ -126,6 +126,7 @@ function buildComponents(response: any): DynmapComponentConfig {
|
|||||||
markers: {
|
markers: {
|
||||||
showLabels: false,
|
showLabels: false,
|
||||||
},
|
},
|
||||||
|
chat: undefined,
|
||||||
playerMarkers: undefined,
|
playerMarkers: undefined,
|
||||||
coordinatesControl: undefined,
|
coordinatesControl: undefined,
|
||||||
linkControl: false,
|
linkControl: false,
|
||||||
@ -188,6 +189,15 @@ function buildComponents(response: any): DynmapComponentConfig {
|
|||||||
position: component.position.replace('-', '') || 'topleft',
|
position: component.position.replace('-', '') || 'topleft',
|
||||||
image: component.logourl || undefined,
|
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({
|
updates.chat.push({
|
||||||
type: 'chat',
|
type: 'chat',
|
||||||
account: entry.account,
|
playerAccount: entry.account,
|
||||||
message: entry.message,
|
playerName: entry.playerName ? sanitizer.sanitize(entry.playerName) : "",
|
||||||
|
message: entry.message ? sanitizer.sanitize(entry.message) : "",
|
||||||
timestamp: entry.timestamp,
|
timestamp: entry.timestamp,
|
||||||
channel: entry.channel || undefined,
|
channel: entry.channel || undefined,
|
||||||
});
|
});
|
||||||
break;
|
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':
|
case 'tile':
|
||||||
if(!entry.name || !entry.timestamp) {
|
if(!entry.name || !entry.timestamp) {
|
||||||
dropped.incomplete++;
|
dropped.incomplete++;
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="chat">
|
<section class="chat">
|
||||||
<ul class="chat__messages">
|
<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>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@ -25,8 +25,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defineComponent, computed} from "@vue/runtime-core";
|
import {defineComponent, computed} from "@vue/runtime-core";
|
||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
|
import ChatMessage from "@/components/chat/ChatMessage.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
ChatMessage
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore(),
|
const store = useStore(),
|
||||||
chat = computed(() => store.state.chat);
|
chat = computed(() => store.state.chat);
|
||||||
@ -39,5 +43,47 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<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>
|
</style>
|
@ -24,7 +24,7 @@
|
|||||||
<CoordinatesControl v-if="coordinatesControlEnabled" :leaflet="leaflet"></CoordinatesControl>
|
<CoordinatesControl v-if="coordinatesControlEnabled" :leaflet="leaflet"></CoordinatesControl>
|
||||||
<LinkControl v-if="linkControlEnabled" :leaflet="leaflet"></LinkControl>
|
<LinkControl v-if="linkControlEnabled" :leaflet="leaflet"></LinkControl>
|
||||||
<ClockControl v-if="clockControlEnabled" :leaflet="leaflet"></ClockControl>
|
<ClockControl v-if="clockControlEnabled" :leaflet="leaflet"></ClockControl>
|
||||||
<ChatControl v-if="false" :leaflet="leaflet"></ChatControl>
|
<ChatControl v-if="chatEnabled" :leaflet="leaflet"></ChatControl>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
124
src/components/chat/ChatMessage.vue
Normal file
124
src/components/chat/ChatMessage.vue
Normal 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
3
src/dynmap.d.ts
vendored
@ -298,7 +298,8 @@ interface DynmapParsedUrl {
|
|||||||
|
|
||||||
interface DynmapChat {
|
interface DynmapChat {
|
||||||
type: 'chat' | 'playerjoin' | 'playerleave';
|
type: 'chat' | 'playerjoin' | 'playerleave';
|
||||||
account: string;
|
playerAccount: string;
|
||||||
|
playerName: string;
|
||||||
channel?: string;
|
channel?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
// source?: string;
|
// source?: string;
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
import {Control, ControlOptions, DomUtil, Map} from 'leaflet';
|
import {Control, ControlOptions, DomUtil, Map} from 'leaflet';
|
||||||
import chat from '@/assets/icons/chat.svg';
|
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 {
|
export class ChatControl extends Control {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -35,6 +38,16 @@ export class ChatControl extends Control {
|
|||||||
<use xlink:href="${chat.url}" />
|
<use xlink:href="${chat.url}" />
|
||||||
</svg>`;
|
</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;
|
return chatButton;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -396,31 +396,7 @@ button {
|
|||||||
* Chat
|
* 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 {
|
.chatinput {
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user