UNPKG

@ulb-darmstadt/shacl-form

Version:
261 lines (241 loc) 10.5 kB
import { Term } from '@rdfjs/types' import { ShaclPropertyTemplate } from "../property-template" import { Editor, InputListEntry, Theme } from "../theme" import { PREFIX_SHACL, PREFIX_XSD, XSD_DATATYPE_STRING } from '../constants' import { Literal, NamedNode } from 'n3' import { Term as N3Term } from 'n3' import css from './default.css?raw' import { RokitInput, RokitSelect, RokitTextArea } from '@ro-kit/ui-widgets' export class DefaultTheme extends Theme { idCtr = 0 constructor(overiddenCss?: string) { super(overiddenCss ? overiddenCss : css) } createDefaultTemplate(label: string, value: Term | null, required: boolean, editor: Editor, template?: ShaclPropertyTemplate): HTMLElement { editor.id = `e${this.idCtr++}` editor.classList.add('editor') if (template?.datatype) { // store datatype on editor, this is used for RDF serialization editor.shaclDatatype = template.datatype } else if (value instanceof Literal) { editor.shaclDatatype = value.datatype } if (template?.minCount !== undefined) { editor.dataset.minCount = String(template.minCount) } if (template?.class) { editor.dataset.class = template.class.value } if (template?.nodeKind) { editor.dataset.nodeKind = template.nodeKind.value } else if (value instanceof NamedNode) { editor.dataset.nodeKind = PREFIX_SHACL + 'IRI' } if (template?.hasValue || template?.readonly) { editor.disabled = true } editor.value = value?.value || template?.defaultValue?.value || '' const labelElem = document.createElement('label') labelElem.htmlFor = editor.id labelElem.innerText = label if (template?.description) { labelElem.setAttribute('title', template.description.value) } const placeholder = template?.description ? template.description.value : template?.pattern ? template.pattern : null if (placeholder) { editor.setAttribute('placeholder', placeholder) } if (required) { editor.setAttribute('required', 'true') labelElem.classList.add('required') } const result = document.createElement('div') result.appendChild(labelElem) result.appendChild(editor) return result } createDateEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement { const editor = new RokitInput() if (template.datatype?.value === PREFIX_XSD + 'dateTime') { editor.type = 'datetime-local' // this enables seconds in dateTime input editor.setAttribute('step', '1') } else { editor.type = 'date' } editor.clearable = true editor.dense = true editor.classList.add('pr-0') const result = this.createDefaultTemplate(label, null, required, editor, template) if (value) { try { let isoDate = new Date(value.value).toISOString() if (template.datatype?.value === PREFIX_XSD + 'dateTime') { isoDate = isoDate.slice(0, 19) } else { isoDate = isoDate.slice(0, 10) } editor.value = isoDate } catch(ex) { console.error(ex, value) } } return result } createTextEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement { let editor if (template.singleLine === false) { editor = new RokitTextArea() editor.resize = 'auto' } else { editor = new RokitInput() } editor.dense = true if (template.pattern) { editor.pattern = template.pattern } if (template.minLength) { editor.minLength = template.minLength } if (template.maxLength) { editor.maxLength = template.maxLength } return this.createDefaultTemplate(label, value, required, editor, template) } createLangStringEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement { const result = this.createTextEditor(label, value, required, template) const editor = result.querySelector(':scope .editor') as Editor let langChooser: HTMLSelectElement | HTMLInputElement if (template.languageIn?.length) { langChooser = document.createElement('select') for (const lang of template.languageIn) { const option = document.createElement('option') option.innerText = lang.value langChooser.appendChild(option) } } else { langChooser = document.createElement('input') langChooser.maxLength = 5 // e.g. en-US langChooser.size = 5 langChooser.placeholder = 'lang?' } langChooser.title = 'Language of the text' langChooser.classList.add('lang-chooser') langChooser.slot = 'suffix' // if lang chooser changes, fire a change event on the text input instead. this is for shacl validation handling. langChooser.addEventListener('change', (ev) => { ev.stopPropagation(); if (editor) { editor.dataset.lang = langChooser.value editor.dispatchEvent(new Event('change', { bubbles: true })) } }) if (value instanceof Literal) { langChooser.value = value.language } editor.dataset.lang = langChooser.value editor.appendChild(langChooser) return result } createBooleanEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement { const editor = document.createElement('input') editor.type = 'checkbox' editor.classList.add('ml-0') const result = this.createDefaultTemplate(label, null, required, editor, template) // 'required' on checkboxes forces the user to tick the checkbox, which is not what we want here editor.removeAttribute('required') result.querySelector(':scope label')?.classList.remove('required') if (value instanceof Literal) { editor.checked = value.value === 'true' } return result } createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement { const editor = document.createElement('input') editor.type = 'file' editor.addEventListener('change', (e) => { if (editor.files?.length) { e.stopPropagation() const reader = new FileReader() reader.readAsDataURL(editor.files[0]) reader.onload = () => { (editor as Editor)['binaryData'] = btoa(reader.result as string) editor.parentElement?.dispatchEvent(new Event('change', { bubbles: true })) } } else { (editor as Editor)['binaryData'] = undefined } }) return this.createDefaultTemplate(label, value, required, editor, template) } createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement { const editor = new RokitInput() editor.type = 'number' editor.clearable = true editor.dense = true editor.classList.add('pr-0') const min = template.minInclusive !== undefined ? template.minInclusive : template.minExclusive !== undefined ? template.minExclusive + 1 : undefined const max = template.maxInclusive !== undefined ? template.maxInclusive : template.maxExclusive !== undefined ? template.maxExclusive - 1 : undefined if (min !== undefined) { editor.min = String(min) } if (max !== undefined) { editor.max = String(max) } if (template.datatype?.value !== PREFIX_XSD + 'integer') { editor.step = '0.1' } return this.createDefaultTemplate(label, value, required, editor, template) } createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement { const editor = new RokitSelect() editor.clearable = true editor.dense = true const result = this.createDefaultTemplate(label, null, required, editor, template) const ul = document.createElement('ul') let isFlatList = true const appendListEntry = (entry: InputListEntry, parent: HTMLUListElement) => { const li = document.createElement('li') if (typeof entry.value === 'string') { li.dataset.value = entry.value li.innerText = entry.label ? entry.label : entry.value } else { if (entry.value instanceof Literal && entry.value.datatype.equals(XSD_DATATYPE_STRING)) { li.dataset.value = entry.value.value } else { // this is needed for typed rdf literals li.dataset.value = (entry.value as N3Term).id } li.innerText = entry.label ? entry.label : entry.value.value } parent.appendChild(li) if (entry.children?.length) { isFlatList = false const ul = document.createElement('ul') li.appendChild(ul) for (const child of entry.children) { appendListEntry(child, ul) } } } for (const item of listEntries) { appendListEntry(item, ul) } if (!isFlatList) { editor.collapse = true } editor.appendChild(ul) if (value) { editor.value = value.value } return result } createButton(label: string, _: boolean): HTMLElement { const button = document.createElement('button') button.type = 'button' button.innerHTML = label return button } }