55// Without this dropping anything onto a browser page will cause chrome etc to jump to diff page
66// throwing away all the user's work.
77
8- /* global alert */
8+ /* global alert window */
99var UI = { }
1010
11+ UI . authn = require ( './signin' )
1112UI . acl = require ( './acl' )
1213UI . icons = require ( './iconBase' )
1314UI . ns = require ( './ns' )
@@ -81,11 +82,13 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
8182
8283 // A world button can be dragged to gve public access.
8384 // later, allow it to be pressed to make pubicly viewable?
84- var publicAccessCell = bottomRow . appendChild ( dom . createElement ( 'td' ) )
85- var publicAccessButton = publicAccessCell . appendChild ( UI . widgets . button ( dom , UI . icons . iconBase + 'noun_98053.svg' , 'Public ') )
86- UI . widgets . makeDraggable ( publicAccessButton , UI . ns . foaf ( 'Agent ') ) // Represent everyone
85+ var bottomLeftCell = bottomRow . appendChild ( dom . createElement ( 'td' ) )
86+ // var bottomMiddleCell = bottomRow .appendChild(dom.createElement('td '))
87+ var bottomRightCell = bottomRow . appendChild ( dom . createElement ( 'td ') )
8788
88- var bigButtonStyle = 'border-radius: 0.3em; background-color: white; border: 0.1em solid #888;'
89+ // var publicAccessButton = bottomLeftCell.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_98053.svg', 'Public'))
90+
91+ const bigButtonStyle = 'border-radius: 0.3em; background-color: white; border: 0.1em solid #888;'
8992
9093 // This is the main function which produces an editable access control.
9194 // There are two of these in all iff the defaults are separate
@@ -156,13 +159,18 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
156159 if ( uri . startsWith ( 'http' ) && uri . split ( '/' ) . length === 3 ) { // there is no third slash
157160 return { pred : 'origin' , obj : obj } // The only way to know an origin alas
158161 }
162+ // @@ This is an almighty kludge needed because drag and drop adds extra slashes to origins
163+ if ( uri . startsWith ( 'http' ) && uri . split ( '/' ) . length === 4 && uri . endsWith ( '/' ) ) { // there IS third slash
164+ console . log ( 'Assuming final slash on dragged origin URI was unintended!' )
165+ return { pred : 'origin' , obj : $rdf . sym ( uri . slice ( 0 , - 1 ) ) } // Fix a URI where the drag and drop system has added a spurious slash
166+ }
159167
160168 if ( ns . vcard ( 'WebID' ) . uri in types ) return { pred : 'agent' , obj : obj }
161169
162170 if ( ns . vcard ( 'Group' ) . uri in types ) {
163171 return { pred : 'agentGroup' , obj : obj } // @@ note vcard membership not RDFs
164172 }
165- if ( obj . sameTerm ( ns . foaf ( 'Agent' ) ) ||
173+ if ( obj . sameTerm ( ns . foaf ( 'Agent' ) ) || obj . sameTerm ( ns . acl ( 'AuthenticatedAgent' ) ) || // AuthenticatedAgent
166174 obj . sameTerm ( ns . rdf ( 'Resource' ) ) || obj . sameTerm ( ns . owl ( 'Thing' ) ) ) {
167175 return { pred : 'agentClass' , obj : obj }
168176 }
@@ -328,9 +336,14 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
328336 }
329337
330338 var res = agentTriage ( u ) // eg 'agent', 'origin', agentClass'
339+ const thing = $rdf . sym ( u )
331340 if ( ! res ) {
332- console . log ( ' looking up dropped thing ' + u )
333- await kb . fetcher . load ( u )
341+ console . log ( ' Not obvious: looking up dropped thing ' + thing )
342+ try {
343+ await kb . fetcher . load ( thing . doc ( ) )
344+ } catch ( err ) {
345+ console . log ( 'Ignore error looking up dropped thing: ' + err )
346+ }
334347 res = agentTriage ( u )
335348 if ( ! res ) {
336349 console . log ( ' Error: Drop fails to drop appropriate thing! ' + u )
@@ -342,10 +355,18 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
342355 }
343356 } // handleOneDroppedURI
344357
358+ async function addNewUIRI ( uri ) {
359+ await handleOneDroppedURI ( uri )
360+ saveAndRestoreUI ( )
361+ }
362+
345363 if ( options . modify ) {
364+ row . addNewURI = addNewUIRI
346365 UI . widgets . makeDropTarget ( row , handleManyDroppedURIs )
347366 }
348- }
367+ return row
368+ } // renderCombo
369+
349370 var syncPanel = function ( ) {
350371 var kids = box . children
351372 for ( var i = 0 ; i < kids . length ; i ++ ) {
@@ -355,13 +376,160 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
355376 } // @@ later -- need to addd combos not in the box?
356377 }
357378
358- var k , combo
379+ function renderAdditionTool ( ele , lastRow ) {
380+ const ns = UI . ns
381+ function removeOthers ( button ) {
382+ button . keep = true
383+ button . parentNode . keep = true
384+ var removeThese = [ ]
385+ for ( var ele of bar . children ) {
386+ if ( ! ele . keep ) removeThese . push ( ele )
387+ }
388+ removeThese . forEach ( e => bar . removeChild ( e ) )
389+ }
390+
391+ function removeBar ( ) {
392+ ele . removeChild ( ele . bar )
393+ ele . bar = null
394+ }
395+ if ( ele . bar ) { // toggle
396+ return removeBar ( )
397+ }
398+ const bar = ele . appendChild ( dom . createElement ( 'div' ) )
399+ ele . bar = bar
400+
401+ /** Buttons to add different types of theings to have access
402+ */
403+
404+ // Person
405+ bar . appendChild ( UI . widgets . button ( dom , UI . icons . iconBase + UI . widgets . iconForClass [ 'vcard:Individual' ] , 'Add Person' , async event => {
406+ removeOthers ( event . target )
407+ let name = await UI . widgets . askName ( dom , kb , bar , ns . vcard ( 'URI' ) , ns . vcard ( 'Individual' ) , 'person' )
408+ if ( ! name ) return removeBar ( ) // user cancelled
409+ const domainNameRegexp = / ^ h t t p s ? : / i
410+ if ( ! name . match ( domainNameRegexp ) ) { // @@ enforce in user input live like a form element
411+ return alert ( 'Not a http URI' )
412+ }
413+ // @@ check it actually is a person and has an owner who agrees they own it
414+ console . log ( 'Adding to ACL person: ' + name )
415+ await lastRow . addNewURI ( name )
416+ removeBar ( )
417+ } ) )
418+
419+ // Group
420+ bar . appendChild ( UI . widgets . button ( dom , UI . icons . iconBase + UI . widgets . iconForClass [ 'vcard:Group' ] , 'Add Group' , async event => {
421+ removeOthers ( event . target )
422+ let name = await UI . widgets . askName ( dom , kb , bar , ns . vcard ( 'URI' ) , ns . vcard ( 'Group' ) , 'group' )
423+ if ( ! name ) return removeBar ( ) // user cancelled
424+ const domainNameRegexp = / ^ h t t p s ? : / i
425+ if ( ! name . match ( domainNameRegexp ) ) { // @@ enforce in user input live like a form element
426+ return alert ( 'Not a http URI' )
427+ }
428+ // @@ check it actually is a group and has an owner who agrees they own it
429+ console . log ( 'Adding to ACL group: ' + name )
430+ await lastRow . addNewURI ( name )
431+ removeBar ( )
432+ } ) )
433+
434+ // General public
435+ bar . appendChild ( UI . widgets . button ( dom , UI . icons . iconBase + UI . widgets . iconForClass [ 'foaf:Agent' ] , 'Add Everyone' , async event => {
436+ statusBlock . textContent = 'Adding the general public to those who can read. Drag the globe to a different level to give them more access.'
437+ await lastRow . addNewURI ( ns . foaf ( 'Agent' ) . uri )
438+ removeBar ( )
439+ } ) )
440+
441+ // AuthenticatedAgent
442+ bar . appendChild ( UI . widgets . button ( dom , UI . icons . iconBase + 'noun_99101.svg' , 'Anyone logged In' , async event => {
443+ statusBlock . textContent = 'Adding the anyone logged in to those who can read. Drag the ID icon to a different level to give them more access.'
444+ await lastRow . addNewURI ( ns . acl ( 'AuthenticatedAgent' ) . uri )
445+ removeBar ( )
446+ } ) )
447+
448+ // Bots
449+ bar . appendChild ( UI . widgets . button ( dom , UI . icons . iconBase + 'noun_Robot_849764.svg' , 'A Software Agent (bot)' , async event => {
450+ removeOthers ( event . target )
451+ let name = await UI . widgets . askName ( dom , kb , bar , ns . vcard ( 'URI' ) , ns . schema ( 'Application' ) , 'bot' )
452+ if ( ! name ) return removeBar ( ) // user cancelled
453+ const domainNameRegexp = / ^ h t t p s ? : / i
454+ if ( ! name . match ( domainNameRegexp ) ) { // @@ enforce in user input live like a form element
455+ return alert ( 'Not a http URI' )
456+ }
457+ // @@ check it actually is a bot and has an owner who agrees they own it
458+ console . log ( 'Adding to ACL bot: ' + name )
459+ await lastRow . addNewURI ( name )
460+ removeBar ( )
461+ } ) )
462+
463+ // Web Apps
464+ bar . appendChild ( UI . widgets . button ( dom , UI . icons . iconBase + 'noun_15177.svg' , 'A Web App (origin)' , async event => {
465+ removeOthers ( event . target )
466+ var context = { div : bar , dom}
467+ await UI . authn . logInLoadProfile ( context )
468+ var trustedApps = kb . each ( context . me , ns . acl ( 'trustedApp' ) )
469+ var trustedOrigins = trustedApps . flatMap ( app => kb . each ( app , ns . acl ( 'origin' ) ) )
470+
471+ bar . appendChild ( dom . createElement ( 'p' ) ) . textContent = `You have ${ trustedOrigins . length } selected web apps.`
472+ var table = bar . appendChild ( dom . createElement ( 'table' ) )
473+ trustedApps . forEach ( app => {
474+ const origin = kb . any ( app , ns . acl ( 'origin' ) )
475+ var thingTR = UI . widgets . personTR ( dom , ns . acl ( 'origin' ) , origin , { } )
476+ var innerTable = dom . createElement ( 'table' )
477+ var innerRow = innerTable . appendChild ( dom . createElement ( 'tr' ) )
478+ var innerLeft = innerRow . appendChild ( dom . createElement ( 'td' ) )
479+ var innerMiddle = innerRow . appendChild ( dom . createElement ( 'td' ) )
480+ var innerRight = innerRow . appendChild ( dom . createElement ( 'td' ) )
481+ innerLeft . appendChild ( thingTR )
482+ innerMiddle . textContent = 'Give access to ' + noun + ' ' + UI . utils . label ( subject ) + '?'
483+ innerRight . appendChild ( UI . widgets . continueButton ( dom , async event => {
484+ await lastRow . addNewURI ( origin . uri )
485+ } ) )
486+ table . appendChild ( innerTable )
487+ } )
488+ table . style = 'margin: em; background-color: #eee;'
489+
490+ // Add the Trusted App pane for managing you set of apps
491+ var trustedAppControl = window . panes . trustedApplications . render ( context . me , dom , { } )
492+ trustedAppControl . style . borderColor = 'orange'
493+ trustedAppControl . style . borderWidth = '0.1em'
494+ trustedAppControl . style . borderRadius = '1em'
495+ bar . appendChild ( trustedAppControl )
496+ const cancel = UI . widgets . cancelButton ( dom , ( ) => bar . removeChild ( trustedAppControl ) )
497+ trustedAppControl . insertBefore ( cancel , trustedAppControl . firstChild )
498+ cancel . style . float = 'right'
499+
500+ let name = await UI . widgets . askName ( dom , kb , bar , null , ns . schema ( 'WebApplication' ) , 'webapp domain' ) // @@ hack
501+ if ( ! name ) return removeBar ( ) // user cancelled
502+ const domainNameRegexp = / ^ ( [ a - z 0 - 9 ] + ( - [ a - z 0 - 9 ] + ) * \. ) + [ a - z ] { 2 , } $ / i
503+ // https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s15.html
504+ if ( ! name . match ( domainNameRegexp ) ) { // @@ enforce in user input live like a form element
505+ return alert ( 'Not a domain name' )
506+ }
507+ const origin = 'https://' + name
508+ console . log ( 'Adding to ACL origin: ' + origin )
509+ await lastRow . addNewURI ( origin )
510+ removeBar ( )
511+ } ) )
512+ }
513+
514+ function renderAddToolBar ( box , lastRow ) {
515+ // const toolRow = box.appendChild(dom.createElement('tr'))
516+ bottomLeftCell . appendChild ( UI . widgets . button ( dom , UI . icons . iconBase + 'noun_34653_green.svg' , 'Add ...' , event => {
517+ renderAdditionTool ( bottomLeftCell , lastRow )
518+ } ) )
519+ }
520+
521+ var k , combo , lastRow
359522 for ( k = 15 ; k > 0 ; k -- ) {
360523 combo = kToCombo ( k )
361524 if ( ( options . modify && recommended [ k ] ) || byCombo [ combo ] ) {
362- renderCombo ( byCombo , combo )
525+ lastRow = renderCombo ( byCombo , combo )
363526 } // if
364527 } // for
528+
529+ if ( options . modify ) {
530+ renderAddToolBar ( box , lastRow )
531+ }
532+
365533 return byCombo
366534 } // ACLControlEditable
367535
@@ -389,7 +557,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
389557 ACLControlEditable ( box , doc , targetACLDoc , kb2 , { modify : false } ) // Add btton to save them as actual
390558 box . style . cssText = 'color: #777;'
391559
392- var editPlease = bottomRow . appendChild ( dom . createElement ( 'button' ) )
560+ var editPlease = bottomRightCell . appendChild ( dom . createElement ( 'button' ) )
393561 editPlease . textContent = 'Set specific sharing\nfor this ' + noun
394562 editPlease . style . cssText = bigButtonStyle
395563 editPlease . addEventListener ( 'click' , function ( event ) {
@@ -399,9 +567,11 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
399567 statusBlock . textContent += ' (Error writing back access control file: ' + message + ')'
400568 } else {
401569 kb . add ( kb2 . statements )
570+ kb . fetcher . requested [ targetACLDoc . uri ] = 'done' // cheat
571+ // kb.fetcher.load(targetACLDoc, {force: true})
402572 statusBlock . textContent = ' (Now editing specific access for this ' + noun + ')'
403573 // box.style.cssText = 'color: black;'
404- bottomRow . removeChild ( editPlease )
574+ bottomRightCell . removeChild ( editPlease )
405575 renderBox ( )
406576 }
407577 } )
@@ -410,7 +580,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
410580 } else { // Not using defaults
411581 var useDefault
412582 var addDefaultButton = function ( prospectiveDefaultHolder ) {
413- useDefault = bottomRow . appendChild ( dom . createElement ( 'button' ) )
583+ useDefault = bottomRightCell . appendChild ( dom . createElement ( 'button' ) )
414584 useDefault . textContent = 'Stop specific sharing for this ' + noun +
415585 ' -- just use default' // + UI.utils.label(thisDefaultHolder)
416586 if ( prospectiveDefaultHolder ) {
0 commit comments