UNPKG

@ulb-darmstadt/shacl-form

Version:
158 lines (144 loc) 6.66 kB
import { Term } from '@rdfjs/types' import { Plugin, PluginOptions } from '../plugin' import { ShaclPropertyTemplate } from '../property-template' import { Editor, fieldFactory } from '../theme' import { Map, NavigationControl, FullscreenControl, LngLatBounds, LngLatLike } from 'mapbox-gl' import MapboxDraw from '@mapbox/mapbox-gl-draw' import mapboxGlCss from 'mapbox-gl/dist/mapbox-gl.css' import mapboxGlDrawCss from '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css' import { Geometry, geometryToWkt, wktToGeometry } from './map-util' const css = ` #shaclMapDialog .closeButton { position: absolute; right: 0; top: 0; z-index: 1; padding: 6px 8px; cursor: pointer; border: 0; background-color: #FFFA; font-size: 24px; } #shaclMapDialog { padding: 0; width:90vw; height: 90vh; margin: auto; } #shaclMapDialog::backdrop { background-color: #0007; } #shaclMapDialog .closeButton:hover { background-color: #FFF } #shaclMapDialog .hint { position: absolute; right: 60px; top: 3px; z-index: 1; padding: 4px 6px; background-color: #FFFA; border-radius: 4px; } .mapboxgl-map { min-height: 300px; } #shaclMapDialogContainer { width:100%; height: 100% } ` const dialogTemplate = ` <dialog id="shaclMapDialog" onclick="event.target==this && this.close()"> <div id="shaclMapDialogContainer"></div> <div class="hint">&#x24D8; Draw a polygon or point, then close dialog</div> <button class="closeButton" type="button" onclick="this.parentElement.close()">&#x2715;</button> </dialog>` export class MapboxPlugin extends Plugin { map: Map | undefined draw: MapboxDraw | undefined currentEditor: Editor | undefined apiKey: string constructor(options: PluginOptions, apiKey: string) { super(options, mapboxGlCss + '\n' + mapboxGlDrawCss + '\n' + css) this.apiKey = apiKey } initEditMode(form: HTMLElement): HTMLDialogElement { form.insertAdjacentHTML('beforeend', dialogTemplate) const container = form.querySelector('#shaclMapDialogContainer') as HTMLElement this.map = new Map({ container: container, style: 'mapbox://styles/mapbox/satellite-streets-v11', zoom: 5, center: { lng: 8.657238961696038, lat: 49.87627570549512 }, attributionControl: false, accessToken: this.apiKey }) this.draw = new MapboxDraw({ displayControlsDefault: false, controls: { point: true, polygon: true } }) this.map.addControl(new NavigationControl(), 'top-left') this.map.addControl(this.draw, 'top-left') this.map.on('idle', () => { // this fixes wrong size of canvas this.map!.resize() }) // @ts-ignore this.map.on('draw.create', () => this.deleteAllButLastDrawing()) const dialog = form.querySelector('#shaclMapDialog') as HTMLDialogElement dialog.addEventListener('close', () => { const scrollY = document.body.style.top document.body.style.position = '' document.body.style.top = '' window.scrollTo(0, parseInt(scrollY || '0') * -1) // set wkt in editor const data = this.draw!.getAll() if (data && data.features.length && this.currentEditor) { const geometry = data.features[0].geometry as Geometry if (geometry.coordinates?.length) { const wkt = geometryToWkt(geometry) this.currentEditor.value = wkt this.currentEditor.dispatchEvent(new Event('change', { bubbles: true })) } } }) return dialog } createEditor(template: ShaclPropertyTemplate, value?: Term): HTMLElement { let dialog = template.config.form.querySelector('#shaclMapDialog') as HTMLDialogElement if (!dialog) { dialog = this.initEditMode(template.config.form) } const button = template.config.theme.createButton('Open&#160;map...', false) button.style.marginLeft = '5px' button.classList.add('open-map-button') button.onclick = () => { this.currentEditor = instance.querySelector('.editor') as Editor this.draw?.deleteAll() const wkt = this.currentEditor.value || '' const geometry = wktToGeometry(wkt) if (geometry && geometry.coordinates?.length) { this.draw?.add(geometry) this.fitToGeometry(this.map!, geometry) } else { this.map?.setZoom(5) } document.body.style.top = `-${window.scrollY}px` document.body.style.position = 'fixed' dialog.showModal() } const instance = fieldFactory(template, value || null) instance.appendChild(button) return instance } createViewer(template: ShaclPropertyTemplate, value: Term): HTMLElement { const container = document.createElement('div') const geometry = wktToGeometry(value.value) if (geometry?.coordinates?.length) { // wait for container to be available in DOM setTimeout(() => { const draw = new MapboxDraw({ displayControlsDefault: false }) const map = new Map({ container: container, style: 'mapbox://styles/mapbox/satellite-streets-v11', zoom: 5, attributionControl: false, accessToken: this.apiKey }) map.addControl(draw) map.addControl(new FullscreenControl()) draw.add(geometry) this.fitToGeometry(map, geometry) }) } return container } fitToGeometry(map: Map, geometry: Geometry) { if (typeof geometry.coordinates[0] === 'number') { // e.g. Point map.setCenter(geometry.coordinates as LngLatLike) map.setZoom(15) } else { // e.g. Polygon const bounds = geometry.coordinates[0].reduce((bounds, coord) => { return bounds.extend(coord as mapboxgl.LngLatLike) }, new LngLatBounds(geometry.coordinates[0][0] as mapboxgl.LngLatLike, geometry.coordinates[0][0] as mapboxgl.LngLatLike)) map.fitBounds(bounds, { padding: 20, animate: false }) } } deleteAllButLastDrawing() { const data = this.draw!.getAll() for (let i = 0; i < data.features.length - 1; i++) { this.draw!.delete(data.features[i].id as string) } } }