Initial work for multiple servers

This commit is contained in:
James Lyne 2021-05-17 03:39:25 +01:00
parent 9720bf90d9
commit 0facb446f5
19 changed files with 1119 additions and 2826 deletions

View File

@ -20,8 +20,49 @@
<meta name="description" content="Minecraft Dynamic Map" /> <meta name="description" content="Minecraft Dynamic Map" />
<title>Minecraft Dynamic Map - LiveAtlas</title> <title>Minecraft Dynamic Map - LiveAtlas</title>
<!-- Remove this if you are using multiple maps -->
<script src="./standalone/config.js"></script>
<script>
window.liveAtlasConfig = {
// Server URLS can be defined here instead of using the standalone/config.js file.
// Multiple servers are supported too.
// servers: {
// creative: {
// label: 'Creative',
// configuration: 'http://dynmap.local/standalone/creative/MySQL_configuration.php',
// update: 'http://dynmap.local/standalone/creative/MySQL_update.php?world={world}&ts={timestamp}',
// sendmessage: 'http://dynmap.local/standalone/creative/MySQL_sendmessage.php',
// login: 'http://dynmap.local/standalone/creative/MySQL_login.php',
// register: 'http://dynmap.local/standalone/creative/MySQL_register.php',
// tiles: 'http://dynmap.local/standalone/creative/MySQL_tiles.php?tile=',
// markers: 'http://dynmap.local/standalone/creative/MySQL_markers.php?marker='
// },
// survival: {
// label: 'Survival',
// configuration: 'http://dynmap.local/standalone/survival/MySQL_configuration.php',
// update: 'http://dynmap.local/standalone/survival/MySQL_update.php?world={world}&ts={timestamp}',
// sendmessage: 'http://dynmap.local/standalone/survival/MySQL_sendmessage.php',
// login: 'http://dynmap.local/standalone/survival/MySQL_login.php',
// register: 'http://dynmap.local/standalone/survival/MySQL_register.php',
// tiles: 'http://dynmap.local/standalone/survival/MySQL_tiles.php?tile=',
// markers: 'http://dynmap.local/standalone/survival/MySQL_markers.php?marker='
// },
// build: {
// label: 'Build',
// configuration: 'http://dynmap.local/standalone/build/MySQL_configuration.php',
// update: 'http://dynmap.local/standalone/build/MySQL_update.php?world={world}&ts={timestamp}',
// sendmessage: 'http://dynmap.local/standalone/build/MySQL_sendmessage.php',
// login: 'http://dynmap.local/standalone/build/MySQL_login.php',
// register: 'http://dynmap.local/standalone/build/MySQL_register.php',
// tiles: 'http://dynmap.local/standalone/build/MySQL_tiles.php?tile=',
// markers: 'http://dynmap.local/standalone/build/MySQL_markers.php?marker='
// }
// }
};
</script>
<style> <style>
/* Theme colours */
:root { :root {
--background-base: #222222; /* Buttons/sidebar */ --background-base: #222222; /* Buttons/sidebar */
--background-dark: #121212; /* Body/map labels */ --background-dark: #121212; /* Body/map labels */
@ -171,7 +212,6 @@
</noscript> </noscript>
<div id="mcmap" class="dynmap"></div> <div id="mcmap" class="dynmap"></div>
<script src="./standalone/config.js"></script>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

3376
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,32 +9,28 @@
"lint": "eslint src", "lint": "eslint src",
"lint:fix": "eslint src --fix" "lint:fix": "eslint src --fix"
}, },
"dependencies": { "dependencies": {},
"core-js": "^3.6.5",
"vue": "^3.0.0"
},
"devDependencies": { "devDependencies": {
"@types/clipboard": "^2.0.1", "@types/clipboard": "^2.0.1",
"@types/leaflet": "^1.5.19", "@types/leaflet": "^1.7.0",
"@typescript-eslint/eslint-plugin": "^4.1.0", "@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.1.0", "@typescript-eslint/parser": "^4.23.0",
"@vitejs/plugin-vue": "^1.2.2", "@vitejs/plugin-vue": "^1.2.2",
"@vue/compiler-sfc": "^3.0.0", "@vue/compiler-sfc": "^3.0.11",
"@vue/eslint-config-typescript": "^5.0.2", "@vue/eslint-config-typescript": "^7.0.0",
"clipboard": "^2.0.6", "clipboard": "^2.0.8",
"eslint": "^7.5.0", "eslint": "^7.26.0",
"eslint-plugin-vue": "^7.0.0-0", "eslint-plugin-vue": "^7.9.0",
"leaflet": "^1.7.1", "leaflet": "^1.7.1",
"normalize-scss": "^7.0.1", "normalize-scss": "^7.0.1",
"sass": "^1.29.0", "sass": "^1.32.13",
"svgo": "^1.1.0", "typescript": "~4.2.4",
"svgo-loader": "^2.1.0",
"typescript": "~3.9.3",
"vite": "^2.3.2", "vite": "^2.3.2",
"vite-plugin-cdn": "^1.0.0-beta.3",
"vite-plugin-svg-sprite-component": "^1.0.8", "vite-plugin-svg-sprite-component": "^1.0.8",
"vue-cli-plugin-svg-sprite": "~1.0.0", "vue": "^3.0.11",
"vue-tsc": "^0.1.2", "vue-tsc": "^0.1.2",
"vuex": "^4.0.0-rc.1" "vuex": "^4.0.0"
}, },
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,

View File

