Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/primitives/components/rdf-form/RDFForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { customElement, property, state } from 'lit/decorators.js'
import { html } from 'lit/html.js'
import WebComponent from '../../lib/WebComponent'
import ns from '../../../lib/ns'
import { loadDocument, sortBySequence } from '../../lib/rdfFormsHelper'
import { sym, Namespace } from 'rdflib'
import { store } from 'solid-logic'

@customElement('solid-ui-rdf-form')
export default class RDFForm extends WebComponent {
@state()
private accessor _parsedUrl: URL | null = null

@property({ type: String })
accessor whichForm = 'this'

@property({ type: String })
accessor rdfTurtleFormatSource = ''

@property({ type: String })
accessor rdfName = ''

@property({ type: String })
set rdfURI (value: string) {
try {
this._parsedUrl = new URL(value)
} catch {
this._parsedUrl = null // Handle invalid URL
}
}

get rdfURI (): string {
return this._parsedUrl ? this._parsedUrl.href : ''
}

render () {
// TODO: detect format
loadDocument(store, this.rdfTurtleFormatSource, this.rdfName, this.rdfURI)
const document = sym(this.rdfURI) // rdflib NamedNode for the document
const exactForm = this.whichForm // If there are more 'a ui:Form' elements in a form file
const formThis = Namespace(this.rdfURI + '#')(exactForm) // NamedNode for #this in the form
console.log('formThis:', formThis.value)

const parts = store.each(formThis, ns.ui('parts'), null, document)
const partsBySequence = sortBySequence(store, parts)
const partItems = (partsBySequence || []).flatMap(item => {
if (item && typeof item === 'object' && 'elements' in item && Array.isArray((item as any).elements)) {
return (item as any).elements
}
return [item]
})
const uiFields = partItems.map(item => {
const types = store.each(item as any, ns.rdf('type'), null, document)
const typeNode = types[0]
const value = typeNode ? ((typeNode as any).value || String(typeNode)) : ((item as any).value || String(item))
const hashIndex = value.lastIndexOf('#')
return hashIndex >= 0 ? value.slice(hashIndex + 1) : value
})
console.log('parts:', parts)
console.log('partsBySequence:', partsBySequence)
console.log('partItems:', partItems)
console.log('document:', document)
console.log('exactForm:', exactForm)
console.log('uiFields:', uiFields)

return html`
${uiFields.map(part => {
switch (part) {
case 'PhoneField':
case 'EmailField':
case 'ColorField':
case 'DateField':
case 'DateTimeField':
case 'TimeField':
case 'NumericField':
case 'IntegerField':
case 'DecimalField':
case 'FloatField':
case 'TextField':
case 'SingleLineTextField':
case 'NamedNodeURIField':
return html`<input rdf=${part}></input>`
case 'MultiLineTextField':
return html`<input rdf=${part}></input>`
case 'BooleanField':
return html`<input rdf=${part}></input>`
case 'TristateField':
return html`<input rdf=${part}></input>`
case 'Classifier':
return html`<input rdf=${part}></input>`
case 'Choice':
return html`<input rdf=${part}></input>`
case 'Multiple':
return html`<input rdf=${part}></input>`
case 'Options':
return html`<input rdf=${part}></input>`
case 'AutocompleteField':
return html`<input rdf=${part}></input>`
case 'Comment':
case 'Heading':
return html`<input rdf=${part}></input>`
default:
return html`<div>Unknown part type: ${part}</div>`
}
})}
`
}
}
92 changes: 92 additions & 0 deletions src/primitives/components/rdf-form/RDForm.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { html } from 'lit'
import { defineStoryRender } from '../../../storybook'
import './RDFForm'

