Skip to content

Commit 58d647c

Browse files
committed
feat: preferred shape selected from root pointer
1 parent c41966b commit 58d647c

File tree

10 files changed

+120
-16
lines changed

10 files changed

+120
-16
lines changed

.changeset/silver-lies-turn.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@hydrofoil/shaperone-playground": patch
3+
"@hydrofoil/shaperone-core": patch
4+
---
5+
6+
Select root shape from graph pointer

demos/lit-html/src/menu/shape.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ const toolsMenu = (() => {
7171
}
7272
})()
7373

74+
const rootShapeMenu = (state: State) => {
75+
const shapeChoices = state.shapes.map(shape => ({
76+
text: shape.value,
77+
checked: shape.term.equals(state.pointer?.term),
78+
pointer: shape,
79+
type: 'root shape',
80+
}))
81+
82+
return [
83+
{ text: 'Autoselect', checked: !state.pointer?.term, type: 'root shape' },
84+
...shapeChoices,
85+
]
86+
}
87+
7488
export function shapeMenu(state: State) {
7589
return [{
7690
text: 'Format',
@@ -86,6 +100,9 @@ export function shapeMenu(state: State) {
86100
}, {
87101
text: 'Fetch shape',
88102
children: fetchShapeMenu(state),
103+
}, {
104+
text: 'Root shape',
105+
children: rootShapeMenu(state),
89106
}, {
90107
text: 'Tools',
91108
children: toolsMenu(),

demos/lit-html/src/shaperone-playground-lit.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { html, render } from 'lit-html'
1010
import '@rdfjs-elements/rdf-editor'
1111
import { connect } from '@captaincodeman/rdx'
1212
import { Quad } from 'rdf-js'
13-
import { store, State } from './state/store'
13+
import { store, State, Dispatch } from './state/store'
1414
import { shapeMenu } from './menu/shape'
1515
import { resourceMenu } from './menu/resource'
1616
import { formMenu } from './menu/formMenu'
@@ -167,7 +167,7 @@ export class ShaperonePlayground extends connect(store(), LitElement) {
167167
}
168168

169169
__setShape(e: CustomEvent) {
170-
store().dispatch.shape.setShape(e.detail.value)
170+
store().dispatch.shape.setShapesGraph(e.detail.value)
171171
store().dispatch.shape.serialized(this.shapeEditor.codeMirror.value)
172172
}
173173

@@ -209,15 +209,18 @@ export class ShaperonePlayground extends connect(store(), LitElement) {
209209
}
210210
}
211211

212-
__editorMenuSelected(dispatch: any, editor: RdfEditor) {
212+
__editorMenuSelected(dispatch: Dispatch['shape'], editor: RdfEditor) {
213213
return (e: CustomEvent) => {
214214
switch (e.detail.value.type) {
215215
case 'format':
216216
dispatch.format(e.detail.value.text)
217217
break
218+
case 'root shape':
219+
dispatch.selectRootShape(e.detail.value.pointer)
220+
break
218221
default:
219222
dispatch.serialized(editor.codeMirror.value)
220-
dispatch.setShape(editor.quads)
223+
dispatch.setShapesGraph(editor.quads)
221224
break
222225
}
223226
}

demos/lit-html/src/state/models/shape.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { turtle } from '@tpluscode/rdf-string'
55
import { Quad } from 'rdf-js'
66
import * as formats from '@rdf-esm/formats-common'
77
import rdfFetch from '@rdfjs/fetch-lite'
8-
import clownface, { AnyPointer } from 'clownface'
8+
import clownface, { AnyPointer, GraphPointer } from 'clownface'
99
import type { Store } from '../store'
1010

1111
const triples = turtle`@prefix ex: <http://example.com/> .
@@ -106,6 +106,7 @@ export interface State {
106106
serialized: string
107107
format: string
108108
pointer?: AnyPointer
109+
shapes: GraphPointer[]
109110
quads: Quad[]
110111
options: {
111112
clearResource: boolean
@@ -118,19 +119,36 @@ export const shape = createModel({
118119
state: <State>{
119120
serialized: triples.toString(),
120121
format: 'text/turtle',
122+
shapes: [],
123+
quads: [],
121124
options: {
122125
clearResource: false,
123126
},
124127
},
125128
reducers: {
126-
setShape(state, quads: Quad[]) {
127-
const pointer = clownface({ dataset: $rdf.dataset(quads) })
129+
setShapesGraph(state, quads: Quad[]) {
130+
let pointer = clownface({ dataset: $rdf.dataset(quads) })
131+
const shapes = pointer.has(rdf.type, [sh.Shape, sh.NodeShape])
132+
if (state.pointer?.term) {
133+
const previousPointer = pointer.node(state.pointer.term)
134+
if (previousPointer.has(rdf.type, [sh.Shape, sh.NodeShape]).terms.length) {
135+
pointer = previousPointer
136+
}
137+
}
138+
128139
return {
129140
...state,
130141
pointer,
142+
shapes: shapes.toArray(),
131143
quads,
132144
}
133145
},
146+
selectRootShape(state, pointer: GraphPointer | undefined) {
147+
return {
148+
...state,
149+
pointer,
150+
}
151+
},
134152
serialized(state, serialized: string): State {
135153
return {
136154
...state,
@@ -165,7 +183,7 @@ export const shape = createModel({
165183

166184
if (shapes.ok) {
167185
const dataset = await shapes.dataset()
168-
dispatch.shape.setShape([...dataset])
186+
dispatch.shape.setShapesGraph([...dataset])
169187
if (clearResource) {
170188
dispatch.resource.replaceGraph({
171189
dataset: [],
@@ -198,7 +216,7 @@ export const shape = createModel({
198216
.addOut(rdf.type, clas)
199217
.addOut(rdfs.label, `${clasName} ${instanceId}`)
200218
})
201-
dispatch.shape.setShape([...dataset])
219+
dispatch.shape.setShapesGraph([...dataset])
202220
}
203221
},
204222
}

demos/lit-html/src/state/store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const store = (() => {
1818
quads: [],
1919
dataset: undefined,
2020
options,
21+
shapes: [],
2122
},
2223
resource: {
2324
format: resource.format,

packages/core/models/forms/effects/resources/setRoot.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default function (store: Store) {
88
const { forms, editors, shapes, resources } = store.getState()
99
const formState = forms.get(form)
1010
const graph = resources.get(form)?.graph
11+
const shapesState = shapes.get(form)
1112
if (!graph || !formState) {
1213
return
1314
}
@@ -23,7 +24,8 @@ export default function (store: Store) {
2324
focusNode: rootPointer,
2425
editors,
2526
shouldEnableEditorChoice: formState.shouldEnableEditorChoice,
26-
shapes: shapes.get(form)?.shapes || [],
27+
shapes: shapesState?.shapes || [],
28+
shape: shapesState?.preferredRootShape,
2729
replaceStack: true,
2830
})
2931
return
@@ -37,7 +39,7 @@ export default function (store: Store) {
3739
form,
3840
focusNode,
3941
editors,
40-
shapes: shapes.get(form)?.shapes || [],
42+
shapes: shapesState?.shapes || [],
4143
shouldEnableEditorChoice: formState.shouldEnableEditorChoice,
4244
})
4345
}

packages/core/models/forms/effects/shapes/setGraph.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,23 @@ export default function setGraph(store: Store) {
1010
return ({ form }: SetShapesGraphParams) => {
1111
const { editors, forms, shapes } = store.getState()
1212
const formState = forms.get(form)
13-
const shapesGraph = shapes.get(form)?.shapesGraph
13+
const shapesState = shapes.get(form)
1414
const graph = store.getState().resources.get(form)?.graph
1515
if (!graph || !formState) {
1616
return
1717
}
1818

19-
if (previousShapes && previousShapes === shapesGraph) {
19+
if (previousShapes && previousShapes === shapesState?.shapesGraph) {
2020
return
2121
}
2222

23-
previousShapes = shapesGraph
23+
previousShapes = shapesState?.shapesGraph
2424
formState.focusStack.forEach((focusNode) => {
2525
dispatch.forms.createFocusNodeState({
2626
form,
2727
focusNode,
2828
editors,
29+
shape: shapesState?.preferredRootShape,
2930
shapes: matchShapes(shapes.get(form)?.shapes).to(focusNode),
3031
shouldEnableEditorChoice: formState.shouldEnableEditorChoice,
3132
})

packages/core/models/shapes/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { setGraph } from './reducers'
66
export interface ShapeState {
77
shapesGraph?: AnyPointer
88
shapes: NodeShape[]
9+
preferredRootShape?: NodeShape
910
}
1011

1112
export type State = Map<symbol, ShapeState>

packages/core/models/shapes/reducers.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,31 @@ export interface SetShapesGraphParams extends BaseParams {
1414
export const setGraph = formStateReducer((state: ShapeState, { shapesGraph }: SetShapesGraphParams) => produce(state, (draft) => {
1515
if ('dataset' in shapesGraph) {
1616
if (state.shapesGraph?.dataset === shapesGraph.dataset) {
17-
return
17+
const samePreferredRootShape = !state.preferredRootShape || (state.preferredRootShape && state.preferredRootShape.equals(shapesGraph.term as any))
18+
if (samePreferredRootShape) {
19+
return
20+
}
1821
}
1922
} else if (state.shapesGraph?.dataset === shapesGraph) {
2023
return
2124
}
2225

23-
const shapesPointer = 'match' in shapesGraph ? cf({ dataset: shapesGraph }) : shapesGraph.any()
26+
let preferredRootShape: Shape | undefined
27+
let shapesPointer: AnyPointer
28+
if ('match' in shapesGraph) {
29+
shapesPointer = cf({ dataset: shapesGraph })
30+
} else {
31+
shapesPointer = shapesGraph.any()
32+
if (shapesGraph.term) {
33+
preferredRootShape = RdfResource.factory.createEntity<Shape>(shapesGraph as any, [ShapeMixin])
34+
}
35+
}
36+
2437
const shapes = shapesPointer
2538
.has(rdf.type, [sh.Shape, sh.NodeShape])
2639
.map(pointer => RdfResource.factory.createEntity<Shape>(pointer, [ShapeMixin]))
2740

2841
draft.shapesGraph = shapesPointer
2942
draft.shapes = shapes
43+
draft.preferredRootShape = preferredRootShape
3044
}))

packages/core/test/models/shapes/reducers/setGraph.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import $rdf from 'rdf-ext'
44
import cf from 'clownface'
55
import ns from '@rdf-esm/namespace'
66
import { rdf, rdfs, sh } from '@tpluscode/rdf-ns-builders'
7+
import { NodeShapeMixin } from '@rdfine/shacl'
78
import { setGraph } from '../../../../models/shapes/reducers'
89
import { testStore } from '../../forms/util'
910

@@ -64,6 +65,46 @@ describe('models/shapes/reducers/setGraph', () => {
6465
expect(shapes.map(s => s.label)).to.include('Shape two')
6566
})
6667

68+
it('sets new preferred shape if it was selected from same dataset', () => {
69+
// given
70+
const { form, store } = testStore()
71+
const before = store.getState().shapes
72+
const shapesGraph = cf({ dataset: $rdf.dataset(), graph: ex.Graph })
73+
shapesGraph.node(ex.Shape1).addOut(rdf.type, sh.Shape).addOut(rdfs.label, 'Shape one')
74+
shapesGraph.node(ex.Shape2).addOut(rdf.type, sh.NodeShape).addOut(rdfs.label, 'Shape two')
75+
before.get(form)!.shapesGraph = shapesGraph
76+
before.get(form)!.preferredRootShape = new NodeShapeMixin.Class(shapesGraph.node(ex.Shape1))
77+
78+
// when
79+
const after = setGraph(before, {
80+
form,
81+
shapesGraph: shapesGraph.node(ex.Shape2),
82+
})
83+
84+
// then
85+
const { preferredRootShape } = after.get(form)!
86+
expect(preferredRootShape?.id).to.deep.eq(ex.Shape2)
87+
})
88+
89+
it('sets preferred root shape is parameter is graph pointer', () => {
90+
// given
91+
const { form, store } = testStore()
92+
const before = store.getState().shapes
93+
const shapesGraph = cf({ dataset: $rdf.dataset(), graph: ex.Graph })
94+
shapesGraph.node(ex.Shape1).addOut(rdf.type, sh.Shape).addOut(rdfs.label, 'Shape one')
95+
shapesGraph.node(ex.Shape2).addOut(rdf.type, sh.NodeShape).addOut(rdfs.label, 'Shape two')
96+
97+
// when
98+
const after = setGraph(before, {
99+
form,
100+
shapesGraph: shapesGraph.node(ex.Shape2),
101+
})
102+
103+
// then
104+
const { preferredRootShape } = after.get(form)!
105+
expect(preferredRootShape?.id).to.deep.eq(ex.Shape2)
106+
})
107+
67108
it('extracts shape resources from graph', () => {
68109
// given
69110
const { form, store } = testStore()

0 commit comments

Comments
 (0)