solid-panes
Version:
Solid-compatible Panes: applets and views for the mashlib and databrowser
922 lines (870 loc) • 42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.schedulePane = void 0;
var UI = _interopRequireWildcard(require("solid-ui"));
var _solidLogic = require("solid-logic");
var $rdf = _interopRequireWildcard(require("rdflib"));
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/* Scheduler Pane
**
**
*/
/* global alert */
/* babel-plugin-inline-import './formsForSchedule.ttl' */
const formText = "@prefix dc: <http://purl.org/dc/elements/1.1/>.\n@prefix foaf: <http://xmlns.com/foaf/0.1/>.\n@prefix cal: <http://www.w3.org/2002/12/cal/ical#>.\n@prefix ui: <http://www.w3.org/ns/ui#>.\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.\n@prefix sched: <http://www.w3.org/ns/pim/schedule#>.\n\n cal:Vevent ui:annotationForm <#form2>, <#form3>; ui:creationForm <#form1> .\n\n<#bigForm>\n dc:title\n \"Schedule event (single form).\";\n a ui:Group;\n ui:parts ( <#form1> <#form2> <#form3> ).\n\n<#form1>\n dc:title\n \"Schedule an event (wizard)\";\n a ui:Form;\n ui:parts (\n <#form1header>\n <#eventTitle>\n <#eventLocation>\n <#eventType>\n <#eventSwitch>\n <#eventComment>\n <#eventAuthor> ).\n\n<#form1header> a ui:Heading; ui:contents \"Schedule an event\" .\n\n<#eventTitle>\n a ui:SingleLineTextField;\n ui:maxLength\n \"128\";\n ui:property\n cal:summary;\n ui:size\n \"40\".\n\n<#eventLocation>\n a ui:SingleLineTextField;\n ui:maxLength\n \"512\";\n ui:property\n cal:location;\n ui:size\n \"40\".\n\n<#eventType> a ui:BooleanField;\n ui:property sched:allDay;\n ui:default true . # Used to be the only way\n\n<#eventSwitch> a ui:Options;\n ui:dependingOn sched:allDay;\n ui:case [ ui:for true; ui:use <#AllDayForm> ];\n ui:case [ ui:for false; ui:use <#NotAllDayForm> ].\n\n <#AllDayForm> a ui:IntegerField ;\n ui:property sched:durationInDays;\n ui:label \"How many days?\";\n ui:min 1;\n ui:default 1 .\n\n <#NotAllDayForm> a ui:IntegerField ;\n ui:property sched:durationInMinutes;\n ui:label \"Duration (mins)\";\n ui:min 5;\n ui:default 55 .\n\n<#eventComment>\n a ui:MultiLineTextField;\n# ui:maxLength\n# \"1048\";\n# ui:size\n# \"40\".\n ui:property\n cal:comment.\n\n<#eventAuthor>\n a ui:Multiple; ui:min 1; ui:part <#eventAuthorGroup>; ui:property dc:author.\n\n <#eventAuthorGroup> a ui:Group; ui:parts ( <#authorName> <#authorEmail> ) .\n <#authorName> a ui:SingleLineTextField; ui:property foaf:name .\n <#authorEmail> a ui:EmailField; ui:label \"email\"; ui:property foaf:mbox .\n\n\n#####################\n\n<#form2> dc:title \"Select possible days or times\"; a ui:Form;\n ui:parts ( <#id1118132113134> <#possibleSwitch> ).\n\n <#id1118132113134> a ui:Heading; ui:contents \"Time proposals\" .\n\n <#possibleSwitch> a ui:Options;\n ui:dependingOn sched:allDay;\n ui:case [ ui:for true; ui:use <#AllDayForm2> ];\n ui:case [ ui:for false; ui:use <#NotAllDayForm2> ].\n\n <#AllDayForm2>\n a ui:Multiple; ui:min 2; ui:part <#posssibleDate>; ui:property sched:option.\n <#posssibleDate> a ui:DateField; ui:property cal:dtstart; ui:label \"date\".\n\n <#NotAllDayForm2>\n a ui:Multiple; ui:min 2; ui:part <#posssibleTime>; ui:property sched:option.\n <#posssibleTime> a ui:DateTimeField; ui:property cal:dtstart; ui:label \"date and time\".\n\n ##################################\n\n <#form3> dc:title \"Select invited people\"; a ui:Form; ui:parts ( <#id1118132113135> <#id1417314641301b> ) .\n<#id1417314641301b>\n a ui:Multiple; ui:min 1; ui:part <#id1417314674292b>; ui:property sched:invitee.\n <#id1417314674292b> a ui:EmailField; ui:label \"email\"; ui:property foaf:mbox .\n <#id1118132113135> a ui:Heading; ui:contents \"Who to invite\" .\n\n# ENDS\n";
const ns = UI.ns;
// @@ Give other combos too-- see schedule ontology
const possibleAvailabilities = [ns.sched('No'), ns.sched('Maybe'), ns.sched('Yes')];
const schedulePane = exports.schedulePane = {
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) {
const kb = context.session.store;
const 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) {
const ns = UI.ns;
const kb = context.session.store;
let newBase = options.newBase;
const thisInstance = options.useExisting || $rdf.sym(options.newBase + 'index.ttl#this');
const 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 constiations of ACL for this app, public read and public read/write
// In all cases owner has read write control
const genACLtext = function (docURI, aclURI, allWrite) {
const g = $rdf.graph();
const auth = $rdf.Namespace('http://www.w3.org/ns/auth/acl#');
let a = g.sym(aclURI + '#a1');
const acl = g.sym(aclURI);
const 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');
};
/*
const setACL3 = function (docURI, allWrite, callbackFunction) {
const aclText = genACLtext(docURI, aclDoc.uri, allWrite)
return UI.acl.setACL(docURI, aclText, callbackFunction)
}
*/
const setACL2 = function setACL2(docURI, allWrite, callbackFunction) {
const 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
const 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(() => {
const 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);
}
const 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
const fetcher = kb.fetcher;
const updater = kb.updater;
let me = options.me || _solidLogic.authn.currentUser();
if (!me) {
console.log('MUST BE LOGGED IN');
alert('NOT LOGGED IN');
return;
}
const base = thisInstance.dir().uri;
let 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');
}
const newResultsDoc = kb.sym(newBase + 'results.ttl');
const toBeCopied = options.noIndexHTML ? {} : [{
local: 'index.html',
contentType: 'text/html'
}];
const agenda = [];
// @@ This needs some form of visible progress bar
for (let f = 0; f < toBeCopied.length; f++) {
const item = toBeCopied[f];
const fun = function copyItem(item) {
agenda.push(function () {
const newURI = newBase + item.local;
console.log('Copying ' + base + item.local + ' to ' + newURI);
const 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(() => _solidLogic.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;
const kb = context.session.store;
const ns = UI.ns;
const invitation = subject;
const appPathSegment = 'app-when-can-we.w3.org'; // how to allocate this string and connect to
// ////////////////////////////////////////////
const fetcher = kb.fetcher;
const updater = kb.updater;
let waitingForLogin = false;
const thisInstance = subject;
const detailsDoc = subject.doc();
const baseDir = detailsDoc.dir();
const base = baseDir.uri;
const resultsDoc = $rdf.sym(base + 'results.ttl');
// const formsURI = base + 'forms.ttl'
// We can't in fact host stuff from there because of CORS
const formsURI = 'https://solidos.github.io/solid-panes/schedule/formsForSchedule.ttl';
const form1 = kb.sym(formsURI + '#form1');
const form2 = kb.sym(formsURI + '#form2');
const form3 = kb.sym(formsURI + '#form3');
$rdf.parse(formText, kb, formsURI, 'text/turtle'); // Load forms directly
const inputStyle = 'background-color: #eef; padding: 0.5em; border: .5em solid white; font-size: 100%'; // font-size: 120%
const buttonIconStyle = 'width: 1.8em; height: 1.8em;';
// Utility functions
const complainIfBad = function (ok, message) {
if (!ok) {
div.appendChild(UI.widgets.errorMessageBlock(dom, message, 'pink'));
}
};
const clearElement = function (ele) {
while (ele.firstChild) {
ele.removeChild(ele.firstChild);
}
return ele;
};
const refreshCellColor = function (cell, value) {
const bg = kb.any(value, UI.ns.ui('backgroundColor'));
if (bg) {
cell.setAttribute('style', 'padding: 0.3em; text-align: center; background-color: ' + bg + ';');
}
};
let me;
_solidLogic.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
//
const newInstanceButton = function () {
const b = UI.login.newAppInstance(dom, {
noun: 'scheduler'
}, initializeNewInstanceInWorkspace);
b.firstChild.setAttribute('style', inputStyle);
return b;
}; // newInstanceButton
// /////////////////////// Create new document files for new instance of app
const initializeNewInstanceInWorkspace = function (ws) {
let 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 + '/';
}
const now = new Date();
newBase += appPathSegment + '/id' + now.getTime() + '/'; // unique id
initializeNewInstanceAtBase(thisInstance, newBase);
};
const initializeNewInstanceAtBase = function (thisInstance, newBase) {
const options = {
thisInstance,
newBase
};
this.mintNew(context, options).then(function (options) {
const 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);
});
};
// ///////////////////////
const 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()
})
*/
};
const 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();
});
};
const showAppropriateDisplay = function showAppropriateDisplay() {
console.log('showAppropriateDisplay()');
_solidLogic.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;
}
const ready = kb.any(subject, ns.sched('ready'));
if (!ready) {
showForms();
} else {
// no editing not author
getResults();
}
});
};
const showSignon = function showSignon() {
clearElement(naviMain);
const signonContext = {
div,
dom
};
UI.login.ensureLoggedIn(signonContext).then(context => {
me = context.me;
waitingForLogin = false; // untested
showAppropriateDisplay();
});
};
const showBootstrap = function showBootstrap() {
const div = clearElement(naviMain);
div.appendChild(UI.login.newAppInstance(dom, {
noun: 'poll'
}, initializeNewInstanceInWorkspace));
div.appendChild(dom.createElement('hr')); // @@
const 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.';
const 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')); // @@
const button = div.appendChild(dom.createElement('button'));
button.setAttribute('style', inputStyle);
button.textContent = 'Start new poll at this URI';
button.addEventListener('click', function (_e) {
let newBase = baseField.value;
if (newBase.slice(-1) !== '/') {
newBase += '/';
}
initializeNewInstanceAtBase(thisInstance, newBase);
});
};
// ///////////// The forms to configure the poll
const doneButton = dom.createElement('button');
const showForms = function () {
clearElement(naviCenter); // Remove refresh button if nec
const div = naviMain;
// form2 depends on sched:allDay; seed a local default for new polls
if (!kb.any(subject, ns.sched('allDay'))) {
kb.add(subject, ns.sched('allDay'), $rdf.literal('true', undefined, $rdf.sym('http://www.w3.org/2001/XMLSchema#boolean')), detailsDoc);
}
const wizard = true;
let currentSlide = 0;
let gotDoneButton = false;
const hasFormControls = function (container) {
return !!container.querySelector('input, select, textarea, button');
};
const asBoolean = function (term, fallback) {
if (!term) return fallback;
const value = (term.value || '').toLowerCase();
if (value === 'true' || value === '1') return true;
if (value === 'false' || value === '0') return false;
return fallback;
};
const renderTimeProposalFallback = function (slide) {
const allDayValue = asBoolean(kb.any(subject, ns.sched('allDay')), true);
const fallbackForm = kb.sym(formsURI + (allDayValue ? '#AllDayForm2' : '#NotAllDayForm2'));
UI.widgets.appendForm(document, slide, {}, subject, fallbackForm, detailsDoc, complainIfBad);
};
if (wizard) {
const forms = [form1, form2, form3];
const slides = [];
currentSlide = 0;
for (let f = 0; f < forms.length; f++) {
const slide = dom.createElement('div');
UI.widgets.appendForm(document, slide, {}, subject, forms[f], detailsDoc, complainIfBad);
// Some stores end up with form2's ui:Options unresolved; force a usable input form.
if (f === 1 && !hasFormControls(slide)) {
renderTimeProposalFallback(slide);
}
slides.push(slide);
}
const 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');
}
};
const 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);
const 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
const 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
const 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);
const 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) {
const title = kb.anyValue(subject, ns.cal('summary')) || kb.anyValue(subject, ns.dc('title')) || '';
const mailto = 'mailto:' + kb.each(subject, ns.sched('invitee')).map(function (who) {
const 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
/*
const setTimesOfDay = function () {
const i, j, x, y, slot, cell, day
const insertables = []
const possibleDays = kb.each(invitation, ns.sched('option'))
.map(function (opt) {return kb.any(opt, ns.cal('dtstart'))})
const cellLookup = []
const 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]))
}
}
}
const query = new $rdf.Query('TimesOfDay')
const v = {}['day', 'label', 'value', 'slot', 'cell'].map(function (x) {
query.consts.push(v[x] = $rdf.constiable(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)
const 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')) })
const possibleTimes = kb.each(invitation, ns.sched('option'))
.map(function (opt) { return kb.any(opt, ns.cal('dtstart')) })
const displayTheMatrix = function () {
const matrix = div.appendChild(UI.matrix.matrixForQuery(
dom, query, v.time, v.author, v.value, options, function () {}))
matrix.setAttribute('class', 'matrix')
const refreshButton = dom.createElement('button')
refreshButton.setAttribute('style', inputStyle)
refreshButton.textContent = 'refresh'
refreshButton.addEventListener('click', function (e) {
refreshButton.disabled = true
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)
}
const dataPointForNT = []
const 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) {
// const point = cellLookup[x.toNT() + y.toNT()]
if (y.sameTerm(me)) {
const callbackFunction = function () { refreshCellColor(cell, value); }; // @@ may need that
const selectOptions = {}
const predicate = ns.sched('timeOfDay')
const cellSubject = dataPointForNT[x.toNT()]
const 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)
}
}
const responses = kb.each(invitation, ns.sched('response'))
const myResponse = null
responses.map(function (r) {
if (kb.holds(r, ns.dc('author'), me)) {
myResponse = r
}
})
const 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 {
const dps = kb.each(myResponse, ns.sched('cell'))
dps.map(function (dataPoint) {
const 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
const 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) {
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
function getResults() {
fetcher.nowOrWhenFetched(resultsDoc.uri, undefined, (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();
}
});
}
function showResults() {
// Now the form for responsing to the poll
//
// div.appendChild(dom.createElement('hr'))
// const invitation = subject
const title = kb.any(invitation, ns.cal('summary'));
const comment = kb.any(invitation, ns.cal('comment'));
const location = kb.any(invitation, ns.cal('location'));
const 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;
}
const author = kb.any(invitation, ns.dc('author'));
if (author) {
const authorName = kb.any(author, ns.foaf('name'));
if (authorName) {
div.appendChild(dom.createElement('p')).textContent = authorName;
}
}
const query = new $rdf.Query('Responses');
const v = {};
const vs = ['time', 'author', 'value', 'resp', 'cell'];
vs.forEach(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 @@@
const 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'));
}).filter(function (time) {
return !!time;
});
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'));
}).filter(function (author) {
return !!author;
});
const possibleTimes = kb.each(invitation, ns.sched('option')).map(function (opt) {
return kb.any(opt, ns.cal('dtstart'));
}).filter(function (time) {
return !!time;
});
const displayTheMatrix = function () {
const matrix = div.appendChild(UI.matrix.matrixForQuery(dom, query, v.time, v.author, v.value, options, function () {}));
matrix.setAttribute('class', 'matrix');
const 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
// const possibleAvailabilities = [ SCHED('No'), SCHED('Maybe'), SCHED('Yes') ]
// const me = authn.currentUser()
const dataPointForNT = [];
const loginContext = {
div: naviCenter,
dom
};
UI.login.ensureLoggedIn(loginContext).then(context => {
const me = context.me;
const 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)) {
const callbackFunction = function () {
refreshCellColor(cell, value);
}; // @@ may need that
const selectOptions = {};
const predicate = ns.sched('availabilty');
if (!x) return;
const cellSubject = dataPointForNT[x.toNT()];
const 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);
}
};
const responses = kb.each(invitation, ns.sched('response'));
let myResponse = null;
responses.forEach(function (r) {
if (kb.holds(r, ns.dc('author'), me)) {
myResponse = r;
}
});
const insertables = []; // list of statements to be stored
const 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 {
const dps = kb.each(myResponse, ns.sched('cell'));
dps.forEach(function (dataPoint) {
const time = kb.any(dataPoint, ns.cal('dtstart'));
if (!time) return;
dataPointForNT[time.toNT()] = dataPoint;
});
}
for (let j = 0; j < possibleTimes.length; j++) {
const possibleTime = possibleTimes[j];
if (!possibleTime) continue;
if (dataPointForNT[possibleTime.toNT()]) continue;
const dataPoint = $rdf.sym(id + '_' + j);
insertables.push($rdf.st(myResponse, ns.sched('cell'), dataPoint, doc));
insertables.push($rdf.st(dataPoint, ns.cal('dtstart'), possibleTime, doc)); // @@
dataPointForNT[possibleTime.toNT()] = dataPoint;
}
if (insertables.length) {
kb.updater.update([], insertables, function (uri, success, errorBody) {
if (!success) {
complainIfBad(success, errorBody);
} else {
displayTheMatrix();
}
});
} else {
// no insertables
displayTheMatrix();
}
}); // @@@@ end of .then
// If I made this in the first place, allow me to edit it.
// @@ optionally -- allows others to if according to original
const instanceCreator = kb.any(subject, ns.foaf('maker')); // owner?
if (!instanceCreator || instanceCreator.sameTerm(me)) {
const 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
const div = dom.createElement('div');
const structure = div.appendChild(dom.createElement('table')); // @@ make responsive style
structure.setAttribute('style', 'background-color: white; min-width: 40em; min-height: 13em;');
const naviLoginoutTR = structure.appendChild(dom.createElement('tr'));
naviLoginoutTR.appendChild(dom.createElement('td'));
naviLoginoutTR.appendChild(dom.createElement('td'));
naviLoginoutTR.appendChild(dom.createElement('td'));
const logInOutButton = null;
/*
const logInOutButton = UI.login.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;')
*/
const naviTop = structure.appendChild(dom.createElement('tr'));
const naviMain = naviTop.appendChild(dom.createElement('td'));
naviMain.setAttribute('colspan', '3');
const 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;')
const naviLeft = naviMenu.appendChild(dom.createElement('td'));
const naviCenter = naviMenu.appendChild(dom.createElement('td'));
const naviRight = naviMenu.appendChild(dom.createElement('td'));
getForms();
return div;
} // render
}; // property list
// ends