@ulb-darmstadt/shacl-form 1.10.4 → 2.0.0-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/bundle.js +412 -0
  2. package/dist/constants.d.ts +16 -16
  3. package/dist/constraints.d.ts +2 -2
  4. package/dist/form.d.ts +4 -3
  5. package/dist/index.js +62 -0
  6. package/dist/plugins/assets/plugin-VN3CfgGe.js +1 -0
  7. package/dist/plugins/file-upload.js +1 -0
  8. package/dist/plugins/leaflet.d.ts +3 -1
  9. package/dist/plugins/leaflet.js +5 -708
  10. package/dist/plugins/map-util.js +1 -0
  11. package/dist/{themes/default.d.ts → theme.default.d.ts} +2 -2
  12. package/package.json +20 -26
  13. package/dist/form-bootstrap.d.ts +0 -5
  14. package/dist/form-bootstrap.js +0 -413
  15. package/dist/form-default.d.ts +0 -5
  16. package/dist/form-default.js +0 -402
  17. package/dist/form-material.d.ts +0 -5
  18. package/dist/form-material.js +0 -722
  19. package/dist/plugins/fixed-list.d.ts +0 -9
  20. package/dist/plugins/mapbox.d.ts +0 -19
  21. package/dist/plugins/mapbox.js +0 -2904
  22. package/dist/themes/bootstrap.d.ts +0 -10
  23. package/dist/themes/material.d.ts +0 -15
  24. package/src/config.ts +0 -110
  25. package/src/constants.ts +0 -30
  26. package/src/constraints.ts +0 -149
  27. package/src/exports.ts +0 -7
  28. package/src/form-bootstrap.ts +0 -12
  29. package/src/form-default.ts +0 -12
  30. package/src/form-material.ts +0 -12
  31. package/src/form.ts +0 -319
  32. package/src/globals.d.ts +0 -2
  33. package/src/group.ts +0 -34
  34. package/src/loader.ts +0 -187
  35. package/src/node.ts +0 -192
  36. package/src/plugin.ts +0 -60
  37. package/src/plugins/file-upload.ts +0 -26
  38. package/src/plugins/fixed-list.ts +0 -19
  39. package/src/plugins/leaflet.ts +0 -196
  40. package/src/plugins/map-util.ts +0 -41
  41. package/src/plugins/mapbox.ts +0 -157
  42. package/src/property-template.ts +0 -151
  43. package/src/property.ts +0 -309
  44. package/src/serialize.ts +0 -96
  45. package/src/shacl-engine.d.ts +0 -2
  46. package/src/styles.css +0 -49
  47. package/src/theme.ts +0 -132
  48. package/src/themes/bootstrap.css +0 -6
  49. package/src/themes/bootstrap.ts +0 -44
  50. package/src/themes/default.css +0 -4
  51. package/src/themes/default.ts +0 -258
  52. package/src/themes/material.css +0 -14
  53. package/src/themes/material.ts +0 -253
  54. package/src/util.ts +0 -275
