solid-ui
Version:
UI library for writing Solid read-write-web applications
1,437 lines (1,162 loc) • 45.4 kB
JavaScript
"use strict";
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.complain = complain;
exports.clearElement = clearElement;
exports.extractLogURI = extractLogURI;
exports.shortDate = shortDate;
exports.formatDateTime = formatDateTime;
exports.timestamp = timestamp;
exports.shortTime = shortTime;
exports.setName = setName;
exports.imagesOf = imagesOf;
exports.findImageFromURI = findImageFromURI;
exports.findImage = findImage;
exports.setImage = setImage;
exports.faviconOrDefault = faviconOrDefault;
exports.deleteButtonWithCheck = deleteButtonWithCheck;
exports.button = button;
exports.cancelButton = cancelButton;
exports.continueButton = continueButton;
exports.askName = askName;
exports.linkIcon = linkIcon;
exports.renderAsRow = renderAsRow;
exports.refreshTree = refreshTree;
exports.attachmentList = attachmentList;
exports.openHrefInOutlineMode = openHrefInOutlineMode;
exports.defaultAnnotationStore = defaultAnnotationStore;
exports.allClassURIs = allClassURIs;
exports.propertyTriage = propertyTriage;
exports.linkButton = linkButton;
exports.removeButton = removeButton;
exports.selectorPanel = selectorPanel;
exports.selectorPanelRefresh = selectorPanelRefresh;
exports.addStyleSheet = addStyleSheet;
exports.isAudio = isAudio;
exports.isVideo = isVideo;
exports.isImage = isImage;
exports.fileUploadButtonDiv = fileUploadButtonDiv;
exports.index = exports.personTR = exports.iconForClass = void 0;
var _rdflib = require("rdflib");
var _iconBase = require("../iconBase");
var ns = _interopRequireWildcard(require("../ns"));
var style = _interopRequireWildcard(require("../style"));
var debug = _interopRequireWildcard(require("../debug"));
var _log = require("../log");
var _jss = require("../jss");
var _dragAndDrop = require("./dragAndDrop");
var _logic = require("../logic");
var utils = _interopRequireWildcard(require("../utils"));
var _error = require("./error");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/* Buttons
*/
/**
* UI Widgets such as buttons
* @packageDocumentation
*/
/* global alert */
var iconBase = _iconBase.icons.iconBase,
originalIconBase = _iconBase.icons.originalIconBase;
var cancelIconURI = iconBase + 'noun_1180156.svg'; // black X
var checkIconURI = iconBase + 'noun_1180158.svg'; // green checkmark; Continue
function getStatusArea(context) {
var box = context && context.statusArea || context && context.div || null;
if (box) return box;
var dom = context && context.dom;
if (!dom && typeof document !== 'undefined') {
dom = document;
}
if (dom) {
var body = dom.getElementsByTagName('body')[0];
box = dom.createElement('div');
body.insertBefore(box, body.firstElementChild);
if (context) {
context.statusArea = box;
}
return box;
}
return null;
}
/**
* Display an error message block
*/
function complain(context, err) {
if (!err) return; // only if error
var ele = getStatusArea(context);
debug.log('Complaint: ' + err);
if (ele) ele.appendChild((0, _error.errorMessageBlock)(context && context.dom || document, err));else alert(err);
}
/**
* Remove all the children of an HTML element
*/
function clearElement(ele) {
while (ele.firstChild) {
ele.removeChild(ele.firstChild);
}
return ele;
}
/**
* To figure out the log URI from the full URI used to invoke the reasoner
*/
function extractLogURI(fullURI) {
var logPos = fullURI.search(/logFile=/);
var rulPos = fullURI.search(/&rulesFile=/);
return fullURI.substring(logPos + 8, rulPos);
}
/**
* By default, converts e.g. '2020-02-19T19:35:28.557Z' to '19:35'
* if today is 19 Feb 2020, and to 'Feb 19' if not.
* @@@ TODO This needs to be changed to local time
* @param noTime Return a string like 'Feb 19' even if it's today.
*/
function shortDate(str, noTime) {
if (!str) return '???';
var month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
try {
var nowZ = new Date().toISOString(); // var nowZ = $rdf.term(now).value
// var n = now.getTimezoneOffset() // Minutes
if (str.slice(0, 10) === nowZ.slice(0, 10) && !noTime) {
return str.slice(11, 16);
}
if (str.slice(0, 4) === nowZ.slice(0, 4)) {
return month[parseInt(str.slice(5, 7), 10) - 1] + ' ' + parseInt(str.slice(8, 10), 10);
}
return str.slice(0, 10);
} catch (e) {
return 'shortdate:' + e;
}
}
/**
* Format a date and time
* @param date for instance `new Date()`
* @param format for instance '{FullYear}-{Month}-{Date}T{Hours}:{Minutes}:{Seconds}.{Milliseconds}'
* @returns for instance '2000-01-15T23:14:23.002'
*/
function formatDateTime(date, format) {
return format.split('{').map(function (s) {
var k = s.split('}')[0];
var width = {
Milliseconds: 3,
FullYear: 4
};
var d = {
Month: 1
};
return s ? ('000' + (date['get' + k]() + (d[k] || 0))).slice(-(width[k] || 2)) + s.split('}')[1] : '';
}).join('');
}
/**
* Get a string representation of the current time
* @returns for instance '2000-01-15T23:14:23.002'
*/
function timestamp() {
return formatDateTime(new Date(), '{FullYear}-{Month}-{Date}T{Hours}:{Minutes}:{Seconds}.{Milliseconds}');
}
/**
* Get a short string representation of the current time
* @returns for instance '23:14:23.002'
*/
function shortTime() {
return formatDateTime(new Date(), '{Hours}:{Minutes}:{Seconds}.{Milliseconds}');
} // ///////////////////// Handy UX widgets
/**
* Sets the best name we have and looks up a better one
*/
function setName(element, x) {
var kb = _logic.store;
var findName = function findName(x) {
var name = kb.any(x, ns.vcard('fn')) || kb.any(x, ns.foaf('name')) || kb.any(x, ns.vcard('organization-name'));
return name ? name.value : null;
};
var name = x.sameTerm(ns.foaf('Agent')) ? 'Everyone' : findName(x);
element.textContent = name || utils.label(x);
if (!name && x.uri) {
if (!kb.fetcher) {
throw new Error('kb has no fetcher');
} // Note this is only a fetch, not a lookUP of all sameAs etc
kb.fetcher.nowOrWhenFetched(x.doc(), undefined, function (_ok) {
element.textContent = findName(x) || utils.label(x); // had: (ok ? '' : '? ') +
});
}
}
/**
* Set of suitable images
* See also [[findImage]]
* @param x The thing for which we want to find an image
* @param kb The RDF store to look in
* @returns It goes looking for triples in `kb`,
* `(subject: x), (predicate: see list below) (object: image-url)`
* to find any image linked from the thing with one of the following
* predicates (in order):
* * ns.sioc('avatar')
* * ns.foaf('img')
* * ns.vcard('logo')
* * ns.vcard('hasPhoto')
* * ns.vcard('photo')
* * ns.foaf('depiction')
*/
function imagesOf(x, kb) {
return kb.each(x, ns.sioc('avatar')).concat(kb.each(x, ns.foaf('img'))).concat(kb.each(x, ns.vcard('logo'))).concat(kb.each(x, ns.vcard('hasPhoto'))).concat(kb.each(x, ns.vcard('photo'))).concat(kb.each(x, ns.foaf('depiction')));
}
/**
* Best logo or avatar or photo etc to represent someone or some group etc
*/
var iconForClass = {
// Potentially extendable by other apps, panes, etc
// Relative URIs to the iconBase
'solid:AppProviderClass': 'noun_144.svg',
// @@ classs name should not contain 'Class'
'solid:AppProvider': 'noun_15177.svg',
// @@
'solid:Pod': 'noun_Cabinet_1434380.svg',
'vcard:Group': 'noun_339237.svg',
'vcard:Organization': 'noun_143899.svg',
'vcard:Individual': 'noun_15059.svg',
'schema:Person': 'noun_15059.svg',
'foaf:Person': 'noun_15059.svg',
'foaf:Agent': 'noun_98053.svg',
'acl:AuthenticatedAgent': 'noun_99101.svg',
'prov:SoftwareAgent': 'noun_Robot_849764.svg',
// Bot
'vcard:AddressBook': 'noun_15695.svg',
'trip:Trip': 'noun_581629.svg',
'meeting:LongChat': 'noun_1689339.svg',
'meeting:Meeting': 'noun_66617.svg',
'meeting:Project': 'noun_1036577.svg',
'ui:Form': 'noun_122196.svg',
'rdfs:Class': 'class-rectangle.svg',
// For RDF developers
'rdf:Property': 'property-diamond.svg',
'owl:Ontology': 'noun_classification_1479198.svg',
'wf:Tracker': 'noun_122196.svg',
'wf:Task': 'noun_17020_gray-tick.svg',
'wf:Open': 'noun_17020_sans-tick.svg',
'wf:Closed': 'noun_17020.svg'
};
/**
* Returns the origin of the URI of a NamedNode
*/
exports.iconForClass = iconForClass;
function tempSite(x) {
// use only while one in rdflib fails with origins 2019
var str = x.uri.split('#')[0];
var p = str.indexOf('//');
if (p < 0) throw new Error('This URI does not have a web site part (origin)');
var q = str.indexOf('/', p + 2);
if (q < 0) {
// no third slash?
return str.slice(0) + '/'; // Add slash to a bare origin
} else {
return str.slice(0, q + 1);
}
}
/**
* Find an image for this thing as a class
*/
function findImageFromURI(x) {
var iconDir = iconBase; // Special cases from URI scheme:
if (typeof x !== 'string' && x.uri) {
if (x.uri.split('/').length === 4 && !x.uri.split('/')[1] && !x.uri.split('/')[3]) {
return iconDir + 'noun_15177.svg'; // App -- this is an origin
} // Non-HTTP URI types imply types
if (x.uri.startsWith('message:') || x.uri.startsWith('mid:')) {
// message: is apple bug-- should be mid:
return iconDir + 'noun_480183.svg'; // envelope noun_567486
}
if (x.uri.startsWith('mailto:')) {
return iconDir + 'noun_567486.svg'; // mailbox - an email desitination
} // For HTTP(s) documents, we could look at the MIME type if we know it.
if (x.uri.startsWith('https:') && x.uri.indexOf('#') < 0) {
return tempSite(x) + 'favicon.ico'; // was x.site().uri + ...
// Todo: make the document icon a fallback for if the favicon does not exist
// todo: pick up a possible favicon for the web page itself from a link
// was: return iconDir + 'noun_681601.svg' // document - under solid assumptions
}
return null;
}
return iconDir + 'noun_10636_grey.svg'; // Grey Circle - some thing
}
/**
* Find something we have as explicit image data for the thing
* See also [[imagesOf]]
* @param thing The thing for which we want to find an image
* @returns The URL of a globe icon if thing equals `ns.foaf('Agent')`
* or `ns.rdf('Resource')`. Otherwise, it goes looking for
* triples in `store`,
* `(subject: thing), (predicate: see list below) (object: image-url)`
* to find any image linked from the thing with one of the following
* predicates (in order):
* * ns.sioc('avatar')
* * ns.foaf('img')
* * ns.vcard('logo')
* * ns.vcard('hasPhoto')
* * ns.vcard('photo')
* * ns.foaf('depiction')
*/
function findImage(thing) {
var kb = _logic.store;
var iconDir = iconBase;
if (thing.sameTerm(ns.foaf('Agent')) || thing.sameTerm(ns.rdf('Resource'))) {
return iconDir + 'noun_98053.svg'; // Globe
}
var image = kb.any(thing, ns.sioc('avatar')) || kb.any(thing, ns.foaf('img')) || kb.any(thing, ns.vcard('logo')) || kb.any(thing, ns.vcard('hasPhoto')) || kb.any(thing, ns.vcard('photo')) || kb.any(thing, ns.foaf('depiction'));
return image ? image.uri : null;
}
/**
* Do the best you can with the data available
*
* @return {Boolean} Are we happy with this icon?
* Sets src AND STYLE of the image.
*/
function trySetImage(element, thing, iconForClassMap) {
var kb = _logic.store;
var explitImage = findImage(thing);
if (explitImage) {
element.setAttribute('src', explitImage);
return true;
} // This is one of the classes we know about - the class itself?
var typeIcon = iconForClassMap[thing.uri];
if (typeIcon) {
element.setAttribute('src', typeIcon);
element.style = style.classIconStyle; // element.style.border = '0.1em solid green;'
// element.style.backgroundColor = '#eeffee' // pale green
return true;
}
var schemeIcon = findImageFromURI(thing);
if (schemeIcon) {
element.setAttribute('src', schemeIcon);
return true; // happy with this -- don't look it up
} // Do we have a generic icon for something in any class its in?
var types = kb.findTypeURIs(thing);
for (var typeURI in types) {
if (iconForClassMap[typeURI]) {
element.setAttribute('src', iconForClassMap[typeURI]);
return false; // maybe we can do better
}
}
element.setAttribute('src', iconBase + 'noun_10636_grey.svg'); // Grey Circle - some thing
return false; // we can do better
}
/**
* ToDo: Also add icons for *properties* like home, work, email, range, domain, comment,
*/
function setImage(element, thing) {
// 20191230a
var kb = _logic.store;
var iconForClassMap = {};
for (var k in iconForClass) {
var pref = k.split(':')[0];
var id = k.split(':')[1];
var theClass = ns[pref](id);
iconForClassMap[theClass.uri] = _rdflib.uri.join(iconForClass[k], iconBase);
}
var happy = trySetImage(element, thing, iconForClassMap);
if (!happy && thing.uri) {
if (!kb.fetcher) {
throw new Error('kb has no fetcher');
}
kb.fetcher.nowOrWhenFetched(thing.doc(), undefined, function (ok) {
if (ok) {
trySetImage(element, thing, iconForClassMap);
}
});
}
} // If a web page, then a favicon, with a fallback to ???
// See, e.g., http://stackoverflow.com/questions/980855/inputting-a-default-image
function faviconOrDefault(dom, x) {
var image = dom.createElement('img');
image.style = style.iconStyle;
var isOrigin = function isOrigin(x) {
if (!x.uri) return false;
var parts = x.uri.split('/');
return parts.length === 3 || parts.length === 4 && parts[3] === '';
};
image.setAttribute('src', iconBase + (isOrigin(x) ? 'noun_15177.svg' : 'noun_681601.svg') // App symbol vs document
);
if (x.uri && x.uri.startsWith('https:') && x.uri.indexOf('#') < 0) {
var res = dom.createElement('object'); // favico with a fallback of a default image if no favicon
res.setAttribute('data', tempSite(x) + 'favicon.ico');
res.setAttribute('type', 'image/x-icon');
res.appendChild(image); // fallback
return res;
} else {
setImage(image, x);
return image;
}
}
/* Two-option dialog pop-up
*/
function renderDeleteConfirmPopup(dom, refererenceElement, prompt, deleteFunction) {
function removePopup() {
refererenceElement.parentElement.removeChild(refererenceElement);
}
function removePopupAndDoDeletion() {
removePopup();
deleteFunction();
}
var popup = dom.createElement('div');
popup.style = style.confirmPopupStyle;
popup.style.position = 'absolute';
popup.style.top = '-1em'; // try leaving original button clear
popup.style.display = 'grid';
popup.style.gridTemplateColumns = 'auto auto';
var affirm = dom.createElement('div');
affirm.style.gridColumn = '1/2';
affirm.style.gridRow = '1'; // @@ sigh; TS. could pass number in fact
var cancel = dom.createElement('div');
cancel.style.gridColumn = '1/2';
cancel.style.gridRow = '2';
var xButton = cancelButton(dom, removePopup);
popup.appendChild(xButton);
xButton.style.gridColumn = '1';
xButton.style.gridRow = '2';
var cancelPrompt = popup.appendChild(dom.createElement('button'));
cancelPrompt.style = style.buttonStyle;
cancelPrompt.style.gridRow = '2';
cancelPrompt.style.gridColumn = '2';
cancelPrompt.textContent = 'Cancel'; // @@ I18n
var affirmIcon = button(dom, _iconBase.icons.iconBase + 'noun_925021.svg', 'Delete it'); // trashcan
popup.appendChild(affirmIcon);
affirmIcon.style.gridRow = '1';
affirmIcon.style.gridColumn = '1';
var sureButtonElt = popup.appendChild(dom.createElement('button'));
sureButtonElt.style = style.buttonStyle;
sureButtonElt.style.gridRow = '1';
sureButtonElt.style.gridColumn = '2';
sureButtonElt.textContent = prompt;
popup.appendChild(sureButtonElt);
affirmIcon.addEventListener('click', removePopupAndDoDeletion);
sureButtonElt.addEventListener('click', removePopupAndDoDeletion); // xButton.addEventListener('click', removePopup)
cancelPrompt.addEventListener('click', removePopup);
return popup;
}
/**
* Delete button with a check you really mean it
* @@ Supress check if command key held down?
*/
function deleteButtonWithCheck(dom, container, noun, deleteFunction) {
function createPopup() {
var refererenceElement = dom.createElement('div');
container.insertBefore(refererenceElement, deleteButton);
refererenceElement.style.position = 'relative'; // Needed as reference for popup
refererenceElement.appendChild(renderDeleteConfirmPopup(dom, refererenceElement, prompt, deleteFunction));
}
var minusIconURI = iconBase + 'noun_2188_red.svg'; // white minus in red #cc0000 circle
var deleteButton = dom.createElement('img');
deleteButton.setAttribute('src', minusIconURI);
deleteButton.setAttribute('style', style.smallButtonStyle); // @@tsc - would set deleteButton.style
deleteButton.style["float"] = 'right'; // Historically this has alwaus floated right
var prompt = 'Remove this ' + noun;
deleteButton.title = prompt; // @@ In an ideal world, make use of hover an accessibility option
deleteButton.classList.add('hoverControlHide');
deleteButton.addEventListener('click', createPopup);
container.classList.add('hoverControl');
container.appendChild(deleteButton);
deleteButton.setAttribute('data-testid', 'deleteButtonWithCheck');
return deleteButton; // or button div? caller may change size of image
}
/**
* Get the button style, based on options.
* See https://design.inrupt.com/atomic-core/?cat=Atoms#Buttons
*/
function getButtonStyle() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
// default to primary color
var color = options.buttonColor === 'Secondary' ? '#01c9ea' : '#7c4dff';
var backgroundColor = color;
var fontColor = '#ffffff';
var borderColor = color; // default to primary color
var hoverBackgroundColor = options.buttonColor === 'Secondary' ? '#37cde6' : '#9f7dff';
var hoverFontColor = fontColor;
if (options.needsBorder) {
backgroundColor = '#ffffff';
fontColor = color;
borderColor = color;
hoverBackgroundColor = color;
hoverFontColor = backgroundColor;
}
return {
'background-color': "".concat(backgroundColor),
color: "".concat(fontColor),
'font-family': 'Raleway, Roboto, sans-serif',
'border-radius': '0.25em',
'border-color': "".concat(borderColor),
border: '1px solid',
cursor: 'pointer',
'font-size': '.8em',
'text-decoration': 'none',
padding: '0.5em 4em',
transition: '0.25s all ease-in-out',
outline: 'none',
'&:hover': {
'background-color': "".concat(hoverBackgroundColor),
color: "".concat(hoverFontColor),
transition: '0.25s all ease-in-out'
}
};
}
/* Make a button
*
* @param dom - the DOM document object
* @Param iconURI - the URI of the icon to use (if any)
* @param text - the tooltip text or possibly button contents text
* @param handler <function> - A handler to called when button is clicked
*
* @returns <dDomElement> - the button
*/
function button(dom, iconURI, text, handler) {
var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {
buttonColor: 'Primary',
needsBorder: false
};
var button = dom.createElement('button');
button.setAttribute('type', 'button'); // button.innerHTML = text // later, user preferences may make text preferred for some
if (iconURI) {
var img = button.appendChild(dom.createElement('img'));
img.setAttribute('src', iconURI);
img.setAttribute('style', 'width: 2em; height: 2em;'); // trial and error. 2em disappears
img.title = text;
button.setAttribute('style', style.buttonStyle);
} else {
button.textContent = text.toLocaleUpperCase();
var _style = getButtonStyle(options);
var _getClasses = (0, _jss.getClasses)(dom.head, {
textButton: _style
}),
classes = _getClasses.classes;
button.classList.add(classes.textButton);
}
if (handler) {
button.addEventListener('click', handler, false);
}
return button;
}
/* Make a cancel button
*
* @param dom - the DOM document object
* @param handler <function> - A handler to called when button is clicked
*
* @returns <dDomElement> - the button
*/
function cancelButton(dom, handler) {
return button(dom, cancelIconURI, 'Cancel', handler);
}
/* Make a continue button
*
* @param dom - the DOM document object
* @param handler <function> - A handler to called when button is clicked
*
* @returns <dDomElement> - the button
*/
function continueButton(dom, handler) {
return button(dom, checkIconURI, 'Continue', handler);
}
/* Grab a name for a new thing
*
* Form to get the name of a new thing before we create it
* @params theClass Misspelt to avoid clashing with the JavaScript keyword
* @returns: a promise of (a name or null if cancelled)
*/
function askName(dom, kb, container, predicate, theClass, noun) {
// eslint-disable-next-line promise/param-names
return new Promise(function (resolve, _reject) {
var form = dom.createElement('div'); // form is broken as HTML behaviour can resurface on js error
// classLabel = utils.label(ns.vcard('Individual'))
predicate = predicate || ns.foaf('name'); // eg 'name' in user's language
noun = noun || (theClass ? utils.label(theClass) : ' '); // eg 'folder' in users's language
var prompt = noun + ' ' + utils.label(predicate) + ': ';
form.appendChild(dom.createElement('p')).textContent = prompt;
var namefield = dom.createElement('input');
namefield.setAttribute('type', 'text');
namefield.setAttribute('size', '100');
namefield.setAttribute('maxLength', '2048'); // No arbitrary limits
namefield.setAttribute('style', style.textInputStyle);
namefield.select(); // focus next user input
form.appendChild(namefield);
container.appendChild(form); // namefield.focus()
function gotName() {
form.parentNode.removeChild(form);
resolve(namefield.value.trim());
}
namefield.addEventListener('keyup', function (e) {
if (e.keyCode === 13) {
gotName();
}
}, false);
form.appendChild(dom.createElement('br'));
form.appendChild(cancelButton(dom, function (_event) {
form.parentNode.removeChild(form);
resolve(null);
}));
form.appendChild(continueButton(dom, function (_event) {
gotName();
}));
namefield.focus();
}); // Promise
} // ////////////////////////////////////////////////////////////////
/**
* A little link icon
*/
function linkIcon(dom, subject, iconURI) {
var anchor = dom.createElement('a');
anchor.setAttribute('href', subject.uri);
if (subject.uri.startsWith('http')) {
// If diff web page
anchor.setAttribute('target', '_blank'); // open in a new tab or window
} // as mailboxes and mail messages do not need new browser window
var img = anchor.appendChild(dom.createElement('img'));
img.setAttribute('src', iconURI || originalIconBase + 'go-to-this.png');
img.setAttribute('style', 'margin: 0.3em;');
return anchor;
}
/**
* A TR to represent a draggable person, etc in a list
*
* pred is unused param at the moment
*/
var personTR = renderAsRow; // The legacy name is used in a lot of places
exports.personTR = personTR;
function renderAsRow(dom, pred, obj, options) {
var tr = dom.createElement('tr');
options = options || {}; // tr.predObj = [pred.uri, obj.uri] moved to acl-control
var td1 = tr.appendChild(dom.createElement('td'));
var td2 = tr.appendChild(dom.createElement('td'));
var td3 = tr.appendChild(dom.createElement('td')); // const image = td1.appendChild(dom.createElement('img'))
var image = options.image || faviconOrDefault(dom, obj);
td1.setAttribute('style', 'vertical-align: middle; width:2.5em; padding:0.5em; height: 2.5em;');
td2.setAttribute('style', 'vertical-align: middle; text-align:left;');
td3.setAttribute('style', 'vertical-align: middle; width:2em; padding:0.5em; height: 4em;');
td1.appendChild(image);
if (options.title) {
td2.textContent = options.title;
} else {
setName(td2, obj); // This is async
}
if (options.deleteFunction) {
deleteButtonWithCheck(dom, td3, options.noun || 'one', options.deleteFunction);
}
if (obj.uri) {
// blank nodes need not apply
if (options.link !== false) {
var anchor = td3.appendChild(linkIcon(dom, obj));
anchor.classList.add('HoverControlHide');
td3.appendChild(dom.createElement('br'));
}
if (options.draggable !== false) {
// default is on
image.setAttribute('draggable', 'false'); // Stop the image being dragged instead - just the TR
(0, _dragAndDrop.makeDraggable)(tr, obj);
}
}
;
tr.subject = obj;
return tr;
}
/**
* Refresh a DOM tree recursively
*/
function refreshTree(root) {
if (root.refresh) {
root.refresh();
return;
}
for (var i = 0; i < root.children.length; i++) {
refreshTree(root.children[i]);
}
}
/**
* Options argument for [[attachmentList]] function
*/
/**
* Component that displays a list of resources, for instance
* the attachments of a message, or the various documents related
* to a meeting.
* Accepts dropping URLs onto it to add attachments to it.
*/
function attachmentList(dom, subject, div) {
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
// options = options || {}
var deleteAttachment = function deleteAttachment(target) {
if (!kb.updater) {
throw new Error('kb has no updater');
}
kb.updater.update((0, _rdflib.st)(subject, predicate, target, doc), [], function (uri, ok, errorBody, _xhr) {
if (ok) {
refresh();
} else {
complain(undefined, 'Error deleting one: ' + errorBody);
}
});
};
function createNewRow(target) {
var theTarget = target;
var opt = {
noun: noun
};
if (modify) {
opt.deleteFunction = function () {
deleteAttachment(theTarget);
};
}
return personTR(dom, predicate, target, opt);
}
var refresh = function refresh() {
var things = kb.each(subject, predicate);
things.sort();
utils.syncTableToArray(attachmentTable, things, createNewRow);
};
function droppedURIHandler(uris) {
var ins = [];
uris.forEach(function (u) {
var target = (0, _rdflib.sym)(u); // Attachment needs text label to disinguish I think not icon.
debug.log('Dropped on attachemnt ' + u); // icon was: iconBase + 'noun_25830.svg'
ins.push((0, _rdflib.st)(subject, predicate, target, doc));
});
if (!kb.updater) {
throw new Error('kb has no updater');
}
kb.updater.update([], ins, function (uri, ok, errorBody, _xhr) {
if (ok) {
refresh();
} else {
complain(undefined, 'Error adding one: ' + errorBody);
}
});
}
function droppedFileHandler(files) {
var _options$uploadFolder, _options$uploadFolder2;
(0, _dragAndDrop.uploadFiles)(kb.fetcher, files, (_options$uploadFolder = options.uploadFolder) === null || _options$uploadFolder === void 0 ? void 0 : _options$uploadFolder.uri, // Files
(_options$uploadFolder2 = options.uploadFolder) === null || _options$uploadFolder2 === void 0 ? void 0 : _options$uploadFolder2.uri, // Pictures
function (theFile, destURI) {
var ins = [(0, _rdflib.st)(subject, predicate, kb.sym(destURI), doc)];
if (!kb.updater) {
throw new Error('kb has no updater');
}
kb.updater.update([], ins, function (uri, ok, errorBody, _xhr) {
if (ok) {
refresh();
} else {
complain(undefined, 'Error adding link to uploaded file: ' + errorBody);
}
});
});
}
var doc = options.doc || subject.doc();
if (options.modify === undefined) options.modify = true;
var modify = options.modify;
var promptIcon = options.promptIcon || iconBase + 'noun_748003.svg'; // target
// const promptIcon = options.promptIcon || (iconBase + 'noun_25830.svg') // paperclip
var predicate = options.predicate || ns.wf('attachment');
var noun = options.noun || 'attachment';
var kb = _logic.store;
var attachmentOuter = div.appendChild(dom.createElement('table'));
attachmentOuter.setAttribute('style', 'margin-top: 1em; margin-bottom: 1em;');
var attachmentOne = attachmentOuter.appendChild(dom.createElement('tr'));
var attachmentLeft = attachmentOne.appendChild(dom.createElement('td'));
var attachmentRight = attachmentOne.appendChild(dom.createElement('td'));
var attachmentTable = attachmentRight.appendChild(dom.createElement('table'));
attachmentTable.appendChild(dom.createElement('tr')) // attachmentTableTop
;
attachmentOuter.refresh = refresh; // Participate in downstream changes
// ;(attachmentTable as any).refresh = refresh <- outer should be best?
refresh();
if (modify) {
// const buttonStyle = 'width; 2em; height: 2em; margin: 0.5em; padding: 0.1em;'
var paperclip = button(dom, promptIcon, 'Drop attachments here'); // paperclip.style = buttonStyle // @@ needed? default has white background
attachmentLeft.appendChild(paperclip);
var fhandler = options.uploadFolder ? droppedFileHandler : null;
(0, _dragAndDrop.makeDropTarget)(paperclip, droppedURIHandler, fhandler); // beware missing the wire of the paparclip!
(0, _dragAndDrop.makeDropTarget)(attachmentLeft, droppedURIHandler, fhandler); // just the outer won't do it
if (options.uploadFolder) {
// Addd an explicit file upload button as well
var buttonDiv = fileUploadButtonDiv(dom, droppedFileHandler);
attachmentLeft.appendChild(buttonDiv); // buttonDiv.children[1].style = buttonStyle
}
}
return attachmentOuter;
} // /////////////////////////////////////////////////////////////////////////////
/**
* Event Handler for links within solid apps.
*
* Note that native links have constraints in Firefox, they
* don't work with local files for instance (2011)
*/
function openHrefInOutlineMode(e) {
e.preventDefault();
e.stopPropagation();
var target = utils.getTarget(e);
var uri = target.getAttribute('href');
if (!uri) return debug.log('openHrefInOutlineMode: No href found!\n');
var dom = window.document;
if (dom.outlineManager) {
// @@ TODO Remove the use of document as a global object
;
dom.outlineManager.GotoSubject(_logic.store.sym(uri), true, undefined, true, undefined);
} else if (window && window.panes && window.panes.getOutliner) {
// @@ TODO Remove the use of window as a global object
;
window.panes.getOutliner().GotoSubject(_logic.store.sym(uri), true, undefined, true, undefined);
} else {
debug.log('ERROR: Can\'t access outline manager in this config');
} // dom.outlineManager.GotoSubject(store.sym(uri), true, undefined, true, undefined)
}
/**
* Make a URI in the Tabulator.org annotation store out of the URI of the thing to be annotated.
*
* @@ Todo: make it a personal preference.
*/
function defaultAnnotationStore(subject) {
if (subject.uri === undefined) return undefined;
var s = subject.uri;
if (s.slice(0, 7) !== 'http://') return undefined;
s = s.slice(7); // Remove
var hash = s.indexOf('#');
if (hash >= 0) s = s.slice(0, hash); // Strip trailing
else {
var slash = s.lastIndexOf('/');
if (slash < 0) return undefined;
s = s.slice(0, slash);
}
return _logic.store.sym('http://tabulator.org/wiki/annnotation/' + s);
}
/**
* Retrieve all RDF class URIs from solid-ui's RDF store
* @returns an object `ret` such that `Object.keys(ret)` is
* the list of all class URIs.
*/
function allClassURIs() {
var set = {};
_logic.store.statementsMatching(undefined, ns.rdf('type'), undefined).forEach(function (st) {
if (st.object.value) set[st.object.value] = true;
});
_logic.store.statementsMatching(undefined, ns.rdfs('subClassOf'), undefined).forEach(function (st) {
if (st.object.value) set[st.object.value] = true;
if (st.subject.value) set[st.subject.value] = true;
});
_logic.store.each(undefined, ns.rdf('type'), ns.rdfs('Class')).forEach(function (c) {
if (c.value) set[c.value] = true;
});
return set;
}
/**
* Figuring which properties we know about
*
* When the user inputs an RDF property, like for a form field
* or when specifying the relationship between two arbitrary things,
* then er can prompt them with properties the session knows about
*
* TODO: Look again by catching this somewhere. (On the kb?)
* TODO: move to diff module? Not really a button.
* @param {Store} kb The quadstore to be searched.
*/
function propertyTriage(kb) {
var possibleProperties = {}; // if (possibleProperties === undefined) possibleProperties = {}
// const kb = store
var dp = {};
var op = {};
var no = 0;
var nd = 0;
var nu = 0;
var pi = kb.predicateIndex; // One entry for each pred
for (var p in pi) {
var object = pi[p][0].object;
if (object.termType === 'Literal') {
dp[p] = true;
nd++;
} else {
op[p] = true;
no++;
}
} // If nothing discovered, then could be either:
var ps = kb.each(undefined, ns.rdf('type'), ns.rdf('Property'));
for (var i = 0; i < ps.length; i++) {
var _p = ps[i].toNT();
if (!op[_p] && !dp[_p]) {
dp[_p] = true;
op[_p] = true;
nu++;
}
}
possibleProperties.op = op;
possibleProperties.dp = dp;
(0, _log.info)("propertyTriage: ".concat(no, " non-lit, ").concat(nd, " literal. ").concat(nu, " unknown."));
return possibleProperties;
}
/**
* General purpose widgets
*/
/**
* A button for jumping
*/
function linkButton(dom, object) {
var b = dom.createElement('button');
b.setAttribute('type', 'button');
b.textContent = 'Goto ' + utils.label(object);
b.addEventListener('click', function (_event) {
// b.parentNode.removeChild(b)
;
dom.outlineManager.GotoSubject(object, true, undefined, true, undefined);
}, true);
return b;
}
/**
* A button to remove some other element from the page
*/
function removeButton(dom, element) {
var b = dom.createElement('button');
b.setAttribute('type', 'button');
b.textContent = '✕'; // MULTIPLICATION X
b.addEventListener('click', function (_event) {
;
element.parentNode.removeChild(element);
}, true);
return b;
} // Description text area
//
// Make a box to demand a description or display existing one
//
// @param dom - the document DOM for the user interface
// @param kb - the graph which is the knowledge base we are working with
// @param subject - a term, the subject of the statement(s) being edited.
// @param predicate - a term, the predicate of the statement(s) being edited
// @param store - The web document being edited
// @param callbackFunction - takes (boolean ok, string errorBody)
// /////////////////////////////////////// Random I/O widgets /////////////
// //// Column Header Buttons
//
// These are for selecting different modes, sources,styles, etc.
//
/*
buttons.headerButtons = function (dom, kb, name, words) {
const box = dom.createElement('table')
var i, word, s = '<tr>'
box.setAttribute('style', 'width: 90%; height: 1.5em')
for (i=0; i<words.length; i++) {
s += '<td><input type="radio" name="' + name + '" id="' + words[i] + '" value='
}
box.innerHTML = s + '</tr>'
}
*/
// ////////////////////////////////////////////////////////////
//
// selectorPanel
//
// A vertical panel for selecting connections to left or right.
//
// @param inverse means this is the object rather than the subject
//
function selectorPanel(dom, kb, type, predicate, inverse, possible, options, callbackFunction, linkCallback) {
return selectorPanelRefresh(dom.createElement('div'), dom, kb, type, predicate, inverse, possible, options, callbackFunction, linkCallback);
}
function selectorPanelRefresh(list, dom, kb, type, predicate, inverse, possible, options, callbackFunction, linkCallback) {
var style0 = 'border: 0.1em solid #ddd; border-bottom: none; width: 95%; height: 2em; padding: 0.5em;';
var selected = null;
list.innerHTML = '';
var refreshItem = function refreshItem(box, x) {
// Scope to hold item and x
var item; // eslint-disable-next-line prefer-const
var image;
var setStyle = function setStyle() {
var already = inverse ? kb.each(undefined, predicate, x) : kb.each(x, predicate);
iconDiv.setAttribute('class', already.length === 0 ? 'hideTillHover' : ''); // See tabbedtab.css
image.setAttribute('src', options.connectIcon || iconBase + 'noun_25830.svg');
image.setAttribute('title', already.length ? already.length : 'attach');
};
var f = index.twoLine.widgetForClass(type); // eslint-disable-next-line prefer-const
item = f(dom, x);
item.setAttribute('style', style0);
var nav = dom.createElement('div');
nav.setAttribute('class', 'hideTillHover'); // See tabbedtab.css
nav.setAttribute('style', 'float:right; width:10%');
var a = dom.createElement('a');
a.setAttribute('href', x.uri);
a.setAttribute('style', 'float:right');
nav.appendChild(a).textContent = '>';
box.appendChild(nav);
var iconDiv = dom.createElement('div');
iconDiv.setAttribute('style', (inverse ? 'float:left;' : 'float:right;') + ' width:30px;');
image = dom.createElement('img');
setStyle();
iconDiv.appendChild(image);
box.appendChild(iconDiv);
item.addEventListener('click', function (event) {
if (selected === item) {
// deselect
item.setAttribute('style', style0);
selected = null;
} else {
if (selected) selected.setAttribute('style', style0);
item.setAttribute('style', style0 + 'background-color: #ccc; color:black;');
selected = item;
}
callbackFunction(x, event, selected === item);
setStyle();
}, false);
image.addEventListener('click', function (event) {
linkCallback(x, event, inverse, setStyle);
}, false);
box.appendChild(item);
return box;
};
for (var i = 0; i < possible.length; i++) {
var box = dom.createElement('div');
list.appendChild(box);
refreshItem(box, possible[i]);
}
return list;
} // ###########################################################################
//
// Small compact views of things
//
var index = {}; // ///////////////////////////////////////////////////////////////////////////
// We need these for anything which is a subject of an attachment.
//
// These should be moved to type-dependeent UI code. Related panes maybe
exports.index = index;
function twoLineDefault(dom, x) {
// Default
var box = dom.createElement('div');
box.textContent = utils.label(x);
return box;
}
/**
* Find a function that can create a widget for a given class
* @param c The RDF class for which we want a widget generator function
*/
function twoLineWidgetForClass(c) {
var widget = index.twoLine[c.uri];
var kb = _logic.store;
if (widget) return widget;
var sup = kb.findSuperClassesNT(c);
for (var cl in sup) {
widget = index.twoLine[kb.fromNT(cl).uri];
if (widget) return widget;
}
return index.twoLine[''];
}
/**
* Display a transaction
* @param x Should have attributes through triples in store:
* * ns.qu('payee') -> a named node
* * ns.qu('date) -> a literal
* * ns.qu('amount') -> a literal
*/
function twoLineTransaction(dom, x) {
var failed = '';
var enc = function enc(p) {
var y = _logic.store.any(x, ns.qu(p));
if (!y) failed += '@@ No value for ' + p + '! ';
return y ? utils.escapeForXML(y.value) : '?'; // @@@@
};
var box = dom.createElement('table');
box.innerHTML = "\n <tr>\n <td colspan=\"2\"> ".concat(enc('payee'), "</td>\n < /tr>\n < tr >\n <td>").concat(enc('date').slice(0, 10), "</td>\n <td style = \"text-align: right;\">").concat(enc('amount'), "</td>\n </tr>");
if (failed) {
box.innerHTML = "\n <tr>\n <td><a href=\"".concat(utils.escapeForXML(x.uri), "\">").concat(utils.escapeForXML(failed), "</a></td>\n </tr>");
}
return box;
}
/**
* Display a trip
* @param x Should have attributes through triples in store:
* * ns.dc('title') -> a literal
* * ns.cal('dtstart') -> a literal
* * ns.cal('dtend') -> a literal
*/
function twoLineTrip(dom, x) {
var enc = function enc(p) {
var y = _logic.store.any(x, p);
return y ? utils.escapeForXML(y.value) : '?';
};
var box = dom.createElement('table');
box.innerHTML = "\n <tr>\n <td colspan=\"2\">".concat(enc(ns.dc('title')), "</td>\n </tr>\n <tr style=\"color: #777\">\n <td>").concat(enc(ns.cal('dtstart')), "</td>\n <td>").concat(enc(ns.cal('dtend')), "</td>\n </tr>");
return box;
}
/**
* Stick a stylesheet link the document if not already there
*/
function addStyleSheet(dom, href) {
var links = dom.querySelectorAll('link');
for (var i = 0; i < links.length; i++) {
if ((links[i].getAttribute('rel') || '') === 'stylesheet' && (links[i].getAttribute('href') || '') === href) {
return;
}
}
var link = dom.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css');
link.setAttribute('href', href);
dom.getElementsByTagName('head')[0].appendChild(link);
} // Figure (or guess) whether this is an image, etc
//
function isAudio(file) {
return isImage(file, 'audio');
}
function isVideo(file) {
return isImage(file, 'video');
}
/**
*
*/
function isImage(file, kind) {
var dcCLasses = {
audio: 'http://purl.org/dc/dcmitype/Sound',
image: 'http://purl.org/dc/dcmitype/Image',
video: 'http://purl.org/dc/dcmitype/MovingImage'
};
var what = kind || 'image'; // See https://github.com/linkeddata/rdflib.js/blob/e367d5088c/src/formula.ts#L554
//
var typeURIs = _logic.store.findTypeURIs(file); // See https://github.com/linkeddata/rdflib.js/blob/d5000f/src/utils-js.js#L14
// e.g.'http://www.w3.org/ns/iana/media-types/audio'
var prefix = _rdflib.Util.mediaTypeClass(what + '/*').uri.split('*')[0];
for (var t in typeURIs) {
if (t.startsWith(prefix)) return true;
}
if (dcCLasses[what] in typeURIs) return true;
return false;
}
/**
* File upload button
* @param dom The DOM aka document
* @param droppedFileHandler Same handler function as drop, takes array of file objects
* @returns {Element} - a div with a button and a inout in it
* The input is hidden, as it is uglky - the user clicks on the nice icons and fires the input.
*/
// See https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications
function fileUploadButtonDiv(dom, droppedFileHandler) {
var div = dom.createElement('div');
var input = div.appendChild(dom.createElement('input'));
input.setAttribute('type', 'file');
input.setAttribute('multiple', 'true');
input.addEventListener('change', function (event) {
debug.log('File drop event: ', event);
if (event.files) {
droppedFileHandler(event.files);
} else if (event.target && event.target.files) {
droppedFileHandler(event.target.files);
} else {
alert('Sorry no files .. internal error?');
}
}, false);
input.style = 'display:none';
var buttonElt = div.appendChild(button(dom, iconBase + 'noun_Upload_76574_000000.svg', 'Upload files', function (_event) {
input.click();
}));
(0, _dragAndDrop.makeDropTarget)(buttonElt, null, droppedFileHandler); // Can also just drop on button
return div;
}
exports.index = index = {
line: {// Approx 80em
},
twoLine: {
// Approx 40em * 2.4em
'': twoLineDefault,
'http://www.w3.org/2000/10/swap/pim/qif#Transaction': twoLineTransaction,
'http://www.w3.org/ns/pim/trip#Trip': twoLineTrip,
widgetForClass: twoLineWidgetForClass
}
};
//# sourceMappingURL=buttons.js.map