diff --git a/.changeset/tidy-suns-relax.md b/.changeset/tidy-suns-relax.md new file mode 100644 index 00000000..875ccd07 --- /dev/null +++ b/.changeset/tidy-suns-relax.md @@ -0,0 +1,5 @@ +--- +"@hydrofoil/shaperone-core": patch +--- + +Do not create a new node when the editor is a `dash:*SelectEditor` diff --git a/packages/core-tests/models/forms/effects/addObject.test.ts b/packages/core-tests/models/forms/effects/addObject.test.ts index 682c9e2a..1b2cfe84 100644 --- a/packages/core-tests/models/forms/effects/addObject.test.ts +++ b/packages/core-tests/models/forms/effects/addObject.test.ts @@ -3,7 +3,7 @@ import cf from 'clownface' import $rdf from 'rdf-ext' import { expect } from 'chai' import { sinon } from '@shaperone/testing' -import { dash } from '@tpluscode/rdf-ns-builders' +import { dash } from '@tpluscode/rdf-ns-builders/loose' import { testStore } from '@shaperone/testing/models/form.js' import { addObject } from '@hydrofoil/shaperone-core/models/forms/effects/addObject.js' import { Store } from '@hydrofoil/shaperone-core/state' @@ -22,12 +22,12 @@ describe('models/forms/effects/addObject', () => { // given const property = propertyShape() const focusNode = cf({ dataset: $rdf.dataset() }).blankNode() - const matchedEditors: SingleEditorMatch[] = [{ + const editors: SingleEditorMatch[] = [{ term: dash.TextFieldEditor, score: 5, meta: {}, }] - store.getState().editors.matchSingleEditors = () => matchedEditors + store.getState().editors.matchSingleEditors = () => editors // when addObject(store)({ @@ -42,7 +42,37 @@ describe('models/forms/effects/addObject', () => { form, property, focusNode, - matchedEditors, + editors, + selectedEditor: dash.TextFieldEditor, + })) + }) + + it('takes property editor as default match', () => { + // given + const property = propertyShape({ + editor: dash.FooEditor, + }) + const focusNode = cf({ dataset: $rdf.dataset() }).blankNode() + store.getState().editors.matchSingleEditors = () => [{ + term: dash.TextFieldEditor, + score: 5, + meta: {}, + }] + + // when + addObject(store)({ + form, + property, + focusNode, + }) + + // then + const dispatch = store.getDispatch() + expect(dispatch.forms.addFormField).to.have.been.calledWith(sinon.match({ + form, + property, + focusNode, + selectedEditor: dash.FooEditor, })) }) }) diff --git a/packages/core-tests/models/forms/reducers/addFormField.test.ts b/packages/core-tests/models/forms/reducers/addFormField.test.ts index 26f0c8e1..83e0ecef 100644 --- a/packages/core-tests/models/forms/reducers/addFormField.test.ts +++ b/packages/core-tests/models/forms/reducers/addFormField.test.ts @@ -20,7 +20,7 @@ describe('core/models/forms/reducers/addObject', () => { }) const focusNode = graph.node(ex.FocusNode) const { form, store } = testStore() - const { editors, forms } = store.getState() + const { forms } = store.getState() forms.get(form)!.focusNodes = testFocusNodeState(focusNode, { properties: [testPropertyState(focusNode.blankNode(), { @@ -38,8 +38,8 @@ describe('core/models/forms/reducers/addObject', () => { form, property, focusNode, - matchedEditors: [], - editors, + editors: [], + selectedEditor: undefined, }) // then @@ -56,7 +56,7 @@ describe('core/models/forms/reducers/addObject', () => { }) const focusNode = graph.node(ex.FocusNode) const { form, store } = testStore() - const { editors, forms } = store.getState() + const { forms } = store.getState() forms.get(form)!.focusNodes = testFocusNodeState(focusNode, { properties: [testPropertyState(focusNode.blankNode(), { @@ -74,8 +74,8 @@ describe('core/models/forms/reducers/addObject', () => { form, property, focusNode, - matchedEditors: [], - editors, + editors: [], + selectedEditor: undefined, }) // then @@ -92,7 +92,7 @@ describe('core/models/forms/reducers/addObject', () => { }) const focusNode = graph.node(ex.FocusNode) const { form, store } = testStore() - const { editors, forms } = store.getState() + const { forms } = store.getState() forms.get(form)!.focusNodes = testFocusNodeState(focusNode, { properties: [testPropertyState(focusNode.blankNode(), { @@ -110,8 +110,8 @@ describe('core/models/forms/reducers/addObject', () => { form, property, focusNode, - matchedEditors: [], - editors, + editors: [], + selectedEditor: undefined, }) // then @@ -127,7 +127,7 @@ describe('core/models/forms/reducers/addObject', () => { }) const focusNode = graph.node(ex.FocusNode) const { form, store } = testStore() - const { editors, forms } = store.getState() + const { forms } = store.getState() forms.get(form)!.focusNodes = testFocusNodeState(focusNode, { properties: [testPropertyState(focusNode.blankNode(), { @@ -145,8 +145,8 @@ describe('core/models/forms/reducers/addObject', () => { form, property, focusNode, - matchedEditors: [], - editors, + editors: [], + selectedEditor: undefined, }) // then @@ -163,7 +163,7 @@ describe('core/models/forms/reducers/addObject', () => { }) const focusNode = graph.node(ex.FocusNode) const { form, store } = testStore() - const { editors, forms } = store.getState() + const { forms } = store.getState() forms.get(form)!.focusNodes = testFocusNodeState(focusNode, { properties: [testPropertyState(focusNode.blankNode(), { @@ -181,12 +181,16 @@ describe('core/models/forms/reducers/addObject', () => { form, property, focusNode, - matchedEditors: [{ + editors: [{ + term: dash.FooEditor, + score: 100, + meta: {} as any, + }, { term: dash.TextFieldEditor, score: 10, meta: {} as any, }], - editors, + selectedEditor: dash.FooEditor, }) // then diff --git a/packages/core-tests/models/resources/effects/forms/addFormField.test.ts b/packages/core-tests/models/resources/effects/forms/addFormField.test.ts index ab413f95..8d8f2044 100644 --- a/packages/core-tests/models/resources/effects/forms/addFormField.test.ts +++ b/packages/core-tests/models/resources/effects/forms/addFormField.test.ts @@ -42,6 +42,7 @@ describe('models/resources/effects/forms/addFormField', () => { form, focusNode, property, + selectedEditor: undefined, }) // then @@ -64,6 +65,7 @@ describe('models/resources/effects/forms/addFormField', () => { form, focusNode, property, + selectedEditor: undefined, }) // then @@ -88,6 +90,7 @@ describe('models/resources/effects/forms/addFormField', () => { form, focusNode, property, + selectedEditor: undefined, }) // then diff --git a/packages/core-tests/models/resources/lib/objectValue.test.ts b/packages/core-tests/models/resources/lib/objectValue.test.ts index 5286b9e1..704f4097 100644 --- a/packages/core-tests/models/resources/lib/objectValue.test.ts +++ b/packages/core-tests/models/resources/lib/objectValue.test.ts @@ -17,9 +17,10 @@ describe('core/models/resources/lib/defaultValue', () => { const property = propertyShape(graph.blankNode(), { defaultValue: literal('foo', xsd.anySimpleType), }) + const focusNode = graph.blankNode() // when - const pointer = defaultValue(property, graph.blankNode()) + const pointer = defaultValue({ property, focusNode }) // then expect(pointer?.term).to.deep.eq(literal('foo', xsd.anySimpleType)) @@ -30,9 +31,10 @@ describe('core/models/resources/lib/defaultValue', () => { const graph = cf({ dataset: $rdf.dataset() }) const property = propertyShape(graph.blankNode(), { }) + const focusNode = graph.blankNode() // when - const pointer = defaultValue(property, graph.blankNode()) + const pointer = defaultValue({ property, focusNode }) // then expect(pointer).to.be.null @@ -44,9 +46,10 @@ describe('core/models/resources/lib/defaultValue', () => { const property = propertyShape(graph.blankNode(), { class: foaf.Person, }) + const focusNode = graph.blankNode() // when - const pointer = defaultValue(property, graph.blankNode()) + const pointer = defaultValue({ property, focusNode }) // then expect(pointer?.term?.termType).to.eq('BlankNode') @@ -59,9 +62,10 @@ describe('core/models/resources/lib/defaultValue', () => { const property = propertyShape(graph.blankNode(), { nodeKind: sh.IRIOrLiteral, }) + const focusNode = graph.blankNode() // when - const pointer = defaultValue(property, graph.blankNode()) + const pointer = defaultValue({ property, focusNode }) // then expect(pointer).to.be.null @@ -73,10 +77,11 @@ describe('core/models/resources/lib/defaultValue', () => { const property = propertyShape(graph.blankNode(), { nodeKind: sh.IRI, }) + const focusNode = graph.blankNode() // when - const first = defaultValue(property, graph.blankNode()) - const second = defaultValue(property, graph.blankNode()) + const first = defaultValue({ property, focusNode }) + const second = defaultValue({ property, focusNode }) // then expect(first?.term).not.to.deep.eq(second) @@ -89,9 +94,10 @@ describe('core/models/resources/lib/defaultValue', () => { nodeKind: sh.IRI, [sh1.iriPrefix.value]: 'http://example.com/foo/', }) + const focusNode = graph.blankNode() // when - const term = defaultValue(property, graph.blankNode())?.term + const term = defaultValue({ property, focusNode })?.term // then expect(term?.termType).to.eq('NamedNode') @@ -105,9 +111,10 @@ describe('core/models/resources/lib/defaultValue', () => { nodeKind: sh.BlankNodeOrIRI, [sh1.iriPrefix.value]: 'http://example.com/foo/', }) + const focusNode = graph.blankNode() // when - const term = defaultValue(property, graph.blankNode())?.term + const term = defaultValue({ property, focusNode })?.term // then expect(term?.termType).to.eq('NamedNode') @@ -121,9 +128,10 @@ describe('core/models/resources/lib/defaultValue', () => { nodeKind: sh.IRIOrLiteral, [sh1.iriPrefix.value]: 'http://example.com/foo/', }) + const focusNode = graph.blankNode() // when - const term = defaultValue(property, graph.blankNode())?.term + const term = defaultValue({ property, focusNode })?.term // then expect(term?.termType).to.eq('NamedNode') @@ -144,9 +152,10 @@ describe('core/models/resources/lib/defaultValue', () => { const property = propertyShape(graph.blankNode(), { nodeKind, }) + const focusNode = graph.blankNode() // when - const pointer = defaultValue(property, graph.blankNode()) + const pointer = defaultValue({ property, focusNode }) // then expect(pointer?.term?.termType).to.eq(termType) @@ -160,9 +169,10 @@ describe('core/models/resources/lib/defaultValue', () => { class: foaf.Agent, [sh1.iriPrefix.value]: 'http://example.com/foo/', }) + const focusNode = graph.blankNode() // when - const pointer = defaultValue(property, graph.blankNode()) + const pointer = defaultValue({ property, focusNode }) // then expect(pointer?.out(rdf.type).term).to.deep.eq(foaf.Agent) @@ -177,12 +187,33 @@ describe('core/models/resources/lib/defaultValue', () => { [dash.editor.value]: dash.InstancesSelectEditor, [sh1.iriPrefix.value]: 'http://example.com/foo/', }) + const focusNode = graph.blankNode() // when - const pointer = defaultValue(property, graph.blankNode()) + const pointer = defaultValue({ property, focusNode }) // then expect(pointer?.out(rdf.type).term).to.be.undefined }) }) + + const selectEditors = [ + dash.EnumSelectEditor, + dash.InstancesSelectEditor, + dash.AutoCompleteEditor, + ] + + selectEditors.forEach((editor) => { + it(`does not create a node when editor is ${editor.value}`, () => { + // given + const graph = cf({ dataset: $rdf.dataset() }) + const property = propertyShape(graph.blankNode(), { + nodeKind: sh.IRI, + }) + const focusNode = graph.blankNode() + + // then + expect(defaultValue({ property, focusNode, editor })).to.be.null + }) + }) }) diff --git a/packages/core/models/forms/effects/addObject.ts b/packages/core/models/forms/effects/addObject.ts index ca9064eb..2929b7c1 100644 --- a/packages/core/models/forms/effects/addObject.ts +++ b/packages/core/models/forms/effects/addObject.ts @@ -1,23 +1,39 @@ import { PropertyShape } from '@rdfine/shacl' +import { NamedNode } from 'rdf-js' import type { Store } from '../../../state' import { FocusNode } from '../../../index' import { BaseParams } from '../../index' +import { SingleEditorMatch } from '../../editors' export function addObject(store: Store) { const dispatch = store.getDispatch() return function ({ form, property, focusNode }: { focusNode: FocusNode; property: PropertyShape } & BaseParams) { - const { editors, resources } = store.getState() + const { editors: editorsState, resources } = store.getState() const graph = resources.get(form)?.graph if (!graph) { return } + const matchedEditors = editorsState.matchSingleEditors({ shape: property }) + let editors: SingleEditorMatch[] + let selectedEditor: NamedNode | undefined + if (property.editor?.id.termType === 'NamedNode') { + selectedEditor = property.editor.id + editors = [ + { term: selectedEditor, score: null, meta: editorsState.metadata.node(selectedEditor) }, + ...matchedEditors, + ] + } else { + editors = matchedEditors + selectedEditor = editors[0]?.term + } + dispatch.forms.addFormField({ form, property, focusNode, - matchedEditors: editors.matchSingleEditors({ shape: property }), editors, + selectedEditor, }) } } diff --git a/packages/core/models/forms/reducers/addFormField.ts b/packages/core/models/forms/reducers/addFormField.ts index 727187a8..98953a07 100644 --- a/packages/core/models/forms/reducers/addFormField.ts +++ b/packages/core/models/forms/reducers/addFormField.ts @@ -4,30 +4,17 @@ import type { FocusNode } from '../../..' import { objectStateProducer } from '../objectStateProducer.js' import { formStateReducer, BaseParams } from '../../index.js' import { canAddObject, canRemoveObject } from '../lib/property.js' -import type { EditorsState, SingleEditorMatch } from '../../editors' +import type { SingleEditorMatch } from '../../editors' import { nextid } from '../lib/objectid.js' export interface Params extends BaseParams { focusNode: FocusNode property: PropertyShape - matchedEditors: SingleEditorMatch[] - editors: EditorsState + editors: SingleEditorMatch[] + selectedEditor: NamedNode | undefined } -export const addFormField = formStateReducer(objectStateProducer((state, { property, editors: { metadata }, matchedEditors }, currentProperty) => { - let editors: SingleEditorMatch[] - let selectedEditor: NamedNode | undefined - if (property.editor?.id.termType === 'NamedNode') { - selectedEditor = property.editor.id - editors = [ - { term: selectedEditor, score: null, meta: metadata.node(selectedEditor) }, - ...matchedEditors, - ] - } else { - editors = matchedEditors - selectedEditor = editors[0]?.term - } - +export const addFormField = formStateReducer(objectStateProducer((state, { property, editors, selectedEditor }, currentProperty) => { currentProperty.objects.push({ key: nextid(), editors, diff --git a/packages/core/models/resources/effects/forms/addFormField.ts b/packages/core/models/resources/effects/forms/addFormField.ts index 5fe7fb6a..cee4aa56 100644 --- a/packages/core/models/resources/effects/forms/addFormField.ts +++ b/packages/core/models/resources/effects/forms/addFormField.ts @@ -6,7 +6,7 @@ import { defaultValue } from '../../lib/objectValue.js' export default function (store: Store) { const dispatch = store.getDispatch() - return function ({ form, focusNode, property }: Pick): void { + return function ({ form, focusNode, property, selectedEditor }: Pick): void { const { resources, editors } = store.getState() const state = resources.get(form) @@ -14,7 +14,7 @@ export default function (store: Store) { return } - const pointer = defaultValue(property, focusNode) + const pointer = defaultValue({ property, focusNode, editor: selectedEditor }) const predicate = property.getPathProperty(true).id if (!pointer || focusNode.has(predicate, pointer).terms.length) { return diff --git a/packages/core/models/resources/effects/forms/createFocusNodeState.ts b/packages/core/models/resources/effects/forms/createFocusNodeState.ts index 4727a876..d3c93e20 100644 --- a/packages/core/models/resources/effects/forms/createFocusNodeState.ts +++ b/packages/core/models/resources/effects/forms/createFocusNodeState.ts @@ -33,7 +33,11 @@ export default function createFocusNodeState(store: Store) { if (object.object) { return { shouldNotify: false } } - const [value] = defaultValue(property.shape, focusNode)?.toArray() || [] + const [value] = defaultValue({ + property: property.shape, + focusNode, + editor: object.selectedEditor, + })?.toArray() || [] if (!value) { return { shouldNotify: false } } diff --git a/packages/core/models/resources/lib/objectValue.ts b/packages/core/models/resources/lib/objectValue.ts index 808fe117..074879f3 100644 --- a/packages/core/models/resources/lib/objectValue.ts +++ b/packages/core/models/resources/lib/objectValue.ts @@ -7,7 +7,13 @@ import { nanoid } from 'nanoid' import sh1 from '../../../ns.js' import type { FocusNode } from '../../../index' -export function defaultValue(property: PropertyShape, focusNode: FocusNode): MultiPointer | null { +interface DefaultValue { + property: PropertyShape + editor?: NamedNode + focusNode: FocusNode +} + +export function defaultValue({ property, focusNode, editor }: DefaultValue): MultiPointer | null { if (property.defaultValue) { return focusNode.node(property.defaultValue) } @@ -17,6 +23,10 @@ export function defaultValue(property: PropertyShape, focusNode: FocusNode): Mul nodeKind = sh.BlankNode } + if (editor && shouldNotCreateNode(editor)) { + return null + } + switch (nodeKind?.value) { case 'http://www.w3.org/ns/shacl#IRI': case 'http://www.w3.org/ns/shacl#IRIOrLiteral': @@ -29,6 +39,12 @@ export function defaultValue(property: PropertyShape, focusNode: FocusNode): Mul } } +function shouldNotCreateNode(editor: NamedNode) { + return editor.equals(dash.EnumSelectEditor) || + editor.equals(dash.InstancesSelectEditor) || + editor.equals(dash.AutoCompleteEditor) +} + function createResourceNode(property: PropertyShape, nodeKind: NodeKind, focusNode: FocusNode) { const uriStart = property.pointer.out(sh1.iriPrefix).value let resourceNode: GraphPointer = focusNode.blankNode() diff --git a/packages/testing/package.json b/packages/testing/package.json index 50afb241..fc5076af 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -7,6 +7,7 @@ "@hydrofoil/shaperone-core": "*", "@rdf-esm/dataset": "^0.5.1", "@rdf-esm/namespace": "^0.5.2", + "@rdfine/dash": "^0.2.4", "@rdfine/shacl": "^0.8.7", "@tpluscode/rdfine": "^0.5.39", "@tpluscode/rdf-ns-builders": "^2.0.0", diff --git a/packages/testing/util.ts b/packages/testing/util.ts index dad94c5d..025f2476 100644 --- a/packages/testing/util.ts +++ b/packages/testing/util.ts @@ -4,8 +4,9 @@ import RdfResource, { Initializer, ResourceIdentifier } from '@tpluscode/rdfine/ import clownface, { GraphPointer } from 'clownface' import * as $rdf from '@rdf-esm/dataset' import PropertyShapeEx from '@hydrofoil/shaperone-core/models/shapes/lib/PropertyShape.js' +import { PropertyShapeMixinEx } from '@rdfine/dash/extensions/sh' -RdfResource.factory.addMixin(PropertyShapeEx) +RdfResource.factory.addMixin(PropertyShapeEx, PropertyShapeMixinEx) function isPointer(arg: GraphPointer | Initializer | undefined): arg is GraphPointer { return (arg && '_context' in arg) || false