package/src/node.ts DELETED
@@ -1,192 +0,0 @@
1
- import { BlankNode, DataFactory, NamedNode, Store } from 'n3'
2
- import { Term } from '@rdfjs/types'
3
- import { PREFIX_SHACL, RDF_PREDICATE_TYPE, OWL_PREDICATE_IMPORTS, SHACL_PREDICATE_PROPERTY, SHACL_PREDICATE_NODE } from './constants'
4
- import { ShaclProperty } from './property'
5
- import { createShaclGroup } from './group'
6
- import { v4 as uuidv4 } from 'uuid'
7
- import { createShaclOrConstraint, resolveShaclOrConstraintOnNode } from './constraints'
8
- import { Config } from './config'
9
-
10
- export class ShaclNode extends HTMLElement {
11
- parent: ShaclNode | undefined
12
- shaclSubject: NamedNode
13
- nodeId: NamedNode | BlankNode
14
- targetClass: NamedNode | undefined
15
- owlImports: NamedNode[] = []
16
- config: Config
17
- linked: boolean
18
-
19
- constructor(shaclSubject: NamedNode, config: Config, valueSubject: NamedNode | BlankNode | undefined, parent?: ShaclNode, nodeKind?: NamedNode, label?: string, linked?: boolean) {
20
- super()
21
-
22
- this.parent = parent
23
- this.config = config
24
- this.shaclSubject = shaclSubject
25
- this.linked = linked || false
26
- let nodeId: NamedNode | BlankNode | undefined = valueSubject
27
- if (!nodeId) {
28
- // if no value subject given, create new node id with a type depending on own nodeKind or given parent property nodeKind
29
- if (!nodeKind) {
30
- const spec = config.store.getObjects(shaclSubject, `${PREFIX_SHACL}nodeKind`, null)
31
- if (spec.length) {
32
- nodeKind = spec[0] as NamedNode
33
- }
34
- }
35
- // if nodeKind is not set, but a value namespace is configured or if nodeKind is sh:IRI, then create a NamedNode
36
- if ((nodeKind === undefined && config.attributes.valuesNamespace) || nodeKind?.value === `${PREFIX_SHACL}IRI`) {
37
- // no requirements on node type, so create a NamedNode and use configured value namespace
38
- nodeId = DataFactory.namedNode(config.attributes.valuesNamespace + uuidv4())
39
- } else {
40
- // otherwise create a BlankNode
41
- nodeId = DataFactory.blankNode(uuidv4())
42
- }
43
- }
44
- this.nodeId = nodeId
45
-
46
- // check if the form already contains the node/value pair to prevent recursion
47
- const id = JSON.stringify([shaclSubject, valueSubject])
48
- if (valueSubject && config.renderedNodes.has(id)) {
49
- // node/value pair is already rendered in the form, so just display a reference
50
- label = label || "Link"
51
- const labelElem = document.createElement('label')
52
- labelElem.innerText = label
53
- labelElem.classList.add('linked')
54
- this.appendChild(labelElem)
55
-
56
- const anchor = document.createElement('a')
57
- let refId = (valueSubject.termType === 'BlankNode') ? '_:' + valueSubject.value : valueSubject.value
58
- anchor.innerText = refId
59
- anchor.classList.add('ref-link')
60
- anchor.onclick = () => {
61
- // if anchor is clicked, scroll referenced shacl node into view
62
- this.config.form.querySelector(`shacl-node[data-node-id='${refId}']`)?.scrollIntoView()
63
- }
64
- this.appendChild(anchor)
65
- this.style.flexDirection = 'row'
66
- } else {
67
- if (valueSubject) {
68
- config.renderedNodes.add(id)
69
- }
70
- this.dataset.nodeId = this.nodeId.id
71
- if (this.config.attributes.showNodeIds !== null) {
72
- const div = document.createElement('div')
73
- div.innerText = `id: ${this.nodeId.id}`
74
- div.classList.add('node-id-display')
75
- this.appendChild(div)
76
- }
77
-
78
- // first initialize owl:imports, this is needed before adding properties to properly resolve class instances etc.
79
- for (const owlImport of config.store.getQuads(shaclSubject, OWL_PREDICATE_IMPORTS, null, null)) {
80
- this.owlImports.push(owlImport.object as NamedNode)
81
- }
82
- // now parse other node quads
83
- for (const quad of config.store.getQuads(shaclSubject, null, null, null)) {
84
- switch (quad.predicate.id) {
85
- case SHACL_PREDICATE_PROPERTY.id:
86
- this.addPropertyInstance(quad.object, config, valueSubject)
87
- break;
88
- case `${PREFIX_SHACL}and`:
89
- // inheritance via sh:and
90
- const list = config.lists[quad.object.value]
91
- if (list?.length) {
92
- for (const shape of list) {
93
- this.prepend(new ShaclNode(shape as NamedNode, config, valueSubject, this))
94
- }
95
- }
96
- else {
97
- console.error('list not found:', quad.object.value, 'existing lists:', config.lists)
98
- }
99
- break;
100
- case SHACL_PREDICATE_NODE.id:
101
- // inheritance via sh:node
102
- this.prepend(new ShaclNode(quad.object as NamedNode, config, valueSubject, this))
103
- break;
104
- case `${PREFIX_SHACL}targetClass`:
105
- this.targetClass = quad.object as NamedNode
106
- break;
107
- case `${PREFIX_SHACL}or`:
108
- this.tryResolve(quad.object, valueSubject, config)
109
- break;
110
- case `${PREFIX_SHACL}xone`:
111
- this.tryResolve(quad.object, valueSubject, config)
112
- break;
113
- }
114
- }
115
-
116
- if (label) {
117
- const header = document.createElement('h1')
118
- header.innerText = label
119
- this.prepend(header)
120
- }
121
- }
122
- }
123
-
124
- toRDF(graph: Store, subject?: NamedNode | BlankNode): (NamedNode | BlankNode) {
125
- if (!subject) {
126
- subject = this.nodeId
127
- }
128
- // output triples only if node is not a link
129
- if (!this.linked) {
130
- for (const shape of this.querySelectorAll(':scope > shacl-node, :scope > .shacl-group > shacl-node, :scope > shacl-property, :scope > .shacl-group > shacl-property')) {
131
- (shape as ShaclNode | ShaclProperty).toRDF(graph, subject)
132
- }
133
- if (this.targetClass) {
134
- graph.addQuad(subject, RDF_PREDICATE_TYPE, this.targetClass, this.config.valuesGraphId)
135
- }
136
- // if this is the root shacl node, check if we should add one of the rdf:type or dcterms:conformsTo predicates
137
- if (this.config.attributes.generateNodeShapeReference && !this.parent) {
138
- graph.addQuad(subject, DataFactory.namedNode(this.config.attributes.generateNodeShapeReference), this.shaclSubject, this.config.valuesGraphId)
139
- }
140
- }
141
- return subject
142
- }
143
-
144
- addPropertyInstance(shaclSubject: Term, config: Config, valueSubject: NamedNode | BlankNode | undefined) {
145
- let parentElement: HTMLElement = this
146
- // check if property belongs to a group
147
- const groupRef = config.store.getQuads(shaclSubject as Term, `${PREFIX_SHACL}group`, null, null)
148
- if (groupRef.length > 0) {
149
- const groupSubject = groupRef[0].object.value
150
- if (config.groups.indexOf(groupSubject) > -1) {
151
- // check if group element already exists, otherwise create it
152
- let group = this.querySelector(`:scope > .shacl-group[data-subject='${groupSubject}']`) as HTMLElement
153
- if (!group) {
154
- group = createShaclGroup(groupSubject, config)
155
- this.appendChild(group)
156
- }
157
- parentElement = group
158
- } else {
159
- console.warn('ignoring unknown group reference', groupRef[0], 'existing groups:', config.groups)
160
- }
161
- }
162
- const property = new ShaclProperty(shaclSubject as NamedNode | BlankNode, this, config, valueSubject)
163
- // do not add empty properties (i.e. properties with no instances). This can be the case e.g. in viewer mode when there is no data for the respective property.
164
- if (property.childElementCount > 0) {
165
- parentElement.appendChild(property)
166
- }
167
- }
168
-
169
- tryResolve(subject: Term, valueSubject: NamedNode | BlankNode | undefined, config: Config) {
170
- const list = config.lists[subject.value]
171
- if (list?.length) {
172
- let resolved = false
173
- if (valueSubject) {
174
- const resolvedPropertySubjects = resolveShaclOrConstraintOnNode(list, valueSubject, config)
175
- if (resolvedPropertySubjects.length) {
176
- for (const propertySubject of resolvedPropertySubjects) {
177
- this.addPropertyInstance(propertySubject, config, valueSubject)
178
- }
179
- resolved = true
180
- }
181
- }
182
- if (!resolved) {
183
- this.appendChild(createShaclOrConstraint(list, this, config))
184
- }
185
- }
186
- else {
187
- console.error('list for sh:or/sh:xone not found:', subject, 'existing lists:', config.lists)
188
- }
189
- }
190
- }
191
-
192
- window.customElements.define('shacl-node', ShaclNode)
package/src/plugin.ts DELETED
@@ -1,60 +0,0 @@
1
- import { ShaclPropertyTemplate } from './property-template'
2
- import { Term } from '@rdfjs/types'
3
-
4
- // store plugins in module scope so that they apply to all shacl-form elements
5
- const plugins: Record<string, Plugin> = {}
6
-
7
- export function registerPlugin(plugin: Plugin) {
8
- if (plugin.predicate === undefined && plugin.datatype === undefined) {
9
- console.warn('not registering plugin because it does neither define "predicate" nor "datatype"', plugin)
10
- } else {
11
- plugins[`${plugin.predicate}^${plugin.datatype}`] = plugin
12
- }
13
- }
14
-
15
- export function listPlugins(): Plugin[] {
16
- return Object.entries(plugins).map((value: [_: string, plugin: Plugin]) => { return value[1] })
17
- }
18
-
19
- export function findPlugin(predicate: string | undefined, datatype: string | undefined): Plugin | undefined {
20
- // first try to find plugin with matching predicate and datatype
21
- let plugin = plugins[`${predicate}^${datatype}`]
22
- if (plugin) {
23
- return plugin
24
- }
25
- // now prefer predicate over datatype
26
- plugin = plugins[`${predicate}^${undefined}`]
27
- if (plugin) {
28
- return plugin
29
- }
30
- // last, try to find plugin with matching datatype
31
- return plugins[`${undefined}^${datatype}`]
32
- }
33
-
34
- export type PluginOptions = {
35
- predicate?: string
36
- datatype?: string
37
- }
38
-
39
- export abstract class Plugin {
40
- predicate: string | undefined
41
- datatype: string | undefined
42
- stylesheet: CSSStyleSheet | undefined
43
-
44
- constructor(options: PluginOptions, css?: string) {
45
- this.predicate = options.predicate
46
- this.datatype = options.datatype
47
- if (css) {
48
- this.stylesheet = new CSSStyleSheet()
49
- this.stylesheet.replaceSync(css)
50
- }
51
- }
52
-
53
- abstract createEditor(template: ShaclPropertyTemplate, value?: Term): HTMLElement
54
-
55
- createViewer(template: ShaclPropertyTemplate, value: Term): HTMLElement {
56
- return template.config.theme.createViewer(template.label, value, template)
57
- }
58
- }
59
-
60
- export type ClassInstanceProvider = (clazz: string) => Promise<string>
@@ -1,26 +0,0 @@
1
- import { Plugin, PluginOptions } from '../plugin'
2
- import { ShaclPropertyTemplate } from '../property-template'
3
-
4
- export class FileUploadPlugin extends Plugin {
5
- onChange: (event: Event) => void
6
- fileType: string | undefined
7
-
8
- constructor(options: PluginOptions, onChange: (event: Event) => void, fileType?: string) {
9
- super(options)
10
- this.onChange = onChange
11
- this.fileType = fileType
12
- }
13
-
14
- createEditor(template: ShaclPropertyTemplate): HTMLElement {
15
- const required = template.minCount !== undefined && template.minCount > 0
16
- const editor = template.config.theme.createFileEditor(template.label, null, required, template)
17
- editor.addEventListener('change', event => {
18
- event.stopPropagation()
19
- this.onChange(event)
20
- })
21
- if (this.fileType) {
22
- editor.querySelector('input[type="file"]')?.setAttribute('accept', this.fileType)
23
- }
24
- return editor
25
- }
26
- }
@@ -1,19 +0,0 @@
1
- import { Plugin, PluginOptions } from '../plugin'
2
- import { Term } from '@rdfjs/types'
3
-
4
- import { ShaclPropertyTemplate } from '../property-template'
5
- import { InputListEntry } from '../theme'
6
-
7
- export class FixedListPlugin extends Plugin {
8
- entries: InputListEntry[]
9
-
10
- constructor(options: PluginOptions, entries: InputListEntry[]) {
11
- super(options)
12
- this.entries = entries
13
- }
14
-
15
- createEditor(template: ShaclPropertyTemplate, value?: Term): HTMLElement {
16
- const required = template.minCount !== undefined && template.minCount > 0
17
- return template.config.theme.createListEditor(template.label, value || null, required, this.entries, template)
18
- }
19
- }
@@ -1,196 +0,0 @@
1
- import * as L from 'leaflet'
2
- import 'leaflet-editable/src/Leaflet.Editable.js'
3
- import leafletCss from 'leaflet/dist/leaflet.css?raw'
4
- import leafletFullscreenCss from 'leaflet.fullscreen/Control.FullScreen.css?raw'
5
- import 'leaflet.fullscreen/Control.FullScreen.js'
6
- import { Term } from '@rdfjs/types'
7
-
8
- import { Plugin, PluginOptions } from '../plugin'
9
- import { Editor, fieldFactory } from '../theme'
10
- import { ShaclPropertyTemplate } from '../property-template'
11
- import { Geometry, geometryToWkt, wktToGeometry, worldBounds } from './map-util'
12
-
13
- const css = `
14
- #shaclMapDialog .closeButton { position: absolute; right: 0; top: 0; z-index: 1; padding: 6px 8px; cursor: pointer; border: 0; background-color: #FFFA; font-size: 24px; z-index: 1000; }
15
- #shaclMapDialog { padding: 0; width:90vw; height: 90vh; margin: auto; }
16
- #shaclMapDialog::backdrop { background-color: #0007; }
17
- #shaclMapDialog .closeButton:hover { background-color: #FFF }
18
- #shaclMapDialog .hint { position: absolute; right: 60px; top: 3px; z-index: 1; padding: 4px 6px; background-color: #FFFA; border-radius: 4px; z-index: 1000; pointer-events: none; }
19
- .leaflet-container { min-height: 300px; }
20
- .fullscreen-icon { background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjYgNTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwLjYgMzYuN0gxNmEuOS45IDAgMCAxLS44LS44di00LjVjMC0uMi4yLS40LjQtLjRoMS40Yy4zIDAgLjUuMi41LjR2M2gzYy4yIDAgLjQuMi40LjV2MS40YzAgLjItLjIuNC0uNC40em0tOS45LS44di00LjVjMC0uMi0uMi0uNC0uNC0uNEg4LjljLS4zIDAtLjUuMi0uNS40djNoLTNjLS4yIDAtLjQuMi0uNC41djEuNGMwIC4yLjIuNC40LjRIMTBjLjQgMCAuOC0uNC44LS44em0wIDEwLjdWNDJjMC0uNC0uNC0uOC0uOC0uOEg1LjRjLS4yIDAtLjQuMi0uNC40djEuNGMwIC4zLjIuNS40LjVoM3YzYzAgLjIuMi40LjUuNGgxLjRjLjIgMCAuNC0uMi40LS40em02LjkgMHYtM2gzYy4yIDAgLjQtLjIuNC0uNXYtMS40YzAtLjItLjItLjQtLjQtLjRIMTZjLS40IDAtLjguNC0uOC44djQuNWMwIC4yLjIuNC40LjRoMS40Yy4zIDAgLjUtLjIuNS0uNHpNNSAxMC4zVjUuOWMwLS41LjQtLjkuOS0uOWg0LjRjLjIgMCAuNC4yLjQuNFY3YzAgLjItLjIuNC0uNC40aC0zdjNjMCAuMi0uMi40LS40LjRINS40YS40LjQgMCAwIDEtLjQtLjR6bTEwLjMtNC45VjdjMCAuMi4yLjQuNC40aDN2M2MwIC4yLjIuNC40LjRoMS41Yy4yIDAgLjQtLjIuNC0uNFY1LjljMC0uNS0uNC0uOS0uOS0uOWgtNC40Yy0uMiAwLS40LjItLjQuNHptNS4zIDkuOUgxOWMtLjIgMC0uNC4yLS40LjR2M2gtM2MtLjIgMC0uNC4yLS40LjR2MS41YzAgLjIuMi40LjQuNGg0LjRjLjUgMCAuOS0uNC45LS45di00LjRjMC0uMi0uMi0uNC0uNC0uNHptLTkuOSA1LjNWMTljMC0uMi0uMi0uNC0uNC0uNGgtM3YtM2MwLS4yLS4yLS40LS40LS40SDUuNGMtLjIgMC0uNC4yLS40LjR2NC40YzAgLjUuNC45LjkuOWg0LjRjLjIgMCAuNC0uMi40LS40eiIgZmlsbD0iY3VycmVudENvbG9yIi8+PC9zdmc+); }
21
- #shaclMapDialogContainer { width:100%; height: 100%; }
22
- `
23
- const dialogTemplate = `
24
- <dialog id="shaclMapDialog" onclick="event.target==this && this.close()">
25
- <div id="shaclMapDialogContainer"></div>
26
- <div class="hint">&#x24D8; Draw a polygon or marker, then close dialog</div>
27
- <button class="closeButton" type="button" onclick="this.parentElement.close()">&#x2715;</button>
28
- </dialog>`
29
-
30
- const defaultCenter = { lng: 8.657238961696038, lat: 49.87627570549512 }
31
- const attribution = '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
32
- const tileSource = 'https://tile.openstreetmap.de/{z}/{x}/{y}.png'
33
- // const tileSource = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
34
-
35
- const markerIcon = L.icon({
36
- iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YaZtr2962HUzbDNpjszW24mRt28p47v7zq/bXZtrp/lWnXr337j3nPCe85NcypgSFdugCpW5YoDAMRaIMqRi6aKq5E3YqDQO3qAwjVWrD8Ncq/RBpykd8oZUb/kaJutow8r1aP9II0WmLKLIsJyv1w/kqw9Ch2MYdB++12Onxee/QMwvf4/Dk/Lfp/i4nxTXtOoQ4pW5Aj7wpici1A9erdAN2OH64x8OSP9j3Ft3b7aWkTg/Fm91siTra0f9on5sQr9INejH6CUUUpavjFNq1B+Oadhxmnfa8RfEmN8VNAsQhPqF55xHkMzz3jSmChWU6f7/XZKNH+9+hBLOHYozuKQPxyMPUKkrX/K0uWnfFaJGS1QPRtZsOPtr3NsW0uyh6NNCOkU3Yz+bXbT3I8G3xE5EXLXtCXbbqwCO9zPQYPRTZ5vIDXD7U+w7rFDEoUUf7ibHIR4y6bLVPXrz8JVZEql13trxwue/uDivd3fkWRbS6/IA2bID4uk0UpF1N8qLlbBlXs4Ee7HLTfV1j54APvODnSfOWBqtKVvjgLKzF5YdEk5ewRkGlK0i33Eofffc7HT56jD7/6U+qH3Cx7SBLNntH5YIPvODnyfIXZYRVDPqgHtLs5ABHD3YzLuespb7t79FY34DjMwrVrcTuwlT55YMPvOBnRrJ4VXTdNnYug5ucHLBjEpt30701A3Ts+HEa73u6dT3FNWwflY86eMHPk+Yu+i6pzUpRrW7SNDg5JHR4KapmM5Wv2E8Tfcb1HoqqHMHU+uWDD7zg54mz5/2BSnizi9T1Dg4QQXLToGNCkb6tb1NU+QAlGr1++eADrzhn/u8Q2YZhQVlZ5+CAOtqfbhmaUCS1ezNFVm2imDbPmPng5wmz+gwh+oHDce0eUtQ6OGDIyR0uUhUsoO3vfDmmgOezH0mZN59x7MBi++WDL1g/eEiU3avlidO671bkLfwbw5XV2P8Pzo0ydy4t2/0eu33xYSOMOD8hTf4CrBtGMSoXfPLchX+J0ruSePw3LZeK0juPJbYzrhkH0io7B3k164hiGvawhOKMLkrQLyVpZg8rHFW7E2uHOL888IBPlNZ1FPzstSJM694fWr6RwpvcJK60+0HCILTBzZLFNdtAzJaohze60T8qBzyh5ZuOg5e7uwQppofEmf2++DYvmySqGBuKaicF1blQjhuHdvCIMvp8whTTfZzI7RldpwtSzL+F1+wkdZ2TBOW2gIF88PBTzD/gpeREAMEbxnJcaJHNHrpzji0gQCS6hdkEeYt9DF/2qPcEC8RM28Hwmr3sdNyht00byAut2k3gufWNtgtOEOFGUwcXWNDbdNbpgBGxEvKkOQsxivJx33iow0Vw5S6SVTrpVq11ysA2Rp7gTfPfktc6zhtXBBC+adRLshf6sG2RfHPZ5EAc4sVZ83yCN00Fk/4kggu40ZTvIEm5g24qtU4KjBrx/BTTH8ifVASAG7gKrnWxJDcU7x8X6Ecczhm3o6YicvsLXWfh3Ch1W0k8x0nXF+0fFxgt4phz8QvypiwCCFKMqXCnqXExjq10beH+UUA7+nG6mdG/Pu0f3LgFcGrl2s0kNNjpmoJ9o4B29CMO8dMT4Q5ox8uitF6fqsrJOr8qnwNbRzv6hSnG5wP+64C7h9lp30hKNtKdWjtdkbuPA19nJ7Tz3zR/ibgARbhb4AlhavcBebmTHcFl2fvYEnW0ox9xMxKBS8btJ+KiEbq9zA4RthQXDhPa0T9TEe69gWupwc6uBUphquXgf+/FrIjweHQS4/pduMe5ERUMHUd9xv8ZR98CxkS4F2n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=',
37
- shadowUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAApCAQAAAACach9AAACMUlEQVR4Ae3ShY7jQBAE0Aoz/f9/HTMzhg1zrdKUrJbdx+Kd2nD8VNudfsL/Th///dyQN2TH6f3y/BGpC379rV+S+qqetBOxImNQXL8JCAr2V4iMQXHGNJxeCfZXhSRBcQMfvkOWUdtfzlLgAENmZDcmo2TVmt8OSM2eXxBp3DjHSMFutqS7SbmemzBiR+xpKCNUIRkdkkYxhAkyGoBvyQFEJEefwSmmvBfJuJ6aKqKWnAkvGZOaZXTUgFqYULWNSHUckZuR1HIIimUExutRxwzOLROIG4vKmCKQt364mIlhSyzAf1m9lHZHJZrlAOMMztRRiKimp/rpdJDc9Awry5xTZCte7FHtuS8wJgeYGrex28xNTd086Dik7vUMscQOa8y4DoGtCCSkAKlNwpgNtphjrC6MIHUkR6YWxxs6Sc5xqn222mmCRFzIt8lEdKx+ikCtg91qS2WpwVfBelJCiQJwvzixfI9cxZQWgiSJelKnwBElKYtDOb2MFbhmUigbReQBV0Cg4+qMXSxXSyGUn4UbF8l+7qdSGnTC0XLCmahIgUHLhLOhpVCtw4CzYXvLQWQbJNmxoCsOKAxSgBJno75avolkRw8iIAFcsdc02e9iyCd8tHwmeSSoKTowIgvscSGZUOA7PuCN5b2BX9mQM7S0wYhMNU74zgsPBj3HU7wguAfnxxjFQGBE6pwN+GjME9zHY7zGp8wVxMShYX9NXvEWD3HbwJf4giO4CFIQxXScH1/TM+04kkBiAAAAAElFTkSuQmCC',
38
-
39
- iconSize: [25, 41], // size of the icon
40
- shadowSize: [41, 41], // size of the shadow
41
- iconAnchor: [12, 41], // point of the icon which will correspond to marker's location
42
- shadowAnchor: [14, 41], // the same for the shadow
43
- popupAnchor: [-3, -76] // point from which the popup should open relative to the iconAnchor
44
- })
45
-
46
- export class LeafletPlugin extends Plugin {
47
- map: L.Map | undefined
48
- currentEditor: Editor | undefined
49
- createdGeometry: Geometry | undefined
50
- displayedShape: L.Polygon | L.Marker | undefined
51
-
52
- constructor(options: PluginOptions) {
53
- super(options, leafletCss + '\n' + leafletFullscreenCss + '\n' + css)
54
- }
55
-
56
- initEditMode(form: HTMLElement): HTMLDialogElement {
57
- form.insertAdjacentHTML('beforeend', dialogTemplate)
58
- const container = form.querySelector('#shaclMapDialogContainer') as HTMLElement
59
- this.map = L.map(container, {
60
- fullscreenControl: true,
61
- editable: true,
62
- layers: [ L.tileLayer(tileSource) ],
63
- zoom: 5,
64
- maxBounds: worldBounds,
65
- center: defaultCenter
66
- })
67
- this.map.attributionControl.addAttribution(attribution)
68
-
69
- const EditControl = L.Control.extend({ options: { position: 'topleft', callback: null, kind: '', html: '' },
70
- onAdd: function (map: L.Map) {
71
- let container = L.DomUtil.create('div', 'leaflet-control leaflet-bar')
72
- let link = L.DomUtil.create('a', '', container)
73
- link.href = '#';
74
- link.title = 'Create a new ' + this.options.kind;
75
- link.innerHTML = this.options.html;
76
- L.DomEvent.on(link, 'click', L.DomEvent.stop).on(link, 'click', () => {
77
- // @ts-ignore
78
- window.LAYER = this.options.callback.call(map.editTools)
79
- }, this)
80
- return container
81
- }
82
- })
83
- this.map.addControl(new (EditControl.extend({
84
- options: {
85
- callback: () => {
86
- this.displayedShape?.remove()
87
- this.displayedShape = this.map?.editTools.startPolygon()
88
- },
89
- kind: 'polygon',
90
- html: '▰'
91
- }
92
- }))())
93
- this.map.addControl(new (EditControl.extend({
94
- options: {
95
- callback: () => {
96
- this.displayedShape?.remove()
97
- this.displayedShape = this.map?.editTools.startMarker(undefined, { icon: markerIcon })
98
- },
99
- kind: 'marker',
100
- html: '•'
101
- }
102
- }))())
103
- this.map.on('editable:drawing:end', () => { this.saveChanges() })
104
- this.map.on('editable:vertex:dragend', () => { this.saveChanges() })
105
-
106
- const dialog = form.querySelector('#shaclMapDialog') as HTMLDialogElement
107
- dialog.addEventListener('close', () => {
108
- const scrollY = document.body.style.top
109
- document.body.style.position = ''
110
- document.body.style.top = ''
111
- window.scrollTo(0, parseInt(scrollY || '0') * -1)
112
- // set wkt in editor
113
- if (this.currentEditor && this.createdGeometry) {
114
- this.currentEditor.value = geometryToWkt(this.createdGeometry)
115
- this.currentEditor.dispatchEvent(new Event('change', { bubbles: true }))
116
- }
117
- })
118
- return dialog
119
- }
120
-
121
- createEditor(template: ShaclPropertyTemplate, value?: Term): HTMLElement {
122
- let dialog = template.config.form.querySelector('#shaclMapDialog') as HTMLDialogElement
123
- if (!dialog) {
124
- dialog = this.initEditMode(template.config.form)
125
- }
126
- const button = template.config.theme.createButton('Open&#160;map...', false)
127
- button.style.marginLeft = '5px'
128
- button.classList.add('open-map-button')
129
- button.onclick = () => {
130
- this.currentEditor = instance.querySelector('.editor') as Editor
131
- this.createdGeometry = undefined
132
- this.displayedShape?.remove()
133
- this.drawAndZoomToGeometry(wktToGeometry(this.currentEditor.value || ''), this.map!)
134
-
135
- document.body.style.top = `-${window.scrollY}px`
136
- document.body.style.position = 'fixed'
137
- dialog.showModal()
138
- }
139
- const instance = fieldFactory(template, value || null, true)
140
- instance.appendChild(button)
141
- return instance
142
- }
143
-
144
- createViewer(_: ShaclPropertyTemplate, value: Term): HTMLElement {
145
- const container = document.createElement('div')
146
- const geometry = wktToGeometry(value.value)
147
- if (geometry?.coordinates?.length) {
148
- const map = L.map(container, {
149
- fullscreenControl: true,
150
- layers: [ L.tileLayer(tileSource) ],
151
- zoom: 5,
152
- center: defaultCenter,
153
- maxBounds: worldBounds
154
- })
155
- map.attributionControl.addAttribution(attribution)
156
- this.drawAndZoomToGeometry(geometry, map)
157
- }
158
- return container
159
- }
160
-
161
- drawAndZoomToGeometry(geometry: Geometry | undefined, map: L.Map) {
162
- setTimeout(() => { map.invalidateSize() })
163
- if (geometry?.type === 'Point') {
164
- const coords = { lng: geometry.coordinates[0], lat: geometry.coordinates[1] }
165
- this.displayedShape = L.marker(coords, { icon: markerIcon }).addTo(map)
166
- map.setView(coords, 15, { animate: false })
167
- } else if (geometry?.type === 'Polygon') {
168
- const coords = geometry.coordinates[0].map((pos) => { return { lng: pos[0], lat: pos[1] }})
169
- const polygon = L.polygon(coords).addTo(map)
170
- this.displayedShape = polygon
171
- map.fitBounds(polygon.getBounds(), { animate: false })
172
- setTimeout(() => {
173
- map.fitBounds(polygon.getBounds(), { animate: false })
174
- map.setView(polygon.getCenter(), undefined, { animate: false })
175
- }, 1)
176
- } else {
177
- map.setZoom(5)
178
- }
179
- }
180
-
181
- saveChanges() {
182
- if (this.displayedShape instanceof L.Marker) {
183
- const pos = this.displayedShape.getLatLng()
184
- this.createdGeometry = { type: 'Point', coordinates: [pos.lng, pos.lat] }
185
- } else if (this.displayedShape instanceof L.Polygon) {
186
- const positions = this.displayedShape.getLatLngs() as L.LatLng[][]
187
- // force closed polygon
188
- if (!positions[0][0].equals(positions[0][positions[0].length - 1])) {
189
- positions[0].push(positions[0][0])
190
- }
191
- this.createdGeometry = { type: 'Polygon', coordinates: [positions[0].map((pos) => { return [ pos.lng, pos.lat ] })] }
192
- } else {
193
- this.createdGeometry = undefined
194
- }
195
- }
196
- }
@@ -1,41 +0,0 @@
1
- import { Point, Polygon } from 'geojson'
2
-
3
- export type Geometry = Point | Polygon
4
-
5
- export const worldBounds: [number, number][] = [[-90, -180], [90, 180]]
6
-
7
- export function wktToGeometry(wkt: string): Geometry | undefined {
8
- const pointCoords = wkt.match(/^POINT\((.*)\)$/)
9
- if (pointCoords?.length == 2) {
10
- const xy = pointCoords[1].split(' ')
11
- if (xy.length === 2) {
12
- return { type: 'Point', coordinates: [parseFloat(xy[0]), parseFloat(xy[1])] }
13
- }
14
- }
15
- const polygonCoords = wkt.match(/^POLYGON[(]{2}(.*)[)]{2}$/)
16
- if (polygonCoords?.length == 2) {
17
- const split = polygonCoords[1].split(',')
18
- if (split.length > 2) {
19
- const coords: number[][][] = []
20
- const outer: number[][] = []
21
- coords.push(outer)
22
- for (const coord of split) {
23
- const xy = coord.split(' ')
24
- if (xy.length === 2) {
25
- outer.push([parseFloat(xy[0]), parseFloat(xy[1])])
26
- }
27
- }
28
- return { type: 'Polygon', coordinates: coords }
29
- }
30
- }
31
- }
32
-
33
- export function geometryToWkt(geometry: Geometry): string {
34
- if (geometry.type === 'Point') {
35
- return `POINT(${geometry.coordinates.join(' ')})`
36
- } else if (geometry.type === 'Polygon') {
37
- return `POLYGON((${geometry.coordinates[0].map(item => { return item.join(' ') }).join(',')}))`
38
- } else {
39
- return ''
40
- }
41
- }