UNPKG

meeting-pane

Version:

Solid-compatible Panes: meeting collaborative tool

1,221 lines (1,133 loc) 42.8 kB
/* Meeting materials and tools Pane ** ** Putting together some of the tools we have to manage a Meeting */ // const VideoRoomPrefix = 'https://appear.in/' const logic = require('solid-logic') const VideoRoomPrefix = 'https://meet.jit.si/' const UI = require('solid-ui') const ns = UI.ns const $rdf = require('rdflib') const meetingDetailsFormText = require('./meetingDetailsForm.js') module.exports = { icon: UI.icons.iconBase + 'noun_66617.svg', name: 'meeting', audience: [ns.solid('PowerUser')], label: function (subject, context) { var kb = context.session.store var ns = UI.ns if (kb.holds(subject, ns.rdf('type'), ns.meeting('Meeting'))) { return 'Meeting' } return null // Suppress pane otherwise }, // Create a new Meeting thing // // returns: A promise of a meeting object // mintClass: UI.ns.meeting('Meeting'), mintNew: function (context, options) { return new Promise(function (resolve, reject) { var kb = context.session.store var ns = UI.ns options.newInstance = options.newInstance || kb.sym(options.newBase + 'index.ttl#this') var meeting = options.newInstance var meetingDoc = meeting.doc() var me = logic.authn.currentUser() if (me) { kb.add(meeting, ns.dc('author'), me, meetingDoc) } kb.add(meeting, ns.rdf('type'), ns.meeting('Meeting'), meetingDoc) kb.add(meeting, ns.dc('created'), new Date(), meetingDoc) kb.add( meeting, ns.ui('backgroundColor'), new $rdf.Literal('#ddddcc', undefined, ns.xsd('color')), meetingDoc ) var toolList = new $rdf.Collection() kb.add(meeting, ns.meeting('toolList'), toolList, meetingDoc) toolList.elements.push(meeting) // Add the meeting itself - see renderMain() kb.updater.put( meetingDoc, kb.statementsMatching(undefined, undefined, undefined, meetingDoc), 'text/turtle', function (uri2, ok, message) { if (ok) { resolve(options) } else { reject(new Error('Error writing meeting configuration: ' + message)) } } ) }) }, // Returns a div render: function (subject, dataBrowserContext) { const dom = dataBrowserContext.dom var kb = dataBrowserContext.session.store var ns = UI.ns var updater = kb.updater var thisPane = this var complain = function complain (message, color) { console.log(message) var pre = dom.createElement('pre') pre.setAttribute('style', 'background-color: ' + color || '#eed' + ';') div.appendChild(pre) pre.appendChild(dom.createTextNode(message)) } var complainIfBad = function (ok, message) { if (!ok) complain(message) } var meeting = subject var meetingDoc = subject.doc() var meetingBase = subject.dir().uri var div = dom.createElement('div') var table = div.appendChild(dom.createElement('table')) table.style = 'width: 100%; height: 100%; margin:0;' var topTR = table.appendChild(dom.createElement('tr')) topTR.appendChild(dom.createElement('div')) // topDiv var mainTR = table.appendChild(dom.createElement('tr')) var toolBar0 = table.appendChild(dom.createElement('td')) var toolBar1 = toolBar0.appendChild(dom.createElement('table')) var toolBar = toolBar1.appendChild(dom.createElement('tr')) topTR.setAttribute('style', 'height: 2em;') // spacer if notthing else var me = null // @@ Put code to find out logged in person var saveBackMeetingDoc = function () { updater.put( meetingDoc, kb.statementsMatching(undefined, undefined, undefined, meetingDoc), 'text/turtle', function (uri2, ok, message) { if (ok) { tabs.refresh() resetTools() } else { message = 'FAILED to save new thing at: ' + meetingDoc + ' : ' + message complain(message) } } ) } var saveAppDocumentLinkAndAddNewThing = function (tool, thing, pred) { var appDoc = thing.doc() if (pred) { kb.add(meeting, pred, thing, appDoc) // Specific Link back to meeting } kb.add(thing, ns.meeting('parentMeeting'), meeting, appDoc) // Generic link back to meeting updater.put( appDoc, kb.statementsMatching(undefined, undefined, undefined, appDoc), 'text/turtle', function (uri2, ok, message) { if (ok) { saveBackMeetingDoc() } else { complain('FAILED to save new tool at: ' + thing + ' : ' + message) } } ) } var makeToolNode = function (target, pred, label, iconURI) { if (pred) { kb.add(meeting, pred, target, meetingDoc) } var x = UI.widgets.newThing(meetingDoc) if (label) kb.add(x, ns.rdfs('label'), label, meetingDoc) if (iconURI) kb.add(x, ns.meeting('icon'), kb.sym(iconURI), meetingDoc) kb.add(x, ns.rdf('type'), ns.meeting('Tool'), meetingDoc) kb.add(x, ns.meeting('target'), target, meetingDoc) var toolList = kb.the(meeting, ns.meeting('toolList')) toolList.elements.push(x) return x } // Map from end-user non-iframeable Google maps URI to G Maps API // Input: like https://www.google.co.uk/maps/place/Mastercard/@53.2717971,-6.2042699,17z/... // Output: function googleMapsSpecial (page) { const initialPrefix = /https:\/\/www\.google\..*\/maps\// const finalPrefix = 'https://www.google.com/maps/embed/v1/' const myPersonalApiKEY = 'AIzaSyB8aaT6bY9tcLCmc2oPCkdUYLmTOWM8R54' // Get your own key! // GET YOUR KEY AT https://developers.google.com/maps/documentation/javascript/ const uri = page.uri if (!uri.match(initialPrefix)) return page if (uri.startsWith(finalPrefix)) return page // Already done const map = uri.replace(initialPrefix, finalPrefix) + '&key=' + myPersonalApiKEY console.log('Converted Google Map URI! ' + map) return $rdf.sym(map) } // //////////////////// DRAG and Drop var handleDroppedThing = function (target) { // @@ idea: look return new Promise(function (resolve) { // Add a meeting tab for a web resource. Alas many resource canot be framed // as they block framing, or are insecure. var addIframeTool = function (target) { var tool = makeToolNode( target, UI.ns.wf('attachment'), UI.utils.label(target), null ) kb.add(tool, UI.ns.meeting('view'), 'iframe', meetingDoc) } var addLink = function (target) { const pred = ns.wf('attachment') kb.add(subject, pred, target, subject.doc()) var toolObject = { icon: 'noun_160581.svg', // right arrow "link" limit: 1, shareTab: true // but many things behind it } var newPaneOptions = { newInstance: subject, // kb.sym(subject.doc().uri + '#LinkListTool'), pane: dataBrowserContext.session.paneRegistry.byName('link'), // the pane to be used to mint a new thing predicate: ns.meeting('attachmentTool'), tabTitle: 'Links', view: 'link', // The pane to be used when it is viewed noIndexHTML: true } return makeNewPaneTool(toolObject, newPaneOptions) } // When paerson added to he meeting, make an ad hoc group // of meeting participants is one does not already exist, and add them var addParticipant = function (target) { var pref = kb.any(target, ns.foaf('preferredURI')) var obj = pref ? kb.sym(pref) : target var group = kb.any(meeting, ns.meeting('attendeeGroup')) var addPersonToGroup = function (obj, group) { var ins = [ $rdf.st(group, UI.ns.vcard('hasMember'), obj, group.doc()) ] // @@@ Complex rules about webid? var name = kb.any(obj, ns.vcard('fn')) || kb.any(obj, ns.foaf('name')) if (name) { ins.push($rdf.st(obj, UI.ns.vcard('fn'), name, group.doc())) } kb.fetcher.nowOrWhenFetched(group.doc(), undefined, function ( ok, _body ) { if (!ok) { complain("Can't read group to add person" + group) return } kb.updater.update([], ins, function (uri, ok, body) { complainIfBad(ok, body) if (ok) { console.log('Addded to particpants OK: ' + obj) } }) }) } if (group) { addPersonToGroup(obj, group) return } makeParticipantsGroup() .then(function (options) { var group = options.newInstance addPersonToGroup(obj, group) kb.fetcher .putBack(meetingDoc, { contentType: 'text/turtle' }) .then(function (_xhr) { console.log('Particiants Group created: ' + group) }) }) .catch(function (err) { complain(err) }) } console.log('Dropped on thing ' + target) // icon was: UI.icons.iconBase + 'noun_25830.svg' var u = target.uri if (u.startsWith('http:') && u.indexOf('#') < 0) { // insecure Plain document addLink(target) return resolve(target) } kb.fetcher.nowOrWhenFetched(target, function (ok, mess) { function addAttachmentTab (target) { target = googleMapsSpecial(target) console.log('make web page attachement tab ' + target) // icon was: UI.icons.iconBase + 'noun_25830.svg' var tool = makeToolNode( target, UI.ns.wf('attachment'), UI.utils.label(target), null ) kb.add(tool, UI.ns.meeting('view'), 'iframe', meetingDoc) return resolve(target) } if (!ok) { console.log( 'Error looking up dropped thing, will just add it anyway. ' + target + ': ' + mess ) return addAttachmentTab(target) // You can still try iframing it. (Could also add to list of links in PersonTR widgets) } else { var obj = target var types = kb.findTypeURIs(obj) for (var ty in types) { console.log(' drop object type includes: ' + ty) } if ( ns.vcard('Individual').uri in types || ns.foaf('Person').uri in types || ns.foaf('Agent').uri in types ) { addParticipant(target) return resolve(target) } if (u.startsWith('https:') && u.indexOf('#') < 0) { // Plain secure document // can we iframe it? var hh = kb.fetcher.getHeader(target, 'x-frame-options') var ok2 = true if (hh) { for (var j = 0; j < hh.length; j++) { console.log('x-frame-options: ' + hh[j]) if (hh[j].indexOf('sameorigin') < 0) { // (and diff origin @@) ok2 = false } if (hh[j].indexOf('deny') < 0) { ok2 = false } } } if (ok2) { target = googleMapsSpecial(target) // tweak Google maps to embed OK addIframeTool(target) // Something we can maybe iframe return resolve(target) } } // Something we cannot iframe, and must link to: console.log('Default: assume web page attachement ' + target) // icon was: UI.icons.iconBase + 'noun_25830.svg' return addAttachmentTab(target) } }) }) // promise } // When a set of URIs are dropped on the tabs var droppedURIHandler = function (uris) { Promise.all( uris.map(function (u) { var target = $rdf.sym(u) // Attachment needs text label to disinguish I think not icon. return handleDroppedThing(target) // can add to meetingDoc but must be sync }) ).then(function (_a) { saveBackMeetingDoc() }) } var droppedFileHandler = function (files) { UI.widgets.uploadFiles( kb.fetcher, files, meeting.dir().uri + 'Files', meeting.dir().uri + 'Pictures', function (theFile, _destURI) { if (theFile.type.startsWith('image/')) { makePicturesFolder('Files') // If necessary } else { makeMaterialsFolder('Pictures') } } ) } // ////////////////////////////////////////////////////// end of drag drop var makeGroup = function (_toolObject) { var newBase = meetingBase + 'Group/' var kb = dataBrowserContext.session.store var group = kb.any(meeting, ns.meeting('particpants')) if (!group) { group = $rdf.sym(newBase + 'index.ttl#this') } console.log('Participant group: ' + group) var tool = makeToolNode( group, ns.meeting('particpants'), 'Particpants', UI.icons.iconBase + 'noun_339237.svg' ) // group: noun_339237.svg 'noun_15695.svg' kb.add(tool, UI.ns.meeting('view'), 'peoplePicker', meetingDoc) saveBackMeetingDoc() } /* var makeAddressBook = function (toolObject) { var newBase = meetingBase + 'Group/' var kb = store var group = kb.any(meeting, ns.meeting('addressBook')) if (!group) { group = $rdf.sym(newBase + 'index.ttl#this') } // Create a tab for the addressbook var div = dom.createElement('div') var context = { dom: dom, div: div } var book UI.login.findAppInstances(context, ns.vcard('AddressBook')).then( function (context) { if (context.instances.length === 0) { complain('You have no solid address book. It is really handy to have one to keep track of people and groups') } else if (context.instances.length > 1) { var s = context.instances.map(function (x) { return '' + x }).join(', ') complain('You have more than one solid address book: ' + s + ' Not supported yet.') } else { // addressbook book = context.instances[0] var tool = makeToolNode(book, ns.meeting('addressBook'), 'Address Book', UI.icons.iconBase + 'noun_15695.svg') // group: noun_339237.svg kb.add(tool, UI.ns.meeting('view'), 'contact', meetingDoc) saveBackMeetingDoc() } } ) } */ var makePoll = function (toolObject) { var newPaneOptions = { useExisting: meeting, // Regard the meeting as being the schedulable event itself. // newInstance: meeting, pane: dataBrowserContext.session.paneRegistry.byName('schedule'), view: 'schedule', // predicate: ns.meeting('schedulingPoll'), // newBase: meetingBase + 'Schedule/', Not needed as uses existing meeting tabTitle: 'Schedule poll', noIndexHTML: true } return makeNewPaneTool(toolObject, newPaneOptions) } var makePicturesFolder = function (folderName) { var toolObject = { icon: 'noun_598334.svg', // Slideshow @@ find a "picture" icon? limit: 1, shareTab: true // but many things behind it } var newPaneOptions = { newInstance: kb.sym(meeting.dir().uri + folderName + '/'), pane: dataBrowserContext.session.paneRegistry.byName('folder'), // @@ slideshow?? predicate: ns.meeting('pictures'), shareTab: true, tabTitle: folderName, view: 'slideshow', noIndexHTML: true } return makeNewPaneTool(toolObject, newPaneOptions) } var makeMaterialsFolder = function (_folderName) { var toolObject = { icon: 'noun_681601.svg', // Document limit: 1, shareTab: true // but many things behind it } var options = { newInstance: kb.sym(meeting.dir().uri + 'Files/'), pane: dataBrowserContext.session.paneRegistry.byName('folder'), predicate: ns.meeting('materialsFolder'), tabTitle: 'Materials', noIndexHTML: true } return makeNewPaneTool(toolObject, options) } var makeParticipantsGroup = function () { var toolObject = { icon: 'noun_339237.svg', // Group of people limit: 1, // Only one tab shareTab: true // but many things behind it } var options = { newInstance: kb.sym(meeting.dir().uri + 'Attendees/index.ttl#this'), pane: dataBrowserContext.session.paneRegistry.byName('contact'), predicate: ns.meeting('attendeeGroup'), tabTitle: 'Attendees', instanceClass: ns.vcard('Group'), instanceName: UI.utils.label(subject) + ' attendees', noIndexHTML: true } return makeNewPaneTool(toolObject, options) } // Make Pad for notes of meeting var makePad = function (toolObject) { var newPaneOptions = { newBase: meetingBase + 'SharedNotes/', predicate: UI.ns.meeting('sharedNotes'), tabTitle: 'Shared Notes', pane: dataBrowserContext.session.paneRegistry.byName('pad') } return makeNewPaneTool(toolObject, newPaneOptions) } // Make Sub-meeting of meeting var makeMeeting = function (toolObject) { UI.widgets .askName( dom, kb, parameterCell, ns.foaf('name'), UI.ns.meeting('Meeting') ) .then(function (name) { if (!name) { return resetTools() } var URIsegment = encodeURIComponent(name) var options = { newBase: meetingBase + URIsegment + '/', // @@@ sanitize predicate: UI.ns.meeting('subMeeting'), tabTitle: name, pane: dataBrowserContext.session.paneRegistry.byName('meeting') } return makeNewPaneTool(toolObject, options) }) .catch(function (e) { complain('Error making new sub-meeting: ' + e) }) } // Returns promise of newPaneOptions // In: options. // me?, predicate, newInstance ?, newBase, instanceClass // out: options. the above plus // me, newInstance function makeNewPaneTool (toolObject, options) { return new Promise(function (resolve, reject) { var kb = dataBrowserContext.session.store if (!options.useExisting) { // useExisting means use existing object in new role var existing = kb.any(meeting, options.predicate) if (existing) { if ( toolObject.limit && toolObject.limit === 1 && !toolObject.shareTab ) { complain( 'Already have ' + existing + ' as ' + UI.utils.label(options.predicate) ) complain('Cant have two') return resolve(null) } if (toolObject.shareTab) { // return existing one console.log( 'Using existing ' + existing + ' as ' + UI.utils.label(options.predicate) ) return resolve({ me: me, newInstance: existing, instanceClass: options.instanceClass }) } } } if (!me && !options.me) { reject(new Error('Username not defined for new tool')) } options.me = options.me || me options.newInstance = options.useExisting || options.newInstance || kb.sym(options.newBase + 'index.ttl#this') options.pane .mintNew(dataBrowserContext, options) .then(function (options) { var tool = makeToolNode( options.newInstance, options.predicate, options.tabTitle, options.pane.icon ) if (options.view) { kb.add(tool, UI.ns.meeting('view'), options.view, meetingDoc) } saveBackMeetingDoc() kb.fetcher .putBack(meetingDoc, { contentType: 'text/turtle' }) .then(function (_xhr) { resolve(options) }) .catch(function (err) { reject(err) }) }) .catch(function (err) { complain(err) reject(err) }) }) } var makeAgenda = function (_toolObject) { // selectTool(icon) } var makeActions = function (_toolObject) { var newBase = meetingBase + 'Actions/' var kb = dataBrowserContext.session.store if (kb.holds(meeting, ns.meeting('actions'))) { console.log('Ignored - already have actions') return // already got one } var appDoc = kb.sym(newBase + 'config.ttl') var newInstance = kb.sym(newBase + 'config.ttl#this') var stateStore = kb.sym(newBase + 'state.ttl') kb.add( newInstance, ns.dc('title'), (kb.anyValue(meeting, ns.cal('summary')) || 'Meeting ') + ' actions', appDoc ) kb.add(newInstance, ns.wf('issueClass'), ns.wf('Task'), appDoc) kb.add(newInstance, ns.wf('initialState'), ns.wf('Open'), appDoc) kb.add(newInstance, ns.wf('stateStore'), stateStore, appDoc) kb.add(newInstance, ns.wf('assigneeClass'), ns.foaf('Person'), appDoc) // @@ set to people in the meeting? kb.add(newInstance, ns.rdf('type'), ns.wf('Tracker'), appDoc) // Flag its type in the chat itself as well as in the master meeting config file kb.add(newInstance, ns.rdf('type'), ns.wf('Tracker'), appDoc) var tool = makeToolNode( newInstance, ns.meeting('actions'), 'Actions', UI.icons.iconBase + 'noun_17020.svg' ) saveAppDocumentLinkAndAddNewThing( tool, newInstance, ns.meeting('actions') ) } var makeChat = function (_toolObject) { var newBase = meetingBase + 'Chat/' var kb = dataBrowserContext.session.store if (kb.holds(meeting, ns.meeting('chat'))) { console.log('Ignored - already have chat') return // already got one } var messageStore = kb.sym(newBase + 'chat.ttl') kb.add(messageStore, ns.rdf('type'), ns.meeting('Chat'), messageStore) var tool = makeToolNode( messageStore, ns.meeting('chat'), 'Chat', UI.icons.iconBase + 'noun_346319.svg' ) saveAppDocumentLinkAndAddNewThing(tool, messageStore, ns.meeting('chat')) } var makeVideoCall = function (_toolObject) { var kb = dataBrowserContext.session.store var newInstance = $rdf.sym(VideoRoomPrefix + UI.utils.genUuid()) if (kb.holds(meeting, ns.meeting('videoCallPage'))) { console.log('Ignored - already have a videoCallPage') return // already got one } kb.add( newInstance, ns.rdf('type'), ns.meeting('VideoCallPage'), meetingDoc ) var tool = makeToolNode( newInstance, ns.meeting('videoCallPage'), 'Video call', UI.icons.iconBase + 'noun_260227.svg' ) kb.add(tool, ns.meeting('view'), 'iframe', meetingDoc) saveBackMeetingDoc() } var makeAttachment = function (_toolObject) { UI.widgets .askName(dom, kb, parameterCell, ns.log('uri'), UI.ns.rdf('Resource')) .then(function (uri) { if (!uri) { return resetTools() } var kb = dataBrowserContext.session.store var ns = UI.ns var target = kb.sym(uri) var tool = makeToolNode( target, ns.wf('attachment'), UI.utils.label(target), null ) kb.add(tool, ns.meeting('view'), 'iframe', meetingDoc) saveBackMeetingDoc() }) .catch(function (e) { complain('Error making new sub-meeting: ' + e) }) } var makeSharing = function (toolObject) { var kb = dataBrowserContext.session.store var ns = UI.ns var target = meeting.dir() if ( toolObject.limit && toolObject.limit === 1 && kb.holds(meeting, ns.wf('sharingControl')) ) { complain('Ignored - already have ' + UI.utils.label(options.predicate)) return } var tool = makeToolNode( target, ns.wf('sharingControl'), 'Sharing', UI.icons.iconBase + 'noun_123691.svg' ) kb.add(tool, ns.meeting('view'), 'sharing', meetingDoc) saveBackMeetingDoc() } var makeNewMeeting = function () { // @@@ make option of continuing series var appDetails = { noun: 'meeting' } var gotWS = function (ws, base) { thisPane .mintNew(dataBrowserContext, { newBase: base }) .then(function (options) { var newInstance = options.newInstance parameterCell.removeChild(mintUI) var p = parameterCell.appendChild(dom.createElement('p')) p.setAttribute('style', 'font-size: 140%;') p.innerHTML = "Your <a target='_blank' href='" + newInstance.uri + "'><b>new meeting</b></a> is ready to be set up. " + "<br/><br/><a target='_blank' href='" + newInstance.uri + "'>Go to your new meeting.</a>" }) .catch(function (err) { parameterCell.removeChild(mintUI) parameterCell.appendChild(UI.widgets.errorMessageBlock(dom, err)) }) } var mintUI = UI.login.selectWorkspace(dom, appDetails, gotWS) parameterCell.appendChild(mintUI) } // //////////////////////////////////////////////////////////// end of new tab creation functions var toolIcons = [ { icon: 'noun_339237.svg', maker: makeGroup, hint: 'Make a group of people', limit: 1 }, { icon: 'noun_346777.svg', maker: makePoll, hint: 'Make a poll to schedule the meeting' }, // When meet THIS or NEXT time { icon: 'noun_48218.svg', maker: makeAgenda, limit: 1, hint: 'Add an agenda list', disabled: true }, // When meet THIS or NEXT time { icon: 'noun_79217.svg', maker: makePad, hint: 'Add a shared notepad' }, { icon: 'noun_346319.svg', maker: makeChat, limit: 1, hint: 'Add a chat channel for the meeting' }, { icon: 'noun_17020.svg', maker: makeActions, limit: 1, hint: 'Add a list of action items' }, // When meet THIS or NEXT time { icon: 'noun_260227.svg', maker: makeVideoCall, limit: 1, hint: 'Add a video call for the meeting' }, { icon: 'noun_25830.svg', maker: makeAttachment, hint: 'Attach meeting materials', disabled: false }, { icon: 'noun_123691.svg', maker: makeSharing, limit: 1, hint: 'Control Sharing', disabled: false }, { icon: 'noun_66617.svg', maker: makeMeeting, hint: 'Make a sub meeting', disabled: false } ] // 'noun_66617.svg' var settingsForm = $rdf.sym( 'https://solid.github.io/solid-panes/meeting/meetingDetailsForm.ttl#settings' ) $rdf.parse( meetingDetailsFormText, kb, settingsForm.doc().uri, 'text/turtle' ) // Load form directly var iconStyle = 'padding: 1em; width: 3em; height: 3em;' var iconCell = toolBar.appendChild(dom.createElement('td')) var parameterCell = toolBar.appendChild(dom.createElement('td')) var star = iconCell.appendChild(dom.createElement('img')) var visible = false // the inividual tools tools star.setAttribute('src', UI.icons.iconBase + 'noun_19460_green.svg') // noun_272948.svg star.setAttribute('style', iconStyle + 'opacity: 50%;') star.setAttribute('title', 'Add another tool to the meeting') var selectNewTool = function (_event) { visible = !visible star.setAttribute( 'style', iconStyle + (visible ? 'background-color: yellow;' : '') ) styleTheIcons(visible ? '' : 'display: none;') } var loginOutButton logic.authn.checkUser().then(webId => { if (webId) { me = webId star.addEventListener('click', selectNewTool) star.setAttribute('style', iconStyle) return } loginOutButton = UI.login.loginStatusBox(dom, webIdUri => { if (webIdUri) { me = kb.sym(webIdUri) parameterCell.removeChild(loginOutButton) // loginOutButton.setAttribute('',iconStyle) // make it match the icons star.addEventListener('click', selectNewTool) star.setAttribute('style', iconStyle) } else { console.log('(Logged out)') me = null } }) loginOutButton.setAttribute('style', 'margin: 0.5em 1em;') parameterCell.appendChild(loginOutButton) }) var iconArray = [] for (var i = 0; i < toolIcons.length; i++) { var foo = function () { var toolObject = toolIcons[i] var icon = iconCell.appendChild(dom.createElement('img')) icon.setAttribute('src', UI.icons.iconBase + toolObject.icon) icon.setAttribute('style', iconStyle + 'display: none;') iconArray.push(icon) icon.tool = toolObject var maker = toolObject.maker if (!toolObject.disabled) { icon.addEventListener('click', function (_event) { selectTool(icon) maker(toolObject) }) } } foo() } var styleTheIcons = function (style) { for (var i = 0; i < iconArray.length; i++) { var st = iconStyle + style if (toolIcons[i].disabled) { st += 'opacity: 0.3;' } iconArray[i].setAttribute('style', st) // eg 'background-color: #ccc;' } } var resetTools = function () { styleTheIcons('display: none;') star.setAttribute('style', iconStyle) } var selectTool = function (icon) { styleTheIcons('display: none;') // 'background-color: #ccc;' icon.setAttribute('style', iconStyle + 'background-color: yellow;') } // ////////////////////////////// var renderTab = function (div, item) { if (kb.holds(item, ns.rdf('type'), ns.meeting('Tool'))) { var target = kb.any(item, ns.meeting('target')) var label = kb.any(item, ns.rdfs('label')) label = label ? label.value : UI.utils.label(target) var s = div.appendChild(dom.createElement('div')) s.textContent = label s.setAttribute('style', 'margin-left: 0.7em') var icon = kb.any(item, ns.meeting('icon')) if (icon) { // Make sure the icon is cleanly on the left of the label var table = div.appendChild(dom.createElement('table')) var tr = table.appendChild(dom.createElement('tr')) var left = tr.appendChild(dom.createElement('td')) var right = tr.appendChild(dom.createElement('td')) // var img = div.appendChild(dom.createElement('img')) var img = left.appendChild(dom.createElement('img')) img.setAttribute('src', icon.uri) // img.setAttribute('style', 'max-width: 1.5em; max-height: 1.5em;') // @@ SVG shrinks to 0 img.setAttribute('style', 'width: 1.5em; height: 1.5em;') // @ img.setAttribute('title', label) right.appendChild(s) } else { div.appendChild(s) } } else { div.textContent = UI.utils.label(item) } } var tipDiv = function (text) { var d = dom.createElement('div') var p = d.appendChild(dom.createElement('p')) p.setAttribute('style', 'margin: 0em; padding:3em; color: #888;') p.textContent = 'Tip: ' + text return d } var renderTabSettings = function (containerDiv, subject) { containerDiv.innerHTML = '' containerDiv.style += 'border-color: #eed;' containerDiv.appendChild(dom.createElement('h3')).textContent = 'Adjust this tab' if (kb.holds(subject, ns.rdf('type'), ns.meeting('Tool'))) { var form = $rdf.sym( 'https://solid.github.io/solid-panes/meeting/meetingDetailsForm.ttl#settings' ) UI.widgets.appendForm( document, containerDiv, {}, subject, form, meeting.doc(), complainIfBad ) var delButton = UI.widgets.deleteButtonWithCheck( dom, containerDiv, 'tab', function () { var toolList = kb.the(meeting, ns.meeting('toolList')) for (var i = 0; i < toolList.elements.length; i++) { if (toolList.elements[i].sameTerm(subject)) { toolList.elements.splice(i, 1) break } } var target = kb.any(subject, ns.meeting('target')) var ds = kb .statementsMatching(subject) .concat(kb.statementsMatching(undefined, undefined, subject)) .concat(kb.statementsMatching(meeting, undefined, target)) kb.remove(ds) // Remove all links to and from the tab node saveBackMeetingDoc() } ) delButton.setAttribute('style', 'width: 1.5em; height: 1.5em;') // delButton.setAttribute('class', '') // delButton.setAttribute('style', 'height: 2em; width: 2em; margin: 1em; border-radius: 0.5em; padding: 1em; font-size: 120%; background-color: red; color: white;') // delButton.textContent = 'Delete this tab' } else { containerDiv.appendChild(dom.createElement('h4')).textContent = '(No adjustments available)' } } const renderMain = function (containerDiv, subject) { var pane = null var table var selectedGroup = null containerDiv.innerHTML = '' var complainIfBad = function (ok, message) { if (!ok) { containerDiv.textContent = '' + message } } var showIframe = function (target) { var iframe = containerDiv.appendChild(dom.createElement('iframe')) // iframe.setAttribute('sandbox', '') // All restrictions iframe.setAttribute('src', target.uri) // See https://stackoverflow.com/questions/325273/make-iframe-to-fit-100-of-containers-remaining-height // Set the container position (sic) so it becaomes a 100% reference for the size of the iframe height 100% /* For now at least , leave the container style as set by the tab system. 20200115b containerDiv.setAttribute( 'style', 'position: relative; top: 0px; left:0px; right:0px; resize: both; overflow:scroll; min-width: 30em; min-height: 30em;' ) */ // iframe.setAttribute('style', 'height: 350px; border: 0; margin: 0; padding: 0; resize:both; overflow:scroll; width: 100%;') // iframe.setAttribute('style', 'border: none; margin: 0; padding: 0; height: 100%; width: 100%; resize: both; overflow:scroll;') iframe.setAttribute( 'style', 'border: none; margin: 0; padding: 0; height: 100%; width: 100%;' ) // Following https://dev.chromium.org/Home/chromium-security/deprecating-permissions-in-cross-origin-iframes : iframe.setAttribute('allow', 'microphone camera') // Allow iframe to request camera and mic // containerDiv.style.resize = 'none' // Remove scroll bars on outer div - don't seem to work so well iframe.setAttribute('name', 'disable-x-frame-options') // For electron: see https://github.com/electron/electron/pull/573 containerDiv.style.padding = 0 } var renderPeoplePicker = function () { var context = { div: containerDiv, dom: dom } containerDiv.appendChild(dom.createElement('h4')).textContent = 'Meeting Participants' var groupPickedCb = function (group) { var toIns = [ $rdf.st( meeting, ns.meeting('particpantGroup'), group, meeting.doc() ) ] kb.updater.update([], toIns, function (uri, ok, message) { if (ok) { selectedGroup = group } else { complain('Cant save participants group: ' + message) } }) } selectedGroup = kb.any(meeting, ns.meeting('particpantGroup')) logic.loadTypeIndexes(context).then(function () { // Assumes that the type index has an entry for addressbook var options = { defaultNewGroupName: 'Meeting Participants', selectedGroup: selectedGroup } var picker = new UI.widgets.PeoplePicker( context.div, context.index.private[0], groupPickedCb, options ) picker.render() }) } var renderDetails = function () { containerDiv.appendChild(dom.createElement('h3')).textContent = 'Details of meeting' var form = $rdf.sym( 'https://solid.github.io/solid-panes/meeting/meetingDetailsForm.ttl#main' ) UI.widgets.appendForm( document, containerDiv, {}, meeting, form, meeting.doc(), complainIfBad ) containerDiv.appendChild( tipDiv( 'Drag URL-bar icons of web pages into the tab bar on the left to add new meeting materials.' ) ) me = logic.authn.currentUser() if (me) { kb.add(meeting, ns.dc('author'), me, meetingDoc) // @@ should nly be on initial creation? } var context = { noun: 'meeting', me: me, statusArea: containerDiv, div: containerDiv, dom: dom } UI.login .registrationControl(context, meeting, ns.meeting('Meeting')) .then(function (_context) { console.log('Registration control finsished.') }) var options = {} UI.pad.manageParticipation( dom, containerDiv, meetingDoc, meeting, me, options ) // "Make a new meeting" button var imageStyle = 'height: 2em; width: 2em; margin:0.5em;' var detailsBottom = containerDiv.appendChild(dom.createElement('div')) var spawn = detailsBottom.appendChild(dom.createElement('img')) spawn.setAttribute('src', UI.icons.iconBase + 'noun_145978.svg') spawn.setAttribute('title', 'Make a fresh new meeting') spawn.addEventListener('click', makeNewMeeting) spawn.setAttribute('style', imageStyle) // "Fork me on Github" button var forka = detailsBottom.appendChild(dom.createElement('a')) forka.setAttribute('href', 'https://github.com/solid/solid-panes') // @@ Move when code moves forka.setAttribute('target', '_blank') var fork = forka.appendChild(dom.createElement('img')) fork.setAttribute('src', UI.icons.iconBase + 'noun_368567.svg') fork.setAttribute('title', 'Fork me on github') fork.setAttribute('style', imageStyle + 'opacity: 50%;') } if (kb.holds(subject, ns.rdf('type'), ns.meeting('Tool'))) { var target = kb.any(subject, ns.meeting('target')) if (target.sameTerm(meeting) && !kb.any(subject, ns.meeting('view'))) { // self reference? force details form renderDetails() // Legacy meeting instances } else { var view = kb.any(subject, ns.meeting('view')) view = view ? view.value : null if (view === 'details') { renderDetails() } else if (view === 'peoplePicker') { renderPeoplePicker() } else if (view === 'iframe') { showIframe(target) } else { pane = view ? dataBrowserContext.session.paneRegistry.byName(view) : null table = containerDiv.appendChild(dom.createElement('table')) table.style.width = '100%' dataBrowserContext .getOutliner(dom) .GotoSubject(target, true, pane, false, undefined, table) } } } else if (subject.sameTerm(meeting)) { // self reference? force details form renderDetails() } else if ( subject.sameTerm(subject.doc()) && !kb.holds(subject, UI.ns.rdf('type'), UI.ns.meeting('Chat')) && !kb.holds(subject, UI.ns.rdf('type'), UI.ns.meeting('PaneView')) // eslint-disable-next-line no-empty ) { } else { table = containerDiv.appendChild(dom.createElement('table')) dataBrowserContext .getOutliner(dom) .GotoSubject(subject, true, undefined, false, undefined, table) } } var options = { dom: dom } options.predicate = ns.meeting('toolList') options.subject = subject options.ordered = true options.orientation = 1 // tabs on LHS options.renderMain = renderMain options.renderTab = renderTab options.renderTabSettings = renderTabSettings options.backgroundColor = kb.anyValue(subject, ns.ui('backgroundColor')) || '#ddddcc' var tabs = mainTR.appendChild(UI.tabs.tabWidget(options)) UI.aclControl.preventBrowserDropEvents(dom) UI.widgets.makeDropTarget( tabs.tabContainer, droppedURIHandler, droppedFileHandler ) UI.widgets.makeDropTarget(iconCell, droppedURIHandler, droppedFileHandler) return div } } // ends