Start chat implementation
This commit is contained in:
parent
aabbe1c70d
commit
d1fff4e120
@ -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>
|
||||
|
48
src/api.ts
48
src/api.ts
@ -16,21 +16,25 @@
|
||||
|
||||
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 {Sanitizer} from "@esri/arcgis-html-sanitizer";
|
||||
import {useStore} from "@/store";
|
||||
|
||||
const sanitizer = new Sanitizer();
|
||||
@ -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
|
||||
dropped.notImplemented++;
|
||||
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;
|
||||
|
6
src/assets/icons/chat.svg
Normal file
6
src/assets/icons/chat.svg
Normal 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
43
src/components/Chat.vue
Normal 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>
|
@ -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,
|
||||
|
43
src/components/map/control/ChatControl.vue
Normal file
43
src/components/map/control/ChatControl.vue
Normal 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
17
src/dynmap.d.ts
vendored
@ -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;
|
||||
}
|
||||
|
40
src/leaflet/control/ChatControl.ts
Normal file
40
src/leaflet/control/ChatControl.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
margin-top: 1rem;
|
||||
|
||||
& + .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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
|
@ -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`);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user