solid-ui
Version:
UI library for Solid applications
238 lines • 11.5 kB
JavaScript
/**
* Contains the [[AccessController]] class
* @packageDocumentation
*/
import { adoptACLDefault, getProspectiveHolder, makeACLGraphbyCombo, sameACL } from './acl';
import { fetcher, graph, UpdateManager } from 'rdflib';
import { AccessGroups } from './access-groups';
import { shortNameForFolder } from './acl-control';
import * as utils from '../utils';
import * as debug from '../debug';
import { style } from '../style';
/**
* Rendered HTML component used in the databrowser's Sharing pane.
*/
export class AccessController {
constructor(subject, noun, context, statusElement, targetIsProtected, targetDoc, targetACLDoc, defaultHolder, defaultACLDoc, prospectiveDefaultHolder, store, dom) {
this.subject = subject;
this.noun = noun;
this.context = context;
this.statusElement = statusElement;
this.targetIsProtected = targetIsProtected;
this.targetDoc = targetDoc;
this.targetACLDoc = targetACLDoc;
this.defaultHolder = defaultHolder;
this.defaultACLDoc = defaultACLDoc;
this.prospectiveDefaultHolder = prospectiveDefaultHolder;
this.store = store;
this.dom = dom;
this.rootElement = dom.createElement('div');
this.rootElement.setAttribute('style', style.aclGroupContent);
this.isContainer = targetDoc.uri.slice(-1) === '/'; // Give default for all directories
if (defaultHolder && defaultACLDoc) {
this.isUsingDefaults = true;
const aclDefaultStore = adoptACLDefault(this.targetDoc, targetACLDoc, defaultHolder, defaultACLDoc);
this.mainCombo = new AccessGroups(targetDoc, targetACLDoc, this, aclDefaultStore, { defaults: this.isContainer });
this.defaultsCombo = null;
this.defaultsDiffer = false;
}
else {
this.isUsingDefaults = false;
this.mainCombo = new AccessGroups(targetDoc, targetACLDoc, this, store);
this.defaultsCombo = new AccessGroups(targetDoc, targetACLDoc, this, store, { defaults: this.isContainer });
this.defaultsDiffer = !sameACL(this.mainCombo.aclMap, this.defaultsCombo.aclMap);
}
}
get isEditable() {
return !this.isUsingDefaults;
}
render() {
this.rootElement.innerHTML = '';
if (this.isUsingDefaults) {
this.renderStatus(`The sharing for this ${this.noun} is the default for folder `);
if (this.defaultHolder) {
const defaultHolderLink = this.statusElement.appendChild(this.dom.createElement('a'));
defaultHolderLink.href = this.defaultHolder.uri;
defaultHolderLink.innerText = shortNameForFolder(this.defaultHolder);
}
}
else if (!this.defaultsDiffer && this.isContainer) {
this.renderStatus('This is also the default for things in this folder.');
}
else {
this.renderStatus('');
}
this.rootElement.appendChild(this.mainCombo.render());
if (this.defaultsCombo && this.defaultsDiffer) {
this.rootElement.appendChild(this.renderRemoveDefaultsController());
this.rootElement.appendChild(this.defaultsCombo.render());
}
else if (this.isEditable && this.isContainer) {
this.rootElement.appendChild(this.renderAddDefaultsController());
}
if (!this.targetIsProtected && this.isUsingDefaults) {
this.rootElement.appendChild(this.renderAddAclsController());
}
else if (!this.targetIsProtected) {
this.rootElement.appendChild(this.renderRemoveAclsController());
}
return this.rootElement;
}
renderRemoveAclsController() {
const useDefaultButton = this.dom.createElement('button');
useDefaultButton.innerText = `Remove custom sharing settings for this ${this.noun} -- just use default${this.prospectiveDefaultHolder ? ` for ${utils.label(this.prospectiveDefaultHolder)}` : ''}`;
useDefaultButton.setAttribute('style', style.bigButton);
useDefaultButton.addEventListener('click', () => this.removeAcls()
.then(() => this.render())
.catch(error => this.renderStatus(error)));
return useDefaultButton;
}
renderAddAclsController() {
const addAclButton = this.dom.createElement('button');
addAclButton.innerText = `Set specific sharing for this ${this.noun}`;
addAclButton.setAttribute('style', style.bigButton);
addAclButton.addEventListener('click', () => this.addAcls()
.then(() => this.render())
.catch(error => this.renderStatus(error)));
return addAclButton;
}
renderAddDefaultsController() {
const containerElement = this.dom.createElement('div');
containerElement.setAttribute('style', style.defaultsController);
const noticeElement = containerElement.appendChild(this.dom.createElement('div'));
noticeElement.innerText = 'Sharing for things within the folder currently tracks sharing for the folder.';
noticeElement.setAttribute('style', style.defaultsControllerNotice);
const button = containerElement.appendChild(this.dom.createElement('button'));
button.innerText = 'Set the sharing of folder contents separately from the sharing for the folder';
button.setAttribute('style', style.bigButton);
button.addEventListener('click', () => this.addDefaults()
.then(() => this.render()));
return containerElement;
}
renderRemoveDefaultsController() {
const containerElement = this.dom.createElement('div');
containerElement.setAttribute('style', style.defaultsController);
const noticeElement = containerElement.appendChild(this.dom.createElement('div'));
noticeElement.innerText = 'Access to things within this folder:';
noticeElement.setAttribute('style', style.defaultsControllerNotice);
const button = containerElement.appendChild(this.dom.createElement('button'));
button.innerText = 'Set default for folder contents to just track the sharing for the folder';
button.setAttribute('style', style.bigButton);
button.addEventListener('click', () => this.removeDefaults()
.then(() => this.render())
.catch(error => this.renderStatus(error)));
return containerElement;
}
renderTemporaryStatus(message) {
// @@ TODO Introduce better system for error notification to user https://github.com/solidos/mashlib/issues/87
this.statusElement.setAttribute('style', style.aclControlBoxStatusRevealed);
this.statusElement.innerText = message;
this.statusElement.setAttribute('style', style.temporaryStatusInit);
setTimeout(() => {
this.statusElement.setAttribute('style', style.temporaryStatusEnd);
});
setTimeout(() => {
this.statusElement.innerText = '';
}, 5000);
}
renderStatus(message) {
// @@ TODO Introduce better system for error notification to user https://github.com/solidos/mashlib/issues/87
if (!message) {
this.statusElement.setAttribute('style', style.aclControlBoxStatusRevealed);
}
this.statusElement.innerText = message;
}
async addAcls() {
if (!this.defaultHolder || !this.defaultACLDoc) {
const message = 'Unable to find defaults to copy';
debug.error(message);
return Promise.reject(message);
}
const aclGraph = adoptACLDefault(this.targetDoc, this.targetACLDoc, this.defaultHolder, this.defaultACLDoc);
aclGraph.statements.forEach(st => this.store.add(st.subject, st.predicate, st.object, this.targetACLDoc));
try {
await this.store.fetcher.putBack(this.targetACLDoc);
this.isUsingDefaults = false;
return Promise.resolve();
}
catch (error) {
const message = ` Error writing back access control file! ${error}`;
debug.error(message);
return Promise.reject(message);
}
}
async addDefaults() {
this.defaultsCombo = new AccessGroups(this.targetDoc, this.targetACLDoc, this, this.store, { defaults: true });
this.defaultsDiffer = true;
}
async removeAcls() {
try {
await this.store.fetcher.delete(this.targetACLDoc.uri, {});
this.isUsingDefaults = true;
try {
this.prospectiveDefaultHolder = await getProspectiveHolder(this.targetDoc.uri);
}
catch (error) {
// No need to show this error in status, but good to warn about it in console
debug.warn(error);
}
}
catch (error) {
const message = `Error deleting access control file: ${this.targetACLDoc}: ${error}`;
debug.error(message);
return Promise.reject(message);
}
}
async removeDefaults() {
const fallbackCombo = this.defaultsCombo;
try {
this.defaultsCombo = null;
this.defaultsDiffer = false;
await this.save();
}
catch (error) {
this.defaultsCombo = fallbackCombo;
this.defaultsDiffer = true;
debug.error(error);
return Promise.reject(error);
}
}
save() {
// build graph
const newAClGraph = graph();
if (!this.isContainer) {
makeACLGraphbyCombo(newAClGraph, this.targetDoc, this.mainCombo.byCombo, this.targetACLDoc, true);
}
else if (this.defaultsCombo && this.defaultsDiffer) {
// Pair of controls
makeACLGraphbyCombo(newAClGraph, this.targetDoc, this.mainCombo.byCombo, this.targetACLDoc, true);
makeACLGraphbyCombo(newAClGraph, this.targetDoc, this.defaultsCombo.byCombo, this.targetACLDoc, false, true);
}
else {
// Linked controls
makeACLGraphbyCombo(newAClGraph, this.targetDoc, this.mainCombo.byCombo, this.targetACLDoc, true, true);
}
// add authenticated fetcher
newAClGraph.fetcher = fetcher(newAClGraph, { fetch: this.store.fetcher._fetch });
const updater = newAClGraph.updater || new UpdateManager(newAClGraph);
// save ACL resource
return new Promise((resolve, reject) => {
updater.put(this.targetACLDoc, newAClGraph.statementsMatching(undefined, undefined, undefined, this.targetACLDoc), 'text/turtle', (uri, ok, message) => {
if (!ok) {
return reject(new Error(`ACL file save failed: ${message}`));
}
this.store.fetcher.unload(this.targetACLDoc);
this.store.add(newAClGraph.statements);
this.store.fetcher.requested[this.targetACLDoc.uri] = 'done'; // missing: save headers
this.mainCombo.store = this.store;
if (this.defaultsCombo) {
this.defaultsCombo.store = this.store;
}
this.defaultsDiffer = !!this.defaultsCombo && !sameACL(this.mainCombo.aclMap, this.defaultsCombo.aclMap);
debug.log('ACL modification: success!');
resolve();
});
});
}
}
//# sourceMappingURL=access-controller.js.map