Start chat implementation

This commit is contained in:
James Lyne 2020-12-17 14:50:12 +00:00
parent aabbe1c70d
commit d1fff4e120
14 changed files with 246 additions and 27 deletions

View File

@ -17,12 +17,14 @@
<template>
<Map></Map>
<Sidebar></Sidebar>
<!-- <Chat v-if="chatEnabled"></Chat>-->
</template>
<script lang="ts">
import {defineComponent, computed, ref, onMounted, onBeforeUnmount, watch} from 'vue';
import Map from './components/Map.vue';
import Sidebar from './components/Sidebar.vue';
import Chat from './components/Chat.vue';
import {useStore} from "./store";
import {ActionTypes} from "@/store/action-types";
import Util from '@/util';
@ -33,6 +35,7 @@ export default defineComponent({
components: {
Map,
Sidebar,
Chat
},
setup() {
@ -41,6 +44,7 @@ export default defineComponent({
updateInterval = computed(() => store.state.configuration.updateInterval),
title = computed(() => store.state.configuration.title),
currentUrl = computed(() => store.getters.url),
chatEnabled = computed(() => store.state.components.chat),
updatesEnabled = ref(false),
updateTimeout = ref(0),
configAttempts = ref(0),
@ -100,6 +104,10 @@ export default defineComponent({
onBeforeUnmount(() => stopUpdates());
parseUrl();
return {
chatEnabled,
}
},
});
</script>

View File

@ -16,19 +16,23 @@
import {
DynmapArea,
DynmapChat,
DynmapCircle,
DynmapComponentConfig,
DynmapConfigurationResponse,
DynmapLine,
DynmapWorldMap,
DynmapMarker,
DynmapMarkerSet,
DynmapMarkerSetUpdates,
DynmapMessageConfig,
DynmapPlayer,
DynmapServerConfig, DynmapTileUpdate, DynmapUpdate,
DynmapUpdateResponse, DynmapUpdates,
DynmapWorld
DynmapServerConfig,
DynmapTileUpdate,
DynmapUpdate,
DynmapUpdateResponse,
DynmapUpdates,
DynmapWorld,
DynmapWorldMap
} from "@/dynmap";
import {Sanitizer} from "@esri/arcgis-html-sanitizer";
import {useStore} from "@/store";
@ -319,7 +323,7 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
const updates = {
markerSets: new Map<string, DynmapMarkerSetUpdates>(),
tiles: [] as DynmapTileUpdate[],
chat: [],
chat: [] as DynmapChat[],
},
dropped = {
stale: 0,
@ -327,7 +331,7 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
noId: 0,
unknownType: 0,
unknownCType: 0,
incompleteTile: 0,
incomplete: 0,
notImplemented: 0,
},
lastUpdate = useStore().state.updateTimestamp;
@ -394,13 +398,33 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
}
case 'chat':
//TODO
if(!entry.account || !entry.message || !entry.timestamp) {
dropped.incomplete++;
continue;
}
if(entry.timestamp < lastUpdate) {
dropped.stale++;
continue;
}
if(entry.source !== 'player') {
dropped.notImplemented++;
continue;
}
updates.chat.push({
type: 'chat',
account: entry.account,
message: entry.message,
timestamp: entry.timestamp,
channel: entry.channel || undefined,
});
break;
case 'tile':
if(!entry.name || !entry.timestamp) {
dropped.incompleteTile++;
dropped.incomplete++;
continue;
}
@ -422,6 +446,10 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
}
}
updates.chat = updates.chat.sort((one, two) => {
return one.timestamp - two.timestamp;
});
console.debug(`Updates: ${accepted} accepted. Rejected: `, dropped);
return updates;

View File

@ -0,0 +1,6 @@
<svg width="255.35pt" height="204.89pt" version="1.0" viewBox="0 0 255.35 204.89" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-2.1211 -27.577)" stroke-width="0">
<path transform="scale(.75)" d="m84.5 36.77c-45.246 0-81.672 36.424-81.672 81.67v56.217c0 45.246 36.426 81.67 81.672 81.67h177.13c45.246 0 81.672-36.424 81.672-81.67v-56.217c0-45.246-36.426-81.67-81.672-81.67zm0.07031 107.41a23.375 23.375 0 0 1 0.12695 0 23.375 23.375 0 0 1 23.375 23.375 23.375 23.375 0 0 1-23.375 23.375 23.375 23.375 0 0 1-23.375-23.375 23.375 23.375 0 0 1 23.248-23.375zm90.25 0a23.375 23.375 0 0 1 0.12696 0 23.375 23.375 0 0 1 23.375 23.375 23.375 23.375 0 0 1-23.375 23.375 23.375 23.375 0 0 1-23.375-23.375 23.375 23.375 0 0 1 23.248-23.375zm90.312 0a23.375 23.375 0 0 1 0.12696 0 23.375 23.375 0 0 1 23.375 23.375 23.375 23.375 0 0 1-23.375 23.375 23.375 23.375 0 0 1-23.375-23.375 23.375 23.375 0 0 1 23.248-23.375z"/>
<path d="m84.929 180.81-27.39 49.754c-0.45922 1.8202 0.23674 2.7233 3.9555 0.91854 18.017-8.7437 98.169-50.072 98.169-50.072" style="paint-order:normal"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

