@ulb-darmstadt/shacl-form 1.10.3 → 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 (55) hide show
  1. package/README.md +1 -1
  2. package/dist/bundle.js +412 -0
  3. package/dist/constants.d.ts +16 -16
  4. package/dist/constraints.d.ts +2 -2
  5. package/dist/form.d.ts +4 -3
  6. package/dist/index.js +62 -0
  7. package/dist/plugins/assets/plugin-VN3CfgGe.js +1 -0
  8. package/dist/plugins/file-upload.js +1 -0
  9. package/dist/plugins/leaflet.d.ts +3 -1
  10. package/dist/plugins/leaflet.js +5 -708
  11. package/dist/plugins/map-util.js +1 -0
  12. package/dist/{themes/default.d.ts → theme.default.d.ts} +2 -2
  13. package/package.json +28 -33
  14. package/dist/form-bootstrap.d.ts +0 -5
  15. package/dist/form-bootstrap.js +0 -413
  16. package/dist/form-default.d.ts +0 -5
  17. package/dist/form-default.js +0 -402
  18. package/dist/form-material.d.ts +0 -5
  19. package/dist/form-material.js +0 -722
  20. package/dist/plugins/fixed-list.d.ts +0 -9
  21. package/dist/plugins/mapbox.d.ts +0 -19
  22. package/dist/plugins/mapbox.js +0 -2870
  23. package/dist/themes/bootstrap.d.ts +0 -10
  24. package/dist/themes/material.d.ts +0 -15
  25. package/src/config.ts +0 -110
  26. package/src/constants.ts +0 -30
  27. package/src/constraints.ts +0 -149
  28. package/src/exports.ts +0 -7
  29. package/src/form-bootstrap.ts +0 -12
  30. package/src/form-default.ts +0 -12
  31. package/src/form-material.ts +0 -12
  32. package/src/form.ts +0 -319
  33. package/src/globals.d.ts +0 -2
  34. package/src/group.ts +0 -34
  35. package/src/loader.ts +0 -187
  36. package/src/node.ts +0 -192
  37. package/src/plugin.ts +0 -60
  38. package/src/plugins/file-upload.ts +0 -26
  39. package/src/plugins/fixed-list.ts +0 -19
  40. package/src/plugins/leaflet.ts +0 -196
  41. package/src/plugins/map-util.ts +0 -41
  42. package/src/plugins/mapbox.ts +0 -157
  43. package/src/property-template.ts +0 -151
  44. package/src/property.ts +0 -309
  45. package/src/serialize.ts +0 -96
  46. package/src/shacl-engine.d.ts +0 -2
  47. package/src/styles.css +0 -49
  48. package/src/theme.ts +0 -132
  49. package/src/themes/bootstrap.css +0 -6
  50. package/src/themes/bootstrap.ts +0 -44
  51. package/src/themes/default.css +0 -4
  52. package/src/themes/default.ts +0 -255
  53. package/src/themes/material.css +0 -14
  54. package/src/themes/material.ts +0 -250
  55. package/src/util.ts +0 -275
