Skip to content

Commit 4da603f

Browse files
committed
Re-enable group builder, begin saving data
1 parent 78ad41a commit 4da603f

2 files changed

Lines changed: 76 additions & 16 deletions

File tree

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@
4444
"homepage": "https://github.com/linkeddata/solid-ui",
4545
"dependencies": {
4646
"escape-html": "^1.0.3",
47+
"node-uuid": "^1.4.7",
4748
"rdflib": "^0.12.1",
48-
"solid-client": "^0.22.4"
49+
"solid-client": "^0.22.4",
50+
"solid-web-client": "0.0.9"
4951
},
5052
"devDependencies": {
5153
"babel-cli": "^6.18.0",

src/widgets/peoplePicker.js

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,22 @@
1010
*
1111
*/
1212
import escape from 'escape-html'
13+
import uuid from 'node-uuid'
14+
import rdf from 'rdflib'
15+
const webClient = require('solid-web-client')(rdf)
1316

1417
import { makeDropTarget } from './dragAndDrop'
1518
import { errorMessageBlock } from './error'
1619
import { iconBase } from '../iconBase'
1720
import ns from '../ns'
18-
import rdf from 'rdflib'
1921
import kb from '../store'
2022

2123
export class PeoplePicker {
2224
constructor (element, typeIndexUrl, groupPickedCb, selectedGroupNode) {
2325
this.element = element
2426
this.typeIndexUrl = typeIndexUrl
2527
this.groupPickedCb = groupPickedCb
26-
this.selectedGroupNode
28+
this.selectedGroupNode = selectedGroupNode
2729
this.onSelectGroup = this.onSelectGroup.bind(this)
2830
}
2931

@@ -38,7 +40,7 @@ export class PeoplePicker {
3840
const selectedGroup = document.createElement('div')
3941
new Group(selectedGroup, this.selectedGroupNode).render()
4042
const changeGroupButton = document.createElement('button')
41-
changeGroupButton.textContent = escape('Choose another group')
43+
changeGroupButton.textContent = escape('Change group')
4244
changeGroupButton.addEventListener('click', event => {
4345
this.selectedGroupNode = null
4446
this.render()
@@ -57,10 +59,20 @@ export class PeoplePicker {
5759
const createNewGroupButton = document.createElement('button')
5860
createNewGroupButton.textContent = escape('Create a new group')
5961
createNewGroupButton.addEventListener('click', event => {
60-
console.log('cannot yet render group builder')
61-
// const groupBuilder = new GroupBuilder(
62-
// this.element,
63-
// ).render()
62+
this.createNewGroup(bookBaseUrl)
63+
.then(({groupNode, graphNode}) => {
64+
new GroupBuilder(
65+
this.element,
66+
graphNode,
67+
groupNode,
68+
this.onSelectGroup
69+
).render()
70+
})
71+
.catch(errorBody => {
72+
this.element.appendChild(
73+
errorMessageBlock(document, escape(`Error creating a new group. (${errorBody})`))
74+
)
75+
})
6476
})
6577

6678
container.appendChild(chooseExistingGroupButton)
@@ -82,11 +94,6 @@ export class PeoplePicker {
8294
}
8395

8496
findGroupIndex (typeIndexUrl) {
85-
// steps:
86-
// - load the type index
87-
// - find the location of the vcard:AddressBook (e.g.
88-
// /Contacts/book.ttl#this)
89-
// - groups are stored in container $bookURI/Group/
9097
return new Promise((resolve, reject) => {
9198
kb.fetcher.nowOrWhenFetched(typeIndexUrl, (ok, err) => {
9299
if (!ok) {
@@ -116,6 +123,28 @@ export class PeoplePicker {
116123
})
117124
}
118125

126+
createNewGroup (bookBaseUrl) {
127+
const groupIndexNode = rdf.namedNode(`${bookBaseUrl}groups.ttl`)
128+
const graphUrl = `${bookBaseUrl}Group/${uuid.v4().slice(0, 8)}.ttl`
129+
const groupGraphNode = rdf.namedNode(graphUrl)
130+
const groupNode = rdf.namedNode(`${graphUrl}#this`)
131+
// NOTE that order matters here. Unfortunately this type of update is
132+
// non-atomic in that solid requires us to send two PATCHes, either of which
133+
// might fail.
134+
const patchPromises = [groupGraphNode, groupIndexNode]
135+
.map(namedGraph => {
136+
const typeStatement = rdf.st(groupNode, ns.rdf('type'), ns.vcard('Group'), namedGraph)
137+
const nameStatement = rdf.st(groupNode, ns.vcard('fn'), 'Untitled Group', namedGraph)
138+
return webClient.patch(namedGraph.value, [], [typeStatement, nameStatement])
139+
.then(() => kb.add([typeStatement, nameStatement]))
140+
})
141+
return Promise.all(patchPromises)
142+
.then(() => ({groupNode, groupGraphNode}))
143+
.catch(err => {
144+
throw new Error(`Couldn't create new group. PATCH failed for (${err.xhr.responseURL})`)
145+
})
146+
}
147+
119148
onSelectGroup (groupNode) {
120149
this.selectedGroupNode = groupNode
121150
this.render()
@@ -140,7 +169,7 @@ export class GroupPicker {
140169
groupContainer.style.display = 'flex'
141170
const groupButton = document.createElement('button')
142171
groupButton.addEventListener('click', this.handleClickGroup(group))
143-
const groupComponent = new Group(groupButton, group).render()
172+
new Group(groupButton, group).render()
144173
groupContainer.appendChild(groupButton)
145174
container.appendChild(groupContainer)
146175
})
@@ -195,7 +224,7 @@ export class Group {
195224
}
196225

197226
export class GroupBuilder {
198-
constructor (element, groupGraph, groupNode, groupChangedCb) {
227+
constructor (element, groupGraph, groupNode, doneBuildingCb, groupChangedCb) {
199228
this.element = element
200229
this.groupGraph = groupGraph
201230
this.groupNode = groupNode
@@ -205,6 +234,7 @@ export class GroupBuilder {
205234
}
206235
}
207236
this.groupChangedCb = groupChangedCb
237+
this.doneBuildingCb = doneBuildingCb
208238
}
209239

210240
refresh () {
@@ -222,14 +252,25 @@ export class GroupBuilder {
222252
makeDropTarget(dropContainer, uris => {
223253
uris.map(uri => {
224254
this.add(uri)
225-
.catch(err => {
255+
.catch(() => {
226256
this.element.appendChild(
227257
errorMessageBlock(document, escape('Could not load the given WebId'))
228258
)
229259
})
230260
})
231261
})
232262

263+
const groupNameInput = document.createElement('input')
264+
groupNameInput.type = 'text'
265+
groupNameInput.value = getWithDefault(this.groupNode, ns.vcard('fn'), 'Untitled Group')
266+
groupNameInput.addEventListener('input', event => {
267+
this.setGroupName(event.target.value)
268+
})
269+
const groupNameLabel = document.createElement('label')
270+
groupNameLabel.textContent = escape('Group Name:')
271+
groupNameLabel.appendChild(groupNameInput)
272+
dropContainer.appendChild(groupNameLabel)
273+
233274
if (kb.any(this.groupNode, ns.vcard('hasMember'))) {
234275
kb.match(this.groupNode, ns.vcard('hasMember'))
235276
.forEach(statement => {
@@ -246,6 +287,13 @@ export class GroupBuilder {
246287
dropContainer.appendChild(copy)
247288
}
248289

290+
const doneBuildingButton = document.createElement('button')
291+
doneBuildingButton.textContent = escape('Done')
292+
doneBuildingButton.addEventListener('click', event => {
293+
this.doneBuildingCb(this.groupNode)
294+
})
295+
dropContainer.appendChild(doneBuildingButton)
296+
249297
this.element.innerHTML = ''
250298
this.element.appendChild(dropContainer)
251299
return this
@@ -269,6 +317,7 @@ export class GroupBuilder {
269317
const statement = [this.groupNode, ns.vcard('hasMember'), webIdNode, this.groupGraph]
270318
if (kb.match(...statement).length < 1) {
271319
kb.add(...statement)
320+
// TODO: sync
272321
}
273322
this.onGroupChanged(null, 'added', webIdNode)
274323
resolve(webIdNode)
@@ -282,6 +331,7 @@ export class GroupBuilder {
282331
return event => {
283332
try {
284333
kb.remove(rdf.st(this.groupNode, ns.vcard('hasMember'), webIdNode, this.groupGraph))
334+
// TODO: sync
285335
this.onGroupChanged(null, 'removed', webIdNode)
286336
} catch (err) {
287337
const name = kb.any(webIdNode, ns.foaf('name'))
@@ -295,6 +345,14 @@ export class GroupBuilder {
295345
return true
296346
}
297347
}
348+
349+
setGroupName (name) {
350+
kb.match(this.groupNode, ns.vcard('fn')).forEach(st => {
351+
kb.remove(st)
352+
kb.add(this.groupNode, ns.vcard('fn'), rdf.literal(name), st.why)
353+
// TODO: sync
354+
})
355+
}
298356
}
299357

300358
class Person {

0 commit comments

Comments
 (0)