const meta = {
title: 'Design System/RDF Form',
args: {
rdfTurtleFormatSource: `
@prefix : <https://solidos.solidcommunity.net/public/2021/solidUiFormTestData/dummyFormTestFile.ttl#>.
@prefix dc: <http://purl.org/dc/elements/1.1/>.
@prefix ui: <http://www.w3.org/ns/ui#>.
@prefix vcard: <http://www.w3.org/2006/vcard/ns#>.

# A Form with 2 fields and a nested subgroup

:form a ui:Form;
ui:parts (:nameField :emailField :addresses) .

:nameField a ui:SingleLineTextField ;
ui:property vcard:fn;
ui:label "name" .

:emailField a ui:EmailField ;
ui:property vcard:hasEmail; # @@ check
ui:label "email" .

:addresses
a ui:Multiple ; # -- Allows zero or one or more
ui:part :oneAddress ;
ui:property vcard:hasAddress .

:oneAddress
a ui:Group ; # A subgroup of the main form
ui:parts ( :street :locality :postcode :region :country ).

:street
a ui:SingleLineTextField ;
ui:maxLength "128" ;
ui:property vcard:street-address ;
ui:size "40" .

:locality
a ui:SingleLineTextField ;
ui:maxLength "128" ;
ui:property vcard:locality ;
ui:size "40" .

:postcode
a ui:SingleLineTextField ;
ui:maxLength "25" ;
ui:property vcard:postal-code ;
ui:size "25" .

:region
a ui:SingleLineTextField ;
ui:maxLength "128" ;
ui:property vcard:region ;
ui:size "40" .

:country
a ui:SingleLineTextField ;
ui:maxLength "128" ;
ui:property vcard:country-name ;
ui:size "40" .
`,
rdfURI: 'https://solidos.solidcommunity.net/public/2021/solidUiFormTestData/dummyFormTestFile.ttl',
whichForm: 'form',
rdfName: 'dummyFormTestFile.ttl'
},

argTypes: {
rdfTurtleFormatSource: { control: 'text' },
rdfURI: { control: 'text' },
whichForm: { control: 'text' },
rdfName: { control: 'text' }
},
} as const

const render = defineStoryRender<typeof meta.argTypes>(({ rdfTurtleFormatSource, rdfURI, whichForm, rdfName }) => {
return html`
<solid-ui-rdf-form
rdfTurtleFormatSource=${rdfTurtleFormatSource}
rdfURI=${rdfURI}
whichForm=${whichForm}
rdfName=${rdfName}>
</solid-ui-rdf-form>
`
})

export default meta

export const Primary = { render }
67 changes: 67 additions & 0 deletions src/primitives/lib/rdfFormsHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { sym, Namespace, parse } from 'rdflib'
import { widgets } from 'solid-ui'
import ns from '../../lib/ns'

const baseUri = 'https://solidos.github.io/solid-ui/src/ontology/'

export function renderForm (
div,
subject, // Represents the RDF that fills the form
formSource, // The imported form Turtle source
formName, // The name of the form file (e.g., 'socialMedia.ttl')
store,
dom,
editableProfile,
whichForm) {
// --- Form resource setup ---
const formUri = baseUri + formName // Full URI to the form file
const exactForm = whichForm || 'this' // If there are more 'a ui:Form' elements in a form file
const formThis = Namespace(formUri + '#')(exactForm) // NamedNode for #this in the form

loadDocument(store, formSource, formName, formUri)

widgets.appendForm(
dom,
div,
{},
subject,
formThis,
editableProfile,
(ok, mes) => {
if (!ok) widgets.errorMessageBlock(dom, mes)
}
)
} // renderForm

// we need to load into the store some additional information about Social Media accounts
export function loadDocument (
store,
documentSource,
documentName,
documentURI
) {
const finalDocumentUri = documentURI || baseUri + documentName // Full URI to the file
const document = sym(finalDocumentUri) // rdflib NamedNode for the document

if (!store.holds(undefined, undefined, undefined, document)) {
// we are using the social media form because it contains the information we need
// the form can be used for both use cases: create UI for edit and render UI for display
parse(documentSource, store, finalDocumentUri, 'text/turtle', () => null) // Load doc directly
}
}

export function sortBySequence (
store,
list
) {
const subfields = list.map(function (p) {
const k = store.any(p, ns.ui('sequence'))
return [k || 9999, p]
})
subfields.sort(function (a, b) {
return a[0] - b[0]
})
return subfields.map(function (pair) {
return pair[1]
})
}