solid-app-set
Version:
Solid-compatible App set
942 lines (808 loc) • 35.5 kB
JavaScript
/* Scheduler Pane
**
**
*/
/* global alert */
const UI = require('solid-ui')
const $rdf = UI.rdf
const ns = UI.ns
// @@ Give other combos too-- see schedule ontology
const possibleAvailabilities = [ ns.sched('No'), ns.sched('Maybe'), ns.sched('Yes') ]
module.exports = {
icon: UI.icons.iconBase + 'noun_346777.svg', // @@ better?
name: 'schedule',
// Does the subject deserve an Scheduler pane?
label: function (subject) {
var kb = UI.store
var t = kb.findTypeURIs(subject)
if (t['http://www.w3.org/ns/pim/schedule#SchedulableEvent']) return 'Scheduling poll'
return null // No under other circumstances
},
// Mint a new Schedule poll
mintClass: ns.sched('SchedulableEvent'),
mintNew: function (options) {
return new Promise(function (resolve, reject) {
var ns = UI.ns
var kb = UI.store
var newBase = options.newBase
var thisInstance = options.useExisting || $rdf.sym(options.newBase + 'index.ttl#this')
var complainIfBad = function (ok, body) {
if (ok) return
console.log('Error in Schedule Pane: Error constructing new scheduler: ' + body)
reject(new Error(body))
}
// ////////////////////// Accesss control
// Two variations of ACL for this app, public read and public read/write
// In all cases owner has read write control
var genACLtext = function (docURI, aclURI, allWrite) {
var g = $rdf.graph()
var auth = $rdf.Namespace('http://www.w3.org/ns/auth/acl#')
var a = g.sym(aclURI + '#a1')
var acl = g.sym(aclURI)
var doc = g.sym(docURI)
g.add(a, UI.ns.rdf('type'), auth('Authorization'), acl)
g.add(a, auth('accessTo'), doc, acl)
g.add(a, auth('agent'), me, acl)
g.add(a, auth('mode'), auth('Read'), acl)
g.add(a, auth('mode'), auth('Write'), acl)
g.add(a, auth('mode'), auth('Control'), acl)
a = g.sym(aclURI + '#a2')
g.add(a, UI.ns.rdf('type'), auth('Authorization'), acl)
g.add(a, auth('accessTo'), doc, acl)
g.add(a, auth('agentClass'), ns.foaf('Agent'), acl)
g.add(a, auth('mode'), auth('Read'), acl)
if (allWrite) {
g.add(a, auth('mode'), auth('Write'), acl)
}
return $rdf.serialize(acl, g, aclURI, 'text/turtle')
}
/*
var setACL3 = function (docURI, allWrite, callbackFunction) {
var aclText = genACLtext(docURI, aclDoc.uri, allWrite)
return UI.acl.setACL(docURI, aclText, callbackFunction)
}
*/
var setACL2 = function setACL2 (docURI, allWrite, callbackFunction) {
var aclDoc = kb.any(kb.sym(docURI),
kb.sym('http://www.iana.org/assignments/link-relations/acl')) // @@ check that this get set by web.js
if (aclDoc) { // Great we already know where it is
var aclText = genACLtext(docURI, aclDoc.uri, allWrite)
return fetcher.webOperation('PUT', aclDoc.uri, { data: aclText, contentType: 'text/turtle' })
.then(result => callbackFunction(true))
.catch(err => {
callbackFunction(false, err.message)
})
} else {
return fetcher.load(docURI)
.catch(err => {
callbackFunction(false, 'Getting headers for ACL: ' + err)
})
.then(() => {
var aclDoc = kb.any(kb.sym(docURI),
kb.sym('http://www.iana.org/assignments/link-relations/acl'))
if (!aclDoc) {
// complainIfBad(false, "No Link rel=ACL header for " + docURI)
throw new Error('No Link rel=ACL header for ' + docURI)
}
var aclText = genACLtext(docURI, aclDoc.uri, allWrite)
return fetcher.webOperation('PUT', aclDoc.uri, { data: aclText, contentType: 'text/turtle' })
})
.then(result => callbackFunction(true))
.catch(err => {
callbackFunction(false, err.message)
})
}
}
// Body of mintNew
var fetcher = kb.fetcher
var updater = kb.updater
var me = options.me || UI.authn.currentUser()
if (!me) {
console.log('MUST BE LOGGED IN')
alert('NOT LOGGED IN')
return
}
var base = thisInstance.dir().uri
var newDetailsDoc, newInstance // , newIndexDoc
if (options.useExisting) {
newInstance = options.useExisting
newBase = thisInstance.dir().uri
newDetailsDoc = newInstance.doc()
// newIndexDoc = null
if (options.newBase) {
throw new Error('mint new scheduler: Illegal - have both new base and existing event')
}
} else {
newDetailsDoc = kb.sym(newBase + 'details.ttl')
// newIndexDoc = kb.sym(newBase + 'index.html')
newInstance = kb.sym(newDetailsDoc.uri + '#event')
}
var newResultsDoc = kb.sym(newBase + 'results.ttl')
var toBeCopied = options.noIndexHTML ? {} : [ { local: 'index.html', contentType: 'text/html' }
// { local: 'forms.ttl', contentType: 'text/turtle'}
// { local: 'schedule.js', contentType: 'application/javascript'} ,
// { local: 'mashlib.js', contentType: 'application/javascript'} , // @@ centrialize after testing?
]
var agenda = []
// @@ This needs some form of visible progress bar
for (var f = 0; f < toBeCopied.length; f++) {
var item = toBeCopied[f]
var fun = function copyItem (item) {
agenda.push(function () {
var newURI = newBase + item.local
console.log('Copying ' + base + item.local + ' to ' + newURI)
var setThatACL = function () {
setACL2(newURI, false, function (ok, message) {
if (!ok) {
complainIfBad(ok, 'FAILED to set ACL ' + newURI + ' : ' + message)
console.log('FAILED to set ACL ' + newURI + ' : ' + message)
} else {
agenda.shift()() // beware too much nesting
}
})
}
kb.fetcher.webCopy(base + item.local, newBase + item.local, item.contentType)
.then(() => UI.authn.checkUser())
.then(webId => {
me = webId
setThatACL()
})
.catch(err => {
console.log('FAILED to copy ' + base + item.local + ' : ' + err.message)
complainIfBad(false, 'FAILED to copy ' + base + item.local + ' : ' + err.message)
})
})
}
fun(item)
}
agenda.push(function createDetailsFile () {
kb.add(newInstance, ns.rdf('type'), ns.sched('SchedulableEvent'), newDetailsDoc)
if (me) {
kb.add(newInstance, ns.dc('author'), me, newDetailsDoc) // Who is sending the invitation?
kb.add(newInstance, ns.foaf('maker'), me, newDetailsDoc) // Uneditable - wh is allowed to edit this?
}
kb.add(newInstance, ns.dc('created'), new Date(), newDetailsDoc)
kb.add(newInstance, ns.sched('resultsDocument'), newDetailsDoc)
updater.put(
newDetailsDoc,
kb.statementsMatching(undefined, undefined, undefined, newDetailsDoc),
'text/turtle',
function (uri2, ok, message) {
if (ok) {
agenda.shift()()
} else {
complainIfBad(ok, 'FAILED to save new scheduler at: ' + newDetailsDoc + ' : ' + message)
console.log('FAILED to save new scheduler at: ' + newDetailsDoc + ' : ' + message)
}
}
)
})
agenda.push(function () {
kb.fetcher.webOperation('PUT', newResultsDoc.uri, { data: '', contentType: 'text/turtle' })
.then(() => {
agenda.shift()()
})
.catch(err => {
complainIfBad(false, 'Failed to initialize empty results file: ' + err.message)
})
})
agenda.push(function () {
setACL2(newResultsDoc.uri, true, function (ok, body) {
complainIfBad(ok, 'Failed to set Read-Write ACL on results file: ' + body)
if (ok) agenda.shift()()
})
})
agenda.push(function () {
setACL2(newDetailsDoc.uri, false, function (ok, body) {
complainIfBad(ok, 'Failed to set read ACL on configuration file: ' + body)
if (ok) agenda.shift()()
})
})
agenda.push(function () { // give the user links to the new app
console.log('Finished minting new scheduler')
resolve(options)
})
agenda.shift()()
// Created new data files.
}) // promise
}, // mintNew
// Render one meeting schedule poll
render: function (subject, dom) {
var kb = UI.store
var ns = UI.ns
var invitation = subject
var appPathSegment = 'app-when-can-we.w3.org' // how to allocate this string and connect to
// ////////////////////////////////////////////
var fetcher = UI.store.fetcher
var updater = kb.updater
var waitingForLogin = false
var thisInstance = subject
var detailsDoc = subject.doc()
var baseDir = detailsDoc.dir()
var base = baseDir.uri
var resultsDoc = $rdf.sym(base + 'results.ttl')
// var formsURI = base + 'forms.ttl'
// We can't in fact host stuff from there because of CORS
var formsURI = 'https://linkeddata.github.io/solid-app-set/schedule/formsForSchedule.ttl'
var form1 = kb.sym(formsURI + '#form1')
var form2 = kb.sym(formsURI + '#form2')
var form3 = kb.sym(formsURI + '#form3')
var formText = require('./formsForSchedule.js')
$rdf.parse(formText, kb, formsURI, 'text/turtle') // Load forms directly
var inputStyle = 'background-color: #eef; padding: 0.5em; border: .5em solid white; font-size: 100%' // font-size: 120%
var buttonIconStyle = 'width: 1.8em; height: 1.8em;'
// Utility functions
var complainIfBad = function (ok, message) {
if (!ok) {
div.appendChild(UI.widgets.errorMessageBlock(dom, message, 'pink'))
}
}
var clearElement = function (ele) {
while (ele.firstChild) {
ele.removeChild(ele.firstChild)
}
return ele
}
var refreshCellColor = function (cell, value) {
var bg = kb.any(value, UI.ns.ui('backgroundColor'))
if (bg) cell.setAttribute('style', 'padding: 0.3em; text-align: center; background-color: ' + bg + ';')
}
var me
UI.authn.checkUser()
.then(webId => {
me = webId
if (logInOutButton) {
logInOutButton.refresh()
}
if (webId && waitingForLogin) {
waitingForLogin = false
showAppropriateDisplay()
}
})
console.log('me: ' + me) // @@ curently not actually used elsewhere
// ////////////////////////////// Reproduction: spawn a new instance
//
// Viral growth path: user of app decides to make another instance
//
var newInstanceButton = function () {
var b = UI.authn.newAppInstance(dom, { noun: 'scheduler' },
initializeNewInstanceInWorkspace)
b.firstChild.setAttribute('style', inputStyle)
return b
} // newInstanceButton
// /////////////////////// Create new document files for new instance of app
var initializeNewInstanceInWorkspace = function (ws) {
var newBase = kb.any(ws, ns.space('uriPrefix'))
if (!newBase) {
newBase = ws.uri.split('#')[0]
} else {
newBase = newBase.value
}
if (newBase.slice(-1) !== '/') {
$rdf.log.error(appPathSegment + ': No / at end of uriPrefix ' + newBase) // @@ paramater?
newBase = newBase + '/'
}
var now = new Date()
newBase += appPathSegment + '/id' + now.getTime() + '/' // unique id
initializeNewInstanceAtBase(thisInstance, newBase)
}
var initializeNewInstanceAtBase = function (thisInstance, newBase) {
var options = {thisInstance: thisInstance, newBase: newBase}
this.mintNew(options).then(function (options) {
var p = div.appendChild(dom.createElement('p'))
p.setAttribute('style', 'font-size: 140%;')
p.innerHTML =
"Your <a href='" + options.newInstance.uri + "'><b>new scheduler</b></a> is ready to be set up. " +
"<br/><br/><a href='" + options.newInstance.uri + "'>Say when you what days work for you.</a>"
}).catch(function (error) {
complainIfBad(false, 'Error createing new scheduler at ' + options.newInstance + ': ' + error)
})
}
// ///////////////////////
var getForms = function () {
console.log('getforms()')
getDetails()
/*
fetcher.nowOrWhenFetched(formsURI, undefined, function (ok, body) {
console.log('getforms() ok? ' + ok)
if (!ok) return complainIfBad(ok, body)
getDetails()
})
*/
}
var getDetails = function () {
console.log('getDetails()') // Looking for blank screen hang-up
fetcher.nowOrWhenFetched(detailsDoc.uri, undefined, function (ok, body) {
console.log('getDetails() ok? ' + ok)
if (!ok) return complainIfBad(ok, body)
showAppropriateDisplay()
})
}
var showAppropriateDisplay = function showAppropriateDisplay () {
console.log('showAppropriateDisplay()')
UI.authn.checkUser()
.then(webId => {
if (!webId) { return showSignon() }
// On gh-pages, the turtle will not load properly (bad mime type)
// but we can trap it as being a non-editable server.
if (!UI.store.updater.editable(detailsDoc.uri, kb) ||
kb.holds(subject, ns.rdf('type'), ns.wf('TemplateInstance'))) {
// This is read-only example e.g. on github pages, etc
showBootstrap(div)
return
}
var ready = kb.any(subject, ns.sched('ready'))
if (!ready) {
showForms()
} else { // no editing not author
getResults()
}
})
}
var showSignon = function showSignon () {
clearElement(naviMain)
let context = {div: div, dom: dom}
UI.authn.logIn(context).then(context => {
me = context.me
waitingForLogin = false // untested
showAppropriateDisplay()
})
}
var showBootstrap = function showBootstrap () {
var div = clearElement(naviMain)
div.appendChild(UI.authn.newAppInstance(
dom, {noun: 'poll'}, initializeNewInstanceInWorkspace))
div.appendChild(dom.createElement('hr')) // @@
var p = div.appendChild(dom.createElement('p'))
p.textContent = 'Where would you like to store the data for the poll? ' +
'Give the URL of the directory where you would like the data stored.'
var baseField = div.appendChild(dom.createElement('input'))
baseField.setAttribute('type', 'text')
baseField.size = 80 // really a string
baseField.label = 'base URL'
baseField.autocomplete = 'on'
div.appendChild(dom.createElement('br')) // @@
var button = div.appendChild(dom.createElement('button'))
button.setAttribute('style', inputStyle)
button.textContent = 'Start new poll at this URI'
button.addEventListener('click', function (e) {
var newBase = baseField.value
if (newBase.slice(-1) !== '/') {
newBase += '/'
}
initializeNewInstanceAtBase(thisInstance, newBase)
})
}
// ///////////// The forms to configure the poll
var doneButton = dom.createElement('button')
var showForms = function () {
clearElement(naviCenter) // Remove refresh button if nec
var div = naviMain
var wizard = true
var currentSlide = 0
var gotDoneButton = false
if (wizard) {
let forms = [ form1, form2, form3 ]
let slides = []
currentSlide = 0
for (var f = 0; f < forms.length; f++) {
let slide = dom.createElement('div')
UI.widgets.appendForm(document, slide, {}, subject, forms[f], detailsDoc, complainIfBad)
slides.push(slide)
}
var refresh = function () {
clearElement(naviMain).appendChild(slides[currentSlide])
if (currentSlide === 0) {
b1.setAttribute('disabled', '')
} else {
b1.removeAttribute('disabled')
}
if (currentSlide === slides.length - 1) {
b2.setAttribute('disabled', '')
if (!gotDoneButton) { // Only expose at last slide seen
naviCenter.appendChild(emailButton) // could also check data shape
naviCenter.appendChild(doneButton) // could also check data shape
gotDoneButton = true
}
} else {
b2.removeAttribute('disabled')
}
}
var b1 = clearElement(naviLeft).appendChild(dom.createElement('button'))
b1.setAttribute('style', inputStyle)
b1.textContent = '<- go back'
b1.addEventListener('click', function (e) {
if (currentSlide > 0) {
currentSlide -= 1
refresh()
}
}, false)
var b2 = clearElement(naviRight).appendChild(dom.createElement('button'))
b2.setAttribute('style', inputStyle)
b2.textContent = 'continue ->'
b2.addEventListener('click', function (e) {
if (currentSlide < slides.length - 1) {
currentSlide += 1
refresh()
}
}, false)
refresh()
} else { // not wizard one big form
// @@@ create the initial config doc if not exist
var table = div.appendChild(dom.createElement('table'))
UI.widgets.appendForm(document, table, {}, subject, form1, detailsDoc, complainIfBad)
UI.widgets.appendForm(document, table, {}, subject, form2, detailsDoc, complainIfBad)
UI.widgets.appendForm(document, table, {}, subject, form3, detailsDoc, complainIfBad)
naviCenter.appendChild(doneButton) // could also check data shape
}
// @@@ link config to results
var insertables = []
insertables.push($rdf.st(subject, ns.sched('availabilityOptions'), ns.sched('YesNoMaybe'), detailsDoc))
insertables.push($rdf.st(subject, ns.sched('ready'), new Date(), detailsDoc))
insertables.push($rdf.st(subject, ns.sched('results'), resultsDoc, detailsDoc)) // @@ also link in results
doneButton.setAttribute('style', inputStyle)
doneButton.textContent = 'Go to poll'
doneButton.addEventListener('click', function (e) {
if (kb.any(subject, ns.sched('ready'))) { // already done
getResults()
naviRight.appendChild(emailButton)
} else {
naviRight.appendChild(emailButton)
UI.store.updater.update([], insertables, function (uri, success, errorBody) {
if (!success) {
complainIfBad(success, errorBody)
} else {
// naviRight.appendChild(emailButton)
getResults()
}
})
}
}, false)
var emailButton = dom.createElement('button')
emailButton.setAttribute('style', inputStyle)
let emailIcon = emailButton.appendChild(dom.createElement('img'))
emailIcon.setAttribute('src', UI.icons.iconBase + 'noun_480183.svg') // noun_480183.svg
emailIcon.setAttribute('style', buttonIconStyle)
// emailButton.textContent = 'email invitations'
emailButton.addEventListener('click', function (e) {
var title = kb.anyValue(subject, ns.cal('summary')) ||
kb.anyValue(subject, ns.dc('title')) || ''
var mailto = 'mailto:' +
kb.each(subject, ns.sched('invitee')).map(function (who) {
var mbox = kb.any(who, ns.foaf('mbox'))
return mbox ? mbox.uri.replace('mailto:', '') : ''
}).join(',') +
'?subject=' + encodeURIComponent(title + '-- When can we meet?') +
'&body=' + encodeURIComponent(title + '\n\nWhen can you?\n\nSee ' + subject + '\n')
// @@ assumed there is a data browser
console.log('Mail: ' + mailto)
window.location.href = mailto
}, false)
} // showForms
// Ask for each day, what times .. @@ to be added some time
/*
var setTimesOfDay = function () {
var i, j, x, y, slot, cell, day
var insertables = []
var possibleDays = kb.each(invitation, ns.sched('option'))
.map(function (opt) {return kb.any(opt, ns.cal('dtstart'))})
var cellLookup = []
var slots = kb.each(invitation, ns.sched('slot'))
if (slots.length === 0) {
for (i = 0; i < 2; i++) {
slot = UI.widgets.newThing(detailsDoc)
insertables.push($rdf.st(invitation, ns.sched('slot'), slot))
insertables.push($rdf.st(slot, ns.rdfs('label'), 'slot ' + (i + 1)))
for (j = 0; j < possibleDays.length; j++) {
day - possibleDays[j]
x = kb.any(slot, ns.rdfs('label'))
y = kb.any(day, ns.cal('dtstart'))
cell = UI.widgets.newThing(detailsDoc)
cellLookup[x.toNT() + y.toNT()] = cell
insertables.push($rdf.st(slot, ns.sched('cell'), cell))
insertables.push($rdf.st(cell, ns.sched('day'), possibleDays[j]))
}
}
}
var query = new $rdf.Query('TimesOfDay')
var v = {}['day', 'label', 'value', 'slot', 'cell'].map(function (x) {
query.vars.push(v[x] = $rdf.variable(x)) })
query.pat.add(invitation, ns.sched('slot'), v.slot)
query.pat.add(v.slot, ns.rdfs('label'), v.label)
query.pat.add(v.slot, ns.sched('cell'), v.cell)
query.pat.add(v.cell, ns.sched('timeOfDay'), v.value)
query.pat.add(v.cell, ns.sched('day'), v.day)
var options = {}
options.set_x = kb.each(subject, ns.sched('slot')) // @@@@@ option -> dtstart in future
options.set_x = options.set_x.map(function (opt) { return kb.any(opt, ns.rdfs('label')) })
options.set_y = kb.each(subject, ns.sched('option')); // @@@@@ option -> dtstart in future
options.set_y = options.set_y.map(function (opt) { return kb.any(opt, ns.cal('dtstart')) })
var possibleTimes = kb.each(invitation, ns.sched('option'))
.map(function (opt) { return kb.any(opt, ns.cal('dtstart')) })
var displayTheMatrix = function () {
var matrix = div.appendChild(UI.matrix.matrixForQuery(
dom, query, v.time, v.author, v.value, options, function () {}))
matrix.setAttribute('class', 'matrix')
var refreshButton = dom.createElement('button')
refreshButton.setAttribute('style', inputStyle)
refreshButton.textContent = 'refresh'
refreshButton.addEventListener('click', function (e) {
refreshButton.disabled = true
UI.store.fetcher.nowOrWhenFetched(subject.doc(), undefined, function (ok, body) {
if (!ok) {
console.log('Cant refresh matrix' + body)
} else {
matrix.refresh()
refreshButton.disabled = false
}
})
}, false)
clearElement(naviCenter)
naviCenter.appendChild(refreshButton)
}
var dataPointForNT = []
var doc = resultsDoc
options.set_y = options.set_y.filter(function (z) { return (! z.sameTerm(me)) })
options.set_y.push(me) // Put me on the end
options.cellFunction = function (cell, x, y, value) {
// var point = cellLookup[x.toNT() + y.toNT()]
if (y.sameTerm(me)) {
var callbackFunction = function () { refreshCellColor(cell, value); }; // @@ may need that
var selectOptions = {}
var predicate = ns.sched('timeOfDay')
var cellSubject = dataPointForNT[x.toNT()]
var selector = UI.widgets.makeSelectForOptions(dom, kb, cellSubject, predicate,
possibleAvailabilities, selectOptions, resultsDoc, callbackFunction)
cell.appendChild(selector)
} else if (value !== null) {
cell.textContent = UI.utils.label(value)
}
}
var responses = kb.each(invitation, ns.sched('response'))
var myResponse = null
responses.map(function (r) {
if (kb.holds(r, ns.dc('author'), me)) {
myResponse = r
}
})
var id = UI.widgets.newThing(doc).uri
if (myResponse === null) {
myResponse = $rdf.sym(id + '_response')
insertables.push($rdf.st(invitation, ns.sched('response'), myResponse, doc))
insertables.push($rdf.st(myResponse, ns.dc('author'), me, doc))
} else {
var dps = kb.each(myResponse, ns.sched('cell'))
dps.map(function (dataPoint) {
var time = kb.any(dataPoint, ns.cal('dtstart'))
dataPointForNT[time.toNT()] = dataPoint
})
}
for (let j = 0; j < possibleTimes.length; j++) {
if (dataPointForNT[possibleTimes[j].toNT()]) continue
var dataPoint = $rdf.sym(id + '_' + j)
insertables.push($rdf.st(myResponse, ns.sched('cell'), dataPoint, doc))
insertables.push($rdf.st(dataPoint, ns.cal('dtstart'), possibleTimes[j], doc)) // @@
dataPointForNT[possibleTimes[j].toNT()] = dataPoint
}
if (insertables.length) {
UI.store.updater.update([], insertables, function (uri, success, errorBody) {
if (!success) {
complainIfBad(success, errorBody)
} else {
displayTheMatrix()
}
})
} else { // no insertables
displayTheMatrix()
}
}
*/
// end setTimesOfDay
// Read or create empty results file
var getResults = function () {
fetcher.nowOrWhenFetched(resultsDoc.uri, (ok, body, response) => {
if (!ok) {
if (response.status === 404) { // / Check explicitly for 404 error
console.log('Initializing details file ' + resultsDoc)
updater.put(resultsDoc, [], 'text/turtle', function (uri2, ok, message) {
if (ok) {
clearElement(naviMain)
showResults()
} else {
complainIfBad(ok, 'FAILED to create results file at: ' + resultsDoc.uri + ' : ' + message)
console.log('FAILED to craete results file at: ' + resultsDoc.uri + ' : ' + message)
}
})
} else { // Other error, not 404 -- do not try to overwite the file
complainIfBad(ok, 'FAILED to read results file: ' + body)
}
} else { // Happy read
clearElement(naviMain)
showResults()
}
})
}
var showResults = function () {
// Now the form for responsing to the poll
//
// div.appendChild(dom.createElement('hr'))
// var invitation = subject
var title = kb.any(invitation, ns.cal('summary'))
var comment = kb.any(invitation, ns.cal('comment'))
var location = kb.any(invitation, ns.cal('location'))
var div = naviMain
if (title) div.appendChild(dom.createElement('h3')).textContent = title
if (location) div.appendChild(dom.createElement('address')).textContent = location.value
if (comment) div.appendChild(dom.createElement('p')).textContent = comment.value
var author = kb.any(invitation, ns.dc('author'))
if (author) {
var authorName = kb.any(author, ns.foaf('name'))
if (authorName) {
div.appendChild(dom.createElement('p')).textContent = authorName
}
}
var query = new $rdf.Query('Responses')
var v = {}
var vs = ['time', 'author', 'value', 'resp', 'cell']
vs.map(function (x) {
query.vars.push(v[x] = $rdf.variable(x))
})
query.pat.add(invitation, ns.sched('response'), v.resp)
query.pat.add(v.resp, ns.dc('author'), v.author)
query.pat.add(v.resp, ns.sched('cell'), v.cell)
query.pat.add(v.cell, ns.sched('availabilty'), v.value)
query.pat.add(v.cell, ns.cal('dtstart'), v.time)
// Sort by by person @@@
var options = {}
options.set_x = kb.each(subject, ns.sched('option')) // @@@@@ option -> dtstart in future
options.set_x = options.set_x.map(function (opt) { return kb.any(opt, ns.cal('dtstart')) })
options.set_y = kb.each(subject, ns.sched('response'))
options.set_y = options.set_y.map(function (resp) { return kb.any(resp, ns.dc('author')) })
var possibleTimes = kb.each(invitation, ns.sched('option'))
.map(function (opt) { return kb.any(opt, ns.cal('dtstart')) })
var displayTheMatrix = function () {
var matrix = div.appendChild(UI.matrix.matrixForQuery(
dom, query, v.time, v.author, v.value, options, function () {}))
matrix.setAttribute('class', 'matrix')
var refreshButton = dom.createElement('button')
refreshButton.setAttribute('style', inputStyle)
// refreshButton.textContent = 'refresh' // noun_479395.svg
let refreshIcon = dom.createElement('img')
refreshIcon.setAttribute('src', UI.icons.iconBase + 'noun_479395.svg')
refreshIcon.setAttribute('style', buttonIconStyle)
refreshButton.appendChild(refreshIcon)
refreshButton.addEventListener('click', function (e) {
refreshButton.disabled = true
UI.store.fetcher.refresh(resultsDoc, function (ok, body) {
if (!ok) {
console.log('Cant refresh matrix' + body)
} else {
matrix.refresh()
refreshButton.disabled = false
}
})
}, false)
clearElement(naviCenter)
naviCenter.appendChild(refreshButton)
}
// @@ Give other combos too-- see schedule ontology
// var possibleAvailabilities = [ SCHED('No'), SCHED('Maybe'), SCHED('Yes') ]
// var me = UI.authn.currentUser()
var dataPointForNT = []
var context = { div: naviCenter, dom: dom }
UI.authn.logIn(context).then(context => {
const me = context.me
var doc = resultsDoc
options.set_y = options.set_y.filter(function (z) { return (!z.sameTerm(me)) })
options.set_y.push(me) // Put me on the end
options.cellFunction = function (cell, x, y, value) {
if (value !== null) {
kb.fetcher.nowOrWhenFetched(value.uri.split('#')[0], undefined, function (ok, error) {
if (ok) refreshCellColor(cell, value)
})
}
if (y.sameTerm(me)) {
var callbackFunction = function () {
refreshCellColor(cell, value)
} // @@ may need that
var selectOptions = {}
var predicate = ns.sched('availabilty')
var cellSubject = dataPointForNT[x.toNT()]
var selector = UI.widgets.makeSelectForOptions(dom, kb, cellSubject, predicate,
possibleAvailabilities, selectOptions, resultsDoc, callbackFunction)
cell.appendChild(selector)
} else if (value !== null) {
cell.textContent = UI.utils.label(value)
}
}
var responses = kb.each(invitation, ns.sched('response'))
var myResponse = null
responses.map(function (r) {
if (kb.holds(r, ns.dc('author'), me)) {
myResponse = r
}
})
var insertables = [] // list of statements to be stored
var id = UI.widgets.newThing(doc).uri
if (myResponse === null) {
myResponse = $rdf.sym(id + '_response')
insertables.push($rdf.st(invitation, ns.sched('response'), myResponse, doc))
insertables.push($rdf.st(myResponse, ns.dc('author'), me, doc))
} else {
var dps = kb.each(myResponse, ns.sched('cell'))
dps.map(function (dataPoint) {
var time = kb.any(dataPoint, ns.cal('dtstart'))
dataPointForNT[time.toNT()] = dataPoint
})
}
for (var j = 0; j < possibleTimes.length; j++) {
if (dataPointForNT[possibleTimes[j].toNT()]) continue
var dataPoint = $rdf.sym(id + '_' + j)
insertables.push($rdf.st(myResponse, ns.sched('cell'), dataPoint, doc))
insertables.push($rdf.st(dataPoint, ns.cal('dtstart'), possibleTimes[j], doc)) // @@
dataPointForNT[possibleTimes[j].toNT()] = dataPoint
}
if (insertables.length) {
UI.store.updater.update([], insertables, function (uri, success, errorBody) {
if (!success) {
complainIfBad(success, errorBody)
} else {
displayTheMatrix()
}
})
} else { // no insertables
displayTheMatrix()
}
})
// If I made this in the first place, allow me to edit it.
// @@ optionally -- allows others to if according to original
var instanceCreator = kb.any(subject, ns.foaf('maker')) // owner?
if (!instanceCreator || instanceCreator.sameTerm(me)) {
var editButton = dom.createElement('button')
editButton.setAttribute('style', inputStyle)
// editButton.textContent = '(Modify the poll)' // noun_344563.svg
let editIcon = dom.createElement('img')
editIcon.setAttribute('src', UI.icons.iconBase + 'noun_344563.svg')
editIcon.setAttribute('style', buttonIconStyle)
editButton.appendChild(editIcon)
editButton.addEventListener('click', function (e) {
clearElement(div)
showForms()
}, false)
clearElement(naviLeft)
naviLeft.appendChild(editButton)
}
// div.appendChild(editButton)
clearElement(naviRight)
naviRight.appendChild(newInstanceButton())
} // showResults
var div = dom.createElement('div')
var structure = div.appendChild(dom.createElement('table')) // @@ make responsive style
structure.setAttribute('style', 'background-color: white; min-width: 40em; min-height: 13em;')
var naviLoginoutTR = structure.appendChild(dom.createElement('tr'))
naviLoginoutTR.appendChild(dom.createElement('td'))
naviLoginoutTR.appendChild(dom.createElement('td'))
naviLoginoutTR.appendChild(dom.createElement('td'))
var logInOutButton = null
/*
var logInOutButton = UI.authn.loginStatusBox(dom, setUser)
// floating divs lead to a mess
// logInOutButton.setAttribute('style', 'float: right') // float the beginning of the end
naviLoginout3.appendChild(logInOutButton)
logInOutButton.setAttribute('style', 'margin-right: 0em;')
*/
var naviTop = structure.appendChild(dom.createElement('tr'))
var naviMain = naviTop.appendChild(dom.createElement('td'))
naviMain.setAttribute('colspan', '3')
var naviMenu = structure.appendChild(dom.createElement('tr'))
naviMenu.setAttribute('class', 'naviMenu')
naviMenu.setAttribute('style', ' text-align: middle; vertical-align: middle; padding-top: 4em; ')
// naviMenu.setAttribute('style', 'margin-top: 3em;')
var naviLeft = naviMenu.appendChild(dom.createElement('td'))
var naviCenter = naviMenu.appendChild(dom.createElement('td'))
var naviRight = naviMenu.appendChild(dom.createElement('td'))
getForms()
return div
} // render
} // property list
// ends