43
src/components/Chat.vue Normal file
View File

@ -0,0 +1,43 @@
<!--
- 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>
<section class="chat">
<ul class="chat__messages">
<li class="message" v-for="message in chat" :key="message.timestamp">{{ message.message || 'aaaa' }}</li>
</ul>
</section>
</template>
<script lang="ts">
import {defineComponent, computed} from "@vue/runtime-core";
import {useStore} from "@/store";
export default defineComponent({
setup() {
const store = useStore(),
chat = computed(() => store.state.chat);
return {
chat,
}
}
})
</script>
<style lang="scss">
</style>

View File

@ -24,6 +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="chatEnabled" :leaflet="leaflet"></ChatControl>-->
</div>
</template>
@ -37,6 +38,7 @@ import MarkerSetLayer from "@/components/map/layer/MarkerSetLayer.vue";
import CoordinatesControl from "@/components/map/control/CoordinatesControl.vue";
import ClockControl from "@/components/map/control/ClockControl.vue";
import LinkControl from "@/components/map/control/LinkControl.vue";
import ChatControl from "@/components/map/control/ChatControl.vue";
import LogoControl from "@/components/map/control/LogoControl.vue";
import {MutationTypes} from "@/store/mutation-types";
import {Coordinate, DynmapPlayer} from "@/dynmap";
@ -52,6 +54,7 @@ export default defineComponent({
CoordinatesControl,
ClockControl,
LinkControl,
ChatControl,
LogoControl
},
@ -67,6 +70,7 @@ export default defineComponent({
coordinatesControlEnabled = computed(() => store.getters.coordinatesControlEnabled),
clockControlEnabled = computed(() => store.getters.clockControlEnabled),
linkControlEnabled = computed(() => store.state.components.linkControl),
chatEnabled = computed(() => store.state.components.chat),
logoControls = computed(() => store.state.components.logoControls),
currentWorld = computed(() => store.state.currentWorld),
@ -86,6 +90,7 @@ export default defineComponent({
coordinatesControlEnabled,
clockControlEnabled,
linkControlEnabled,
chatEnabled,
logoControls,
followTarget,
panTarget,

View File

@ -0,0 +1,43 @@
<!--
- 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.
-->
<script lang="ts">
import {defineComponent, onMounted, onUnmounted} from "@vue/runtime-core";
import {ChatControl} from "@/leaflet/control/ChatControl";
import DynmapMap from "@/leaflet/DynmapMap";
export default defineComponent({
props: {
leaflet: {
type: Object as () => DynmapMap,
required: true,
}
},
setup(props) {
const control = new ChatControl({
position: 'topleft',
});
onMounted(() => props.leaflet.addControl(control));
onUnmounted(() => props.leaflet.removeControl(control));
},
render() {
return null;
}
})
</script>

17
src/dynmap.d.ts vendored
View File

@ -79,6 +79,7 @@ interface DynmapComponentConfig {
clockControl ?: ClockControlOptions;
linkControl: boolean;
logoControls: Array<LogoControlOptions>;
chat?: DynmapChatConfig;
}
interface DynmapMarkersConfig {
@ -95,6 +96,13 @@ interface DynmapPlayerMarkersConfig {
smallFaces: boolean;
}
interface DynmapChatConfig {
allowUrlName: boolean;
showPlayerFaces: boolean;
messageLifetime: number;
messageHistory: number;
}
interface DynmapWorld {
seaLevel: number;
name: string;
@ -275,3 +283,12 @@ interface DynmapParsedUrl {
location?: Coordinate;
zoom?: number;
}
interface DynmapChat {
type: 'chat' | 'playerjoin' | 'playerleave';
account: string;
channel?: string;
message?: string;
// source?: string;
timestamp: number;
}

View File

@ -0,0 +1,40 @@
/*
* 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.
*/
import {Control, ControlOptions, DomUtil, Map} from 'leaflet';
import chat from '@/assets/icons/chat.svg';
export class ChatControl extends Control {
// @ts-ignore
options: ControlOptions
constructor(options: ControlOptions) {
super(options);
}
onAdd(map: Map) {
const chatButton = DomUtil.create('button', 'leaflet-control-chat') as HTMLButtonElement;
chatButton.type = 'button';
chatButton.title = 'Chat';
chatButton.innerHTML = `
<svg class="svg-icon" viewBox="${chat.viewBox}">
<use xlink:href="${chat.url}" />
</svg>`;
return chatButton;
}
}

View File

@ -70,7 +70,9 @@
}
.leaflet-control-link,
.leaflet-control-loading {
.leaflet-control-chat,
.leaflet-control-loading,
.leaflet-control-layers-toggle {
@extend %button;
width: 5rem;
height: 5rem;
@ -150,13 +152,17 @@
.leaflet-top {
padding-top: 1rem;
flex-direction: column;
top: 0;
bottom: 7rem;
align-items: flex-start;
.leaflet-control {
order: 2;
min-width: 5rem;
& + .leaflet-control {
margin-top: 1rem;
&:first-child {
margin-top: 0;
}
}
@ -179,6 +185,11 @@
margin-top: 1rem !important;
}
}
.leaflet-control-chat {
margin-top: auto;
order: 1000;
}
}
.leaflet-bottom {

View File

@ -425,20 +425,29 @@ button {
*/
.chat {
@extend %panel;
position: absolute;
bottom: 0;
left: 32px;
z-index:50;
bottom: 7rem;
left: 7rem;
width: 50rem;
max-width: calc(100% - 8rem);
max-height: 20rem;
display: flex;
box-sizing: border-box;
border-color: rgba(0,0,0,0.5);
background: rgba(0,0,0,0.6);
.chat__messages {
display: flex;
flex-direction: column-reverse;
list-style: none;
overflow: auto;
margin: 0;
padding: 0;
border-style: solid;
border-width: 1px 1px 0 1px;
border-radius: 3px 3px 0 0;
margin-left: 10px;
.message {
font-size: 1.6rem;
margin-bottom: 0.5rem
}
}
}
.chatinput {

View File

@ -136,6 +136,7 @@ export const actions: ActionTree<State, State> & Actions = {
commit(MutationTypes.INCREMENT_REQUEST_ID, undefined);
commit(MutationTypes.ADD_MARKER_SET_UPDATES, update.updates.markerSets);
commit(MutationTypes.ADD_TILE_UPDATES, update.updates.tiles);
commit(MutationTypes.ADD_CHAT, update.updates.chat);
return dispatch(ActionTypes.SET_PLAYERS, update.players).then(() => {
return update;

View File

@ -25,6 +25,7 @@ export enum MutationTypes {
SET_UPDATE_TIMESTAMP = 'setUpdateTimestamp',
ADD_MARKER_SET_UPDATES = 'addMarkerSetUpdates',
ADD_TILE_UPDATES = 'addTileUpdates',
ADD_CHAT = 'addChat',
POP_MARKER_UPDATES = 'popMarkerUpdates',
POP_AREA_UPDATES = 'popAreaUpdates',
POP_CIRCLE_UPDATES = 'popCircleUpdates',

View File

@ -33,7 +33,7 @@ import {
DynmapPlayer,
DynmapServerConfig, DynmapTileUpdate,
DynmapWorld,
DynmapWorldState, DynmapParsedUrl
DynmapWorldState, DynmapParsedUrl, DynmapChat
} from "@/dynmap";
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
@ -53,6 +53,7 @@ export type Mutations<S = State> = {
[MutationTypes.SET_UPDATE_TIMESTAMP](state: S, time: Date): void
[MutationTypes.ADD_MARKER_SET_UPDATES](state: S, updates: Map<string, DynmapMarkerSetUpdates>): void
[MutationTypes.ADD_TILE_UPDATES](state: S, updates: Array<DynmapTileUpdate>): void
[MutationTypes.ADD_CHAT](state: State, chat: Array<DynmapChat>): void
[MutationTypes.POP_MARKER_UPDATES](state: S, payload: {markerSet: string, amount: number}): Array<DynmapMarkerUpdate>
[MutationTypes.POP_AREA_UPDATES](state: S, payload: {markerSet: string, amount: number}): Array<DynmapAreaUpdate>
@ -194,6 +195,10 @@ export const mutations: MutationTree<State> & Mutations = {
state.pendingTileUpdates = state.pendingTileUpdates.concat(updates);
},
[MutationTypes.ADD_CHAT](state: State, chat: Array<DynmapChat>) {
state.chat.unshift(...chat);
},
[MutationTypes.POP_MARKER_UPDATES](state: State, {markerSet, amount}): Array<DynmapMarkerUpdate> {
if(!state.markerSets.has(markerSet)) {
console.log(`Marker set ${markerSet} doesn't exist`);

View File

@ -20,7 +20,7 @@ import {
DynmapMessageConfig,
DynmapPlayer,
DynmapServerConfig, DynmapTileUpdate,
DynmapWorld, DynmapWorldState, Coordinate, DynmapParsedUrl
DynmapWorld, DynmapWorldState, Coordinate, DynmapParsedUrl, DynmapChat
} from "@/dynmap";
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
@ -33,6 +33,7 @@ export type State = {
maps: Map<string, DynmapWorldMap>;
players: Map<string, DynmapPlayer>;
markerSets: Map<string, DynmapMarkerSet>;
chat: DynmapChat[];
pendingSetUpdates: Map<string, DynmapMarkerSetUpdates>;
pendingTileUpdates: Array<DynmapTileUpdate>;
@ -88,6 +89,7 @@ export const state: State = {
worlds: new Map(), //Defined (loaded) worlds with maps from configuration.json
maps: new Map(), //Defined maps from configuration.json
players: new Map(), //Online players from world.json
chat: [],
markerSets: new Map(), //Markers from world_markers.json. Contents of each set isn't reactive for performance reasons.
pendingSetUpdates: new Map(), //Pending updates to markers/areas/etc for each marker set