diff --git a/src/App.vue b/src/App.vue
index f386eaf..d051c00 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -17,12 +17,14 @@
+
diff --git a/src/api.ts b/src/api.ts
index 065b6c0..32cd0bb 100644
--- a/src/api.ts
+++ b/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): DynmapUpdates {
const updates = {
markerSets: new Map(),
tiles: [] as DynmapTileUpdate[],
- chat: [],
+ chat: [] as DynmapChat[],
},
dropped = {
stale: 0,
@@ -327,7 +331,7 @@ function buildUpdates(data: Array): 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): 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): DynmapUpdates {
}
}
+ updates.chat = updates.chat.sort((one, two) => {
+ return one.timestamp - two.timestamp;
+ });
+
console.debug(`Updates: ${accepted} accepted. Rejected: `, dropped);
return updates;
diff --git a/src/assets/icons/chat.svg b/src/assets/icons/chat.svg
new file mode 100644
index 0000000..9a3e4ca
--- /dev/null
+++ b/src/assets/icons/chat.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/components/Chat.vue b/src/components/Chat.vue
new file mode 100644
index 0000000..f6b6a58
--- /dev/null
+++ b/src/components/Chat.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+ - {{ message.message || 'aaaa' }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/Map.vue b/src/components/Map.vue
index bc4f4c2..4201793 100644
--- a/src/components/Map.vue
+++ b/src/components/Map.vue
@@ -24,6 +24,7 @@
+
@@ -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,
diff --git a/src/components/map/control/ChatControl.vue b/src/components/map/control/ChatControl.vue
new file mode 100644
index 0000000..c54c74a
--- /dev/null
+++ b/src/components/map/control/ChatControl.vue
@@ -0,0 +1,43 @@
+
+
+
diff --git a/src/dynmap.d.ts b/src/dynmap.d.ts
index d3cb476..72ab80a 100644
--- a/src/dynmap.d.ts
+++ b/src/dynmap.d.ts
@@ -79,6 +79,7 @@ interface DynmapComponentConfig {
clockControl ?: ClockControlOptions;
linkControl: boolean;
logoControls: Array;
+ 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;
@@ -274,4 +282,13 @@ interface DynmapParsedUrl {
map?: string;
location?: Coordinate;
zoom?: number;
-}
\ No newline at end of file
+}
+
+interface DynmapChat {
+ type: 'chat' | 'playerjoin' | 'playerleave';
+ account: string;
+ channel?: string;
+ message?: string;
+ // source?: string;
+ timestamp: number;
+}
diff --git a/src/leaflet/control/ChatControl.ts b/src/leaflet/control/ChatControl.ts
new file mode 100644
index 0000000..dc6825b
--- /dev/null
+++ b/src/leaflet/control/ChatControl.ts
@@ -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 = `
+ `;
+
+ return chatButton;
+ }
+}
diff --git a/src/scss/leaflet/_controls.scss b/src/scss/leaflet/_controls.scss
index 2f13fd9..1870a87 100644
--- a/src/scss/leaflet/_controls.scss
+++ b/src/scss/leaflet/_controls.scss
@@ -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 {
diff --git a/src/scss/style.scss b/src/scss/style.scss
index 12a55f1..f91d0dc 100644
--- a/src/scss/style.scss
+++ b/src/scss/style.scss
@@ -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 {
diff --git a/src/store/actions.ts b/src/store/actions.ts
index 1301c9f..025b363 100644
--- a/src/store/actions.ts
+++ b/src/store/actions.ts
@@ -136,6 +136,7 @@ export const actions: ActionTree & 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;
diff --git a/src/store/mutation-types.ts b/src/store/mutation-types.ts
index 3de17ca..a19ecce 100644
--- a/src/store/mutation-types.ts
+++ b/src/store/mutation-types.ts
@@ -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',
diff --git a/src/store/mutations.ts b/src/store/mutations.ts
index b8858c7..0ac5f02 100644
--- a/src/store/mutations.ts
+++ b/src/store/mutations.ts
@@ -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 = {
[MutationTypes.SET_UPDATE_TIMESTAMP](state: S, time: Date): void
[MutationTypes.ADD_MARKER_SET_UPDATES](state: S, updates: Map): void
[MutationTypes.ADD_TILE_UPDATES](state: S, updates: Array): void
+ [MutationTypes.ADD_CHAT](state: State, chat: Array): void
[MutationTypes.POP_MARKER_UPDATES](state: S, payload: {markerSet: string, amount: number}): Array
[MutationTypes.POP_AREA_UPDATES](state: S, payload: {markerSet: string, amount: number}): Array
@@ -194,6 +195,10 @@ export const mutations: MutationTree & Mutations = {
state.pendingTileUpdates = state.pendingTileUpdates.concat(updates);
},
+ [MutationTypes.ADD_CHAT](state: State, chat: Array) {
+ state.chat.unshift(...chat);
+ },
+
[MutationTypes.POP_MARKER_UPDATES](state: State, {markerSet, amount}): Array {
if(!state.markerSets.has(markerSet)) {
console.log(`Marker set ${markerSet} doesn't exist`);
diff --git a/src/store/state.ts b/src/store/state.ts
index 7dff6cd..75dc6c0 100644
--- a/src/store/state.ts
+++ b/src/store/state.ts
@@ -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;
players: Map;
markerSets: Map;
+ chat: DynmapChat[];
pendingSetUpdates: Map;
pendingTileUpdates: Array;
@@ -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