Merge branch 'pl3xmap'

This commit is contained in:
James Lyne 2021-05-20 13:09:09 +01:00
commit f2bfbf732a
14 changed files with 199 additions and 260 deletions

View File

@ -29,6 +29,7 @@ import {useStore} from "@/store";
import {ActionTypes} from "@/store/action-types";
import {parseUrl} from '@/util';
import {MutationTypes} from "@/store/mutation-types";
import {LiveAtlasServerDefinition} from "@/index";
export default defineComponent({
name: 'App',
@ -62,7 +63,7 @@ export default defineComponent({
return;
}
const error = `Failed to load server configuration for '${store.state.currentServer}'`;
const error = `Failed to load server configuration for '${store.state.currentServer.id}'`;
console.error(`${error}:`, e);
window.showSplashError(`${error}\n${e}`, false, ++configAttempts.value);
setTimeout(() => loadConfiguration(), 1000);
@ -125,10 +126,14 @@ export default defineComponent({
watch(title, (title) => document.title = title);
watch(currentUrl, (url) => window.history.replaceState({}, '', url));
watch(currentServer, (newServer) => {
watch(currentServer, (newServer: LiveAtlasServerDefinition) => {
window.showSplash();
stopUpdates();
if(!newServer) {
return;
}
//Cleanup
store.commit(MutationTypes.CLEAR_PLAYERS, undefined);
store.commit(MutationTypes.CLEAR_CURRENT_MAP, undefined);
@ -136,9 +141,9 @@ export default defineComponent({
store.commit(MutationTypes.CLEAR_WORLDS, undefined);
store.commit(MutationTypes.CLEAR_MARKER_SETS, undefined);
window.history.replaceState({}, '', newServer);
window.history.replaceState({}, '', newServer.id);
loadConfiguration();
});
}, {deep: true});
watch(configurationHash, (newHash, oldHash) => {
if(newHash && oldHash) {
window.showSplash();

View File

@ -30,14 +30,12 @@ import {
DynmapTileUpdate,
DynmapUpdate,
DynmapUpdateResponse,
DynmapUpdates, DynmapUrlConfig,
DynmapUpdates,
DynmapWorld,
DynmapWorldMap
} from "@/dynmap";
import {useStore} from "@/store";
import ChatError from "@/errors/ChatError";
import {LiveAtlasDynmapServerDefinition, LiveAtlasServerDefinition} from "@/index";
import ConfigurationError from "@/errors/ConfigurationError";
const titleColours = /§[0-9a-f]/ig;
@ -556,96 +554,6 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
return updates;
}
const validateLiveAtlasConfiguration = (config: any): Map<string, LiveAtlasServerDefinition> => {
const check = '\nCheck your server configuration in index.html is correct.',
result = new Map<string, LiveAtlasServerDefinition>();
if (!Object.keys(config).length) {
throw new ConfigurationError(`No servers defined. ${check}`);
}
for (const server in config) {
if (!Object.hasOwnProperty.call(config, server)) {
continue;
}
const serverConfig = config[server];
if (!serverConfig || serverConfig.constructor !== Object || !Object.keys(serverConfig).length) {
throw new ConfigurationError(`Server '${server}': Configuration missing. ${check}`);
}
serverConfig.id = server;
serverConfig.type = 'dynmap';
if (!serverConfig.dynmap || serverConfig.dynmap.constructor !== Object) {
throw new ConfigurationError(`Server '${server}': Dynmap configuration object missing. ${check}`);
}
if (!serverConfig.dynmap.configuration) {
throw new ConfigurationError(`Server '${server}': Dynmap configuration URL missing. ${check}`);
}
if (!serverConfig.dynmap.update) {
throw new ConfigurationError(`Server '${server}': Dynmap update URL missing. ${check}`);
}
if (!serverConfig.dynmap.markers) {
throw new ConfigurationError(`Server '${server}': Dynmap markers URL missing. ${check}`);
}
if (!serverConfig.dynmap.tiles) {
throw new ConfigurationError(`Server '${server}': Dynmap tiles URL missing. ${check}`);
}
if (!serverConfig.dynmap.sendmessage) {
throw new ConfigurationError(`Server '${server}': Dynmap sendmessage URL missing. ${check}`);
}
result.set(server, serverConfig);
}
return result;
};
const validateDynmapConfiguration = (config: DynmapUrlConfig): Map<string, LiveAtlasDynmapServerDefinition> => {
const check = '\nCheck your standalone/config.js file exists and is being loaded correctly.';
if (!config) {
throw new ConfigurationError(`Dynmap configuration is missing. ${check}`);
}
if (!config.configuration) {
throw new ConfigurationError(`Dynmap configuration URL is missing. ${check}`);
}
if (!config.update) {
throw new ConfigurationError(`Dynmap update URL is missing. ${check}`);
}
if (!config.markers) {
throw new ConfigurationError(`Dynmap markers URL is missing. ${check}`);
}
if (!config.tiles) {
throw new ConfigurationError(`Dynmap tiles URL is missing. ${check}`);
}
if (!config.sendmessage) {
throw new ConfigurationError(`Dynmap sendmessage URL is missing. ${check}`);
}
const result = new Map<string, LiveAtlasDynmapServerDefinition>();
result.set('dynmap', {
id: 'dynmap',
label: 'dynmap',
type: 'dynmap',
dynmap: config
});
return result;
};
async function fetchJSON(url: string, signal: AbortSignal) {
let response, json;
@ -685,18 +593,6 @@ let configurationAbort: AbortController | undefined = undefined,
updateAbort: AbortController | undefined = undefined;
export default {
validateConfiguration(): Map<string, LiveAtlasServerDefinition> {
if (!window.liveAtlasConfig) {
throw new ConfigurationError(`Configuration object is missing`);
}
if (typeof window.liveAtlasConfig.servers !== 'undefined') {
return validateLiveAtlasConfiguration(window.liveAtlasConfig.servers || {});
}
return validateDynmapConfiguration(window.config?.url || null);
},
async getConfiguration(): Promise<DynmapConfigurationResponse> {
if(configurationAbort) {
configurationAbort.abort();

View File

@ -19,10 +19,10 @@ import {defineComponent, onUnmounted, computed, watch} from "@vue/runtime-core";
import {DynmapWorldMap} from "@/dynmap";
import {Map} from 'leaflet';
import {useStore} from "@/store";
import {HDMapType} from "@/leaflet/mapType/HDMapType";
import {MutationTypes} from "@/store/mutation-types";
import {ActionTypes} from "@/store/action-types";
import {getMinecraftTime} from "@/util";
import {DynmapTileLayer} from "@/leaflet/tileLayer/DynmapTileLayer";
export default defineComponent({
props: {
@ -46,7 +46,7 @@ export default defineComponent({
const store = useStore(),
night = computed(() => getMinecraftTime(store.state.currentWorldState.timeOfDay).night),
layer = new HDMapType({
layer = new DynmapTileLayer({
errorTileUrl: 'images/blank.png',
mapSettings: Object.freeze(JSON.parse(JSON.stringify(props.map))),
night: night.value,

View File

@ -15,8 +15,8 @@
-->
<template>
<li :class="{'server': true, 'server--selected': server.id === currentServer}">
<button type="button" :class="{'active': server.id === currentServer}"
<li :class="{'server': true, 'server--selected': server.id === currentServer.id}">
<button type="button" :class="{'active': server.id === currentServer.id}"
:title="server.label || server.id" @click="setCurrentServer(server.id)">{{ server.label || server.id }}
</button>
</li>
@ -38,7 +38,7 @@ export default defineComponent({
},
computed: {
currentServer(): string | undefined {
currentServer(): LiveAtlasServerDefinition | undefined {
return useStore().state.currentServer;
}
},

View File

@ -1,62 +0,0 @@
/*
* Copyright 2020 James Lyne
*
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
* These portions are Copyright 2020 Dynmap Contributors.
*
* 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 {Util} from 'leaflet';
import HDProjection from "@/leaflet/projection/HDProjection";
import {Coordinate} from "@/dynmap";
import {DynmapTileLayer, DynmapTileLayerOptions} from "@/leaflet/tileLayer/DynmapTileLayer";
export interface HDMapTypeOptions extends DynmapTileLayerOptions {}
export interface HDMapType extends DynmapTileLayer {
}
export class HDMapType extends DynmapTileLayer {
constructor(options: DynmapTileLayerOptions) {
super(options);
options.maxZoom = this._mapSettings.nativeZoomLevels + this._mapSettings.extraZoomLevels;
options.maxNativeZoom = this._mapSettings.nativeZoomLevels;
options.zoomReverse = true;
options.tileSize = 128;
options.minZoom = 0;
Util.setOptions(this, options);
this._projection = Object.freeze(new HDProjection({
mapToWorld: this._mapSettings.mapToWorld,
worldToMap: this._mapSettings.worldToMap,
nativeZoomLevels: this._mapSettings.nativeZoomLevels,
}));
}
getTileName(coords: Coordinate) {
const info = super.getTileInfo(coords);
// Y is inverted for HD-map.
info.y = -info.y;
info.scaledy = info.y >> 5;
return `${info.prefix}${info.nightday}/${info.scaledx}_${info.scaledy}/${info.zoom}${info.x}_${info.y}.${info.fmt}`;
}
zoomprefix(amount: number) {
// amount == 0 -> ''
// amount == 1 -> 'z_'
// amount == 2 -> 'zz_'
return 'z'.repeat(amount) + (amount === 0 ? '' : '_');
}
}

View File

@ -20,25 +20,42 @@
import {Util, LatLng, Class} from 'leaflet';
import {Coordinate} from "@/dynmap";
export interface DynmapProjectionOptions {}
export interface DynmapProjectionOptions {
mapToWorld: [number, number, number, number, number, number, number, number, number],
worldToMap: [number, number, number, number, number, number, number, number, number],
nativeZoomLevels: number
}
export interface DynmapProjection {
options: DynmapProjectionOptions
locationToLatLng(location: Coordinate): LatLng;
latLngToLocation(latLng: LatLng, y: number): Coordinate;
}
export class DynmapProjection extends Class {
constructor(options?: DynmapProjectionOptions) {
constructor(options: DynmapProjectionOptions) {
super();
Util.setOptions(this, options);
}
locationToLatLng(location: Coordinate): LatLng {
return new LatLng(location.x, location.z);
const wtp = this.options.worldToMap,
lat = wtp[3] * location.x + wtp[4] * location.y + wtp[5] * location.z,
lng = wtp[0] * location.x + wtp[1] * location.y + wtp[2] * location.z;
return new LatLng(
-((128 - lat) / (1 << this.options.nativeZoomLevels)),
lng / (1 << this.options.nativeZoomLevels));
}
latLngToLocation(latLng: LatLng, y: number): Coordinate {
return {x: latLng.lat, y, z: latLng.lng};
const ptw = this.options.mapToWorld,
lat = latLng.lng * (1 << this.options.nativeZoomLevels),
lon = 128 + latLng.lat * (1 << this.options.nativeZoomLevels),
x = ptw[0] * lat + ptw[1] * lon + ptw[2] * y,
z = ptw[6] * lat + ptw[7] * lon + ptw[8] * y;
return {x: x, y: y, z: z};
}
}

View File

@ -1,61 +0,0 @@
/*
* Copyright 2020 James Lyne
*
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
* These portions are Copyright 2020 Dynmap Contributors.
*
* 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 {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
import {Util, LatLng} from 'leaflet';
import {Coordinate} from "@/dynmap";
export interface HDProjectionOptions {
mapToWorld: [number, number, number, number, number, number, number, number, number],
worldToMap: [number, number, number, number, number, number, number, number, number],
nativeZoomLevels: number
}
export interface HDProjection extends DynmapProjection {
options: HDProjectionOptions
}
export class HDProjection extends DynmapProjection {
constructor(options: HDProjectionOptions) {
super(options);
Util.setOptions(this, options);
}
locationToLatLng(location: Coordinate): LatLng {
const wtp = this.options.worldToMap,
lat = wtp[3] * location.x + wtp[4] * location.y + wtp[5] * location.z,
lng = wtp[0] * location.x + wtp[1] * location.y + wtp[2] * location.z;
return new LatLng(
-((128 - lat) / (1 << this.options.nativeZoomLevels)),
lng / (1 << this.options.nativeZoomLevels));
}
latLngToLocation(latLng: LatLng, y: number): Coordinate {
const ptw = this.options.mapToWorld,
lat = latLng.lng * (1 << this.options.nativeZoomLevels),
lon = 128 + latLng.lat * (1 << this.options.nativeZoomLevels),
x = ptw[0] * lat + ptw[1] * lon + ptw[2] * y,
z = ptw[6] * lat + ptw[7] * lon + ptw[8] * y;
return {x: x, y: y, z: z};
}
}
export default HDProjection;

View File

@ -69,18 +69,29 @@ export interface TileInfo {
fmt: string;
}
// noinspection JSUnusedGlobalSymbols
export class DynmapTileLayer extends TileLayer {
constructor(options: DynmapTileLayerOptions) {
super('', options);
this._mapSettings = options.mapSettings;
options.maxZoom = this._mapSettings.nativeZoomLevels + this._mapSettings.extraZoomLevels;
options.maxNativeZoom = this._mapSettings.nativeZoomLevels;
options.zoomReverse = true;
options.tileSize = 128;
options.minZoom = 0;
Util.setOptions(this, options);
if (options.mapSettings === null) {
throw new TypeError("mapSettings missing");
}
this._projection = new DynmapProjection({});
this._mapSettings = options.mapSettings;
this._projection = new DynmapProjection({
mapToWorld: this._mapSettings.mapToWorld,
worldToMap: this._mapSettings.worldToMap,
nativeZoomLevels: this._mapSettings.nativeZoomLevels,
});
this._cachedTileUrls = Object.seal(new Map());
this._namedTiles = Object.seal(new Map());
this._loadQueue = [];
@ -99,8 +110,12 @@ export class DynmapTileLayer extends TileLayer {
}
}
getTileName(coords: Coordinate): string {
throw "getTileName not implemented";
getTileName(coords: Coordinate) {
const info = this.getTileInfo(coords);
// Y is inverted for HD-map.
info.y = -info.y;
info.scaledy = info.y >> 5;
return `${info.prefix}${info.nightday}/${info.scaledx}_${info.scaledy}/${info.zoom}${info.x}_${info.y}.${info.fmt}`;
}
getTileUrl(coords: Coordinate) {
@ -232,7 +247,10 @@ export class DynmapTileLayer extends TileLayer {
// Some helper functions.
zoomprefix(amount: number) {
return 'z'.repeat(amount);
// amount == 0 -> ''
// amount == 1 -> 'z_'
// amount == 2 -> 'zz_'
return 'z'.repeat(amount) + (amount === 0 ? '' : '_');
}
getTileInfo(coords: Coordinate): TileInfo {

View File

@ -15,14 +15,14 @@
*/
import { createApp } from 'vue'
import App from './App.vue'
import API from './api';
import App from './App.vue';
import {store} from "@/store";
import 'leaflet/dist/leaflet.css';
import 'normalize-scss/sass/normalize/_import-now.scss';
import '@/scss/style.scss';
import {MutationTypes} from "@/store/mutation-types";
import {validateConfiguration} from "@/util";
const splash = document.getElementById('splash'),
splashSpinner = document.getElementById('splash__spinner'),
@ -86,7 +86,7 @@ window.showSplashError = function(message: string, fatal: boolean, attempts: num
console.info(`LiveAtlas version ${store.state.version} - https://github.com/JLyne/LiveAtlas`);
try {
const config = API.validateConfiguration();
const config = validateConfiguration();
store.commit(MutationTypes.SET_SERVERS, config);

View File

@ -18,7 +18,6 @@ import {MutationTypes} from "@/store/mutation-types";
import {ActionContext, ActionTree} from "vuex";
import {State} from "@/store/state";
import {ActionTypes} from "@/store/action-types";
import API from '@/api';
import {Mutations} from "@/store/mutations";
import {
DynmapAreaUpdate, DynmapCircleUpdate,
@ -28,6 +27,7 @@ import {
DynmapPlayer, DynmapTileUpdate,
DynmapUpdateResponse, DynmapWorld
} from "@/dynmap";
import {getAPI} from "@/util";
type AugmentedActionContext = {
commit<K extends keyof Mutations>(
@ -81,7 +81,7 @@ export const actions: ActionTree<State, State> & Actions = {
//Clear any existing has to avoid triggering a second config load, after this load changes the hash
commit(MutationTypes.CLEAR_CONFIGURATION_HASH, undefined);
const config = await API.getConfiguration();
const config = await getAPI().getConfiguration();
commit(MutationTypes.SET_CONFIGURATION, config.config);
commit(MutationTypes.SET_MESSAGES, config.messages);
@ -150,7 +150,7 @@ export const actions: ActionTree<State, State> & Actions = {
return Promise.reject("No current world");
}
const update = await API.getUpdate(state.updateRequestId, state.currentWorld.name, state.updateTimestamp.valueOf())
const update = await getAPI().getUpdate(state.updateRequestId, state.currentWorld.name, state.updateTimestamp.valueOf());
commit(MutationTypes.SET_WORLD_STATE, update.worldState);
commit(MutationTypes.SET_UPDATE_TIMESTAMP, new Date(update.timestamp));
@ -195,7 +195,7 @@ export const actions: ActionTree<State, State> & Actions = {
throw new Error("No current world");
}
const markerSets = await API.getMarkerSets(state.currentWorld.name)
const markerSets = await getAPI().getMarkerSets(state.currentWorld.name)
commit(MutationTypes.SET_MARKER_SETS, markerSets);
return markerSets;
@ -262,6 +262,6 @@ export const actions: ActionTree<State, State> & Actions = {
},
async [ActionTypes.SEND_CHAT_MESSAGE]({commit, state}, message: string): Promise<void> {
await API.sendChatMessage(message);
await getAPI().sendChatMessage(message);
},
}

View File

@ -81,6 +81,6 @@ export const getters: GetterTree<State, State> & Getters = {
throw RangeError("No current server");
}
return state.servers.get(state.currentServer) as LiveAtlasDynmapServerDefinition;
return state.currentServer as LiveAtlasDynmapServerDefinition;
},
}

View File

@ -92,7 +92,7 @@ export const mutations: MutationTree<State> & Mutations = {
[MutationTypes.SET_SERVERS](state: State, config: Map<string, LiveAtlasServerDefinition>) {
state.servers = config;
if(state.currentServer && !state.servers.has(state.currentServer)) {
if(state.currentServer && !state.servers.has(state.currentServer.id)) {
state.currentServer = undefined;
}
},
@ -414,7 +414,7 @@ export const mutations: MutationTree<State> & Mutations = {
throw new RangeError(`Unknown server ${serverName}`);
}
state.currentServer = serverName;
state.currentServer = state.servers.get(serverName);
},
//Sets the currently active map/world

View File

@ -51,7 +51,7 @@ export type State = {
followTarget?: DynmapPlayer;
panTarget?: DynmapPlayer;
currentServer?: string;
currentServer?: LiveAtlasServerDefinition;
currentWorldState: DynmapWorldState;
currentWorld?: DynmapWorld;
currentMap?: DynmapWorldMap;

View File

@ -14,8 +14,11 @@
* limitations under the License.
*/
import {DynmapPlayer} from "@/dynmap";
import API from '@/api';
import {DynmapPlayer, DynmapUrlConfig} from "@/dynmap";
import {useStore} from "@/store";
import {LiveAtlasDynmapServerDefinition, LiveAtlasServerDefinition} from "@/index";
import ConfigurationError from "@/errors/ConfigurationError";
interface HeadQueueEntry {
cacheKey: string;
@ -202,3 +205,126 @@ export const parseMapSearchParams = (query: URLSearchParams) => {
legacy: true,
}
}
const validateLiveAtlasConfiguration = (config: any): Map<string, LiveAtlasServerDefinition> => {
const check = '\nCheck your server configuration in index.html is correct.',
result = new Map<string, LiveAtlasServerDefinition>();
if (!Object.keys(config).length) {
throw new ConfigurationError(`No servers defined. ${check}`);
}
for (const server in config) {
if (!Object.hasOwnProperty.call(config, server)) {
continue;
}
const serverConfig = config[server];
if (!serverConfig || serverConfig.constructor !== Object || !Object.keys(serverConfig).length) {
throw new ConfigurationError(`Server '${server}': Configuration missing. ${check}`);
}
serverConfig.id = server;
serverConfig.type = serverConfig.type || 'dynmap';
switch(serverConfig.type) {
case 'dynmap':
if (!serverConfig.dynmap || serverConfig.dynmap.constructor !== Object) {
throw new ConfigurationError(`Server '${server}': Dynmap configuration object missing. ${check}`);
}
if (!serverConfig.dynmap.configuration) {
throw new ConfigurationError(`Server '${server}': Dynmap configuration URL missing. ${check}`);
}
if (!serverConfig.dynmap.update) {
throw new ConfigurationError(`Server '${server}': Dynmap update URL missing. ${check}`);
}
if (!serverConfig.dynmap.markers) {
throw new ConfigurationError(`Server '${server}': Dynmap markers URL missing. ${check}`);
}
if (!serverConfig.dynmap.tiles) {
throw new ConfigurationError(`Server '${server}': Dynmap tiles URL missing. ${check}`);
}
if (!serverConfig.dynmap.sendmessage) {
throw new ConfigurationError(`Server '${server}': Dynmap sendmessage URL missing. ${check}`);
}
break;
case 'pl3xmap':
case 'plexmap':
if (!serverConfig.plexmap || serverConfig.plexmap.constructor !== Object) {
throw new ConfigurationError(`Server '${server}': Pl3xmap configuration object missing. ${check}`);
}
}
result.set(server, serverConfig);
}
return result;
};
const validateDynmapConfiguration = (config: DynmapUrlConfig): Map<string, LiveAtlasDynmapServerDefinition> => {
const check = '\nCheck your standalone/config.js file exists and is being loaded correctly.';
if (!config) {
throw new ConfigurationError(`Dynmap configuration is missing. ${check}`);
}
if (!config.configuration) {
throw new ConfigurationError(`Dynmap configuration URL is missing. ${check}`);
}
if (!config.update) {
throw new ConfigurationError(`Dynmap update URL is missing. ${check}`);
}
if (!config.markers) {
throw new ConfigurationError(`Dynmap markers URL is missing. ${check}`);
}
if (!config.tiles) {
throw new ConfigurationError(`Dynmap tiles URL is missing. ${check}`);
}
if (!config.sendmessage) {
throw new ConfigurationError(`Dynmap sendmessage URL is missing. ${check}`);
}
const result = new Map<string, LiveAtlasDynmapServerDefinition>();
result.set('dynmap', {
id: 'dynmap',
label: 'dynmap',
type: 'dynmap',
dynmap: config
});
return result;
};
export const validateConfiguration = (): Map<string, LiveAtlasServerDefinition> => {
if (!window.liveAtlasConfig) {
throw new ConfigurationError(`Configuration object is missing`);
}
if (typeof window.liveAtlasConfig.servers !== 'undefined') {
return validateLiveAtlasConfiguration(window.liveAtlasConfig.servers || {});
}
return validateDynmapConfiguration(window.config?.url || null);
};
export const getAPI = () => {
const store = useStore();
if(!store.state.currentServer) {
throw new RangeError("No current server");
}
return API;
}