Added the maps-module to the iwmlib.
Migrated a ot of the content from the tuesch to the iwmlib. This is before the decoupeling of the layers.
This commit is contained in:
@@ -0,0 +1,548 @@
|
||||
import PIXIApp from '../app.js'
|
||||
import { CoordinateDisplay } from '../../../../js/display.js'
|
||||
import { DOMLayer, PIXILayer } from '../../../../src/layers/js/layer.js'
|
||||
import { MapLayer } from './geolayer.js'
|
||||
import { RigidScatterContainer } from './scatter.js'
|
||||
import { EventHandler } from './utils.js'
|
||||
import { Points } from '../../utils.js'
|
||||
import Logging from '../../logging.js'
|
||||
|
||||
/**
|
||||
* MapApp is responsible for showing fullscreen
|
||||
* map applications.
|
||||
*
|
||||
*/
|
||||
export default class MapApp extends PIXIApp {
|
||||
constructor(opts = {}) {
|
||||
super(opts)
|
||||
|
||||
// Default values.
|
||||
opts = Object.assign(
|
||||
{
|
||||
debug: false,
|
||||
maps: {}, //required
|
||||
view: null,
|
||||
startmap: null,
|
||||
coordsLogging: false,
|
||||
overlays: {},
|
||||
keycodes: {},
|
||||
showHotkeys: false,
|
||||
imageMapZoomHeight: 256, //Defines the zoomvalue 1 for all image maps inside the mapapp.
|
||||
focus: null,
|
||||
zoom: 1,
|
||||
onMapChanged: null,
|
||||
onSizeChanged: null,
|
||||
onTransform: null
|
||||
},
|
||||
opts,
|
||||
{
|
||||
alpha: 0.5
|
||||
}
|
||||
)
|
||||
|
||||
this.submaps = []
|
||||
this.overlayElements = new Map()
|
||||
this.debug = opts.debug
|
||||
this.fpsLogging = opts.fpsLogging
|
||||
this.showHotkeys = opts.showHotkeys
|
||||
this.keycodes = this._extractKeyCodes(opts.keycodes)
|
||||
this.coordsLogging = opts.coordsLogging
|
||||
this.startmap = opts.startmap
|
||||
this.overlays = opts.overlays
|
||||
this.maps = opts.maps
|
||||
this.focus = opts.focus
|
||||
this.zoom = opts.zoom
|
||||
|
||||
this.onMapChanged = new EventHandler('mapChanged', {
|
||||
listeners: opts.onMapChanged
|
||||
})
|
||||
this.onSizeChanged = new EventHandler('sizeChanged', {
|
||||
listeners: opts.onSizeChanged
|
||||
})
|
||||
this.onTransform = new EventHandler('transformed', {
|
||||
listeners: opts.onTransform
|
||||
})
|
||||
|
||||
/**
|
||||
* When in debug mode, this allows the user to copy the center coordinates to the clipboard.
|
||||
*/
|
||||
if (this.debug) {
|
||||
this.DRAW_MODES = {
|
||||
PIXI_POINT: 0,
|
||||
PATH_MODE: 1,
|
||||
POLYGON_MODE: 2,
|
||||
|
||||
getName: function(num) {
|
||||
let result = null
|
||||
for (const [key, val] of Object.entries(this)) {
|
||||
if (val == num) result = key
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
this.drawMode = this.DRAW_MODES.PIXI_POINT
|
||||
this.drawData = []
|
||||
}
|
||||
this._setupKeyboardUtils()
|
||||
|
||||
Logging.log('Application start')
|
||||
}
|
||||
|
||||
setup() {
|
||||
super.setup()
|
||||
|
||||
// TODO get access to fps display
|
||||
let fpsDisplay
|
||||
this.stage.children.forEach(element => {
|
||||
if (element.refreshFps) fpsDisplay = element
|
||||
})
|
||||
|
||||
this.pixiLayer = new PIXILayer({
|
||||
name: 'Pixi Root',
|
||||
container: this.scene
|
||||
})
|
||||
|
||||
this.domLayer = new DOMLayer({
|
||||
name: 'DOM Root',
|
||||
container: document.body
|
||||
})
|
||||
|
||||
if (!this.startmap) {
|
||||
let firstMap = Object.keys(this.maps)[0]
|
||||
if (firstMap != null) this.startmap = firstMap
|
||||
else {
|
||||
console.error('No map was set. Set a map first, before running the setup command!')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//console.log('startup', this.startmap, this.maps)
|
||||
|
||||
this.mapLayer = new MapLayer(this.startmap, this.maps, this.scene, {
|
||||
name: 'Map Layer',
|
||||
focus: this.focus,
|
||||
zoom: this.zoom
|
||||
})
|
||||
|
||||
this.mapLayer.changeHandler.add(this._mapChanged.bind(this))
|
||||
this.pixiLayer.place(this.mapLayer)
|
||||
this._mapChanged(null)
|
||||
|
||||
this.pixiUiLayer = new PIXILayer({ name: 'Pixi UI' })
|
||||
this.pixiLayer.placeLayer(this.pixiUiLayer)
|
||||
|
||||
this.domUiLayer = new DOMLayer({ name: 'DOM UI' })
|
||||
this.domLayer.placeLayer(this.domUiLayer)
|
||||
|
||||
if (this.fpsLogging && fpsDisplay) this.pixiUiLayer.place(fpsDisplay)
|
||||
|
||||
if (this.coordsLogging) {
|
||||
this.coordsDisplay = new CoordinateDisplay(this)
|
||||
this.pixiUiLayer.place(this.coordsDisplay)
|
||||
}
|
||||
|
||||
this.__dragging = false
|
||||
this.__events = new Map()
|
||||
|
||||
const scene = this.scene
|
||||
scene.interactive = true
|
||||
scene.on('pointercancel', this.__onEnd.bind(this))
|
||||
scene.on('pointerdown', this.__onStart.bind(this))
|
||||
scene.on('pointermove', this.__onMove.bind(this))
|
||||
scene.on('pointerout', this.__onEnd.bind(this))
|
||||
scene.on('pointerup', this.__onEnd.bind(this))
|
||||
scene.on('pointerupoutside', this.__onEnd.bind(this))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
layout(width, height) {
|
||||
this.scene.resize(width, height)
|
||||
this.mapLayer.mapview.update()
|
||||
this.onSizeChanged.call(this)
|
||||
}
|
||||
|
||||
sceneFactory() {
|
||||
return new RigidScatterContainer(this.width, this.height, this.renderer, {
|
||||
app: this,
|
||||
showBounds: true,
|
||||
showTouches: true,
|
||||
showPolygon: true,
|
||||
container: this
|
||||
})
|
||||
}
|
||||
|
||||
addMaps(maps) {
|
||||
for (let key in maps) {
|
||||
this.addMap(key, maps[key])
|
||||
}
|
||||
}
|
||||
|
||||
selectMap(key) {
|
||||
if (this.maps[key]) {
|
||||
if (this.mapLayer) this.mapLayer.changeMap(key)
|
||||
} else {
|
||||
console.error(`Selected map ("${key}") was not (yet) added to the mapapp.`)
|
||||
}
|
||||
}
|
||||
|
||||
setMap(key, map) {
|
||||
this.addMap(key, map)
|
||||
this.selectMap(key)
|
||||
}
|
||||
|
||||
addMap(key, map) {
|
||||
this.maps[key] = map
|
||||
}
|
||||
|
||||
transformed(event) {
|
||||
this.onTransform.call(this, event)
|
||||
}
|
||||
|
||||
_mapChanged(lastMap) {
|
||||
if (lastMap) {
|
||||
lastMap.flushHandlers()
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: A problem is that the map layer has no container
|
||||
* on its own and it lives in the ScatterContainer.
|
||||
* Here we guarantee, that the layer order is as it
|
||||
* is defined in the layers.
|
||||
*/
|
||||
this.pixiLayer.layers.forEach(layer => {
|
||||
if (layer !== this.mapLayer) {
|
||||
layer.parent.container.removeChild(layer.container)
|
||||
layer.parent.container.addChild(layer.container)
|
||||
}
|
||||
})
|
||||
|
||||
this.map.onTransform.add(this.transformed.bind(this))
|
||||
this.transformed()
|
||||
this.onMapChanged.call(this, this.map)
|
||||
}
|
||||
|
||||
_doesOverlayElementExist(layer, type, name) {
|
||||
let layerElements = this.overlayElements.get(layer)
|
||||
return layerElements != undefined && layerElements[type] != null && layerElements[type][name] != null
|
||||
}
|
||||
|
||||
_getOverlayElement(layer, type, name) {
|
||||
let layerElements = this.overlayElements.get(layer)
|
||||
return layerElements[type][name]
|
||||
}
|
||||
|
||||
_setOverlayElement(layer, type, name, value) {
|
||||
let obj = this.overlayElements.get(layer)
|
||||
if (obj == undefined) obj = {}
|
||||
|
||||
if (obj[type] == null) obj[type] = {}
|
||||
|
||||
obj[type][name] = value
|
||||
|
||||
this.overlayElements.set(layer, obj)
|
||||
}
|
||||
|
||||
place(layer) {
|
||||
if (layer instanceof PIXILayer) {
|
||||
this.pixiLayer.place(layer)
|
||||
} else if (layer instanceof DOMLayer) {
|
||||
this.domLayer.place(layer)
|
||||
} else {
|
||||
console.error('Could not add layer to ', this, layer)
|
||||
}
|
||||
}
|
||||
|
||||
addMapOverlay(layer) {
|
||||
this.mapLayer.place(layer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the current coordinates to the clipboard.
|
||||
*/
|
||||
locationToClipboard() {
|
||||
let hidden = document.createElement('input')
|
||||
document.body.appendChild(hidden)
|
||||
hidden.value = '"location":' + JSON.stringify(app.mapLayer.mapview.focus)
|
||||
hidden.readOnly = true
|
||||
hidden.select()
|
||||
document.execCommand('Copy')
|
||||
this.showNotification('Copied location to clipboard.')
|
||||
document.body.removeChild(hidden)
|
||||
}
|
||||
|
||||
pathToClipboard() {
|
||||
let hidden = document.createElement('input')
|
||||
document.body.appendChild(hidden)
|
||||
|
||||
this.drawData.push(this.mapLayer.mapview.focus)
|
||||
|
||||
if (this.drawMode == this.DRAW_MODES.POLYGON_MODE) {
|
||||
let data = {
|
||||
type: 'Polygon',
|
||||
coordinates: [this.drawData]
|
||||
}
|
||||
|
||||
hidden.value = '"geometry":' + JSON.stringify(data)
|
||||
} else {
|
||||
// PATH_MODE is default.
|
||||
hidden.value = JSON.stringify(this.drawData)
|
||||
}
|
||||
hidden.select()
|
||||
document.execCommand('Copy')
|
||||
this.showNotification('Location appended in clipboard.')
|
||||
document.body.removeChild(hidden)
|
||||
}
|
||||
|
||||
get map() {
|
||||
return this.mapLayer.map
|
||||
}
|
||||
|
||||
get activeMapKey() {
|
||||
return this.mapLayer.active
|
||||
}
|
||||
|
||||
getRelativePosition(x, y) {
|
||||
return {
|
||||
x: x * app.width,
|
||||
y: y * app.height
|
||||
}
|
||||
}
|
||||
|
||||
clearDrawData() {
|
||||
this.drawData = []
|
||||
}
|
||||
|
||||
showNotification(msg) {
|
||||
let notification = document.createElement('div')
|
||||
notification.classList.add('notification')
|
||||
|
||||
let text = document.createElement('p')
|
||||
text.innerHTML = msg
|
||||
notification.appendChild(text)
|
||||
|
||||
/**
|
||||
* TODO: move styling (except opacity) to css.
|
||||
*/
|
||||
Object.assign(notification.style, {
|
||||
opacity: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
position: 'fixed',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
pointerEvents: 'none'
|
||||
})
|
||||
|
||||
document.body.appendChild(notification)
|
||||
|
||||
let popAnimation = new TimelineLite({
|
||||
onComplete: () => {
|
||||
notification.parentNode.removeChild(notification)
|
||||
}
|
||||
})
|
||||
let animationSpeed = 0.5
|
||||
|
||||
popAnimation
|
||||
.to(notification, animationSpeed, {
|
||||
opacity: 1
|
||||
})
|
||||
.to(notification, animationSpeed, {
|
||||
opacity: 0
|
||||
})
|
||||
}
|
||||
|
||||
_currentLocationToString() {}
|
||||
|
||||
_matchKeyCode(event, keyCode) {
|
||||
// If keycode does not exist or is invalid - return.
|
||||
if (!keyCode || keyCode.key == null) return false
|
||||
|
||||
let code = keyCode.key
|
||||
if (event.shiftKey) code -= 32
|
||||
const key = event.which == code || event.keyCode == code || event.charCode == code
|
||||
|
||||
const ctrl = keyCode.ctrlKey == event.ctrlKey
|
||||
const shift = keyCode.shiftKey == event.shiftKey
|
||||
const alt = keyCode.altKey == event.altKey
|
||||
|
||||
return key && ctrl && shift && alt
|
||||
}
|
||||
|
||||
_setupKeyboardUtils() {
|
||||
document.body.addEventListener('keypress', event => {
|
||||
this._checkForKeyCode(event)
|
||||
})
|
||||
}
|
||||
|
||||
_checkForKeyCode(event) {
|
||||
if (this._matchKeyCode(event, this.keycodes.copyCoordinate)) {
|
||||
event.preventDefault()
|
||||
|
||||
switch (this.drawMode) {
|
||||
case this.DRAW_MODES.PIXI_POINT:
|
||||
this.locationToClipboard()
|
||||
break
|
||||
case this.DRAW_MODES.PATH_MODE:
|
||||
case this.DRAW_MODES.POLYGON_MODE:
|
||||
this.pathToClipboard()
|
||||
break
|
||||
default:
|
||||
console.error(
|
||||
`Draw mode is not implemented yet: ${this.DRAW_MODES.getName(this.drawMode)}(${this.drawMode}).`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (this._matchKeyCode(event, this.keycodes.togglePathMode)) {
|
||||
if (this.drawMode == this.DRAW_MODES.PATH_MODE) {
|
||||
this.showNotification('Path Mode disabled.')
|
||||
this._resetDrawMode()
|
||||
} else {
|
||||
this.drawMode = this.DRAW_MODES.PATH_MODE
|
||||
this.showNotification('Path Mode enabled.')
|
||||
this.clearDrawData()
|
||||
}
|
||||
}
|
||||
|
||||
//When SHIFT+P is pressed POLYGON-MODE is toggled:
|
||||
if (this._matchKeyCode(event, this.keycodes.togglePolygonMode)) {
|
||||
if (this.drawMode == this.DRAW_MODES.POLYGON_MODE) {
|
||||
this.showNotification('Polygon Mode disabled.')
|
||||
this._resetDrawMode()
|
||||
} else {
|
||||
this.drawMode = this.DRAW_MODES.POLYGON_MODE
|
||||
this.showNotification('Polygon Mode enabled.')
|
||||
this.clearDrawData()
|
||||
}
|
||||
}
|
||||
|
||||
// When SHIFT+X is pressed toggle crosshair
|
||||
if (this.keycodes.toggleUi && this._matchKeyCode(event, this.keycodes.toggleUi)) {
|
||||
if (this.pixiUiLayer.visible) {
|
||||
this.pixiUiLayer.hide()
|
||||
this.domUiLayer.hide()
|
||||
} else {
|
||||
this.pixiUiLayer.show()
|
||||
this.domUiLayer.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_resetDrawMode() {
|
||||
this.drawMode = this.DRAW_MODES.PIXI_POINT
|
||||
}
|
||||
|
||||
_extractKeyCodes(keycodeText) {
|
||||
let out = {}
|
||||
|
||||
for (let [name, combinationString] of Object.entries(keycodeText)) {
|
||||
let keys = combinationString.split('+')
|
||||
out[name] = {
|
||||
key: null,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
altKey: false
|
||||
}
|
||||
let errors = []
|
||||
|
||||
const special = ['shift', 'ctrl', 'alt']
|
||||
keys.forEach(key => {
|
||||
if (key.length == 1) {
|
||||
if (out[name].key) {
|
||||
const error = 'Tried to set multiple keys as keycode. This is currently not supported.'
|
||||
errors.push(error)
|
||||
} else out[name].key = key.charCodeAt(0)
|
||||
} else {
|
||||
key = key.toLowerCase()
|
||||
if (special.indexOf(key) != -1) {
|
||||
out[name][key + 'Key'] = true
|
||||
} else {
|
||||
const error = 'Wrong / non-implemented special character OR typo in key: ' + key
|
||||
errors.push(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Disable hotkey if an error occured.
|
||||
if (errors.length > 0)
|
||||
out[name] = {
|
||||
key: null,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
altKey: false
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
__onStart(event) {
|
||||
this.__dragging = true
|
||||
|
||||
let hittedSubmap = null
|
||||
let center = null
|
||||
for (const submap of this.submaps) {
|
||||
const radius = submap.container.width / 2
|
||||
const distance = Points.distance(submap.center, event.data.global)
|
||||
if (distance < radius) {
|
||||
hittedSubmap = submap
|
||||
center = submap.center
|
||||
}
|
||||
}
|
||||
|
||||
this.__events.set(event.data.identifier, {
|
||||
event,
|
||||
submap: hittedSubmap,
|
||||
center
|
||||
})
|
||||
}
|
||||
|
||||
__onMove(event) {
|
||||
if (this.__dragging) {
|
||||
const myevent = this.__events.get(event.data.identifier)
|
||||
|
||||
if (myevent && myevent.submap) {
|
||||
const submap = myevent.submap
|
||||
const center = submap.center
|
||||
const radius = submap.container.width / 2
|
||||
const distance = Points.distance(center, event.data.global) / submap.scatter.scale
|
||||
if (distance > radius) {
|
||||
//submap.resize((distance) * 2, .2)
|
||||
//submap.centerAt(myevent.center)
|
||||
}
|
||||
}
|
||||
|
||||
// for (const submap of this.submaps) {
|
||||
// const center = submap.center
|
||||
// const radius = submap.container.width / 2
|
||||
// const distance = Points.distance(center, event.data.global) / submap.scatter.scale
|
||||
// const inside = distance < radius + 10
|
||||
|
||||
// console.log(distance, radius)
|
||||
|
||||
// if (inside) {
|
||||
// // (this.width + 80) / 2 * this.scatter.scale
|
||||
// //const width = (submap.width + 80) / 2 * submap.scatter.scale
|
||||
// //console.log(width)
|
||||
|
||||
// if (distance > radius) {
|
||||
// submap.resize((distance) * 2, .2)
|
||||
// }
|
||||
// } else {
|
||||
// if (distance < radius + 20) {
|
||||
// //submap.resize((distance - 30) * 2, .2)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
__onEnd(event) {
|
||||
this.__dragging = false
|
||||
this.__events.delete(event.data.identifier)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user