@ -44,6 +44,7 @@ export default defineComponent({
updateInterval = computed(() => store.state.configuration.updateInterval), updateInterval = computed(() => store.state.configuration.updateInterval),
title = computed(() => store.state.configuration.title), title = computed(() => store.state.configuration.title),
currentUrl = computed(() => store.getters.url), currentUrl = computed(() => store.getters.url),
currentServer = computed(() => store.state.currentServer),
chatBoxEnabled = computed(() => store.state.components.chatBox), chatBoxEnabled = computed(() => store.state.components.chatBox),
chatVisible = computed(() => store.state.ui.visibleElements.has('chat')), chatVisible = computed(() => store.state.ui.visibleElements.has('chat')),
updatesEnabled = ref(false), updatesEnabled = ref(false),
@ -51,24 +52,14 @@ export default defineComponent({
configAttempts = ref(0), configAttempts = ref(0),
loadConfiguration = () => { loadConfiguration = () => {
let validated = false; return store.dispatch(ActionTypes.LOAD_CONFIGURATION, undefined).then(() => {
startUpdates();
API.validateConfiguration().then(() => { window.hideSplash();
validated = true;
return store.dispatch(ActionTypes.LOAD_CONFIGURATION, undefined).then(() => {
startUpdates();
window.hideSplash();
});
}).catch(e => { }).catch(e => {
console.error('Failed to load server configuration: ', e); console.error('Failed to load server configuration: ', e);
window.showSplashError(e, !validated, ++configAttempts.value); window.showSplashError(e, false, ++configAttempts.value);
setTimeout(() => loadConfiguration(), 1000);
//Don't retry if config isn't valid });
if(validated) {
setTimeout(() => loadConfiguration(), 1000);
}
})
}, },
startUpdates = () => { startUpdates = () => {
@ -121,6 +112,15 @@ export default defineComponent({
watch(title, (title) => document.title = title); watch(title, (title) => document.title = title);
watch(currentUrl, (url) => window.history.replaceState({}, '', url)); watch(currentUrl, (url) => window.history.replaceState({}, '', url));
watch(currentServer, (newServer) => {
window.showSplash();
stopUpdates();
window.history.replaceState({}, '', newServer);
store.commit(MutationTypes.CLEAR_PARSED_URL, undefined);
store.commit(MutationTypes.CLEAR_CURRENT_ZOOM, undefined);
loadConfiguration();
});
onMounted(() => loadConfiguration()); onMounted(() => loadConfiguration());
onBeforeUnmount(() => stopUpdates()); onBeforeUnmount(() => stopUpdates());

View File

@ -36,6 +36,7 @@ import {
} from "@/dynmap"; } from "@/dynmap";
import {useStore} from "@/store"; import {useStore} from "@/store";
import ChatError from "@/errors/ChatError"; import ChatError from "@/errors/ChatError";
import {LiveAtlasServerDefinition} from "@/index";
function buildServerConfig(response: any): DynmapServerConfig { function buildServerConfig(response: any): DynmapServerConfig {
return { return {
@ -118,22 +119,22 @@ function buildWorlds(response: any): Array<DynmapWorld> {
function buildComponents(response: any): DynmapComponentConfig { function buildComponents(response: any): DynmapComponentConfig {
const components: DynmapComponentConfig = { const components: DynmapComponentConfig = {
markers: { markers: {
showLabels: false, showLabels: false,
}, },
chatBox: undefined, chatBox: undefined,
chatBalloons: false, chatBalloons: false,
playerMarkers: undefined, playerMarkers: undefined,
coordinatesControl: undefined, coordinatesControl: undefined,
linkControl: false, linkControl: false,
clockControl: undefined, clockControl: undefined,
logoControls: [], logoControls: [],
}; };
(response.components || []).forEach((component: any) => { (response.components || []).forEach((component: any) => {
const type = component.type || "unknown"; const type = component.type || "unknown";
switch(type) { switch (type) {
case "markers": case "markers":
components.markers = { components.markers = {
showLabels: component.showlabel || false, showLabels: component.showlabel || false,
@ -195,7 +196,7 @@ function buildComponents(response: any): DynmapComponentConfig {
break; break;
case "chat": case "chat":
if(response.allowwebchat) { if (response.allowwebchat) {
components.chatSending = { components.chatSending = {
loginRequired: response['webchat-requires-login'] || false, loginRequired: response['webchat-requires-login'] || false,
maxLength: response['chatlengthlimit'] || 256, maxLength: response['chatlengthlimit'] || 256,
@ -236,7 +237,7 @@ function buildMarkerSet(id: string, data: any): any {
function buildMarkers(data: any): Map<string, DynmapMarker> { function buildMarkers(data: any): Map<string, DynmapMarker> {
const markers = Object.freeze(new Map()) as Map<string, DynmapMarker>; const markers = Object.freeze(new Map()) as Map<string, DynmapMarker>;
for(const key in data) { for (const key in data) {
if (!Object.prototype.hasOwnProperty.call(data, key)) { if (!Object.prototype.hasOwnProperty.call(data, key)) {
continue; continue;
} }
@ -267,7 +268,7 @@ function buildMarker(marker: any): DynmapMarker {
function buildAreas(data: any): Map<string, DynmapArea> { function buildAreas(data: any): Map<string, DynmapArea> {
const areas = Object.freeze(new Map()) as Map<string, DynmapArea>; const areas = Object.freeze(new Map()) as Map<string, DynmapArea>;
for(const key in data) { for (const key in data) {
if (!Object.prototype.hasOwnProperty.call(data, key)) { if (!Object.prototype.hasOwnProperty.call(data, key)) {
continue; continue;
} }
@ -279,7 +280,7 @@ function buildAreas(data: any): Map<string, DynmapArea> {
} }
function buildArea(area: any): DynmapArea { function buildArea(area: any): DynmapArea {
return { return {
style: { style: {
color: area.color || '#ff0000', color: area.color || '#ff0000',
opacity: area.opacity || 1, opacity: area.opacity || 1,
@ -301,7 +302,7 @@ function buildArea(area: any): DynmapArea {
function buildLines(data: any): Map<string, DynmapLine> { function buildLines(data: any): Map<string, DynmapLine> {
const lines = Object.freeze(new Map()) as Map<string, DynmapLine>; const lines = Object.freeze(new Map()) as Map<string, DynmapLine>;
for(const key in data) { for (const key in data) {
if (!Object.prototype.hasOwnProperty.call(data, key)) { if (!Object.prototype.hasOwnProperty.call(data, key)) {
continue; continue;
} }
@ -333,7 +334,7 @@ function buildLine(line: any): DynmapLine {
function buildCircles(data: any): Map<string, DynmapCircle> { function buildCircles(data: any): Map<string, DynmapCircle> {
const circles = Object.freeze(new Map()) as Map<string, DynmapCircle>; const circles = Object.freeze(new Map()) as Map<string, DynmapCircle>;
for(const key in data) { for (const key in data) {
if (!Object.prototype.hasOwnProperty.call(data, key)) { if (!Object.prototype.hasOwnProperty.call(data, key)) {
continue; continue;
} }
@ -370,10 +371,10 @@ function buildCircle(circle: any): DynmapCircle {
function buildUpdates(data: Array<any>): DynmapUpdates { function buildUpdates(data: Array<any>): DynmapUpdates {
const updates = { const updates = {
markerSets: new Map<string, DynmapMarkerSetUpdates>(), markerSets: new Map<string, DynmapMarkerSetUpdates>(),
tiles: [] as DynmapTileUpdate[], tiles: [] as DynmapTileUpdate[],
chat: [] as DynmapChat[], chat: [] as DynmapChat[],
}, },
dropped = { dropped = {
stale: 0, stale: 0,
noSet: 0, noSet: 0,
@ -387,15 +388,15 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
let accepted = 0; let accepted = 0;
for(const entry of data) { for (const entry of data) {
switch(entry.type) { switch (entry.type) {
case 'component': { case 'component': {
if(lastUpdate && entry.timestamp < lastUpdate) { if (lastUpdate && entry.timestamp < lastUpdate) {
dropped.stale++; dropped.stale++;
continue; continue;
} }
if(!entry.id) { if (!entry.id) {
dropped.noId++; dropped.noId++;
continue; continue;
} }
@ -403,17 +404,17 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
//Set updates don't have a set field, the id is the set //Set updates don't have a set field, the id is the set
const set = entry.msg.startsWith("set") ? entry.id : entry.set; const set = entry.msg.startsWith("set") ? entry.id : entry.set;
if(!set) { if (!set) {
dropped.noSet++; dropped.noSet++;
continue; continue;
} }
if(entry.ctype !== 'markers') { if (entry.ctype !== 'markers') {
dropped.unknownCType++; dropped.unknownCType++;
continue; continue;
} }
if(!updates.markerSets.has(set)) { if (!updates.markerSets.has(set)) {
updates.markerSets.set(set, { updates.markerSets.set(set, {
areaUpdates: [], areaUpdates: [],
markerUpdates: [], markerUpdates: [],
@ -429,21 +430,21 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
removed: entry.msg.endsWith('deleted'), removed: entry.msg.endsWith('deleted'),
}; };
if(entry.msg.startsWith("set")) { if (entry.msg.startsWith("set")) {
markerSetUpdates!.removed = update.removed; markerSetUpdates!.removed = update.removed;
markerSetUpdates!.payload = update.removed ? undefined : buildMarkerSet(set, entry); markerSetUpdates!.payload = update.removed ? undefined : buildMarkerSet(set, entry);
} else if(entry.msg.startsWith("marker")) { } else if (entry.msg.startsWith("marker")) {
update.payload = update.removed ? undefined : buildMarker(entry); update.payload = update.removed ? undefined : buildMarker(entry);
markerSetUpdates!.markerUpdates.push(Object.freeze(update)); markerSetUpdates!.markerUpdates.push(Object.freeze(update));
} else if(entry.msg.startsWith("area")) { } else if (entry.msg.startsWith("area")) {
update.payload = update.removed ? undefined : buildArea(entry); update.payload = update.removed ? undefined : buildArea(entry);
markerSetUpdates!.areaUpdates.push(Object.freeze(update)); markerSetUpdates!.areaUpdates.push(Object.freeze(update));
} else if(entry.msg.startsWith("circle")) { } else if (entry.msg.startsWith("circle")) {
update.payload = update.removed ? undefined : buildCircle(entry); update.payload = update.removed ? undefined : buildCircle(entry);
markerSetUpdates!.circleUpdates.push(Object.freeze(update)); markerSetUpdates!.circleUpdates.push(Object.freeze(update));
} else if(entry.msg.startsWith("line")) { } else if (entry.msg.startsWith("line")) {
update.payload = update.removed ? undefined : buildLine(entry); update.payload = update.removed ? undefined : buildLine(entry);
markerSetUpdates!.lineUpdates.push(Object.freeze(update)); markerSetUpdates!.lineUpdates.push(Object.freeze(update));
} }
@ -454,17 +455,17 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
} }
case 'chat': case 'chat':
if(!entry.message || !entry.timestamp) { if (!entry.message || !entry.timestamp) {
dropped.incomplete++; dropped.incomplete++;
continue; continue;
} }
if(entry.timestamp < lastUpdate) { if (entry.timestamp < lastUpdate) {
dropped.stale++; dropped.stale++;
continue; continue;
} }
if(entry.source !== 'player' && entry.source !== 'web') { if (entry.source !== 'player' && entry.source !== 'web') {
dropped.notImplemented++; dropped.notImplemented++;
continue; continue;
} }
@ -481,12 +482,12 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
break; break;
case 'playerjoin': case 'playerjoin':
if(!entry.account || !entry.timestamp) { if (!entry.account || !entry.timestamp) {
dropped.incomplete++; dropped.incomplete++;
continue; continue;
} }
if(entry.timestamp < lastUpdate) { if (entry.timestamp < lastUpdate) {
dropped.stale++; dropped.stale++;
continue; continue;
} }
@ -500,12 +501,12 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
break; break;
case 'playerquit': case 'playerquit':
if(!entry.account || !entry.timestamp) { if (!entry.account || !entry.timestamp) {
dropped.incomplete++; dropped.incomplete++;
continue; continue;
} }
if(entry.timestamp < lastUpdate) { if (entry.timestamp < lastUpdate) {
dropped.stale++; dropped.stale++;
continue; continue;
} }
@ -519,12 +520,12 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
break; break;
case 'tile': case 'tile':
if(!entry.name || !entry.timestamp) { if (!entry.name || !entry.timestamp) {
dropped.incomplete++; dropped.incomplete++;
continue; continue;
} }
if(lastUpdate && entry.timestamp < lastUpdate) { if (lastUpdate && entry.timestamp < lastUpdate) {
dropped.stale++; dropped.stale++;
continue; continue;
} }
@ -553,45 +554,105 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
} }
export default { export default {
validateConfiguration(): Promise<void> { validateConfiguration(): Promise<Map<string, LiveAtlasServerDefinition>> {
if (typeof window.liveAtlasConfig.servers !== 'undefined') {
return this.validateLiveAtlasConfiguration(window.liveAtlasConfig.servers);
}
return this.validateDynmapConfiguration(window.config.url ?? null);
},
validateLiveAtlasConfiguration(config: any): Promise<Map<string, LiveAtlasServerDefinition>> {
const check = '\nCheck your LiveAtlas configuration in index.html is correct.',
result = new Map<string, LiveAtlasServerDefinition>();
if (!Object.keys(config).length) {
return Promise.reject(`No servers defined in LiveAtlas configuration.`);
}
for (const server in config) {
if (!Object.hasOwnProperty.call(config, server)) {
continue;
}
const serverConfig = config[server];
if (!serverConfig || serverConfig.constructor !== Object) {
return Promise.reject(`Server '${server} has an invalid configuration. ${check}`);
}
if (!serverConfig.configuration) {
return Promise.reject(`Server '${server} has no configuration URL. ${check}`);
}
if (!serverConfig.update) {
return Promise.reject(`Server '${server} has no update URL. ${check}`);
}
if (!serverConfig.markers) {
return Promise.reject(`Server '${server} has no markers URL. ${check}`);
}
if (!serverConfig.tiles) {
return Promise.reject(`Server '${server} has no tiles URL. ${check}`);
}
if (!serverConfig.sendmessage) {
return Promise.reject(`Server '${server} has no sendmessage URL. ${check}`);
}
serverConfig.id = server;
result.set(server, serverConfig);
}
return Promise.resolve(result);
},
validateDynmapConfiguration(config: LiveAtlasServerDefinition): Promise<Map<string, LiveAtlasServerDefinition>> {
const check = '\nCheck your standalone/config.js file exists and is being loaded correctly.'; const check = '\nCheck your standalone/config.js file exists and is being loaded correctly.';
if(!window.config || !window.config.url) { if (!config) {
return Promise.reject(`Dynmap's configuration is missing. ${check}`); return Promise.reject(`Dynmap configuration is missing. ${check}`);
} }
if(!window.config.url.configuration) { if (!config.configuration) {
return Promise.reject(`Dynmap's configuration URL is missing. ${check}`); return Promise.reject(`Dynmap configuration URL is missing. ${check}`);
} }
if(!window.config.url.update) { if (!config.update) {
return Promise.reject(`Dynmap's update URL is missing. ${check}`); return Promise.reject(`Dynmap update URL is missing. ${check}`);
} }
if(!window.config.url.markers) { if (!config.markers) {
return Promise.reject(`Dynmap's markers URL is missing. ${check}`); return Promise.reject(`Dynmap markers URL is missing. ${check}`);
} }
if(!window.config.url.tiles) { if (!config.tiles) {
return Promise.reject(`Dynmap's tiles URL is missing. ${check}`); return Promise.reject(`Dynmap tiles URL is missing. ${check}`);
} }
if(!window.config.url.sendmessage) { if (!config.sendmessage) {
return Promise.reject(`Dynmap's sendmessage URL is missing. ${check}`); return Promise.reject(`Dynmap sendmessage URL is missing. ${check}`);
} }
return Promise.resolve(); config.id = 'dynmap';
config.label = 'Dynmap';
const result = new Map<string, LiveAtlasServerDefinition>();
result.set('dynmap', config);
return Promise.resolve(result);
}, },
getConfiguration(): Promise<DynmapConfigurationResponse> { getConfiguration(): Promise<DynmapConfigurationResponse> {
return fetch(window.config.url.configuration).then(response => { return fetch(useStore().getters.serverConfig.configuration).then(response => {
if (!response.ok) { if (!response.ok) {
throw new Error('Network request failed: ' + response.statusText); throw new Error('Network request failed: ' + response.statusText);
} }
return response.json(); return response.json();
}).then((response): DynmapConfigurationResponse => { }).then((response): DynmapConfigurationResponse => {
if(response.error === 'login-required') { if (response.error === 'login-required') {
throw new Error("Login required"); throw new Error("Login required");
} else if (response.error) { } else if (response.error) {
throw new Error(response.error); throw new Error(response.error);
@ -608,7 +669,7 @@ export default {
}, },
getUpdate(requestId: number, world: string, timestamp: number): Promise<DynmapUpdateResponse> { getUpdate(requestId: number, world: string, timestamp: number): Promise<DynmapUpdateResponse> {
let url = window.config.url.update; let url = useStore().getters.serverConfig.update;
url = url.replace('{world}', world); url = url.replace('{world}', world);
url = url.replace('{timestamp}', timestamp.toString()); url = url.replace('{timestamp}', timestamp.toString());
@ -674,7 +735,7 @@ export default {
}, },
getMarkerSets(world: string): Promise<Map<string, DynmapMarkerSet>> { getMarkerSets(world: string): Promise<Map<string, DynmapMarkerSet>> {
const url = `${window.config.url.markers}_markers_/marker_${world}.json`; const url = `${useStore().getters.serverConfig.markers}_markers_/marker_${world}.json`;
return fetch(url).then(response => { return fetch(url).then(response => {
if (!response.ok) { if (!response.ok) {
@ -687,8 +748,8 @@ export default {
response.sets = response.sets || {}; response.sets = response.sets || {};
for(const key in response.sets) { for (const key in response.sets) {
if(!Object.prototype.hasOwnProperty.call(response.sets, key)) { if (!Object.prototype.hasOwnProperty.call(response.sets, key)) {
continue; continue;
} }
@ -714,18 +775,18 @@ export default {
sendChatMessage(message: string) { sendChatMessage(message: string) {
const store = useStore(); const store = useStore();
if(!store.state.components.chatSending) { if (!store.state.components.chatSending) {
return Promise.reject(new ChatError("Chat is not enabled")); return Promise.reject(new ChatError("Chat is not enabled"));
} }
return fetch(window.config.url.sendmessage, { return fetch(useStore().getters.serverConfig.sendmessage, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
name: null, name: null,
message: message, message: message,
}) })
}).then((response) => { }).then((response) => {
if(response.status === 403) { //Rate limited if (response.status === 403) { //Rate limited
throw new ChatError(store.state.messages.chatCooldown throw new ChatError(store.state.messages.chatCooldown
.replace('%interval%', store.state.components.chatSending!.cooldown.toString())); .replace('%interval%', store.state.components.chatSending!.cooldown.toString()));
} }
@ -740,7 +801,7 @@ export default {
throw new ChatError(store.state.messages.chatNotAllowed); throw new ChatError(store.state.messages.chatNotAllowed);
} }
}).catch(e => { }).catch(e => {
if(!(e instanceof ChatError)) { if (!(e instanceof ChatError)) {
console.error('Unexpected error while sending chat message'); console.error('Unexpected error while sending chat message');
console.trace(e); console.trace(e);
} }

View File

@ -168,6 +168,11 @@ export default defineComponent({
min-height: 0; min-height: 0;
overflow: auto; overflow: auto;
will-change: transform; will-change: transform;
margin-bottom: 1rem;
&:last-child {
margin-bottom: 0;
}
} }
.section__skeleton { .section__skeleton {

View File

@ -0,0 +1,68 @@
<!--
- 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="{'server': true, 'server--selected': server.id === currentServer.id}">
<button type="button" :title="server.label || server.id" @click="setCurrentServer(server.id)">{{ server.label || server.id }}</button>
</li>
</template>
<script lang="ts">
import {useStore} from "@/store";
import {defineComponent} from 'vue';
import {MutationTypes} from "@/store/mutation-types";
import {LiveAtlasServerDefinition} from "@/index";
export default defineComponent({
name: 'ServerListItem',
props: {
server: {
type: Object as () => LiveAtlasServerDefinition,
required: true
}
},
computed: {
currentServer(): LiveAtlasServerDefinition | undefined {
return useStore().state.currentServer;
}
},
methods: {
setCurrentServer(serverId: string) {
useStore().commit(MutationTypes.SET_CURRENT_SERVER, serverId);
}
}
});
</script>
<style lang="scss" scoped>
.server {
height: 3.2rem;
button {
text-align: left;
display: block;
height: 100%;
width: 100%;
border-radius: 0.5rem;
}
&.server--selected button {
background-color: var(--background-hover);
}
}
</style>

View File

@ -15,6 +15,12 @@
--> -->
<template> <template>
<section class="sidebar__section" v-if="servers.size > 1">
<span class="section__heading">Servers</span>
<ul class="section__content">
<ServerListItem :server="server" v-for="[name, server] in servers" :key="name"></ServerListItem>
</ul>
</section>
<section class="sidebar__section"> <section class="sidebar__section">
<span class="section__heading">{{ heading }}</span> <span class="section__heading">{{ heading }}</span>
<ul class="section__content"> <ul class="section__content">
@ -28,13 +34,15 @@
<script lang="ts"> <script lang="ts">
import WorldListItem from './WorldListItem.vue'; import WorldListItem from './WorldListItem.vue';
import ServerListItem from './ServerListItem.vue';
import {defineComponent} from 'vue'; import {defineComponent} from 'vue';
import {useStore} from "@/store"; import {useStore} from "@/store";
export default defineComponent({ export default defineComponent({
name: 'WorldList', name: 'WorldList',
components: { components: {
WorldListItem WorldListItem,
ServerListItem
}, },
computed: { computed: {
@ -44,6 +52,10 @@ export default defineComponent({
worlds() { worlds() {
return useStore().state.worlds; return useStore().state.worlds;
},
servers() {
return useStore().state.servers;
} }
}, },
}); });

8
src/dynmap.d.ts vendored
View File

@ -21,16 +21,14 @@ import {ClockControlOptions} from "@/leaflet/control/ClockControl";
declare global { declare global {
interface Window { interface Window {
config: DynmapConfig; config: { url: DynmapUrlConfig };
liveAtlasConfig: any,
hideSplash: Function; hideSplash: Function;
showSplash: Function;
showSplashError: Function; showSplashError: Function;
} }
} }
type DynmapConfig = {
url: DynmapUrlConfig;
};
type DynmapUrlConfig = { type DynmapUrlConfig = {
configuration: string; configuration: string;
update: string; update: string;

6
src/index.d.ts vendored
View File

@ -1,5 +1,6 @@
import { ComponentCustomProperties } from 'vue' import { ComponentCustomProperties } from 'vue'
import {State, Store} from "@/store"; import {State, Store} from "@/store";
import {DynmapUrlConfig} from "@/dynmap";
declare module "*.png" { declare module "*.png" {
const value: any; const value: any;
@ -18,3 +19,8 @@ declare module '@vue/runtime-core' {
$store: Store<State> $store: Store<State>
} }
} }
interface LiveAtlasServerDefinition extends DynmapUrlConfig {
id: string
label?: string
}

View File

@ -18,6 +18,7 @@
*/ */
import {DivIconOptions, PointExpression, Icon, DivIcon, DomUtil, point} from 'leaflet'; import {DivIconOptions, PointExpression, Icon, DivIcon, DomUtil, point} from 'leaflet';
import {useStore} from "@/store";
export interface DynmapIconOptions extends DivIconOptions { export interface DynmapIconOptions extends DivIconOptions {
icon: string; icon: string;
@ -59,7 +60,7 @@ export class DynmapIcon extends DivIcon {
} }
const div = markerContainer.cloneNode(false) as HTMLDivElement, const div = markerContainer.cloneNode(false) as HTMLDivElement,
url = `${window.config.url.markers}_markers_/${this.options.icon}.png`, url = `${useStore().getters.serverConfig.markers}_markers_/${this.options.icon}.png`,
size = point(this.options.iconSize as PointExpression); size = point(this.options.iconSize as PointExpression);
this._image = markerIcon.cloneNode(false) as HTMLImageElement; this._image = markerIcon.cloneNode(false) as HTMLImageElement;
@ -95,7 +96,7 @@ export class DynmapIcon extends DivIcon {
update(options: DynmapIconOptions) { update(options: DynmapIconOptions) {
if(this._image && options.icon !== this.options.icon) { if(this._image && options.icon !== this.options.icon) {
this._image!.src = `${window.config.url.markers}_markers_/${options.icon}.png`; this._image!.src = `${useStore().getters.serverConfig.markers}_markers_/${options.icon}.png`;
this.options.icon = options.icon; this.options.icon = options.icon;
} }

View File

@ -20,6 +20,7 @@
import {TileLayer, Coords, DoneCallback, TileLayerOptions, DomUtil, Util, LatLng} from 'leaflet'; import {TileLayer, Coords, DoneCallback, TileLayerOptions, DomUtil, Util, LatLng} from 'leaflet';
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection"; import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
import {Coordinate, DynmapWorldMap} from "@/dynmap"; import {Coordinate, DynmapWorldMap} from "@/dynmap";
import {store} from "@/store";
export interface DynmapTileLayerOptions extends TileLayerOptions { export interface DynmapTileLayerOptions extends TileLayerOptions {
mapSettings: DynmapWorldMap; mapSettings: DynmapWorldMap;
@ -111,7 +112,7 @@ export class DynmapTileLayer extends TileLayer {
if (!url) { if (!url) {
const path = escape(`${this._mapSettings.world.name}/${name}`); const path = escape(`${this._mapSettings.world.name}/${name}`);
url = `${window.config.url.tiles}${path}`; url = `${store.getters.serverConfig.tiles}${path}`;
if(typeof timestamp !== 'undefined') { if(typeof timestamp !== 'undefined') {
url += (url.indexOf('?') === -1 ? `?timestamp=${timestamp}` : `&timestamp=${timestamp}`); url += (url.indexOf('?') === -1 ? `?timestamp=${timestamp}` : `&timestamp=${timestamp}`);

View File

@ -16,11 +16,13 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import API from './api';
import {store} from "@/store"; import {store} from "@/store";
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import 'normalize-scss/sass/normalize/_import-now.scss'; import 'normalize-scss/sass/normalize/_import-now.scss';
import '@/scss/style.scss'; import '@/scss/style.scss';
import {MutationTypes} from "@/store/mutation-types";
const splash = document.getElementById('splash'), const splash = document.getElementById('splash'),
splashSpinner = document.getElementById('splash__spinner'), splashSpinner = document.getElementById('splash__spinner'),
@ -30,14 +32,37 @@ const splash = document.getElementById('splash'),
splashAttempt = document.getElementById('splash__error-attempt'), splashAttempt = document.getElementById('splash__error-attempt'),
svgs = import.meta.globEager('/assets/icons/*.svg'); svgs = import.meta.globEager('/assets/icons/*.svg');
window.hideSplash = function() { window.showSplash = function() {
if(!splash) {
return;
}
splash.ontransitionend = null;
splash.hidden = false;
requestAnimationFrame(function() { requestAnimationFrame(function() {
if(splash) { if(splash) {
splash.style.opacity = '0'; splash.style.opacity = '1';
} }
}); });
}; };
window.hideSplash = function() {
if(!splash) {
return;
}
splash.ontransitionend = function(e) {
if(e.target === splash) {
splash.hidden = true;
}
};
requestAnimationFrame(function() {
splash.style.opacity = '0';
});
};
window.showSplashError = function(message: string, fatal: boolean, attempts: number) { window.showSplashError = function(message: string, fatal: boolean, attempts: number) {
if(splashError) { if(splashError) {
splashError.setAttribute('aria-hidden', 'false'); splashError.setAttribute('aria-hidden', 'false');
@ -61,17 +86,24 @@ window.showSplashError = function(message: string, fatal: boolean, attempts: num
} }
}; };
if(splash) {
splash.addEventListener('transitionend', function(e) {
if(e.target === splash) {
splash.hidden = true;
}
});
}
console.info(`LiveAtlas version ${store.state.version} - https://github.com/JLyne/LiveAtlas`); console.info(`LiveAtlas version ${store.state.version} - https://github.com/JLyne/LiveAtlas`);
const app = createApp(App).use(store); API.validateConfiguration().then((config) => {
store.commit(MutationTypes.SET_SERVERS, config);
// app.config.performance = true; if(config.size > 1) {
app.mount('#mcmap'); const lastSegment = window.location.pathname.split('/').pop(),
serverName = lastSegment && config.has(lastSegment) ? lastSegment : config.keys().next().value;
store.commit(MutationTypes.SET_CURRENT_SERVER, serverName);
} else {
store.commit(MutationTypes.SET_CURRENT_SERVER, config.keys().next().value);
}
console.log(store.state.currentServer);
const app = createApp(App).use(store);
// app.config.performance = true;
app.mount('#mcmap');
});

View File

@ -78,7 +78,11 @@ export interface Actions {
export const actions: ActionTree<State, State> & Actions = { export const actions: ActionTree<State, State> & Actions = {
[ActionTypes.LOAD_CONFIGURATION]({commit, state}): Promise<DynmapConfigurationResponse> { [ActionTypes.LOAD_CONFIGURATION]({commit, state}): Promise<DynmapConfigurationResponse> {
commit(MutationTypes.CLEAR_MARKER_SETS, undefined);
commit(MutationTypes.CLEAR_PLAYERS, undefined);
return API.getConfiguration().then(config => { return API.getConfiguration().then(config => {
commit(MutationTypes.CLEAR_WORLDS, undefined);
commit(MutationTypes.SET_CONFIGURATION, config.config); commit(MutationTypes.SET_CONFIGURATION, config.config);
commit(MutationTypes.SET_MESSAGES, config.messages); commit(MutationTypes.SET_MESSAGES, config.messages);
commit(MutationTypes.SET_WORLDS, config.worlds); commit(MutationTypes.SET_WORLDS, config.worlds);

View File

@ -17,6 +17,7 @@
import {GetterTree} from "vuex"; import {GetterTree} from "vuex";
import {State} from "@/store/state"; import {State} from "@/store/state";
import {getMinecraftTime} from "@/util"; import {getMinecraftTime} from "@/util";
import {LiveAtlasServerDefinition} from "@/index";
export type Getters = { export type Getters = {
playerMarkersEnabled(state: State): boolean; playerMarkersEnabled(state: State): boolean;
@ -25,6 +26,7 @@ export type Getters = {
night(state: State): boolean; night(state: State): boolean;
mapBackground(state: State, getters: GetterTree<State, State> & Getters): string; mapBackground(state: State, getters: GetterTree<State, State> & Getters): string;
url(state: State, getters: GetterTree<State, State> & Getters): string; url(state: State, getters: GetterTree<State, State> & Getters): string;
serverConfig(state: State, getters: GetterTree<State, State> & Getters): LiveAtlasServerDefinition;
} }
export const getters: GetterTree<State, State> & Getters = { export const getters: GetterTree<State, State> & Getters = {
@ -72,5 +74,13 @@ export const getters: GetterTree<State, State> & Getters = {
} }
return `#${state.currentWorld.name};${state.currentMap.name};${locationString};${zoom}`; return `#${state.currentWorld.name};${state.currentMap.name};${locationString};${zoom}`;
},
serverConfig(state: State): LiveAtlasServerDefinition {
if(!state.currentServer) {
throw RangeError("No current server");
}
return state.servers.get(state.currentServer) as LiveAtlasServerDefinition;
} }
} }

View File

@ -15,12 +15,15 @@
*/ */
export enum MutationTypes { export enum MutationTypes {
SET_SERVERS ='setServers',
SET_CONFIGURATION = 'setConfiguration', SET_CONFIGURATION = 'setConfiguration',
SET_MESSAGES = 'setMessages', SET_MESSAGES = 'setMessages',
SET_WORLDS = 'setWorlds', SET_WORLDS = 'setWorlds',
SET_COMPONENTS = 'setComponents', SET_COMPONENTS = 'setComponents',
SET_MARKER_SETS = 'setMarkerSets', SET_MARKER_SETS = 'setMarkerSets',
CLEAR_MARKER_SETS = 'clearMarkerSets',
ADD_WORLD = 'addWorld', ADD_WORLD = 'addWorld',
CLEAR_WORLDS = 'clearWorlds',
SET_WORLD_STATE = 'setWorldState', SET_WORLD_STATE = 'setWorldState',
SET_UPDATE_TIMESTAMP = 'setUpdateTimestamp', SET_UPDATE_TIMESTAMP = 'setUpdateTimestamp',
ADD_MARKER_SET_UPDATES = 'addMarkerSetUpdates', ADD_MARKER_SET_UPDATES = 'addMarkerSetUpdates',
@ -32,15 +35,18 @@ export enum MutationTypes {
POP_LINE_UPDATES = 'popLineUpdates', POP_LINE_UPDATES = 'popLineUpdates',
POP_TILE_UPDATES = 'popTileUpdates', POP_TILE_UPDATES = 'popTileUpdates',
INCREMENT_REQUEST_ID = 'incrementRequestId', INCREMENT_REQUEST_ID = 'incrementRequestId',
SET_PLAYERS = 'setPlayers',
SET_PLAYERS_ASYNC = 'setPlayersAsync', SET_PLAYERS_ASYNC = 'setPlayersAsync',
CLEAR_PLAYERS = 'clearPlayers',
SYNC_PLAYERS = 'syncPlayers', SYNC_PLAYERS = 'syncPlayers',
SET_CURRENT_SERVER = 'setCurrentServer',
SET_CURRENT_MAP = 'setCurrentMap', SET_CURRENT_MAP = 'setCurrentMap',
SET_CURRENT_PROJECTION = 'setCurrentProjection', SET_CURRENT_PROJECTION = 'setCurrentProjection',
SET_CURRENT_LOCATION = 'setCurrentLocation', SET_CURRENT_LOCATION = 'setCurrentLocation',
SET_CURRENT_ZOOM = 'setCurrentZoom', SET_CURRENT_ZOOM = 'setCurrentZoom',
CLEAR_CURRENT_ZOOM = 'clearCurrentZoom',
SET_PARSED_URL = 'setParsedUrl', SET_PARSED_URL = 'setParsedUrl',
CLEAR_PARSED_URL = 'clearParsedUrl',
SET_FOLLOW_TARGET = 'setFollowTarget', SET_FOLLOW_TARGET = 'setFollowTarget',
SET_PAN_TARGET = 'setPanTarget', SET_PAN_TARGET = 'setPanTarget',

View File

@ -32,6 +32,7 @@ import {
DynmapWorldState, DynmapParsedUrl, DynmapChat, DynmapUIElement DynmapWorldState, DynmapParsedUrl, DynmapChat, DynmapUIElement
} from "@/dynmap"; } from "@/dynmap";
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection"; import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
import {LiveAtlasServerDefinition} from "@/index";
export type CurrentMapPayload = { export type CurrentMapPayload = {
worldName: string; worldName: string;
@ -39,12 +40,15 @@ export type CurrentMapPayload = {
} }
export type Mutations<S = State> = { export type Mutations<S = State> = {
[MutationTypes.SET_SERVERS](state: S, servers: Map<string, LiveAtlasServerDefinition>): void
[MutationTypes.SET_CONFIGURATION](state: S, config: DynmapServerConfig): void [MutationTypes.SET_CONFIGURATION](state: S, config: DynmapServerConfig): void
[MutationTypes.SET_MESSAGES](state: S, messages: DynmapMessageConfig): void [MutationTypes.SET_MESSAGES](state: S, messages: DynmapMessageConfig): void
[MutationTypes.SET_WORLDS](state: S, worlds: Array<DynmapWorld>): void [MutationTypes.SET_WORLDS](state: S, worlds: Array<DynmapWorld>): void
[MutationTypes.SET_COMPONENTS](state: S, worlds: DynmapComponentConfig): void [MutationTypes.SET_COMPONENTS](state: S, worlds: DynmapComponentConfig): void
[MutationTypes.SET_MARKER_SETS](state: S, worlds: Map<string, DynmapMarkerSet>): void [MutationTypes.SET_MARKER_SETS](state: S, worlds: Map<string, DynmapMarkerSet>): void
[MutationTypes.CLEAR_MARKER_SETS](state: S): void
[MutationTypes.ADD_WORLD](state: S, world: DynmapWorld): void [MutationTypes.ADD_WORLD](state: S, world: DynmapWorld): void
[MutationTypes.CLEAR_WORLDS](state: S): void
[MutationTypes.SET_WORLD_STATE](state: S, worldState: DynmapWorldState): void [MutationTypes.SET_WORLD_STATE](state: S, worldState: DynmapWorldState): void
[MutationTypes.SET_UPDATE_TIMESTAMP](state: S, time: Date): void [MutationTypes.SET_UPDATE_TIMESTAMP](state: S, time: Date): void
[MutationTypes.ADD_MARKER_SET_UPDATES](state: S, updates: Map<string, DynmapMarkerSetUpdates>): void [MutationTypes.ADD_MARKER_SET_UPDATES](state: S, updates: Map<string, DynmapMarkerSetUpdates>): void
@ -60,11 +64,15 @@ export type Mutations<S = State> = {
[MutationTypes.INCREMENT_REQUEST_ID](state: S): void [MutationTypes.INCREMENT_REQUEST_ID](state: S): void
[MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set<DynmapPlayer>): Set<DynmapPlayer> [MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set<DynmapPlayer>): Set<DynmapPlayer>
[MutationTypes.SYNC_PLAYERS](state: S, keep: Set<string>): void [MutationTypes.SYNC_PLAYERS](state: S, keep: Set<string>): void
[MutationTypes.CLEAR_PLAYERS](state: S): void
[MutationTypes.SET_CURRENT_SERVER](state: S, server: string): void
[MutationTypes.SET_CURRENT_MAP](state: S, payload: CurrentMapPayload): void [MutationTypes.SET_CURRENT_MAP](state: S, payload: CurrentMapPayload): void
[MutationTypes.SET_CURRENT_PROJECTION](state: S, payload: DynmapProjection): void [MutationTypes.SET_CURRENT_PROJECTION](state: S, payload: DynmapProjection): void
[MutationTypes.SET_CURRENT_LOCATION](state: S, payload: Coordinate): void [MutationTypes.SET_CURRENT_LOCATION](state: S, payload: Coordinate): void
[MutationTypes.SET_CURRENT_ZOOM](state: S, payload: number): void [MutationTypes.SET_CURRENT_ZOOM](state: S, payload: number): void
[MutationTypes.CLEAR_CURRENT_ZOOM](state: S): void
[MutationTypes.SET_PARSED_URL](state: S, payload: DynmapParsedUrl): void [MutationTypes.SET_PARSED_URL](state: S, payload: DynmapParsedUrl): void
[MutationTypes.CLEAR_PARSED_URL](state: S): void
[MutationTypes.SET_FOLLOW_TARGET](state: S, payload: DynmapPlayer): void [MutationTypes.SET_FOLLOW_TARGET](state: S, payload: DynmapPlayer): void
[MutationTypes.SET_PAN_TARGET](state: S, payload: DynmapPlayer): void [MutationTypes.SET_PAN_TARGET](state: S, payload: DynmapPlayer): void
[MutationTypes.CLEAR_FOLLOW_TARGET](state: S, a?: void): void [MutationTypes.CLEAR_FOLLOW_TARGET](state: S, a?: void): void
@ -78,6 +86,15 @@ export type Mutations<S = State> = {
} }
export const mutations: MutationTree<State> & Mutations = { export const mutations: MutationTree<State> & Mutations = {
// Sets configuration options from the initial config fetch
[MutationTypes.SET_SERVERS](state: State, config: Map<string, LiveAtlasServerDefinition>) {
state.servers = config;
if(state.currentServer && !state.servers.has(state.currentServer)) {
state.currentServer = undefined;
}
},
// Sets configuration options from the initial config fetch // Sets configuration options from the initial config fetch
[MutationTypes.SET_CONFIGURATION](state: State, config: DynmapServerConfig) { [MutationTypes.SET_CONFIGURATION](state: State, config: DynmapServerConfig) {
state.configuration = Object.assign(state.configuration, config); state.configuration = Object.assign(state.configuration, config);
@ -128,10 +145,31 @@ export const mutations: MutationTree<State> & Mutations = {
} }
}, },
//Sets the existing marker sets from the last marker fetch
[MutationTypes.CLEAR_MARKER_SETS](state: State) {
state.markerSets.clear();
state.pendingSetUpdates.clear();
},
[MutationTypes.ADD_WORLD](state: State, world: DynmapWorld) { [MutationTypes.ADD_WORLD](state: State, world: DynmapWorld) {
state.worlds.set(world.name, world); state.worlds.set(world.name, world);
}, },
[MutationTypes.CLEAR_WORLDS](state: State) {
console.log('??????');
state.worlds.clear();
state.maps.clear();
state.currentMap = undefined;
state.currentWorld = undefined;
state.followTarget = undefined;
state.panTarget = undefined;
state.currentWorldState.timeOfDay = 0;
state.currentWorldState.raining = false;
state.currentWorldState.thundering = false;
},
//Sets the current world state an update fetch //Sets the current world state an update fetch
[MutationTypes.SET_WORLD_STATE](state: State, worldState: DynmapWorldState) { [MutationTypes.SET_WORLD_STATE](state: State, worldState: DynmapWorldState) {
state.currentWorldState = Object.assign(state.currentWorldState, worldState); state.currentWorldState = Object.assign(state.currentWorldState, worldState);
@ -341,6 +379,25 @@ export const mutations: MutationTree<State> & Mutations = {
} }
}, },
//Removes all players not found in the provided keep set
[MutationTypes.CLEAR_PLAYERS](state: State) {
state.followTarget = undefined;
state.panTarget = undefined;
for(const [key, player] of state.players) {
state.players.delete(key);
}
},
//Sets the currently active server
[MutationTypes.SET_CURRENT_SERVER](state: State, serverName) {
if(!state.servers.has(serverName)) {
throw new RangeError(`Unknown server ${serverName}`);
}
state.currentServer = serverName;
},
//Sets the currently active map/world //Sets the currently active map/world
[MutationTypes.SET_CURRENT_MAP](state: State, {worldName, mapName}) { [MutationTypes.SET_CURRENT_MAP](state: State, {worldName, mapName}) {
mapName = [worldName, mapName].join('_'); mapName = [worldName, mapName].join('_');
@ -380,11 +437,26 @@ export const mutations: MutationTree<State> & Mutations = {
state.currentZoom = payload; state.currentZoom = payload;
}, },
[MutationTypes.CLEAR_CURRENT_ZOOM](state: State) {
state.currentZoom = 0;
},
//Sets the result of parsing the current map url, if present and valid //Sets the result of parsing the current map url, if present and valid
[MutationTypes.SET_PARSED_URL](state: State, payload: DynmapParsedUrl) { [MutationTypes.SET_PARSED_URL](state: State, payload: DynmapParsedUrl) {
state.parsedUrl = payload; state.parsedUrl = payload;
}, },
//Clear any existing parsed url
[MutationTypes.CLEAR_PARSED_URL](state: State) {
state.parsedUrl = {
world: undefined,
map: undefined,
location: undefined,
zoom: undefined,
legacy: false,
};
},
//Set the follow target, which the map will automatically pan to keep in view //Set the follow target, which the map will automatically pan to keep in view
[MutationTypes.SET_FOLLOW_TARGET](state: State, player: DynmapPlayer) { [MutationTypes.SET_FOLLOW_TARGET](state: State, player: DynmapPlayer) {
state.followTarget = player; state.followTarget = player;

View File

@ -23,9 +23,11 @@ import {
DynmapWorld, DynmapWorldState, Coordinate, DynmapParsedUrl, DynmapChat, DynmapUIElement DynmapWorld, DynmapWorldState, Coordinate, DynmapParsedUrl, DynmapChat, DynmapUIElement
} from "@/dynmap"; } from "@/dynmap";
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection"; import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
import {LiveAtlasServerDefinition} from "@/index";
export type State = { export type State = {
version: string; version: string;
servers: Map<string, LiveAtlasServerDefinition>;
configuration: DynmapServerConfig; configuration: DynmapServerConfig;
messages: DynmapMessageConfig; messages: DynmapMessageConfig;
components: DynmapComponentConfig; components: DynmapComponentConfig;
@ -48,6 +50,7 @@ export type State = {
followTarget?: DynmapPlayer; followTarget?: DynmapPlayer;
panTarget?: DynmapPlayer; panTarget?: DynmapPlayer;
currentServer?: string;
currentWorldState: DynmapWorldState; currentWorldState: DynmapWorldState;
currentWorld?: DynmapWorld; currentWorld?: DynmapWorld;
currentMap?: DynmapWorldMap; currentMap?: DynmapWorldMap;
@ -69,6 +72,7 @@ export type State = {
export const state: State = { export const state: State = {
version: (import.meta.env.VITE_VERSION || 'Unknown') as string, version: (import.meta.env.VITE_VERSION || 'Unknown') as string,
servers: new Map(),
configuration: { configuration: {
version: '', version: '',
@ -150,6 +154,7 @@ export const state: State = {
followTarget: undefined, followTarget: undefined,
panTarget: undefined, panTarget: undefined,
currentServer: undefined,
currentWorld: undefined, currentWorld: undefined,
currentMap: undefined, currentMap: undefined,
currentLocation: { currentLocation: {

View File

@ -99,7 +99,7 @@ const tickHeadQueue = () => {
src = (head.size === 'body') ? `faces/body/${head.account}.png` :`faces/${head.size}x${head.size}/${head.account}.png`; src = (head.size === 'body') ? `faces/body/${head.account}.png` :`faces/${head.size}x${head.size}/${head.account}.png`;
headsLoading.add(head.cacheKey); headsLoading.add(head.cacheKey);
head.image.src = concatURL(window.config.url.markers, src); head.image.src = concatURL(useStore().getters.serverConfig.markers, src);
tickHeadQueue(); tickHeadQueue();
} }