Skip to content

Commit a9bf0a3

Browse files
committed
Make node label editable
1 parent 9372657 commit a9bf0a3

File tree

17 files changed

+412
-112
lines changed

17 files changed

+412
-112
lines changed

src/features/math/Statement.tsx

Lines changed: 123 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import React from 'react'
2-
import { highlight, select, selectSelectedNode } from './mathSlice'
3-
import Math from './Math'
1+
import React, { useEffect, useState } from 'react'
42
import { useAppDispatch, useAppSelector } from '../../app/hooks'
3+
import Math from './Math'
4+
import { highlight, select, selectSelectedNode, updateNode } from './mathSlice'
55
import { GraphNode } from './types'
66

77
interface NodeListProps {
@@ -37,6 +37,125 @@ const NodeList: React.FC<NodeListProps> = ({ title, nodes }: NodeListProps) => {
3737
)
3838
}
3939

40+
const NodeLabelEdit = ({
41+
node,
42+
onFinish,
43+
}: {
44+
node: GraphNode
45+
onFinish: () => void
46+
}) => {
47+
const [label, setLabel] = useState(node.label.en)
48+
const [type, setType] = useState(node.type)
49+
const [editType, setEditType] = useState(false)
50+
const dispatch = useAppDispatch()
51+
52+
useEffect(() => {
53+
setLabel(node.label.en)
54+
setType(node.type)
55+
}, [node])
56+
57+
const availableTypes: GraphNode['type'][] = ['definition', 'statement']
58+
59+
const handleCancel = () => {
60+
onFinish()
61+
setEditType(false)
62+
setLabel(node.label.en)
63+
setType(node.type)
64+
}
65+
66+
const handleSelectTag = (type: GraphNode['type']) => {
67+
setEditType(false)
68+
setType(type)
69+
}
70+
71+
const handleSave = () => {
72+
dispatch(
73+
updateNode({
74+
type,
75+
label: { en: label },
76+
id: node.uri,
77+
document: node.document.id,
78+
}),
79+
)
80+
handleCancel()
81+
}
82+
83+
return (
84+
<>
85+
<p className="card-header-title">
86+
<input
87+
className="input is-small"
88+
type="text"
89+
placeholder="label"
90+
value={label}
91+
onChange={e => setLabel(e.target.value)}
92+
size={10}
93+
/>
94+
<div className="tags">
95+
{editType ? (
96+
availableTypes.map(type => (
97+
<span
98+
key={type}
99+
className="tag is-info is-light"
100+
onClick={() => handleSelectTag(type)}
101+
>
102+
{type}
103+
</span>
104+
))
105+
) : (
106+
<span className="tag is-info">
107+
{type}
108+
<button
109+
className="delete is-small"
110+
onClick={() => setEditType(true)}
111+
></button>
112+
</span>
113+
)}
114+
</div>
115+
</p>
116+
<button
117+
className="card-header-icon"
118+
aria-label="cancel editing"
119+
title="cancel editing"
120+
onClick={handleCancel}
121+
>
122+
<i className="icon icon-cancel has-text-danger" aria-hidden="true"></i>
123+
</button>
124+
<button
125+
className="card-header-icon"
126+
aria-label="save changes"
127+
title="save changes"
128+
onClick={handleSave}
129+
>
130+
<i className="icon icon-ok has-text-success" aria-hidden="true"></i>
131+
</button>
132+
</>
133+
)
134+
}
135+
136+
const NodeLabel = ({ node }: { node: GraphNode }) => {
137+
const [edit, setEdit] = useState(false)
138+
return edit ? (
139+
<NodeLabelEdit node={node} onFinish={() => setEdit(false)} />
140+
) : (
141+
<>
142+
<p className="card-header-title">
143+
<a href={node.uri}>{node.label.en}</a>
144+
<span className="tag is-info">{node.type}</span>
145+
</p>
146+
{node.document.access.user.write && (
147+
<button
148+
className="card-header-icon"
149+
aria-label="edit label"
150+
onClick={() => setEdit(true)}
151+
>
152+
<i className="icon icon-edit" aria-hidden="true"></i>
153+
</button>
154+
)}
155+
</>
156+
)
157+
}
158+
40159
const Statement = () => {
41160
const dispatch = useAppDispatch()
42161
const onSelectNode = (uri: string) => dispatch(select(uri))
@@ -48,9 +167,7 @@ const Statement = () => {
48167
return (
49168
<div className="card">
50169
<header className="card-header">
51-
<p className="card-header-title">
52-
<a href={node.uri}>{node.label.en}</a>
53-
</p>
170+
<NodeLabel node={node} />
54171
<span className="card-header-icon">
55172
<button
56173
className="delete"

src/features/math/mathAPI.ts

Lines changed: 91 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,111 @@ import {
33
getSolidDataset,
44
getTerm,
55
getTermAll,
6+
getThing,
67
getThingAll,
8+
saveSolidDatasetAt,
9+
setStringWithLocale,
10+
setThing,
11+
setUrl,
12+
Thing,
13+
ThingPersisted,
714
UrlString,
815
} from '@inrupt/solid-client'
916
import { fetch } from '@inrupt/solid-client-authn-browser'
1017
import { rdf, rdfs } from 'rdf-namespaces'
11-
import { Definition, Statement } from './types'
18+
import { Definition, PartialNode, Statement } from './types'
1219

1320
interface Graph {
1421
nodes: (Definition | Statement)[]
1522
links: [string, string][]
1623
}
1724

18-
export const fetchGraph = async (uri: UrlString): Promise<Graph> => {
19-
const dataset = await getSolidDataset(uri, { fetch })
25+
const term = {
26+
definition: 'https://terms.math.livegraph.org#Definition',
27+
statement: 'https://terms.math.livegraph.org#Statement',
28+
dependsOn: 'https://terms.math.livegraph.org#dependsOn',
29+
}
30+
31+
export const fetchGraph = async (documentUri: UrlString): Promise<Graph> => {
32+
const dataset = await getSolidDataset(documentUri, { fetch })
2033
const things = getThingAll(dataset)
2134
const graph: Graph = { nodes: [], links: [] }
2235
things.forEach(thing => {
23-
const uri = asUrl(thing)
24-
const type = getTerm(thing, rdf.type)?.value ?? ''
25-
const description = getTerm(thing, rdf.value)?.value ?? ''
26-
const label = getTerm(thing, rdfs.label)?.value ?? ''
27-
switch (type) {
28-
case 'https://terms.math.livegraph.org#Definition':
29-
graph.nodes.push({
30-
id: uri,
31-
type: 'definition',
32-
label: { en: label },
33-
description: { en: description },
34-
dependencies: [],
35-
dependents: [],
36-
examples: [],
37-
created: 0,
38-
updated: 0,
39-
})
40-
break
41-
case 'https://terms.math.livegraph.org#Statement':
42-
graph.nodes.push({
43-
id: uri,
44-
type: 'statement',
45-
label: { en: label },
46-
description: { en: description },
47-
dependencies: [],
48-
dependents: [],
49-
examples: [],
50-
proofs: [],
51-
created: 0,
52-
updated: 0,
53-
})
54-
break
55-
default:
56-
break
57-
}
58-
getTermAll(thing, 'https://terms.math.livegraph.org#dependsOn').forEach(
59-
dependency => {
60-
graph.links.push([uri, dependency.value])
61-
},
62-
)
36+
const node = thingToNode(thing, documentUri)
37+
graph.nodes.push(node)
38+
getTermAll(thing, term.dependsOn).forEach(dependency => {
39+
graph.links.push([node.id, dependency.value])
40+
})
6341
})
6442
return graph
6543
}
44+
45+
export const updateNode = async (
46+
node: PartialNode,
47+
): Promise<Definition | Statement> => {
48+
// we want to save any partial data that are provided
49+
// so how do we do it?
50+
51+
// save a label
52+
53+
const dataset = await getSolidDataset(node.document, { fetch })
54+
const thing = getThing(dataset, node.id) as ThingPersisted
55+
if (thing) {
56+
let newThing = thing
57+
if (node.label) {
58+
newThing = setStringWithLocale(newThing, rdfs.label, node.label.en, 'en')
59+
}
60+
61+
if (node.type) {
62+
newThing = setUrl(newThing, rdf.type, term[node.type])
63+
}
64+
65+
if (newThing !== thing) {
66+
const newDataset = setThing(dataset, newThing)
67+
await saveSolidDatasetAt(node.document, newDataset, { fetch })
68+
}
69+
return thingToNode(newThing, node.document)
70+
}
71+
throw new Error('node to update not found')
72+
}
73+
74+
const thingToNode = (
75+
thing: Thing,
76+
document: string,
77+
): Definition | Statement => {
78+
const uri = asUrl(thing)
79+
const type = getTerm(thing, rdf.type)?.value ?? ''
80+
const description = getTerm(thing, rdf.value)?.value ?? ''
81+
const label = getTerm(thing, rdfs.label)?.value ?? ''
82+
switch (type) {
83+
case term.definition:
84+
return {
85+
id: uri,
86+
type: 'definition',
87+
label: { en: label },
88+
description: { en: description },
89+
dependencies: [],
90+
dependents: [],
91+
examples: [],
92+
created: 0,
93+
updated: 0,
94+
document,
95+
}
96+
case term.statement:
97+
return {
98+
id: uri,
99+
type: 'statement',
100+
label: { en: label },
101+
description: { en: description },
102+
dependencies: [],
103+
dependents: [],
104+
examples: [],
105+
proofs: [],
106+
created: 0,
107+
updated: 0,
108+
document,
109+
}
110+
default:
111+
throw new Error('thing is not a Definition or Statement')
112+
}
113+
}

0 commit comments

Comments
 (0)