Basic support for overviewer
This commit is contained in:
parent
68eccb2b5b
commit
b43f1f0fe6
@ -3,6 +3,7 @@
|
||||
<module2copyright>
|
||||
<element module="Dynmap" copyright="Dynmap" />
|
||||
<element module="Original" copyright="Original" />
|
||||
<element module="Overviewer." copyright="Overviewer" />
|
||||
</module2copyright>
|
||||
</settings>
|
||||
</component>
|
@ -1,3 +1,3 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<scope name="Original" pattern="!file:src/leaflet/control/ClockControl.ts&&!file:src/leaflet/control/CoordinatesControl.ts&&!file:src/leaflet/control/LinkControl.ts&&!file:src/leaflet/control/LogoControl.ts&&!file:src/leaflet/icon/PlayerIcon.ts&&!file:src/leaflet/icon/GenericIcon.ts&&!file:src/leaflet/tileLayer/DynmapTileLayer.ts&&!file:src/util/areas.ts&&!file:src/util/circles.ts&&!file:src/util/lines.ts&&!file:src/util/markers.ts&&!file[LiveAtlas]:standalone/*&&!file:src/model/LiveAtlasProjection.ts&&!file:src/leaflet/control/LiveAtlasLayerControl.ts&&!file[LiveAtlas]:patches/*&&!file[LiveAtlas]:public/*&&!file[LiveAtlas]:.idea/*&&!file[LiveAtlas]:.idea//*&&!file[LiveAtlas]:patches//*&&!file[LiveAtlas]:public//*&&!file[LiveAtlas]:standalone//*&&!file:FUNDING.yml&&!file:README.md&&!file:tsconfig.json&&!file:.gitignore&&!file:.env&&!file:LICENSE.md&&!file:package-lock.json&&!file:package.json&&!file:vite.config.ts&&!file:index.html&&!file:src/leaflet/control/LoadingControl.ts&&!file:src/scss/style.scss&&!file[LiveAtlas]:src/assets/icons//*" />
|
||||
</component>
|
||||
<scope name="Original" pattern="!file:src/leaflet/control/ClockControl.ts&&!file:src/leaflet/control/CoordinatesControl.ts&&!file:src/leaflet/control/LinkControl.ts&&!file:src/leaflet/control/LogoControl.ts&&!file:src/leaflet/icon/PlayerIcon.ts&&!file:src/leaflet/icon/GenericIcon.ts&&!file:src/leaflet/tileLayer/DynmapTileLayer.ts&&!file:src/util/areas.ts&&!file:src/util/circles.ts&&!file:src/util/lines.ts&&!file:src/util/markers.ts&&!file[LiveAtlas]:standalone/*&&!file:src/model/LiveAtlasProjection.ts&&!file:src/leaflet/control/LiveAtlasLayerControl.ts&&!file[LiveAtlas]:patches/*&&!file[LiveAtlas]:public/*&&!file[LiveAtlas]:.idea/*&&!file[LiveAtlas]:.idea//*&&!file[LiveAtlas]:patches//*&&!file[LiveAtlas]:public//*&&!file[LiveAtlas]:standalone//*&&!file:FUNDING.yml&&!file:README.md&&!file:tsconfig.json&&!file:.gitignore&&!file:.env&&!file:LICENSE.md&&!file:package-lock.json&&!file:package.json&&!file:vite.config.ts&&!file:index.html&&!file:src/leaflet/control/LoadingControl.ts&&!file:src/scss/style.scss&&!file[LiveAtlas]:src/assets/icons//*&&!file:src/providers/OverviewerMapProvider.ts&&!file:src/providers/DynmapMapProvider.ts&&!file:src/leaflet/projection/OverviewerProjection.ts&&!file:src/leaflet/tileLayer/OverviewerTileLayer.ts" />
|
||||
</component>
|
3
src/index.d.ts
vendored
3
src/index.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
* Copyright 2022 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -104,6 +104,7 @@ interface LiveAtlasServerDefinition {
|
||||
dynmap?: DynmapUrlConfig;
|
||||
pl3xmap?: string;
|
||||
squaremap?: string;
|
||||
overviewer?: string;
|
||||
}
|
||||
|
||||
// Messages defined directly in LiveAtlas and used for all servers
|
||||
|
98
src/leaflet/projection/OverviewerProjection.ts
Normal file
98
src/leaflet/projection/OverviewerProjection.ts
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2022 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/overviewer/Minecraft-Overviewer.
|
||||
* These portions are Copyright 2022 Minecraft Overviewer 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 {LatLng} from 'leaflet';
|
||||
import {Coordinate, LiveAtlasProjection} from "@/index";
|
||||
|
||||
export interface OverviewerProjectionOptions {
|
||||
upperRight: number,
|
||||
lowerRight: number,
|
||||
lowerLeft: number,
|
||||
northDirection: number,
|
||||
nativeZoomLevels: number,
|
||||
tileSize: number,
|
||||
}
|
||||
|
||||
export class OverviewerProjection implements LiveAtlasProjection {
|
||||
private readonly upperRight: number;
|
||||
private readonly lowerRight: number;
|
||||
private readonly lowerLeft: number;
|
||||
private readonly northDirection: number;
|
||||
private readonly nativeZoomLevels: number;
|
||||
private readonly tileSize: number;
|
||||
private readonly perPixel: number;
|
||||
|
||||
constructor(options: OverviewerProjectionOptions) {
|
||||
this.upperRight = options.upperRight;
|
||||
this.lowerRight = options.lowerRight;
|
||||
this.lowerLeft = options.lowerLeft;
|
||||
this.northDirection = options.northDirection;
|
||||
this.nativeZoomLevels = options.nativeZoomLevels || 1;
|
||||
this.tileSize = options.tileSize;
|
||||
|
||||
this.perPixel = 1.0 / (this.tileSize * Math.pow(2, this.nativeZoomLevels));
|
||||
}
|
||||
|
||||
locationToLatLng(location: Coordinate): LatLng {
|
||||
let lng = 0.5 - (1.0 / Math.pow(2, this.nativeZoomLevels + 1));
|
||||
let lat = 0.5;
|
||||
|
||||
if (this.northDirection === this.upperRight) {
|
||||
const temp = location.x;
|
||||
location.x = -location.z + 15;
|
||||
location.z = temp;
|
||||
} else if(this.northDirection === this.lowerRight) {
|
||||
location.x = -location.x + 15;
|
||||
location.z = -location.z + 15;
|
||||
} else if(this.northDirection === this.lowerLeft) {
|
||||
const temp = location.x;
|
||||
location.x = location.z;
|
||||
location.z = -temp + 15;
|
||||
}
|
||||
|
||||
lng += 12 * location.x * this.perPixel;
|
||||
lat -= 6 * location.x * this.perPixel;
|
||||
|
||||
lng += 12 * location.z * this.perPixel;
|
||||
lat += 6 * location.z * this.perPixel;
|
||||
|
||||
lng += 12 * this.perPixel;
|
||||
lat += 12 * (256 - location.y) * this.perPixel;
|
||||
|
||||
return new LatLng(-lat * this.tileSize, lng * this.tileSize);
|
||||
}
|
||||
|
||||
latLngToLocation(latLng: LatLng, y: number): Coordinate {
|
||||
const lat = (-latLng.lat / this.tileSize) - 0.5;
|
||||
const lng = (latLng.lng / this.tileSize) - (0.5 - (1.0 / Math.pow(2, this.nativeZoomLevels + 1)));
|
||||
|
||||
const x = Math.floor((lng - 2 * lat) / (24 * this.perPixel)) + (256 - y),
|
||||
z = Math.floor((lng + 2 * lat) / (24 * this.perPixel)) - (256 - y);
|
||||
|
||||
if (this.northDirection == this.upperRight) {
|
||||
return {x: z, y, z: -x + 15}
|
||||
} else if (this.northDirection == this.lowerRight) {
|
||||
return {x: -x + 15, y, z: -y + 15}
|
||||
} else if (this.northDirection == this.lowerLeft) {
|
||||
return {x: -z + 15, y, z: x}
|
||||
}
|
||||
|
||||
return {x, y, z};
|
||||
}
|
||||
}
|
63
src/leaflet/tileLayer/OverviewerTileLayer.ts
Normal file
63
src/leaflet/tileLayer/OverviewerTileLayer.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2022 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/overviewer/Minecraft-Overviewer.
|
||||
* These portions are Copyright 2022 Minecraft Overviewer 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 {LiveAtlasTileLayer, LiveAtlasTileLayerOptions} from "@/leaflet/tileLayer/LiveAtlasTileLayer";
|
||||
import {Store, useStore} from "@/store";
|
||||
import {Coords, Util} from "leaflet";
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export class OverviewerTileLayer extends LiveAtlasTileLayer {
|
||||
private readonly _baseUrl: string;
|
||||
private readonly _store: Store = useStore();
|
||||
|
||||
constructor(options: LiveAtlasTileLayerOptions) {
|
||||
super('', options);
|
||||
|
||||
options.zoomReverse = false;
|
||||
|
||||
Util.setOptions(this, options);
|
||||
|
||||
this._mapSettings = options.mapSettings;
|
||||
this._baseUrl = this._store.state.currentMapProvider!.getTilesUrl();
|
||||
}
|
||||
|
||||
getTileUrl(coords: Coords): string {
|
||||
let url = this._mapSettings.name;
|
||||
const zoom = coords.z,
|
||||
urlBase = this._mapSettings.prefix;
|
||||
|
||||
if(coords.x < 0 || coords.x >= Math.pow(2, zoom) ||
|
||||
coords.y < 0 || coords.y >= Math.pow(2, zoom)) {
|
||||
url += '/blank';
|
||||
} else if(zoom === 0) {
|
||||
url += '/base';
|
||||
} else {
|
||||
for(let z = zoom - 1; z >= 0; --z) {
|
||||
const x = Math.floor(coords.x / Math.pow(2, z)) % 2;
|
||||
const y = Math.floor(coords.y / Math.pow(2, z)) % 2;
|
||||
url += '/' + (x + 2 * y);
|
||||
}
|
||||
}
|
||||
url = url + '.' + this._mapSettings.imageFormat;
|
||||
// if(typeof overviewerConfig.map.cacheTag !== 'undefined') {
|
||||
// url += '?c=' + overviewerConfig.map.cacheTag;
|
||||
// }
|
||||
return(this._baseUrl + urlBase + url);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
* Copyright 2022 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -30,6 +30,7 @@ import DynmapMapProvider from "@/providers/DynmapMapProvider";
|
||||
import Pl3xmapMapProvider from "@/providers/Pl3xmapMapProvider";
|
||||
import {showSplashError} from "@/util/splash";
|
||||
import ConfigurationError from "@/errors/ConfigurationError";
|
||||
import OverviewerMapProvider from "@/providers/OverviewerMapProvider";
|
||||
|
||||
const splash = document.getElementById('splash'),
|
||||
svgs = import.meta.globEager('/assets/icons/*.svg');
|
||||
@ -55,6 +56,7 @@ store.subscribe((mutation, state) => {
|
||||
registerMapProvider('dynmap', DynmapMapProvider);
|
||||
registerMapProvider('pl3xmap', Pl3xmapMapProvider);
|
||||
registerMapProvider('squaremap', Pl3xmapMapProvider);
|
||||
registerMapProvider('overviewer', OverviewerMapProvider);
|
||||
|
||||
const config = window.liveAtlasConfig;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
* Copyright 2022 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -56,8 +56,8 @@ export default abstract class MapProvider implements LiveAtlasMapProvider {
|
||||
throw new Error('Provider does not support registration');
|
||||
}
|
||||
|
||||
protected static async fetchJSON(url: string, options: any) {
|
||||
let response, json;
|
||||
protected static async fetch(url: string, options: any) {
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await fetch(url, options);
|
||||
@ -76,6 +76,30 @@ export default abstract class MapProvider implements LiveAtlasMapProvider {
|
||||
throw new Error(`Network request failed (${response.statusText || 'Unknown'})`);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
protected static async fetchText(url: string, options: any) {
|
||||
const response = await this.fetch(url, options);
|
||||
let text;
|
||||
|
||||
try {
|
||||
text = await response.text();
|
||||
} catch(e) {
|
||||
if(e instanceof DOMException && e.name === 'AbortError') {
|
||||
console.warn(`Request aborted (${url}`);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
protected static async fetchJSON(url: string, options: any) {
|
||||
const response = await this.fetch(url, options);
|
||||
let json;
|
||||
|
||||
try {
|
||||
json = await response.json();
|
||||
} catch(e) {
|
||||
@ -90,6 +114,10 @@ export default abstract class MapProvider implements LiveAtlasMapProvider {
|
||||
return json;
|
||||
}
|
||||
|
||||
protected static async getText(url: string, signal: AbortSignal) {
|
||||
return MapProvider.fetchText(url, {signal, credentials: 'include'});
|
||||
}
|
||||
|
||||
protected static async getJSON(url: string, signal: AbortSignal) {
|
||||
return MapProvider.fetchJSON(url, {signal, credentials: 'include'});
|
||||
}
|
||||
|
215
src/providers/OverviewerMapProvider.ts
Normal file
215
src/providers/OverviewerMapProvider.ts
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2022 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/overviewer/Minecraft-Overviewer.
|
||||
* These portions are Copyright 2022 Minecraft Overviewer 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 {
|
||||
LiveAtlasComponentConfig, LiveAtlasDimension,
|
||||
LiveAtlasServerConfig,
|
||||
LiveAtlasServerMessageConfig,
|
||||
LiveAtlasWorldDefinition
|
||||
} from "@/index";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import MapProvider from "@/providers/MapProvider";
|
||||
import {
|
||||
getDefaultMinecraftHead, runSandboxed,
|
||||
} from "@/util";
|
||||
import ConfigurationError from "@/errors/ConfigurationError";
|
||||
import {LiveAtlasTileLayer, LiveAtlasTileLayerOptions} from "@/leaflet/tileLayer/LiveAtlasTileLayer";
|
||||
import {OverviewerTileLayer} from "@/leaflet/tileLayer/OverviewerTileLayer";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
import {OverviewerProjection} from "@/leaflet/projection/OverviewerProjection";
|
||||
|
||||
export default class OverviewerMapProvider extends MapProvider {
|
||||
private configurationAbort?: AbortController = undefined;
|
||||
|
||||
constructor(config: string) {
|
||||
super(config);
|
||||
|
||||
if(!this.config) {
|
||||
throw new ConfigurationError("URL missing");
|
||||
}
|
||||
|
||||
if(this.config.slice(-1) !== '/') {
|
||||
this.config = `${config}/`;
|
||||
}
|
||||
}
|
||||
|
||||
private static buildServerConfig(response: any): LiveAtlasServerConfig {
|
||||
return {
|
||||
title: 'Minecraft Overviewer',
|
||||
|
||||
//Not used by overviewer
|
||||
expandUI: false,
|
||||
defaultZoom: 1,
|
||||
defaultMap: undefined,
|
||||
defaultWorld: undefined,
|
||||
followMap: undefined,
|
||||
followZoom: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private static buildMessagesConfig(response: any): LiveAtlasServerMessageConfig {
|
||||
return {
|
||||
worldsHeading: 'Worlds',
|
||||
playersHeading: 'Players',
|
||||
|
||||
//Not used by pl3xmap
|
||||
chatPlayerJoin: '',
|
||||
chatPlayerQuit: '',
|
||||
chatAnonymousJoin: '',
|
||||
chatAnonymousQuit: '',
|
||||
chatErrorNotAllowed: '',
|
||||
chatErrorRequiresLogin: '',
|
||||
chatErrorCooldown: '',
|
||||
}
|
||||
}
|
||||
|
||||
private buildWorlds(serverResponse: any): Array<LiveAtlasWorldDefinition> {
|
||||
const worlds: Map<string, LiveAtlasWorldDefinition> = new Map<string, LiveAtlasWorldDefinition>();
|
||||
|
||||
(serverResponse.worlds || []).forEach((world: string) => {
|
||||
worlds.set(world, {
|
||||
name: world,
|
||||
displayName: world,
|
||||
dimension: 'overworld' as LiveAtlasDimension,
|
||||
seaLevel: 64,
|
||||
center: {x: 0, y: 64, z: 0},
|
||||
defaultZoom: undefined,
|
||||
maps: new Set<LiveAtlasMapDefinition>(),
|
||||
});
|
||||
});
|
||||
|
||||
(serverResponse.tilesets || []).forEach((tileset: any) => {
|
||||
if(!tileset?.world || !worlds.has(tileset.world)) {
|
||||
console.warn(`Ignoring tileset with unknown world ${tileset.world}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const world = worlds.get(tileset.world) as LiveAtlasWorldDefinition,
|
||||
nativeZoomLevels = tileset.zoomLevels,
|
||||
tileSize = serverResponse.CONST.tileSize;
|
||||
|
||||
world.maps.add(new LiveAtlasMapDefinition({
|
||||
world,
|
||||
name: tileset.path,
|
||||
displayName: tileset.name || tileset.path,
|
||||
background: tileset.bgcolor,
|
||||
imageFormat: tileset.imgextension,
|
||||
nativeZoomLevels,
|
||||
extraZoomLevels: 0,
|
||||
tileSize,
|
||||
prefix: tileset.base,
|
||||
projection: new OverviewerProjection({
|
||||
upperRight: serverResponse.CONST.UPPERRIGHT,
|
||||
lowerLeft: serverResponse.CONST.LOWERLEFT,
|
||||
lowerRight: serverResponse.CONST.LOWERRIGHT,
|
||||
northDirection: tileset.north_direction,
|
||||
nativeZoomLevels,
|
||||
tileSize,
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
return Array.from(worlds.values());
|
||||
}
|
||||
|
||||
private static buildComponents(response: any): LiveAtlasComponentConfig {
|
||||
const components: LiveAtlasComponentConfig = {
|
||||
coordinatesControl: undefined,
|
||||
linkControl: true,
|
||||
layerControl: response?.map?.controls?.overlays,
|
||||
|
||||
//Not configurable
|
||||
markers: {
|
||||
showLabels: false,
|
||||
},
|
||||
|
||||
//Not used by Overviewer
|
||||
players: {
|
||||
markers: undefined,
|
||||
imageUrl: getDefaultMinecraftHead,
|
||||
showImages: false,
|
||||
grayHiddenPlayers: false,
|
||||
},
|
||||
chatBox: undefined,
|
||||
chatBalloons: false,
|
||||
clockControl: undefined,
|
||||
logoControls: [],
|
||||
login: false,
|
||||
};
|
||||
|
||||
if(response?.map?.controls?.coordsBox) {
|
||||
components.coordinatesControl = {
|
||||
showY: false,
|
||||
label: 'Location: ',
|
||||
showRegion: true,
|
||||
showChunk: false,
|
||||
}
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
async loadServerConfiguration(): Promise<void> {
|
||||
if(this.configurationAbort) {
|
||||
this.configurationAbort.abort();
|
||||
}
|
||||
|
||||
this.configurationAbort = new AbortController();
|
||||
|
||||
const baseUrl = this.config,
|
||||
response = await OverviewerMapProvider.getText(`${baseUrl}overviewerConfig.js`, this.configurationAbort.signal);
|
||||
|
||||
try {
|
||||
const result = await runSandboxed(response + ' return overviewerConfig;'),
|
||||
config = OverviewerMapProvider.buildServerConfig(result);
|
||||
|
||||
this.store.commit(MutationTypes.SET_SERVER_CONFIGURATION, config);
|
||||
this.store.commit(MutationTypes.SET_SERVER_MESSAGES, OverviewerMapProvider.buildMessagesConfig(result));
|
||||
this.store.commit(MutationTypes.SET_WORLDS, this.buildWorlds(result));
|
||||
this.store.commit(MutationTypes.SET_COMPONENTS, OverviewerMapProvider.buildComponents(result));
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async populateWorld(world: LiveAtlasWorldDefinition) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
createTileLayer(options: LiveAtlasTileLayerOptions): LiveAtlasTileLayer {
|
||||
return new OverviewerTileLayer(options);
|
||||
}
|
||||
|
||||
startUpdates() {
|
||||
//TODO
|
||||
}
|
||||
|
||||
stopUpdates() {
|
||||
//TODO
|
||||
}
|
||||
|
||||
getTilesUrl(): string {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
getMarkerIconUrl(icon: string): string {
|
||||
return ''; //TODO
|
||||
}
|
||||
}
|
76
src/util.ts
76
src/util.ts
@ -19,8 +19,10 @@ import {useStore} from "@/store";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
import {
|
||||
Coordinate,
|
||||
HeadQueueEntry, LiveAtlasBounds,
|
||||
LiveAtlasGlobalMessageConfig, LiveAtlasLocation,
|
||||
HeadQueueEntry,
|
||||
LiveAtlasBounds,
|
||||
LiveAtlasGlobalMessageConfig,
|
||||
LiveAtlasLocation,
|
||||
LiveAtlasMessageConfig,
|
||||
LiveAtlasPlayer,
|
||||
LiveAtlasPlayerImageSize,
|
||||
@ -297,3 +299,73 @@ export const getMiddle = (bounds: LiveAtlasBounds): LiveAtlasLocation => {
|
||||
z: bounds.min.z + ((bounds.max.z - bounds.min.z) / 2),
|
||||
};
|
||||
}
|
||||
|
||||
const createIframeSandbox = () => {
|
||||
const frame = document.createElement('iframe');
|
||||
frame.hidden = true;
|
||||
frame.sandbox.add('allow-scripts');
|
||||
frame.srcdoc = `<script>window.addEventListener("message", function(e) {
|
||||
if(!e.data?.key) {
|
||||
console.warn('Ignoring postmessage without key');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
e.source.postMessage({
|
||||
key: e.data.key,
|
||||
success: true,
|
||||
result: Function('', "'use strict';" + e.data.code)(),
|
||||
}, e.origin);
|
||||
} catch(ex) {
|
||||
e.source.postMessage({
|
||||
key: e.data.key,
|
||||
success: false,
|
||||
error: ex
|
||||
}, e.origin);
|
||||
}
|
||||
})</script>`;
|
||||
|
||||
window.addEventListener('message', e => {
|
||||
if(e.origin !== "null" || e.source !== frame.contentWindow) {
|
||||
console.warn('Ignoring postmessage with invalid source');
|
||||
return;
|
||||
}
|
||||
|
||||
if(!e.data?.key) {
|
||||
console.warn('Ignoring postmessage without key');
|
||||
return;
|
||||
}
|
||||
|
||||
if(!sandboxSuccessCallbacks.has(e.data.key)) {
|
||||
console.warn('Ignoring postmessage with invalid key');
|
||||
return;
|
||||
}
|
||||
|
||||
if(e.data.success) {
|
||||
sandboxSuccessCallbacks.get(e.data.key)!.call(this, e.data.result);
|
||||
} else {
|
||||
sandboxErrorCallbacks.get(e.data.key)!.call(this, e.data.error);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(frame);
|
||||
return frame.contentWindow;
|
||||
}
|
||||
|
||||
const sandboxWindow: Window | null = createIframeSandbox();
|
||||
const sandboxSuccessCallbacks: Map<number, (result?: any) => void> = new Map();
|
||||
const sandboxErrorCallbacks: Map<number, (reason?: any) => void> = new Map();
|
||||
|
||||
export const runSandboxed = async (code: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const key = Math.random();
|
||||
|
||||
sandboxSuccessCallbacks.set(key, resolve);
|
||||
sandboxErrorCallbacks.set(key, reject);
|
||||
|
||||
sandboxWindow!.postMessage({
|
||||
key,
|
||||
code,
|
||||
}, '*');
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user