1010 *
1111 */
1212import escape from 'escape-html'
13+ import uuid from 'node-uuid'
14+ import rdf from 'rdflib'
15+ const webClient = require ( 'solid-web-client' ) ( rdf )
1316
1417import { makeDropTarget } from './dragAndDrop'
1518import { errorMessageBlock } from './error'
1619import { iconBase } from '../iconBase'
1720import ns from '../ns'
18- import rdf from 'rdflib'
1921import kb from '../store'
2022
2123export 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
197226export 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
300358class Person {
0 commit comments