solid-ui
Version:
UI library for Solid applications
858 lines • 35.3 kB
JavaScript
import { BlankNode, st } from 'rdflib';
import { authn, authSession, CrossOriginForbiddenError, FetchError, getSuggestedIssuers, NotEditableError, offlineTestID, SameOriginForbiddenError, solidLogicSingleton, UnauthorizedError, WebOperationError } from 'solid-logic';
import * as debug from '../debug';
import { style } from '../style';
import { alert } from '../log';
import ns from '../ns';
import { Signup } from '../signup/signup.js';
import * as utils from '../utils';
import * as widgets from '../widgets';
const store = solidLogicSingleton.store;
const { loadPreferences, loadProfile } = solidLogicSingleton.profile;
const { getScopedAppInstances, getRegistrations, loadAllTypeIndexes, getScopedAppsFromIndex, deleteTypeIndexRegistration } = solidLogicSingleton.typeIndex;
/**
* Resolves with the logged in user's WebID
*
* @param context
*/
// used to be logIn
export function ensureLoggedIn(context) {
const me = authn.currentUser();
if (me) {
authn.saveUser(me, context);
return Promise.resolve(context);
}
return new Promise((resolve) => {
authn.checkUser().then((webId) => {
// Already logged in?
if (webId) {
debug.log(`logIn: Already logged in as ${webId}`);
return resolve(context);
}
if (!context.div || !context.dom) {
return resolve(context);
}
const box = loginStatusBox(context.dom, (webIdUri) => {
authn.saveUser(webIdUri, context);
resolve(context); // always pass growing context
});
context.div.appendChild(box);
});
});
}
/**
* Loads preference file
* Do this after having done log in and load profile
*
* @private
*
* @param context
*/
// used to be logInLoadPreferences
export async function ensureLoadedPreferences(context) {
if (context.preferencesFile)
return Promise.resolve(context); // already done
// const statusArea = context.statusArea || context.div || null
let progressDisplay;
/* COMPLAIN FUNCTION NOT USED/TAKING IT OUT FOR NOW
function complain (message) {
message = `ensureLoadedPreferences: ${message}`
if (statusArea) {
// statusArea.innerHTML = ''
statusArea.appendChild(widgets.errorMessageBlock(context.dom, message))
}
debug.log(message)
// reject(new Error(message))
} */
try {
context = await ensureLoadedProfile(context);
// console.log('back in Solid UI after logInLoadProfile', context)
const preferencesFile = await loadPreferences(context.me);
if (progressDisplay) {
progressDisplay.parentNode.removeChild(progressDisplay);
}
context.preferencesFile = preferencesFile;
}
catch (err) {
let m2;
if (err instanceof UnauthorizedError) {
m2 =
'Oops — you are not authenticated (properly logged in), so SolidOS cannot read your preferences file. Try logging out and then logging back in.';
alert(m2);
}
else if (err instanceof CrossOriginForbiddenError) {
m2 = `Unauthorized: Assuming preference file blocked for origin ${window.location.origin}`;
context.preferencesFileError = m2;
return context;
}
else if (err instanceof SameOriginForbiddenError) {
m2 =
'You are not authorized to read your preference file. This may be because you are using an untrusted web app.';
debug.warn(m2);
return context;
}
else if (err instanceof NotEditableError) {
m2 =
'You are not authorized to edit your preference file. This may be because you are using an untrusted web app.';
debug.warn(m2);
return context;
}
else if (err instanceof WebOperationError) {
m2 =
'You are not authorized to edit your preference file. This may be because you are using an untrusted web app.';
debug.warn(m2);
}
else if (err instanceof FetchError) {
m2 = `Strange: Error ${err.status} trying to read your preference file.${err.message}`;
alert(m2);
}
else {
throw new Error(`(via loadPrefs) ${err}`);
}
}
return context;
}
/**
* Logs the user in and loads their WebID profile document into the store
*
* @param context
*
* @returns Resolves with the context after login / fetch
*/
// used to be logInLoadProfile
export async function ensureLoadedProfile(context) {
if (context.publicProfile) {
return context;
} // already done
try {
const logInContext = await ensureLoggedIn(context);
if (!logInContext.me) {
throw new Error('Could not log in');
}
context.publicProfile = await loadProfile(logInContext.me);
}
catch (err) {
if (context.div && context.dom) {
context.div.appendChild(widgets.errorMessageBlock(context.dom, err.message));
}
throw new Error(`Can't log in: ${err}`);
}
return context;
}
/**
* Returns promise of context with arrays of symbols
*
* leaving the `isPublic` param undefined will bring in community index things, too
*/
export async function findAppInstances(context, theClass, isPublic) {
let items = context.me ? await getScopedAppInstances(theClass, context.me) : [];
if (isPublic === true) { // old API - not recommended!
items = items.filter(item => item.scope.label === 'public');
}
else if (isPublic === false) {
items = items.filter(item => item.scope.label === 'private');
}
context.instances = items.map(item => item.instance);
return context;
}
export function scopeLabel(context, scope) {
const mine = context.me && context.me.sameTerm(scope.agent);
const name = mine ? '' : utils.label(scope.agent) + ' ';
return `${name}${scope.label}`;
}
/**
* UI to control registration of instance
*/
export async function registrationControl(context, instance, theClass) {
function registrationStatements(index) {
const registrations = getRegistrations(instance, theClass);
const reg = registrations.length ? registrations[0] : widgets.newThing(index);
return [
st(reg, ns.solid('instance'), instance, index),
st(reg, ns.solid('forClass'), theClass, index)
];
}
function renderScopeCheckbox(scope) {
const statements = registrationStatements(scope.index);
const name = scopeLabel(context, scope);
const label = `${name} link to this ${context.noun}`;
return widgets.buildCheckboxForm(context.dom, solidLogicSingleton.store, label, null, statements, form, scope.index);
}
/// / body of registrationControl
const dom = context.dom;
if (!dom || !context.div) {
throw new Error('registrationControl: need dom and div');
}
const box = dom.createElement('div');
context.div.appendChild(box);
context.me = authn.currentUser(); // @@
const me = context.me;
if (!me) {
box.innerHTML = '<p style="margin:2em;">(Log in to save a link to this)</p>';
return context;
}
let scopes; // @@ const
try {
scopes = await loadAllTypeIndexes(me);
}
catch (e) {
let msg;
if (context.div && context.preferencesFileError) {
msg = '(Lists of stuff not available)';
context.div.appendChild(dom.createElement('p')).textContent = msg;
}
else if (context.div) {
msg = `registrationControl: Type indexes not available: ${e}`;
context.div.appendChild(widgets.errorMessageBlock(context.dom, e));
}
debug.log(msg);
return context;
}
box.innerHTML = '<table><tbody></tbody></table>'; // tbody will be inserted anyway
box.setAttribute('style', 'font-size: 120%; text-align: right; padding: 1em; border: solid gray 0.05em;');
const tbody = box.children[0].children[0];
const form = new BlankNode(); // @@ say for now
for (const scope of scopes) {
const row = tbody.appendChild(dom.createElement('tr'));
row.appendChild(renderScopeCheckbox(scope)); // @@ index
}
return context;
}
export function renderScopeHeadingRow(context, store, scope) {
const backgroundColor = { private: '#fee', public: '#efe' };
const { dom } = context;
const name = scopeLabel(context, scope);
const row = dom.createElement('tr');
const cell = row.appendChild(dom.createElement('td'));
cell.setAttribute('colspan', '3');
cell.style.backgoundColor = backgroundColor[scope.label] || 'white';
const header = cell.appendChild(dom.createElement('h3'));
header.textContent = name + ' links';
header.style.textAlign = 'left';
return row;
}
/**
* UI to List at all registered things
*/
export async function registrationList(context, options) {
const dom = context.dom;
const div = context.div;
const box = dom.createElement('div');
div.appendChild(box);
context.me = authn.currentUser(); // @@
if (!context.me) {
box.innerHTML = '<p style="margin:2em;">(Log in list your stuff)</p>';
return context;
}
const scopes = await loadAllTypeIndexes(context.me); // includes community indexes
// console.log('@@ registrationList ', scopes)
box.innerHTML = '<table><tbody></tbody></table>'; // tbody will be inserted anyway
box.setAttribute('style', 'font-size: 120%; text-align: right; padding: 1em; border: solid #eee 0.5em;');
const table = box.firstChild;
const tbody = table.firstChild;
for (const scope of scopes) { // need some predicate for listing/adding agents
const headingRow = renderScopeHeadingRow(context, store, scope);
tbody.appendChild(headingRow);
const items = await getScopedAppsFromIndex(scope, options.type || null); // any class
if (items.length === 0)
headingRow.style.display = 'none';
// console.log(`registrationList: @@ instance items for class ${options.type || 'undefined' }:`, items)
for (const item of items) {
const row = widgets.personTR(dom, ns.solid('instance'), item.instance, {
deleteFunction: async () => {
await deleteTypeIndexRegistration(item);
tbody.removeChild(row);
}
});
row.children[0].style.paddingLeft = '3em';
tbody.appendChild(row);
}
}
return context;
} // registrationList
/**
* Bootstrapping identity
* (Called by `loginStatusBox()`)
*
* @param dom
* @param setUserCallback
*
* @returns
*/
function signInOrSignUpBox(dom, setUserCallback, options = {}) {
options = options || {};
const signInButtonStyle = options.buttonStyle || style.signInAndUpButtonStyle;
const box = dom.createElement('div');
const magicClassName = 'SolidSignInOrSignUpBox';
debug.log('widgets.signInOrSignUpBox');
box.setUserCallback = setUserCallback;
box.setAttribute('class', magicClassName);
box.setAttribute('style', 'display:flex;');
// Sign in button with PopUP
const signInPopUpButton = dom.createElement('input'); // multi
box.appendChild(signInPopUpButton);
signInPopUpButton.setAttribute('type', 'button');
signInPopUpButton.setAttribute('value', 'Log in');
signInPopUpButton.setAttribute('style', `${signInButtonStyle}${style.headerBannerLoginInput}` + style.signUpBackground);
authSession.events.on('login', () => {
const me = authn.currentUser();
// const sessionInfo = authSession.info
// if (sessionInfo && sessionInfo.isLoggedIn) {
if (me) {
// const webIdURI = sessionInfo.webId
const webIdURI = me.uri;
// setUserCallback(webIdURI)
const divs = dom.getElementsByClassName(magicClassName);
debug.log(`Logged in, ${divs.length} panels to be serviced`);
// At the same time, satisfy all the other login boxes
for (let i = 0; i < divs.length; i++) {
const div = divs[i];
// @@ TODO Remove the need to manipulate HTML elements
if (div.setUserCallback) {
try {
div.setUserCallback(webIdURI);
const parent = div.parentNode;
if (parent) {
parent.removeChild(div);
}
}
catch (e) {
debug.log(`## Error satisfying login box: ${e}`);
div.appendChild(widgets.errorMessageBlock(dom, e));
}
}
}
}
});
signInPopUpButton.addEventListener('click', () => {
const offline = offlineTestID();
if (offline)
return setUserCallback(offline.uri);
renderSignInPopup(dom);
}, false);
// Sign up button
const signupButton = dom.createElement('input');
box.appendChild(signupButton);
signupButton.setAttribute('type', 'button');
signupButton.setAttribute('value', 'Sign Up for Solid');
signupButton.setAttribute('style', `${signInButtonStyle}${style.headerBannerLoginInput}` + style.signInBackground);
signupButton.addEventListener('click', function (_event) {
const signupMgr = new Signup();
signupMgr.signup().then(function (uri) {
debug.log('signInOrSignUpBox signed up ' + uri);
setUserCallback(uri);
});
}, false);
return box;
}
export function renderSignInPopup(dom) {
/**
* Issuer Menu
*/
const issuerPopup = dom.createElement('div');
issuerPopup.setAttribute('style', 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; display: flex; justify-content: center; align-items: center;');
dom.body.appendChild(issuerPopup);
const issuerPopupBox = dom.createElement('div');
issuerPopupBox.setAttribute('style', `
background-color: white;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.2);
-o-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.2);
border-radius: 4px;
min-width: 400px;
padding: 10px;
z-index : 10;
`);
issuerPopup.appendChild(issuerPopupBox);
const issuerPopupBoxTopMenu = dom.createElement('div');
issuerPopupBoxTopMenu.setAttribute('style', `
border-bottom: 1px solid #DDD;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
`);
issuerPopupBox.appendChild(issuerPopupBoxTopMenu);
const issuerPopupBoxLabel = dom.createElement('label');
issuerPopupBoxLabel.setAttribute('style', 'margin-right: 5px; font-weight: 800');
issuerPopupBoxLabel.innerText = 'Select an identity provider';
const issuerPopupBoxCloseButton = dom.createElement('button');
issuerPopupBoxCloseButton.innerHTML =
'<img src="https://solidos.github.io/solid-ui/src/icons/noun_1180156.svg" style="width: 2em; height: 2em;" title="Cancel">';
issuerPopupBoxCloseButton.setAttribute('style', 'background-color: transparent; border: none;');
issuerPopupBoxCloseButton.addEventListener('click', () => {
issuerPopup.remove();
});
issuerPopupBoxTopMenu.appendChild(issuerPopupBoxLabel);
issuerPopupBoxTopMenu.appendChild(issuerPopupBoxCloseButton);
const loginToIssuer = async (issuerUri) => {
try {
// clear authorization metadata from store
solidLogicSingleton.store.updater.flagAuthorizationMetadata();
// Save hash
const preLoginRedirectHash = new URL(window.location.href).hash;
if (preLoginRedirectHash) {
window.localStorage.setItem('preLoginRedirectHash', preLoginRedirectHash);
}
window.localStorage.setItem('loginIssuer', issuerUri);
// Login
const locationUrl = new URL(window.location.href);
locationUrl.hash = ''; // remove hash part
await authSession.login({
redirectUrl: locationUrl.href,
oidcIssuer: issuerUri
});
}
catch (err) {
alert(err.message);
}
};
/**
* Text-based idp selection
*/
const issuerTextContainer = dom.createElement('div');
issuerTextContainer.setAttribute('style', `
border-bottom: 1px solid #DDD;
display: flex;
flex-direction: column;
padding-top: 10px;
`);
const issuerTextInputContainer = dom.createElement('div');
issuerTextInputContainer.setAttribute('style', `
display: flex;
flex-direction: row;
`);
const issuerTextLabel = dom.createElement('label');
issuerTextLabel.innerText = 'Enter the URL of your identity provider:';
issuerTextLabel.setAttribute('style', 'color: #888');
const issuerTextInput = dom.createElement('input');
issuerTextInput.setAttribute('type', 'text');
issuerTextInput.setAttribute('style', 'margin-left: 0 !important; flex: 1; margin-right: 5px !important');
issuerTextInput.setAttribute('placeholder', 'https://example.com');
issuerTextInput.value = localStorage.getItem('loginIssuer') || '';
const issuerTextGoButton = dom.createElement('button');
issuerTextGoButton.innerText = 'Go';
issuerTextGoButton.setAttribute('style', 'margin-top: 12px; margin-bottom: 12px;');
issuerTextGoButton.addEventListener('click', () => {
loginToIssuer(issuerTextInput.value);
});
issuerTextContainer.appendChild(issuerTextLabel);
issuerTextInputContainer.appendChild(issuerTextInput);
issuerTextInputContainer.appendChild(issuerTextGoButton);
issuerTextContainer.appendChild(issuerTextInputContainer);
issuerPopupBox.appendChild(issuerTextContainer);
/**
* Button-based idp selection
*/
const issuerButtonContainer = dom.createElement('div');
issuerButtonContainer.setAttribute('style', `
display: flex;
flex-direction: column;
padding-top: 10px;
`);
const issuerBottonLabel = dom.createElement('label');
issuerBottonLabel.innerText = 'Or pick an identity provider from the list below:';
issuerBottonLabel.setAttribute('style', 'color: #888');
issuerButtonContainer.appendChild(issuerBottonLabel);
getSuggestedIssuers().forEach((issuerInfo) => {
const issuerButton = dom.createElement('button');
issuerButton.innerText = issuerInfo.name;
issuerButton.setAttribute('style', 'height: 38px; margin-top: 10px');
issuerButton.addEventListener('click', () => {
loginToIssuer(issuerInfo.uri);
});
issuerButtonContainer.appendChild(issuerButton);
});
issuerPopupBox.appendChild(issuerButtonContainer);
}
/**
* Login status box
*
* A big sign-up/sign in box or a logout box depending on the state
*
* @param dom
* @param listener
*
* @returns
*/
export function loginStatusBox(dom, listener = null, options = {}) {
// 20190630
let me = offlineTestID();
// @@ TODO Remove the need to cast HTML element to any
const box = dom.createElement('div');
function setIt(newidURI) {
if (!newidURI) {
return;
}
// const uri = newidURI.uri || newidURI
// me = sym(uri)
me = authn.saveUser(newidURI);
box.refresh();
if (listener)
listener(me.uri);
}
function logoutButtonHandler(_event) {
const oldMe = me;
authSession.logout().then(function () {
const message = `Your WebID was ${oldMe}. It has been forgotten.`;
me = null;
try {
alert(message);
}
catch (_e) {
window.alert(message);
}
box.refresh();
if (listener)
listener(null);
}, (err) => {
alert('Fail to log out:' + err);
});
}
function logoutButton(me, options) {
const signInButtonStyle = options.buttonStyle || style.signInAndUpButtonStyle;
let logoutLabel = 'WebID logout';
if (me) {
const nick = solidLogicSingleton.store.any(me, ns.foaf('nick')) ||
solidLogicSingleton.store.any(me, ns.foaf('name'));
if (nick) {
logoutLabel = 'Logout ' + nick.value;
}
}
const signOutButton = dom.createElement('input');
// signOutButton.className = 'WebIDCancelButton'
signOutButton.setAttribute('type', 'button');
signOutButton.setAttribute('value', logoutLabel);
signOutButton.setAttribute('style', `${signInButtonStyle}`);
signOutButton.addEventListener('click', logoutButtonHandler, false);
return signOutButton;
}
box.refresh = function () {
const sessionInfo = authSession.info;
if (sessionInfo && sessionInfo.webId && sessionInfo.isLoggedIn) {
me = solidLogicSingleton.store.sym(sessionInfo.webId);
}
else {
me = null;
}
if ((me && box.me !== me.uri) || (!me && box.me)) {
widgets.clearElement(box);
if (me) {
box.appendChild(logoutButton(me, options));
}
else {
box.appendChild(signInOrSignUpBox(dom, setIt, options));
}
}
box.me = me ? me.uri : null;
};
box.refresh();
function trackSession() {
me = authn.currentUser();
box.refresh();
}
trackSession();
authSession.events.on('login', trackSession);
authSession.events.on('logout', trackSession);
box.me = '99999'; // Force refresh
box.refresh();
return box;
}
authSession.events.on('logout', async () => {
const issuer = window.localStorage.getItem('loginIssuer');
if (issuer) {
try {
// clear authorization metadata from store
solidLogicSingleton.store.updater.flagAuthorizationMetadata();
const wellKnownUri = new URL(issuer);
wellKnownUri.pathname = '/.well-known/openid-configuration';
const wellKnownResult = await fetch(wellKnownUri.toString());
if (wellKnownResult.status === 200) {
const openidConfiguration = await wellKnownResult.json();
if (openidConfiguration && openidConfiguration.end_session_endpoint) {
await fetch(openidConfiguration.end_session_endpoint, { credentials: 'include' });
}
}
}
catch (_err) {
// Do nothing
}
}
window.location.reload();
});
/**
* Workspace selection etc
* See https://github.com/solidos/userguide/issues/16
*/
/**
* Returns a UI object which, if it selects a workspace,
* will callback(workspace, newBase).
* See https://github.com/solidos/userguide/issues/16 for more info on workspaces.
*
* If necessary, will get an account, preference file, etc. In sequence:
*
* - If not logged in, log in.
* - Load preference file
* - Prompt user for workspaces
* - Allows the user to just type in a URI by hand
*
* Calls back with the workspace and the base URI
*
* @param dom
* @param appDetails
* @param callbackWS
*/
export function selectWorkspace(dom, appDetails, callbackWS) {
const noun = appDetails.noun;
const appPathSegment = appDetails.appPathSegment;
const me = offlineTestID();
const box = dom.createElement('div');
const context = { me, dom, div: box };
function say(s, background) {
box.appendChild(widgets.errorMessageBlock(dom, s, background));
}
function figureOutBase(ws) {
const newBaseNode = solidLogicSingleton.store.any(ws, ns.space('uriPrefix'));
let newBaseString;
if (!newBaseNode) {
newBaseString = ws.uri.split('#')[0];
}
else {
newBaseString = newBaseNode.value;
}
if (newBaseString.slice(-1) !== '/') {
debug.log(`${appPathSegment}: No / at end of uriPrefix ${newBaseString}`); // @@ paramater?
newBaseString = `${newBaseString}/`;
}
const now = new Date();
newBaseString += `${appPathSegment}/id${now.getTime()}/`; // unique id
return newBaseString;
}
function displayOptions(context) {
// console.log('displayOptions!', context)
async function makeNewWorkspace(_event) {
const row = table.appendChild(dom.createElement('tr'));
const cell = row.appendChild(dom.createElement('td'));
cell.setAttribute('colspan', '3');
cell.style.padding = '0.5em';
const newBase = encodeURI(await widgets.askName(dom, solidLogicSingleton.store, cell, ns.solid('URL'), ns.space('Workspace'), 'Workspace'));
const newWs = widgets.newThing(context.preferencesFile);
const newData = [
st(context.me, ns.space('workspace'), newWs, context.preferencesFile),
st(newWs, ns.space('uriPrefix'), newBase, context.preferencesFile)
];
if (!solidLogicSingleton.store.updater) {
throw new Error('store has no updater');
}
await solidLogicSingleton.store.updater.update([], newData);
// @@ now refresh list of workspaces
}
// const status = ''
const id = context.me;
const preferencesFile = context.preferencesFile;
let newBase = null;
// A workspace specifically defined in the private preference file:
let w = solidLogicSingleton.store.each(id, ns.space('workspace'), undefined, preferencesFile); // Only trust preference file here
// A workspace in a storage in the public profile:
const storages = solidLogicSingleton.store.each(id, ns.space('storage')); // @@ No provenance requirement at the moment
if (w.length === 0 && storages) {
say(`You don't seem to have any workspaces. You have ${storages.length} storage spaces.`, 'white');
storages
.map(function (s) {
w = w.concat(solidLogicSingleton.store.each(s, ns.ldp('contains')));
return w;
})
.filter((file) => {
return file.id ? ['public', 'private'].includes(file.id().toLowerCase()) : '';
});
}
if (w.length === 1) {
say(`Workspace used: ${w[0].uri}`, 'white'); // @@ allow user to see URI
newBase = figureOutBase(w[0]);
// callbackWS(w[0], newBase)
// } else if (w.length === 0) {
}
// Prompt for ws selection or creation
// say( w.length + " workspaces for " + id + "Choose one.");
const table = dom.createElement('table');
table.setAttribute('style', 'border-collapse:separate; border-spacing: 0.5em;');
// const popup = window.open(undefined, '_blank', { height: 300, width:400 }, false)
box.appendChild(table);
// Add a field for directly adding the URI yourself
// const hr = box.appendChild(dom.createElement('hr')) // @@
box.appendChild(dom.createElement('hr')); // @@
const p = box.appendChild(dom.createElement('p'));
p.setAttribute('style', style.commentStyle);
p.textContent = `Where would you like to store the data for the ${noun}?
Give the URL of the folder where you would like the data stored.
It can be anywhere in solid world - this URI is just an idea.`;
// @@ TODO Remove the need to cast baseField to any
const baseField = box.appendChild(dom.createElement('input'));
baseField.setAttribute('type', 'text');
baseField.setAttribute('style', style.textInputStyle);
baseField.size = 80; // really a string
baseField.label = 'base URL';
baseField.autocomplete = 'on';
if (newBase) {
// set to default
baseField.value = newBase;
}
context.baseField = baseField;
box.appendChild(dom.createElement('br')); // @@
const button = box.appendChild(dom.createElement('button'));
button.setAttribute('style', style.buttonStyle);
button.textContent = `Start new ${noun} at this URI`;
button.addEventListener('click', function (_event) {
let newBase = baseField.value.replace(' ', '%20'); // do not re-encode in general, as % encodings may exist
if (newBase.slice(-1) !== '/') {
newBase += '/';
}
callbackWS(null, newBase);
});
// Now go set up the table of spaces
// const row = 0
w = w.filter(function (x) {
return !solidLogicSingleton.store.holds(x, ns.rdf('type'), // Ignore master workspaces
ns.space('MasterWorkspace'));
});
let col1, col2, col3, tr, ws, localStyle, comment;
const cellStyle = 'height: 3em; margin: 1em; padding: 1em white; border-radius: 0.3em;';
const deselectedStyle = `${cellStyle}border: 0px;`;
// const selectedStyle = cellStyle + 'border: 1px solid black;'
for (let i = 0; i < w.length; i++) {
ws = w[i];
tr = dom.createElement('tr');
if (i === 0) {
col1 = dom.createElement('td');
col1.setAttribute('rowspan', `${w.length}`);
col1.textContent = 'Choose a workspace for this:';
col1.setAttribute('style', 'vertical-align:middle;');
tr.appendChild(col1);
}
col2 = dom.createElement('td');
localStyle = solidLogicSingleton.store.anyValue(ws, ns.ui('style'));
if (!localStyle) {
// Otherwise make up arbitrary colour
const hash = function (x) {
return x.split('').reduce(function (a, b) {
a = (a << 5) - a + b.charCodeAt(0);
return a & a;
}, 0);
};
const bgcolor = `#${((hash(ws.uri) & 0xffffff) | 0xc0c0c0).toString(16)}`; // c0c0c0 forces pale
localStyle = `color: black ; background-color: ${bgcolor};`;
}
col2.setAttribute('style', deselectedStyle + localStyle);
tr.target = ws.uri;
let label = solidLogicSingleton.store.any(ws, ns.rdfs('label'));
if (!label) {
label = ws.uri.split('/').slice(-1)[0] || ws.uri.split('/').slice(-2)[0];
}
col2.textContent = label || '???';
tr.appendChild(col2);
if (i === 0) {
col3 = dom.createElement('td');
col3.setAttribute('rowspan', `${w.length}1`);
// col3.textContent = '@@@@@ remove';
col3.setAttribute('style', 'width:50%;');
tr.appendChild(col3);
}
table.appendChild(tr);
comment = solidLogicSingleton.store.any(ws, ns.rdfs('comment'));
comment = comment ? comment.value : 'Use this workspace';
col2.addEventListener('click', function (_event) {
col3.textContent = comment ? comment.value : '';
col3.setAttribute('style', deselectedStyle + localStyle);
const button = dom.createElement('button');
button.textContent = 'Continue';
// button.setAttribute('style', style);
const newBase = figureOutBase(ws);
baseField.value = newBase; // show user proposed URI
button.addEventListener('click', function (_event) {
button.disabled = true;
callbackWS(ws, newBase);
button.textContent = '---->';
}, true); // capture vs bubble
col3.appendChild(button);
}, true); // capture vs bubble
}
// last line with "Make new workspace"
const trLast = dom.createElement('tr');
col2 = dom.createElement('td');
col2.setAttribute('style', cellStyle);
col2.textContent = '+ Make a new workspace';
col2.addEventListener('click', makeNewWorkspace);
trLast.appendChild(col2);
table.appendChild(trLast);
} // displayOptions
// console.log('kicking off async operation')
ensureLoadedPreferences(context) // kick off async operation
.then(displayOptions)
.catch((err) => {
// console.log("err from async op")
box.appendChild(widgets.errorMessageBlock(context.dom, err));
});
return box; // return the box element, while login proceeds
} // selectWorkspace
/**
* Creates a new instance of an app.
*
* An instance of an app could be e.g. an issue tracker for a given project,
* or a chess game, or calendar, or a health/fitness record for a person.
*
* Note that this use of the term 'app' refers more to entries in the user's
* type index than to actual software applications that use the personal data
* to which these entries point.
*
* @param dom
* @param appDetails
* @param callback
*
* @returns A div with a button in it for making a new app instance
*/
export function newAppInstance(dom, appDetails, callback) {
const gotWS = function (ws, base) {
// log.debug("newAppInstance: Selected workspace = " + (ws? ws.uri : 'none'))
callback(ws, base);
};
const div = dom.createElement('div');
const b = dom.createElement('button');
b.setAttribute('type', 'button');
div.appendChild(b);
b.innerHTML = `Make new ${appDetails.noun}`;
b.addEventListener('click', (_event) => {
div.appendChild(selectWorkspace(dom, appDetails, gotWS));
}, false);
div.appendChild(b);
return div;
}
/**
* Retrieves whether the currently logged in user is a power user
* and/or a developer
*/
export async function getUserRoles() {
try {
const { me, preferencesFile, preferencesFileError } = await ensureLoadedPreferences({});
if (!preferencesFile || preferencesFileError) {
throw new Error(preferencesFileError);
}
return solidLogicSingleton.store.each(me, ns.rdf('type'), null, preferencesFile.doc());
}
catch (error) {
debug.warn('Unable to fetch your preferences - this was the error: ', error);
}
return [];
}
/**
* Filters which panes should be available, based on the result of [[getUserRoles]]
*/
export async function filterAvailablePanes(panes) {
const userRoles = await getUserRoles();
return panes.filter((pane) => isMatchingAudience(pane, userRoles));
}
function isMatchingAudience(pane, userRoles) {
const audience = pane.audience || [];
return audience.reduce((isMatch, audienceRole) => isMatch && !!userRoles.find((role) => role.equals(audienceRole)), true);
}
//# sourceMappingURL=login.js.map