Skip to content

Commit

Permalink
feat: handle multiple empty fields
Browse files Browse the repository at this point in the history
  • Loading branch information
tpluscode committed Nov 12, 2020
1 parent 0d4be59 commit c16e00b
Show file tree
Hide file tree
Showing 25 changed files with 109 additions and 71 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-dodos-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hydrofoil/shaperone-playground": patch
---

Do not sync resource changes in loop between form and rdf editor
6 changes: 6 additions & 0 deletions .changeset/green-crabs-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@hydrofoil/shaperone-core": patch
"@hydrofoil/shaperone-wc": patch
---

Handle multiple empty object fields
2 changes: 1 addition & 1 deletion demos/lit-html/src/shaperone-playground-lit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export class ShaperonePlayground extends connect(store(), LitElement) {

__saveResource() {
if (this.form.value) {
store().dispatch.resource.replaceGraph({ dataset: this.form.value })
store().dispatch.resource.replaceGraph({ dataset: this.form.value, updatePointer: false })
}
}

Expand Down
20 changes: 14 additions & 6 deletions demos/lit-html/src/state/models/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,22 @@ export const resource = createModel({
selectedResource: pointer.value,
},
reducers: {
replaceGraph(state, { dataset }: { dataset: Quad[] | DatasetCore }) {
setResourceQuads(state, dataset: DatasetCore) {
return {
...state,
quads: [...dataset],
}
},
replaceGraph(state, { dataset, updatePointer = true }: { dataset: Quad[] | DatasetCore; updatePointer?: boolean }) {
const graph = Array.isArray(dataset) ? cf({ dataset: $rdf.dataset(dataset) }) : cf({ dataset })
let pointer
let { pointer } = state

if (state.selectedResource) {
pointer = graph.namedNode(state.selectedResource)
} else {
pointer = graph.namedNode('')
if (updatePointer) {
if (state.selectedResource) {
pointer = graph.namedNode(state.selectedResource)
} else {
pointer = graph.namedNode('')
}
}

return {
Expand Down
12 changes: 6 additions & 6 deletions packages/core/models/forms/effects/resources/setRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@ export default function (store: Store) {
return
}

const focusNode = rootPointer
if (focusNode === formState.focusStack[0]) {
if (rootPointer === formState.focusStack[0]) {
return
}

if (!formState.focusStack.length || rootPointer.value !== formState.focusStack[0].value) {
dispatch.forms.truncateFocusNodes({ form, focusNode })
dispatch.forms.truncateFocusNodes({ form, focusNode: rootPointer })
dispatch.forms.createFocusNodeState({
form,
focusNode,
focusNode: rootPointer,
editors,
shouldEnableEditorChoice: formState.shouldEnableEditorChoice,
shapes: shapes.get(form)?.shapes || [],
Expand All @@ -31,11 +30,12 @@ export default function (store: Store) {
}

for (const currentFocusNode of formState.focusStack) {
if (!currentFocusNode.out().values) break
const focusNode = rootPointer.node(currentFocusNode)
if (!focusNode.out().values) break

dispatch.forms.createFocusNodeState({
form,
focusNode: currentFocusNode,
focusNode,
editors,
shapes: shapes.get(form)?.shapes || [],
shouldEnableEditorChoice: formState.shouldEnableEditorChoice,
Expand Down
5 changes: 3 additions & 2 deletions packages/core/models/forms/effects/shapes/setGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { SetShapesGraphParams } from '../../../shapes/reducers'
export default function setGraph(store: Store) {
const dispatch = store.getDispatch()

return ({ form }: SetShapesGraphParams) => {
return ({ form, shapesGraph }: SetShapesGraphParams) => {
const { editors, forms, shapes } = store.getState()
const formState = forms.get(form)
const currentGraph = shapes.get(form)?.shapesGraph
const graph = store.getState().resources.get(form)?.graph
if (!graph || !formState) {
if (!graph || !formState || currentGraph?.dataset === shapesGraph || currentGraph === shapesGraph) {
return
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/models/forms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import resourcesEffects from './effects/resources'
import type { Store } from '../../state'

export interface PropertyObjectState {
key: string
object?: GraphPointer
editors: SingleEditorMatch[]
selectedEditor: NamedNode | undefined
Expand Down
2 changes: 2 additions & 0 deletions packages/core/models/forms/lib/stateBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { dash } from '@tpluscode/rdf-ns-builders'
import { NamedNode, Term } from 'rdf-js'
import RdfResource from '@tpluscode/rdfine/RdfResource'
import TermMap from '@rdf-esm/term-map'
import { nanoid } from 'nanoid'
import type { EditorsState } from '../../editors/index'
import type { FocusNodeState, PropertyGroupState, PropertyObjectState, PropertyState, ShouldEnableEditorChoice } from '../index'
import { FocusNode } from '../../../index'
Expand Down Expand Up @@ -42,6 +43,7 @@ export function initialiseObjectState({ shape, editors, shouldEnableEditorChoice
}

return {
key: nanoid(),
object,
editors: matchedEditors,
selectedEditor,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/models/forms/reducers/addFormField.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { PropertyShape } from '@rdfine/shacl'
import produce from 'immer'
import { nanoid } from 'nanoid'
import type { FocusNode } from '../../..'
import type { FormState } from '../index'
import { formStateReducer, BaseParams } from '../../index'
Expand All @@ -21,6 +22,7 @@ export const addFormField = formStateReducer((state: FormState, { focusNode, pro
}

currentProperty.objects.push({
key: nanoid(),
editors,
selectedEditor: editors[0]?.term,
editorSwitchDisabled: !state.shouldEnableEditorChoice(),
Expand Down
7 changes: 3 additions & 4 deletions packages/core/models/forms/reducers/removeObject.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import type { PropertyShape } from '@rdfine/shacl'
import produce from 'immer'
import { GraphPointer } from 'clownface'
import { BaseParams, formStateReducer } from '../../index'
import type { FocusNode } from '../../../index'
import { canAddObject, canRemoveObject } from '../lib/property'
import type { FormState } from '../index'
import type { FormState, PropertyObjectState } from '../index'

export interface RemoveObjectParams extends BaseParams {
focusNode: FocusNode
property: PropertyShape
object: GraphPointer
object: PropertyObjectState
}

export const removeObject = formStateReducer((state: FormState, { focusNode, property, object }: RemoveObjectParams) => produce(state, (state) => {
Expand All @@ -20,7 +19,7 @@ export const removeObject = formStateReducer((state: FormState, { focusNode, pro
return
}

const objects = propertyState.objects.filter(o => !o.object?.term.equals(object?.term))
const objects = propertyState.objects.filter(o => o.key !== object.key)

propertyState.objects = objects
propertyState.canRemove = canRemoveObject(property, objects.length)
Expand Down
8 changes: 5 additions & 3 deletions packages/core/models/forms/reducers/updateObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Term } from 'rdf-js'
import type { PropertyShape } from '@rdfine/shacl'
import produce from 'immer'
import { MultiPointer } from 'clownface'
import { nanoid } from 'nanoid'
import { BaseParams, formStateReducer } from '../../index'
import type { FormState, PropertyObjectState } from '../index'
import type { FocusNode } from '../../../index'
Expand All @@ -10,7 +11,7 @@ import { EditorsState } from '../../editors'
export interface UpdateObjectParams extends BaseParams {
focusNode: FocusNode
property: PropertyShape
oldValue?: Term
object: PropertyObjectState
newValue: Term
}

Expand All @@ -21,15 +22,15 @@ export interface ReplaceObjectsParams extends BaseParams {
editors: EditorsState
}

export const updateObject = formStateReducer((state: FormState, { focusNode, property, oldValue, newValue }: UpdateObjectParams) => produce(state, (draft) => {
export const updateObject = formStateReducer((state: FormState, { focusNode, property, object, newValue }: UpdateObjectParams) => produce(state, (draft) => {
const focusNodeState = draft.focusNodes[focusNode.value]

const propertyState = focusNodeState.properties.find(p => p.shape.equals(property))
if (!propertyState) {
return
}

const objectState = propertyState.objects.find(o => o.object?.term.equals(oldValue))
const objectState = propertyState.objects.find(o => o.key === object.key)
if (objectState) {
objectState.object = focusNodeState.focusNode.node(newValue)
}
Expand All @@ -46,6 +47,7 @@ export const setPropertyObjects = formStateReducer((state: FormState, { focusNod
propertyState.objects = objects.map<PropertyObjectState>((object) => {
const suitableEditors = editors.matchSingleEditors({ shape: property, object })
return {
key: nanoid(),
object,
editors: suitableEditors,
selectedEditor: suitableEditors[0]?.term,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function (store: Store) {

state.graph.node(focusNode)
.deleteOut(pathProperty.id)
.addOut(pathProperty.id, objects.filter(o => !o.equals(removed.term)))
.addOut(pathProperty.id, objects.filter(o => !o.equals(removed.object?.term)))

notify({
store,
Expand Down
11 changes: 9 additions & 2 deletions packages/core/models/resources/effects/forms/updateObject.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { GraphPointer } from 'clownface'
import type { Store } from '../../../../state'
import * as updateObject from '../../../forms/reducers/updateObject'
import { getPathProperty } from '../../lib/property'
import { notify } from '../../lib/notify'

type Params = Omit<updateObject.UpdateObjectParams, 'object'> & {
object: {
object?: GraphPointer
}
}

export default function (store: Store) {
return function ({ form, focusNode, property, oldValue, newValue }: updateObject.UpdateObjectParams) {
return function ({ form, focusNode, property, object, newValue }: Params) {
const { resources } = store.getState()
const state = resources.get(form)
const pathProperty = getPathProperty(property)!.id
Expand All @@ -15,7 +22,7 @@ export default function (store: Store) {
const objects = state.graph.node(focusNode)
.out(pathProperty)
.terms
.filter(term => !term.equals(oldValue))
.filter(term => !term.equals(object.object?.term))

state.graph.node(focusNode)
.deleteOut(pathProperty)
Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"@tpluscode/rdfine": "^0.5.15",
"@zazuko/rdf-vocabularies": "^2020.11.2",
"clownface": "^1.1.0",
"immer": "^7.0.9"
"immer": "^7.0.9",
"nanoid": "^3.1.16"
},
"devDependencies": {
"@rdf-esm/data-model": "^0.5.3",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/test/models/editors/lib/match.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { expect } from 'chai'
import { testStore } from '../../forms/util'
import { Editor, EditorsState, SingleEditor, MultiEditor } from '../../../../models/editors'
import { matchSingleEditors, matchMultiEditors } from '../../../../models/editors/lib/match'
import { propertyShape } from '../../../util'

describe('models/editors/lib/match', () => {
let editors: EditorsState
let shape: PropertyShape

beforeEach(() => {
({ editors } = testStore().store.getState())
shape = propertyShape()
})

describe('matchSingleEditors', () => {
Expand Down
63 changes: 23 additions & 40 deletions packages/core/test/models/forms/reducers/removeObject.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { expect } from 'chai'
import clownface from 'clownface'
import $rdf from 'rdf-ext'
import { removeObject } from '../../../../models/forms/reducers/removeObject'
import { RecursivePartial, testStore } from '../util'
import { RecursivePartial, testObjectState, testStore } from '../util'
import { Store } from '../../../../state'
import { FocusNode } from '../../../../index'
import { propertyShape } from '../../../util'
Expand All @@ -23,47 +23,21 @@ describe('models/forms/reducers/removeObject', () => {
formState = store.getState().forms.get(form)!
})

it('removes state objects with matching value', () => {
it('removes state objects with matching key', () => {
// given
const property = propertyShape()
const object = focusNode.literal('1')
formState.focusNodes[focusNode.value] = {
properties: [{
shape: property,
objects: [{
object: focusNode.literal('1'),
}, {
object: focusNode.literal('1'),
}, {
object: focusNode.literal('2'),
}],
}],
}

// when
const afterState = removeObject(store.getState().forms, {
form,
focusNode,
object,
property,
const object = testObjectState(focusNode.literal('1'), {
key: 'remove',
})

// then
const afterProperty = afterState.get(form)?.focusNodes[focusNode.value].properties[0]
expect(afterProperty?.objects).to.have.length(1)
expect(afterProperty?.objects[0].object?.term).to.deep.eq($rdf.literal('2'))
})

it('does not remove state objects without values', () => {
// given
const property = propertyShape()
const object = focusNode.literal('1')
formState.focusNodes[focusNode.value] = {
properties: [{
shape: property,
objects: [{
key: 'remove',
}, {
key: 'remove',
}, {
key: 'foo',
}],
}],
}
Expand All @@ -78,22 +52,27 @@ describe('models/forms/reducers/removeObject', () => {

// then
const afterProperty = afterState.get(form)?.focusNodes[focusNode.value].properties[0]
expect(afterProperty?.objects).to.have.length(3)
expect(afterProperty?.objects).to.have.length(1)
expect(afterProperty?.objects[0].key).to.eq('foo')
})

it('updates canRemove flag', () => {
// given
const property = propertyShape({
maxCount: 2,
})
const object = focusNode.literal('1')
const object = testObjectState(focusNode.literal('1'), {
key: 'remove',
})
formState.focusNodes[focusNode.value] = {
properties: [{
shape: property,
canRemove: false,
objects: [{
object: focusNode.node(object),
}, {}],
key: 'remove',
}, {
key: 'stay',
}],
}],
}

Expand All @@ -115,14 +94,18 @@ describe('models/forms/reducers/removeObject', () => {
const property = propertyShape({
maxCount: 2,
})
const object = focusNode.literal('1')
const object = testObjectState(focusNode.literal('1'), {
key: 'remove',
})
formState.focusNodes[focusNode.value] = {
properties: [{
shape: property,
canAdd: false,
objects: [{
object: focusNode.node(object),
}, {}],
key: 'remove',
}, {
key: 'stay',
}],
}],
}

Expand Down
Loading

0 comments on commit c16e00b

Please sign in to comment.