solid-panes
Version:
Solid-compatible Panes: applets and views for the mashlib and databrowser
410 lines (388 loc) • 17.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _solidUi = require("solid-ui");
var _solidLogic = require("solid-logic");
var _rdflib = require("rdflib");
/* pad Pane
**
*/
const paneDef = {
// icon: (module.__dirname || __dirname) + 'images/ColourOn.png',
icon: _solidUi.icons.iconBase + 'noun_79217.svg',
name: 'pad',
audience: [_solidUi.ns.solid('PowerUser')],
// Does the subject deserve an pad pane?
label: function (subject, context) {
const t = context.session.store.findTypeURIs(subject);
if (t['http://www.w3.org/ns/pim/pad#Notepad']) {
return 'pad';
}
return null; // No under other circumstances
},
mintClass: _solidUi.ns.pad('Notepad'),
mintNew: function (context, newPaneOptions) {
const store = context.session.store;
const updater = store.updater;
if (newPaneOptions.me && !newPaneOptions.me.uri) {
throw new Error('notepad mintNew: Invalid userid');
}
const newInstance = newPaneOptions.newInstance = newPaneOptions.newInstance || store.sym(newPaneOptions.newBase + 'index.ttl#this');
// const newInstance = kb.sym(newBase + 'pad.ttl#thisPad');
const newPadDoc = newInstance.doc();
store.add(newInstance, _solidUi.ns.rdf('type'), _solidUi.ns.pad('Notepad'), newPadDoc);
store.add(newInstance, _solidUi.ns.dc('title'), 'Shared Notes', newPadDoc);
store.add(newInstance, _solidUi.ns.dc('created'), new Date(), newPadDoc); // @@ TODO Remove casting
if (newPaneOptions.me) {
store.add(newInstance, _solidUi.ns.dc('author'), newPaneOptions.me, newPadDoc);
}
// kb.add(newInstance, ns.pad('next'), newInstance, newPadDoc);
// linked list empty @@
const chunk = store.sym(newInstance.uri + '_line0');
store.add(newInstance, _solidUi.ns.pad('next'), chunk, newPadDoc); // Linked list has one entry
store.add(chunk, _solidUi.ns.pad('next'), newInstance, newPadDoc);
store.add(chunk, _solidUi.ns.dc('author'), newPaneOptions.me, newPadDoc);
store.add(chunk, _solidUi.ns.sioc('content'), '', newPadDoc);
return new Promise(function (resolve, reject) {
if (!updater) {
reject(new Error('Have no updater'));
return;
}
updater.put(newPadDoc, store.statementsMatching(undefined, undefined, undefined, newPadDoc), 'text/turtle', function (uri2, ok, message) {
if (ok) {
resolve(newPaneOptions);
} else {
reject(new Error('FAILED to save new tool at: ' + uri2 + ' : ' + message));
}
});
});
},
// and follow instructions there
// @@ TODO Set better type for paneOptions
render: function (subject, context, paneOptions) {
const dom = context.dom;
const store = context.session.store;
// Utility functions
const complainIfBad = function (ok, message) {
if (!ok) {
div.appendChild(_solidUi.widgets.errorMessageBlock(dom, message, 'pink'));
}
};
const clearElement = function (ele) {
while (ele.firstChild) {
ele.removeChild(ele.firstChild);
}
return ele;
};
// Access 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 = (0, _rdflib.graph)();
const auth = (0, _rdflib.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, _solidUi.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, _solidUi.ns.rdf('type'), auth('Authorization'), acl);
g.add(a, auth('accessTo'), doc, acl);
g.add(a, auth('agentClass'), _solidUi.ns.foaf('Agent'), acl);
g.add(a, auth('mode'), auth('Read'), acl);
if (allWrite) {
g.add(a, auth('mode'), auth('Write'), acl);
}
// TODO: Figure out why `serialize` isn't on the type definition according to TypeScript:
return (0, _rdflib.serialize)(acl, g, aclURI, 'text/turtle');
};
/**
* @param docURI
* @param allWrite
* @param callbackFunction
*
* @returns {Promise<Response>}
*/
const setACL = function setACL(docURI, allWrite, callbackFunction) {
const aclDoc = store.any((0, _rdflib.sym)(docURI), (0, _rdflib.sym)('http://www.iana.org/assignments/link-relations/acl')); // @@ check that this get set by web.js
if (!fetcher) {
throw new Error('Have no fetcher');
}
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(() => 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 = store.any((0, _rdflib.sym)(docURI), (0, _rdflib.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(() => callbackFunction(true)).catch(err => {
callbackFunction(false, err.message);
});
}
};
// Reproduction: spawn a new instance
//
// Viral growth path: user of app decides to make another instance
const newInstanceButton = function () {
const button = div.appendChild(dom.createElement('button'));
button.textContent = 'Start another pad';
button.addEventListener('click', function () {
return showBootstrap(subject, spawnArea, 'pad');
});
return button;
};
// Option of either using the workspace system or just typing in a URI
const showBootstrap = function showBootstrap(thisInstance, container, noun) {
const div = clearElement(container);
const appDetails = {
noun: 'notepad'
};
div.appendChild(_solidUi.login.newAppInstance(dom, appDetails, (workspace, newBase) => {
// FIXME: not sure if this will work at all, just
// trying to get the types to match - Michiel.
return initializeNewInstanceInWorkspace(new _rdflib.NamedNode(workspace || newBase));
}));
div.appendChild(dom.createElement('hr')); // @@
const p = div.appendChild(dom.createElement('p'));
p.textContent = 'Where would you like to store the data for the ' + noun + '? ' + '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.textContent = 'Start new ' + noun + ' at this URI';
button.addEventListener('click', function (_e) {
let newBase = baseField.value;
if (newBase.slice(-1) !== '/') {
newBase += '/';
}
initializeNewInstanceAtBase(thisInstance, newBase);
});
};
// Create new document files for new instance of app
const initializeNewInstanceInWorkspace = function (ws) {
// @@ TODO Clean up type for newBase
let newBase = store.any(ws, _solidUi.ns.space('uriPrefix'));
if (!newBase) {
newBase = ws.uri.split('#')[0];
} else {
newBase = newBase.value;
}
if (newBase.slice(-1) !== '/') {
_rdflib.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 here = (0, _rdflib.sym)(thisInstance.uri.split('#')[0]);
const base = here; // @@ ???
const newPadDoc = store.sym(newBase + 'pad.ttl');
const newIndexDoc = store.sym(newBase + 'index.html');
const toBeCopied = [{
local: 'index.html',
contentType: 'text/html'
}];
const newInstance = store.sym(newPadDoc.uri + '#thisPad');
// log.debug("\n Ready to put " + kb.statementsMatching(undefined, undefined, undefined, there)); //@@
const agenda = [];
let f; // @@ This needs some form of visible progress bar
for (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 () {
setACL(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
}
});
};
if (!store.fetcher) {
throw new Error('Store has no fetcher');
}
store.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 createNewPadDataFile() {
store.add(newInstance, _solidUi.ns.rdf('type'), PAD('Notepad'), newPadDoc);
// TODO @@ Remove casting of add
store.add(newInstance, _solidUi.ns.dc('created'), new Date(),
// @@ TODO Remove casting
newPadDoc);
if (me) {
store.add(newInstance, _solidUi.ns.dc('author'), me, newPadDoc);
}
store.add(newInstance, PAD('next'), newInstance, newPadDoc); // linked list empty
// Keep a paper trail @@ Revisit when we have non-public ones @@ Privacy
store.add(newInstance, _solidUi.ns.space('inspiration'), thisInstance, padDoc);
store.add(newInstance, _solidUi.ns.space('inspiration'), thisInstance, newPadDoc);
if (!updater) {
throw new Error('Have no updater');
}
updater.put(newPadDoc, store.statementsMatching(undefined, undefined, undefined, newPadDoc), 'text/turtle', function (_uri2, ok, message) {
if (ok) {
agenda.shift()();
} else {
complainIfBad(ok, 'FAILED to save new notepad at: ' + newPadDoc.uri + ' : ' + message);
console.log('FAILED to save new notepad at: ' + newPadDoc.uri + ' : ' + message);
}
});
});
agenda.push(function () {
setACL(newPadDoc.uri, true, function (ok, body) {
complainIfBad(ok, 'Failed to set Read-Write ACL on pad data file: ' + body);
if (ok) agenda.shift()();
});
});
agenda.push(function () {
// give the user links to the new app
const p = div.appendChild(dom.createElement('p'));
p.setAttribute('style', 'font-size: 140%;');
p.innerHTML = 'Your <a href=\'' + newIndexDoc.uri + '\'><b>new notepad</b></a> is ready. ' + '<br/><br/><a href=\'' + newIndexDoc.uri + '\'>Go to new pad</a>';
});
agenda.shift()();
// Created new data files.
};
// Update on incoming changes
const showResults = function (exists) {
console.log('showResults()');
me = _solidLogic.authn.currentUser();
_solidLogic.authn.checkUser().then(webId => {
me = webId;
});
const title = store.any(subject, _solidUi.ns.dc('title')) || store.any(subject, _solidUi.ns.vcard('fn'));
if (paneOptions.solo && typeof window !== 'undefined' && title) {
window.document.title = title.value;
}
options.exists = exists;
padEle = _solidUi.pad.notepad(dom, padDoc, subject, me, options);
naviMain.appendChild(padEle);
const partipationTarget = store.any(subject, _solidUi.ns.meeting('parentMeeting')) || subject;
_solidUi.pad.manageParticipation(dom, naviMiddle2, padDoc, partipationTarget, me, options);
if (!store.updater) {
throw new Error('Store has no updater');
}
store.updater.setRefreshHandler(padDoc, padEle.reloadAndSync); // initiated =
};
// Read or create empty data file
const loadPadData = function () {
if (!fetcher) {
throw new Error('Have no fetcher');
}
fetcher.nowOrWhenFetched(padDoc.uri, undefined, function (ok, body, response) {
if (!ok) {
if (response.status === 404) {
// / Check explicitly for 404 error
console.log('Initializing results file ' + padDoc);
if (!updater) {
throw new Error('Have no updater');
}
updater.put(padDoc, [], 'text/turtle', function (_uri2, ok, message) {
if (ok) {
clearElement(naviMain);
showResults(false);
} else {
complainIfBad(ok, 'FAILED to create results file at: ' + padDoc.uri + ' : ' + message);
console.log('FAILED to craete results file at: ' + padDoc.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);
if (store.holds(subject, _solidUi.ns.rdf('type'), _solidUi.ns.wf('TemplateInstance'))) {
showBootstrap(subject, naviMain, 'pad');
}
showResults(true);
naviMiddle3.appendChild(newInstanceButton());
}
});
};
// Body of Pane
const appPathSegment = 'app-pad.timbl.com'; // how to allocate this string and connect to
const fetcher = store.fetcher;
const updater = store.updater;
let me;
const PAD = (0, _rdflib.Namespace)('http://www.w3.org/ns/pim/pad#');
const thisInstance = subject;
const padDoc = subject.doc();
let padEle;
const div = dom.createElement('div');
// Build the DOM
const structure = div.appendChild(dom.createElement('table')); // @@ make responsive style
structure.setAttribute('style', 'background-color: white; min-width: 94%; margin-right:3% margin-left: 3%; min-height: 13em;');
const naviLoginoutTR = structure.appendChild(dom.createElement('tr'));
naviLoginoutTR.appendChild(dom.createElement('td')); // naviLoginout1
naviLoginoutTR.appendChild(dom.createElement('td'));
naviLoginoutTR.appendChild(dom.createElement('td'));
const naviTop = structure.appendChild(dom.createElement('tr')); // stuff
const naviMain = naviTop.appendChild(dom.createElement('td'));
naviMain.setAttribute('colspan', '3');
const naviMiddle = structure.appendChild(dom.createElement('tr')); // controls
const naviMiddle1 = naviMiddle.appendChild(dom.createElement('td'));
const naviMiddle2 = naviMiddle.appendChild(dom.createElement('td'));
const naviMiddle3 = naviMiddle.appendChild(dom.createElement('td'));
const naviStatus = structure.appendChild(dom.createElement('tr')); // status etc
const statusArea = naviStatus.appendChild(dom.createElement('div'));
const naviSpawn = structure.appendChild(dom.createElement('tr')); // create new
const spawnArea = naviSpawn.appendChild(dom.createElement('div'));
const naviMenu = structure.appendChild(dom.createElement('tr'));
naviMenu.setAttribute('class', 'naviMenu');
// naviMenu.setAttribute('style', 'margin-top: 3em;');
naviMenu.appendChild(dom.createElement('td')); // naviLeft
naviMenu.appendChild(dom.createElement('td'));
naviMenu.appendChild(dom.createElement('td'));
const options = {
statusArea,
timingArea: naviMiddle1
};
loadPadData();
return div;
}
};
// ends
var _default = exports.default = paneDef;