@@ -1,157 +0,0 @@
1
- import { Term } from '@rdfjs/types'
2
- import { Plugin, PluginOptions } from '../plugin'
3
- import { ShaclPropertyTemplate } from '../property-template'
4
- import { Editor, fieldFactory } from '../theme'
5
- import { Map, NavigationControl, FullscreenControl, LngLatBounds, LngLatLike } from 'mapbox-gl'
6
- import MapboxDraw from '@mapbox/mapbox-gl-draw'
7
- import mapboxGlCss from 'mapbox-gl/dist/mapbox-gl.css?raw'
8
- import mapboxGlDrawCss from '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css?raw'
9
- import { Geometry, geometryToWkt, wktToGeometry } from './map-util'
10
-
11
- const css = `
12
- #shaclMapDialog .closeButton { position: absolute; right: 0; top: 0; z-index: 1; padding: 6px 8px; cursor: pointer; border: 0; background-color: #FFFA; font-size: 24px; }
13
- #shaclMapDialog { padding: 0; width:90vw; height: 90vh; margin: auto; }
14
- #shaclMapDialog::backdrop { background-color: #0007; }
15
- #shaclMapDialog .closeButton:hover { background-color: #FFF }
16
- #shaclMapDialog .hint { position: absolute; right: 60px; top: 3px; z-index: 1; padding: 4px 6px; background-color: #FFFA; border-radius: 4px; }
17
- .mapboxgl-map { min-height: 300px; }
18
- #shaclMapDialogContainer { width:100%; height: 100% }
19
- `
20
- const dialogTemplate = `
21
- <dialog id="shaclMapDialog" onclick="event.target==this && this.close()">
22
- <div id="shaclMapDialogContainer"></div>
23
- <div class="hint">&#x24D8; Draw a polygon or point, then close dialog</div>
24
- <button class="closeButton" type="button" onclick="this.parentElement.close()">&#x2715;</button>
25
- </dialog>`
26
-
27
-
28
- export class MapboxPlugin extends Plugin {
29
- map: Map | undefined
30
- draw: MapboxDraw | undefined
31
- currentEditor: Editor | undefined
32
- apiKey: string
33
-
34
- constructor(options: PluginOptions, apiKey: string) {
35
- super(options, mapboxGlCss + '\n' + mapboxGlDrawCss + '\n' + css)
36
- this.apiKey = apiKey
37
- }
38
-
39
- initEditMode(form: HTMLElement): HTMLDialogElement {
40
- form.insertAdjacentHTML('beforeend', dialogTemplate)
41
- const container = form.querySelector('#shaclMapDialogContainer') as HTMLElement
42
- this.map = new Map({
43
- container: container,
44
- style: 'mapbox://styles/mapbox/satellite-streets-v11',
45
- zoom: 5,
46
- center: { lng: 8.657238961696038, lat: 49.87627570549512 },
47
- attributionControl: false,
48
- accessToken: this.apiKey
49
- })
50
-
51
- this.draw = new MapboxDraw({
52
- displayControlsDefault: false,
53
- controls: { point: true, polygon: true }
54
- })
55
- this.map.addControl(new NavigationControl(), 'top-left')
56
- this.map.addControl(this.draw, 'top-left')
57
-
58
- this.map.on('idle', () => {
59
- // this fixes wrong size of canvas
60
- this.map!.resize()
61
- })
62
- // @ts-ignore
63
- this.map.on('draw.create', () => this.deleteAllButLastDrawing())
64
-
65
- const dialog = form.querySelector('#shaclMapDialog') as HTMLDialogElement
66
- dialog.addEventListener('close', () => {
67
- const scrollY = document.body.style.top
68
- document.body.style.position = ''
69
- document.body.style.top = ''
70
- window.scrollTo(0, parseInt(scrollY || '0') * -1)
71
- // set wkt in editor
72
- const data = this.draw!.getAll()
73
- if (data && data.features.length && this.currentEditor) {
74
- const geometry = data.features[0].geometry as Geometry
75
- if (geometry.coordinates?.length) {
76
- const wkt = geometryToWkt(geometry)
77
- this.currentEditor.value = wkt
78
- this.currentEditor.dispatchEvent(new Event('change', { bubbles: true }))
79
- }
80
- }
81
- })
82
- return dialog
83
- }
84
-
85
- createEditor(template: ShaclPropertyTemplate, value?: Term): HTMLElement {
86
- let dialog = template.config.form.querySelector('#shaclMapDialog') as HTMLDialogElement
87
- if (!dialog) {
88
- dialog = this.initEditMode(template.config.form)
89
- }
90
- const button = template.config.theme.createButton('Open&#160;map...', false)
91
- button.style.marginLeft = '5px'
92
- button.classList.add('open-map-button')
93
- button.onclick = () => {
94
- this.currentEditor = instance.querySelector('.editor') as Editor
95
- this.draw?.deleteAll()
96
-
97
- const wkt = this.currentEditor.value || ''
98
- const geometry = wktToGeometry(wkt)
99
- if (geometry && geometry.coordinates?.length) {
100
- this.draw?.add(geometry)
101
- this.fitToGeometry(this.map!, geometry)
102
- } else {
103
- this.map?.setZoom(5)
104
- }
105
- document.body.style.top = `-${window.scrollY}px`
106
- document.body.style.position = 'fixed'
107
- dialog.showModal()
108
- }
109
- const instance = fieldFactory(template, value || null, true)
110
- instance.appendChild(button)
111
- return instance
112
- }
113
-
114
- createViewer(_: ShaclPropertyTemplate, value: Term): HTMLElement {
115
- const container = document.createElement('div')
116
- const geometry = wktToGeometry(value.value)
117
- if (geometry?.coordinates?.length) {
118
- // wait for container to be available in DOM
119
- setTimeout(() => {
120
- const draw = new MapboxDraw({ displayControlsDefault: false })
121
- const map = new Map({
122
- container: container,
123
- style: 'mapbox://styles/mapbox/satellite-streets-v11',
124
- zoom: 5,
125
- attributionControl: false,
126
- accessToken: this.apiKey
127
- })
128
- map.addControl(draw)
129
- map.addControl(new FullscreenControl())
130
- draw.add(geometry)
131
- this.fitToGeometry(map, geometry)
132
- })
133
- }
134
- return container
135
- }
136
-
137
- fitToGeometry(map: Map, geometry: Geometry) {
138
- if (typeof geometry.coordinates[0] === 'number') {
139
- // e.g. Point
140
- map.setCenter(geometry.coordinates as LngLatLike)
141
- map.setZoom(15)
142
- } else {
143
- // e.g. Polygon
144
- const bounds = geometry.coordinates[0].reduce((bounds, coord) => {
145
- return bounds.extend(coord as mapboxgl.LngLatLike)
146
- }, new LngLatBounds(geometry.coordinates[0][0] as mapboxgl.LngLatLike, geometry.coordinates[0][0] as mapboxgl.LngLatLike))
147
- map.fitBounds(bounds, { padding: 20, animate: false })
148
- }
149
- }
150
-
151
- deleteAllButLastDrawing() {
152
- const data = this.draw!.getAll()
153
- for (let i = 0; i < data.features.length - 1; i++) {
154
- this.draw!.delete(data.features[i].id as string)
155
- }
156
- }
157
- }
@@ -1,151 +0,0 @@
1
- import { Literal, NamedNode, Quad, DataFactory } from 'n3'
2
- import { Term } from '@rdfjs/types'
3
- import { OWL_PREDICATE_IMPORTS, PREFIX_DASH, PREFIX_OA, PREFIX_RDF, PREFIX_SHACL, SHACL_PREDICATE_CLASS, SHACL_PREDICATE_TARGET_CLASS } from './constants'
4
- import { Config } from './config'
5
- import { findLabel, prioritizeByLanguage, removePrefixes } from './util'
6
- import { ShaclNode } from './node'
7
-
8
- const mappers: Record<string, (template: ShaclPropertyTemplate, term: Term) => void> = {
9
- [`${PREFIX_SHACL}name`]: (template, term) => { const literal = term as Literal; template.name = prioritizeByLanguage(template.config.languages, template.name, literal) },
10
- [`${PREFIX_SHACL}description`]: (template, term) => { const literal = term as Literal; template.description = prioritizeByLanguage(template.config.languages, template.description, literal) },
11
- [`${PREFIX_SHACL}path`]: (template, term) => { template.path = term.value },
12
- [`${PREFIX_SHACL}node`]: (template, term) => { template.node = term as NamedNode },
13
- [`${PREFIX_SHACL}datatype`]: (template, term) => { template.datatype = term as NamedNode },
14
- [`${PREFIX_SHACL}nodeKind`]: (template, term) => { template.nodeKind = term as NamedNode },
15
- [`${PREFIX_SHACL}minCount`]: (template, term) => { template.minCount = parseInt(term.value) },
16
- [`${PREFIX_SHACL}maxCount`]: (template, term) => { template.maxCount = parseInt(term.value) },
17
- [`${PREFIX_SHACL}minLength`]: (template, term) => { template.minLength = parseInt(term.value) },
18
- [`${PREFIX_SHACL}maxLength`]: (template, term) => { template.maxLength = parseInt(term.value) },
19
- [`${PREFIX_SHACL}minInclusive`]: (template, term) => { template.minInclusive = parseInt(term.value) },
20
- [`${PREFIX_SHACL}maxInclusive`]: (template, term) => { template.maxInclusive = parseInt(term.value) },
21
- [`${PREFIX_SHACL}minExclusive`]: (template, term) => { template.minExclusive = parseInt(term.value) },
22
- [`${PREFIX_SHACL}maxExclusive`]: (template, term) => { template.maxExclusive = parseInt(term.value) },
23
- [`${PREFIX_SHACL}pattern`]: (template, term) => { template.pattern = term.value },
24
- [`${PREFIX_SHACL}order`]: (template, term) => { template.order = parseInt(term.value) },
25
- [`${PREFIX_DASH}singleLine`]: (template, term) => { template.singleLine = term.value === 'true' },
26
- [`${PREFIX_DASH}readonly`]: (template, term) => { template.readonly = term.value === 'true' },
27
- [`${PREFIX_OA}styleClass`]: (template, term) => { template.cssClass = term.value },
28
- [`${PREFIX_SHACL}and`]: (template, term) => { template.shaclAnd = term.value },
29
- [`${PREFIX_SHACL}in`]: (template, term) => { template.shaclIn = term.value },
30
- // sh:datatype might be undefined, but sh:languageIn defined. this is undesired. the spec says, that strings without a lang tag are not valid if sh:languageIn is set. but the shacl validator accepts these as valid. to prevent this, we just set the datatype here to 'langString'.
31
- [`${PREFIX_SHACL}languageIn`]: (template, term) => { template.languageIn = template.config.lists[term.value]; template.datatype = DataFactory.namedNode(PREFIX_RDF + 'langString') },
32
- [`${PREFIX_SHACL}defaultValue`]: (template, term) => { template.defaultValue = term },
33
- [`${PREFIX_SHACL}hasValue`]: (template, term) => { template.hasValue = term },
34
- [`${PREFIX_SHACL}qualifiedValueShape`]: (template, term) => { template.qualifiedValueShape = term },
35
- [`${PREFIX_SHACL}qualifiedMinCount`]: (template, term) => { template.minCount = parseInt(term.value) },
36
- [`${PREFIX_SHACL}qualifiedMaxCount`]: (template, term) => { template.maxCount = parseInt(term.value) },
37
- [OWL_PREDICATE_IMPORTS.id]: (template, term) => { template.owlImports.push(term as NamedNode) },
38
- [SHACL_PREDICATE_CLASS.id]: (template, term) => {
39
- template.class = term as NamedNode
40
- // try to find node shape that has requested target class
41
- const nodeShapes = template.config.store.getSubjects(SHACL_PREDICATE_TARGET_CLASS, term, null)
42
- if (nodeShapes.length > 0) {
43
- template.node = nodeShapes[0] as NamedNode
44
- }
45
- },
46
- [`${PREFIX_SHACL}or`]: (template, term) => {
47
- const list = template.config.lists[term.value]
48
- if (list?.length) {
49
- template.shaclOr = list
50
- } else {
51
- console.error('list for sh:or not found:', term.value, 'existing lists:', template.config.lists)
52
- }
53
- },
54
- [`${PREFIX_SHACL}xone`]: (template, term) => {
55
- const list = template.config.lists[term.value]
56
- if (list?.length) {
57
- template.shaclXone = list
58
- } else {
59
- console.error('list for sh:xone not found:', term.value, 'existing lists:', template.config.lists)
60
- }
61
- }
62
- }
63
-
64
- export class ShaclPropertyTemplate {
65
- parent: ShaclNode
66
- label = ''
67
- name: Literal | undefined
68
- description: Literal | undefined
69
- path: string | undefined
70
- node: NamedNode | undefined
71
- class: NamedNode | undefined
72
- minCount: number | undefined
73
- maxCount: number | undefined
74
- minLength: number | undefined
75
- maxLength: number | undefined
76
- minInclusive: number | undefined
77
- maxInclusive: number | undefined
78
- minExclusive: number | undefined
79
- maxExclusive: number | undefined
80
- singleLine: boolean | undefined
81
- readonly: boolean | undefined
82
- cssClass: string | undefined
83
- defaultValue: Term | undefined
84
- pattern: string | undefined
85
- order: number | undefined
86
- nodeKind: NamedNode | undefined
87
- shaclAnd: string | undefined
88
- shaclIn: string | undefined
89
- shaclOr: Term[] | undefined
90
- shaclXone: Term[] | undefined
91
- languageIn: Term[] | undefined
92
- datatype: NamedNode | undefined
93
- hasValue: Term | undefined
94
- qualifiedValueShape: Term | undefined
95
- owlImports: NamedNode[] = []
96
-
97
- config: Config
98
- extendedShapes: NamedNode[] = []
99
-
100
- constructor(quads: Quad[], parent: ShaclNode, config: Config) {
101
- this.parent = parent
102
- this.config = config
103
- this.merge(quads)
104
- if (this.qualifiedValueShape) {
105
- this.merge(config.store.getQuads(this.qualifiedValueShape, null, null, null))
106
- }
107
- }
108
-
109
- merge(quads: Quad[]): ShaclPropertyTemplate {
110
- for (const quad of quads) {
111
- mappers[quad.predicate.id]?.call(this, this, quad.object)
112
- }
113
- // provide best fitting label for UI
114
- this.label = this.name?.value || findLabel(quads, this.config.languages)
115
- if (!this.label && !this.shaclAnd) {
116
- this.label = this.path ? removePrefixes(this.path, this.config.prefixes) : 'unknown'
117
- }
118
- // register extended shapes
119
- if (this.node) {
120
- this.extendedShapes.push(this.node)
121
- }
122
- if (this.shaclAnd) {
123
- const list = this.config.lists[this.shaclAnd]
124
- if (list?.length) {
125
- for (const node of list) {
126
- this.extendedShapes.push(node as NamedNode)
127
- }
128
- }
129
- }
130
- return this
131
- }
132
-
133
- clone(): ShaclPropertyTemplate {
134
- const copy = Object.assign({}, this)
135
- // arrays are not cloned but referenced, so create them manually
136
- copy.extendedShapes = [ ...this.extendedShapes ]
137
- copy.owlImports = [ ...this.owlImports ]
138
- if (this.languageIn) {
139
- copy.languageIn = [ ...this.languageIn ]
140
- }
141
- if (this.shaclOr) {
142
- copy.shaclOr = [ ...this.shaclOr ]
143
- }
144
- if (this.shaclXone) {
145
- copy.shaclXone = [ ...this.shaclXone ]
146
- }
147
- copy.merge = this.merge.bind(copy)
148
- copy.clone = this.clone.bind(copy)
149
- return copy
150
- }
151
- }
package/src/property.ts DELETED
@@ -1,309 +0,0 @@
1
- import { BlankNode, DataFactory, Literal, NamedNode, Quad, Store } from 'n3'
2
- import { Term } from '@rdfjs/types'
3
- import { ShaclNode } from './node'
4
- import { createShaclOrConstraint, resolveShaclOrConstraintOnProperty } from './constraints'
5
- import { findInstancesOf, focusFirstInputElement } from './util'
6
- import { Config } from './config'
7
- import { ShaclPropertyTemplate } from './property-template'
8
- import { Editor, fieldFactory, InputListEntry } from './theme'
9
- import { toRDF } from './serialize'
10
- import { findPlugin } from './plugin'
11
- import { DATA_GRAPH, RDF_PREDICATE_TYPE, SHACL_PREDICATE_TARGET_CLASS } from './constants'
12
- import { RokitButton, RokitCollapsible, RokitSelect } from '@ro-kit/ui-widgets'
13
-
14
- export class ShaclProperty extends HTMLElement {
15
- template: ShaclPropertyTemplate
16
- addButton: RokitSelect | undefined
17
- container: HTMLElement
18
-
19
- constructor(shaclSubject: BlankNode | NamedNode, parent: ShaclNode, config: Config, valueSubject?: NamedNode | BlankNode) {
20
- super()
21
- this.template = new ShaclPropertyTemplate(config.store.getQuads(shaclSubject, null, null, null), parent, config)
22
- this.container = this
23
- if (this.template.extendedShapes.length && this.template.config.attributes.collapse !== null && (!this.template.maxCount || this.template.maxCount > 1)) {
24
- const collapsible = new RokitCollapsible()
25
- collapsible.classList.add('collapsible', 'shacl-group');
26
- collapsible.open = config.attributes.collapse === 'open';
27
- collapsible.label = this.template.label;
28
- this.container = collapsible
29
- }
30
-
31
- if (this.template.order !== undefined) {
32
- this.style.order = `${this.template.order}`
33
- }
34
- if (this.template.cssClass) {
35
- this.classList.add(this.template.cssClass)
36
- }
37
- if (config.editMode && !parent.linked) {
38
- this.addButton = this.createAddButton()
39
- this.container.appendChild(this.addButton)
40
- }
41
-
42
- // bind existing values
43
- if (this.template.path) {
44
- let values: Quad[] = []
45
- if (valueSubject) {
46
- if (parent.linked) {
47
- // for linked resource, get values in all graphs
48
- values = config.store.getQuads(valueSubject, this.template.path, null, null)
49
- } else {
50
- // get values only from data graph
51
- values = config.store.getQuads(valueSubject, this.template.path, null, DATA_GRAPH)
52
- }
53
- }
54
- let valuesContainHasValue = false
55
- for (const value of values) {
56
- // ignore values that do not conform to this property.
57
- // this might be the case when there are multiple properties with the same sh:path in a NodeShape.
58
- if (this.isValueValid(value.object)) {
59
- this.addPropertyInstance(value.object)
60
- if (this.template.hasValue && value.object.equals(this.template.hasValue)) {
61
- valuesContainHasValue = true
62
- }
63
- }
64
- }
65
- if (config.editMode && this.template.hasValue && !valuesContainHasValue && !parent.linked) {
66
- // sh:hasValue is defined in shapes graph, but does not exist in data graph, so force it
67
- this.addPropertyInstance(this.template.hasValue)
68
- }
69
- }
70
-
71
- if (config.editMode && !parent.linked) {
72
- this.addEventListener('change', () => { this.updateControls() })
73
- this.updateControls()
74
- }
75
-
76
- if (this.container instanceof RokitCollapsible) {
77
- // in view mode, show collapsible only when we have something to show
78
- if ((config.editMode && !parent.linked) || this.container.childElementCount > 0) {
79
- this.appendChild(this.container)
80
- }
81
- }
82
- }
83
-
84
- addPropertyInstance(value?: Term): HTMLElement {
85
- let instance: HTMLElement
86
- if (this.template.shaclOr?.length || this.template.shaclXone?.length) {
87
- const options = this.template.shaclOr?.length ? this.template.shaclOr : this.template.shaclXone as Term[]
88
- let resolved = false
89
- if (value) {
90
- const resolvedOptions = resolveShaclOrConstraintOnProperty(options, value, this.template.config)
91
- if (resolvedOptions.length) {
92
- instance = createPropertyInstance(this.template.clone().merge(resolvedOptions), value, true)
93
- resolved = true
94
- }
95
- }
96
- if (!resolved) {
97
- instance = createShaclOrConstraint(options, this, this.template.config)
98
- appendRemoveButton(instance, '')
99
- }
100
- } else {
101
- // check if value is part of the data graph. if not, create a linked resource
102
- let linked = false
103
- if (value && !(value instanceof Literal)) {
104
- const clazz = this.getRdfClassToLinkOrCreate()
105
- if (clazz && this.template.config.store.countQuads(value, RDF_PREDICATE_TYPE, clazz, DATA_GRAPH) === 0) {
106
- // value is not in data graph, so must be a link in the shapes graph
107
- linked = true
108
- }
109
- }
110
- instance = createPropertyInstance(this.template, value, undefined, linked || this.template.parent.linked)
111
- }
112
- if (this.addButton) {
113
- this.container.insertBefore(instance!, this.addButton)
114
- } else {
115
- this.container.appendChild(instance!)
116
- }
117
- return instance!
118
- }
119
-
120
- updateControls() {
121
- let instanceCount = this.querySelectorAll(":scope > .property-instance, :scope > .shacl-or-constraint, :scope > shacl-node").length
122
- if (instanceCount === 0 && (!this.template.extendedShapes.length || (this.template.minCount !== undefined && this.template.minCount > 0))) {
123
- this.addPropertyInstance()
124
- instanceCount = this.querySelectorAll(":scope > .property-instance, :scope > .shacl-or-constraint, :scope > shacl-node").length
125
- }
126
- let mayRemove: boolean
127
- if (this.template.minCount !== undefined) {
128
- mayRemove = instanceCount > this.template.minCount
129
- } else {
130
- mayRemove = this.template.extendedShapes.length > 0 || instanceCount > 1
131
- }
132
-
133
- const mayAdd = this.template.maxCount === undefined || instanceCount < this.template.maxCount
134
- this.classList.toggle('may-remove', mayRemove)
135
- this.classList.toggle('may-add', mayAdd)
136
- }
137
-
138
- toRDF(graph: Store, subject: NamedNode | BlankNode) {
139
- for (const instance of this.querySelectorAll(':scope > .property-instance, :scope > .collapsible > .property-instance')) {
140
- const pathNode = DataFactory.namedNode((instance as HTMLElement).dataset.path!)
141
- if (instance.firstChild instanceof ShaclNode) {
142
- const shapeSubject = instance.firstChild.toRDF(graph)
143
- graph.addQuad(subject, pathNode, shapeSubject, this.template.config.valuesGraphId)
144
- } else {
145
- for (const editor of instance.querySelectorAll<Editor>(':scope > .editor')) {
146
- const value = toRDF(editor)
147
- if (value) {
148
- graph.addQuad(subject, pathNode, value, this.template.config.valuesGraphId)
149
- }
150
- }
151
- }
152
- }
153
- }
154
-
155
- getRdfClassToLinkOrCreate() {
156
- if (this.template.class && this.template.node) {
157
- return this.template.class
158
- }
159
- else {
160
- for (const node of this.template.extendedShapes) {
161
- // if this property has no sh:class but sh:node, then use the node shape's sh:targetClass to find protiential instances
162
- const targetClasses = this.template.config.store.getObjects(node, SHACL_PREDICATE_TARGET_CLASS, null)
163
- if (targetClasses.length > 0) {
164
- return targetClasses[0] as NamedNode
165
- }
166
- }
167
- }
168
- return undefined
169
- }
170
-
171
- isValueValid(value: Term) {
172
- if (!this.template.extendedShapes.length) {
173
- // property has no node shape, so value is valid
174
- return true
175
- }
176
- // property has node shape(s), so check if value conforms to any targetClass
177
- for (const node of this.template.extendedShapes) {
178
- const targetClasses = this.template.config.store.getObjects(node, SHACL_PREDICATE_TARGET_CLASS, null)
179
- for (const targetClass of targetClasses) {
180
- if (this.template.config.store.countQuads(value, RDF_PREDICATE_TYPE, targetClass, null) > 0) {
181
- return true
182
- }
183
- }
184
- }
185
- return false
186
- }
187
-
188
- createAddButton() {
189
- const addButton = new RokitSelect()
190
- addButton.dense = true
191
- addButton.label = "+ " + this.template.label
192
- addButton.title = 'Add ' + this.template.label
193
- addButton.autoGrowLabelWidth = true
194
- addButton.classList.add('add-button')
195
-
196
- // load potential value candidates for linking
197
- let instances: InputListEntry[] = []
198
- let clazz = this.getRdfClassToLinkOrCreate()
199
- if (clazz) {
200
- instances = findInstancesOf(clazz, this.template)
201
- }
202
- if (instances.length === 0) {
203
- // no class instances found, so create an add button that creates a new instance
204
- addButton.emptyMessage = ''
205
- addButton.inputMinWidth = 0
206
- addButton.addEventListener('click', _ => {
207
- addButton.blur()
208
- const instance = this.addPropertyInstance()
209
- instance.classList.add('fadeIn')
210
- this.updateControls()
211
- setTimeout(() => {
212
- focusFirstInputElement(instance)
213
- instance.classList.remove('fadeIn')
214
- }, 200)
215
- })
216
- } else {
217
- // some instances found, so create an add button that can create a new instance or link existing ones
218
- const ul = document.createElement('ul')
219
- const newItem = document.createElement('li')
220
- newItem.innerHTML = '&#xFF0B; Create new ' + this.template.label + '...'
221
- newItem.dataset.value = 'new'
222
- newItem.classList.add('large')
223
- ul.appendChild(newItem)
224
- const divider = document.createElement('li')
225
- divider.classList.add('divider')
226
- ul.appendChild(divider)
227
- const header = document.createElement('li')
228
- header.classList.add('header')
229
- header.innerText = 'Or link existing:'
230
- ul.appendChild(header)
231
- for (const instance of instances) {
232
- const li = document.createElement('li')
233
- const itemValue = (typeof instance.value === 'string') ? instance.value : instance.value.value
234
- li.innerText = instance.label ? instance.label : itemValue
235
- li.dataset.value = JSON.stringify(instance.value)
236
- ul.appendChild(li)
237
- }
238
- addButton.appendChild(ul)
239
- addButton.collapsibleWidth = '250px'
240
- addButton.collapsibleOrientationLeft = ''
241
- addButton.addEventListener('change', () => {
242
- if (addButton.value === 'new') {
243
- // user wants to create a new instance
244
- this.addPropertyInstance()
245
- } else {
246
- // user wants to link existing instance
247
- const value = JSON.parse(addButton.value) as Term
248
- this.container.insertBefore(createPropertyInstance(this.template, value, true, true), addButton)
249
- }
250
- addButton.value = ''
251
- })
252
- }
253
- return addButton
254
- }
255
- }
256
-
257
- export function createPropertyInstance(template: ShaclPropertyTemplate, value?: Term, forceRemovable = false, linked = false): HTMLElement {
258
- let instance: HTMLElement
259
- if (template.extendedShapes.length) {
260
- instance = document.createElement('div')
261
- instance.classList.add('property-instance')
262
- for (const node of template.extendedShapes) {
263
- instance.appendChild(new ShaclNode(node, template.config, value as NamedNode | BlankNode | undefined, template.parent, template.nodeKind, template.label, linked))
264
- }
265
- } else {
266
- const plugin = findPlugin(template.path, template.datatype?.value)
267
- if (plugin) {
268
- if (template.config.editMode && !linked) {
269
- instance = plugin.createEditor(template, value)
270
- } else {
271
- instance = plugin.createViewer(template, value!)
272
- }
273
- } else {
274
- instance = fieldFactory(template, value || null, template.config.editMode && !linked)
275
- }
276
- instance.classList.add('property-instance')
277
- if (linked) {
278
- instance.classList.add('linked')
279
- }
280
- }
281
- if (template.config.editMode) {
282
- appendRemoveButton(instance, template.label, forceRemovable)
283
- }
284
- instance.dataset.path = template.path
285
- return instance
286
- }
287
-
288
- function appendRemoveButton(instance: HTMLElement, label: string, forceRemovable = false) {
289
- const removeButton = new RokitButton()
290
- removeButton.classList.add('remove-button', 'clear')
291
- removeButton.title = 'Remove ' + label
292
- removeButton.dense = true
293
- removeButton.icon = true
294
- removeButton.addEventListener('click', _ => {
295
- instance.classList.remove('fadeIn')
296
- instance.classList.add('fadeOut')
297
- setTimeout(() => {
298
- const parent = instance.parentElement
299
- instance.remove()
300
- parent?.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }))
301
- }, 200)
302
- })
303
- if (forceRemovable) {
304
- removeButton.classList.add('persistent')
305
- }
306
- instance.appendChild(removeButton)
307
- }
308
-
309
- window.customElements.define('shacl-property', ShaclProperty)