src/schedule/schedulePane.js
/* 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',
audience: [ns.solid('PowerUser')],
// Does the subject deserve an Scheduler pane?
label: function (subject, context) {
var kb = context.session.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 (context, options) {
return new Promise(function (resolve, reject) {
var ns = UI.ns
var kb = context.session.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' }]
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')
options.newInstance = newInstance
resolve(options)
})
agenda.shift()()
// Created new data files.
}) // promise
}, // mintNew
// Render one meeting schedule poll
render: function (subject, context) {
const dom = context.dom
var kb = context.session.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 = kb.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://solid.github.io/solid-panes/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(context, 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 (
!kb.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)
const signonContext = { div: div, dom: dom }
UI.authn.logIn(signonContext).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) {
const forms = [form1, form2, form3]
const slides = []
currentSlide = 0
for (var f = 0; f < forms.length; f++) {
const 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)
kb.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)
const 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
const 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
kb.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 loginContext = { div: naviCenter, dom: dom }
UI.authn.logIn(loginContext).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) {
kb.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
const 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