solid-ui
Version:
UI library for writing Solid read-write-web applications
978 lines (790 loc) • 32.3 kB
JavaScript
;
/* UI Widgets such as buttons
*/
/* global alert */
module.exports = {};
var buttons = {};
var UI = {
icons: require('../iconBase'),
log: require('../log'),
ns: require('../ns'),
store: require('../store'),
style: require('../style') // widgets: widgets // @@
};
var utils = require('../utils');
var error = require('./error');
var dragAndDrop = require('./dragAndDrop');
var cancelIconURI = UI.icons.iconBase + 'noun_1180156.svg'; // black X
var checkIconURI = UI.icons.iconBase + 'noun_1180158.svg'; // green checkmark; Continue
function getStatusArea(context) {
var box = context.statusArea || context.div || null;
if (box) return box;
var dom = context.dom;
if (!dom && typeof document !== 'undefined') {
dom = document;
}
if (dom) {
var body = dom.getElementsByTagName('body')[0];
box = dom.createEvent('div');
body.insertBefore(box, body.firstElementChild);
context.statusArea = box;
return box;
}
return null;
}
function complain(context, err) {
if (!err) return; // only if error
var ele = context.statusArea || context.div || getStatusArea(context);
console.log('Complaint: ' + err);
if (ele) ele.appendChild(error.errorMessageBlock(context.dom, err));else alert(err);
}
buttons.complain = complain; // var UI.ns = require('./ns.js')
// var utilsModule = require('./utils')
// var aclControlModule = require('./acl-control')
// paneUtils = {}
buttons.clearElement = function (ele) {
while (ele.firstChild) {
ele.removeChild(ele.firstChild);
}
return ele;
};
buttons.refreshTree = function (root) {
if (root.refresh) {
root.refresh();
return;
}
for (var i = 0; i < root.children.length; i++) {
buttons.refreshTree(root.children[i]);
}
}; // To figure out the log URI from the full URI used to invoke the reasoner
buttons.extractLogURI = function (fullURI) {
var logPos = fullURI.search(/logFile=/);
var rulPos = fullURI.search(/&rulesFile=/);
return fullURI.substring(logPos + 8, rulPos);
}; // @@@ This needs to be changed to local timee
// noTime - only give date, no time,
buttons.shortDate = function (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;
}
};
buttons.formatDateTime = function (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('');
};
buttons.timestamp = function () {
return buttons.formatDateTime(new Date(), '{FullYear}-{Month}-{Date}T{Hours}:{Minutes}:{Seconds}.{Milliseconds}');
};
buttons.shortTime = function () {
return buttons.formatDateTime(new Date(), '{Hours}:{Minutes}:{Seconds}.{Milliseconds}');
}; // ///////////////////// Handy UX widgets
// Sets the best name we have and looks up a better one
//
buttons.setName = function (element, x) {
var kb = UI.store;
var ns = UI.ns;
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) {
// 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
buttons.imagesOf = function (x, kb) {
var ns = UI.ns;
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('photo'))).concat(kb.each(x, ns.foaf('depiction')));
}; // Best logo or avater or photo etc to represent someone or some group etc
//
buttons.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',
// @@
'vcard:Group': 'noun_339237.svg',
'rdfs:Class': 'noun_339237.svg',
// @@ Make different from group! Icon???
'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:Meeting': 'noun_66617.svg',
'meeting:LongChat': 'noun_1689339.svg',
'ui:Form': 'noun_122196.svg'
};
var tempSite = 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);
}
};
buttons.findImageByClass = function findImageByClass(x) {
var kb = UI.store;
var ns = UI.ns;
var iconDir = UI.icons.iconBase;
var types = kb.findTypeURIs(x);
if (ns.solid('AppProvider').uri in types) {
return iconDir + 'noun_15177.svg'; // App
}
if (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 aapple 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 docuent icon a fallback for if the favicon does not exist
// todo: pick up a possible favicon for the web page istelf from a link
// was: return iconDir + 'noun_681601.svg' // document - under solid assumptions
}
}
ns['prov'] = $rdf.Namespace('http://www.w3.org/ns/prov#'); // In case not yet there
for (var k in buttons.iconForClass) {
var pref = k.split(':')[0];
var id = k.split(':')[1];
var klass = ns[pref](id);
if (klass.uri in types || klass.uri === x.uri) {
// Allow full URI in new additions
return $rdf.uri.join(buttons.iconForClass[k], UI.icons.iconBase);
}
}
return iconDir + 'noun_10636_grey.svg'; // Grey Circle - some thing
};
buttons.findImage = function (thing) {
var kb = UI.store;
var ns = UI.ns;
var iconDir = UI.icons.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;
}; // @@ Also add icons for *properties* like home, work, email, range, domain, comment,
buttons.setImage = function (element, profile) {
var kb = UI.store;
var uri = buttons.findImage(profile);
element.setAttribute('src', uri || buttons.findImageByClass(profile));
if (!uri && profile.uri) {
kb.fetcher.nowOrWhenFetched(profile.doc(), undefined, function () {
element.setAttribute('src', buttons.findImage(profile) || buttons.findImageByClass(profile));
});
}
}; // If a web page then a favicon with a fallback to
// See eg http://stackoverflow.com/questions/980855/inputting-a-default-image
var faviconOrDefault = function faviconOrDefault(dom, x) {
var image = dom.createElement('img');
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', UI.icons.iconBase + (isOrigin(x) ? 'noun_15177.svg' : 'noun_681601.svg'));
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 {
buttons.setImage(image, x);
return image;
}
}; // Delete button with a check you really mean it
//
// @@ Supress check if command key held down?
//
buttons.deleteButtonWithCheck = function (dom, container, noun, deleteFunction) {
var minusIconURI = UI.icons.iconBase + 'noun_2188_red.svg'; // white minus in red #cc0000 circle
// var delButton = dom.createElement('button')
var img = dom.createElement('img');
img.setAttribute('src', minusIconURI); // plus sign
img.setAttribute('style', 'margin: 0.2em; width: 1em; height:1em');
img.title = 'Remove this ' + noun;
var delButton = img;
container.appendChild(delButton);
container.setAttribute('class', 'hoverControl'); // See tabbedtab.css (sigh global CSS)
delButton.setAttribute('class', 'hoverControlHide'); // delButton.setAttribute('style', 'color: red; margin-right: 0.3em; foat:right; text-align:right')
delButton.addEventListener('click', function (e) {
container.removeChild(delButton); // Ask -- are you sure?
var cancelButton = dom.createElement('button'); // cancelButton.textContent = 'cancel'
cancelButton.setAttribute('style', UI.style.buttonStyle);
var img = cancelButton.appendChild(dom.createElement('img'));
img.setAttribute('src', cancelIconURI);
img.setAttribute('style', UI.style.buttonStyle);
container.appendChild(cancelButton).addEventListener('click', function (e) {
container.removeChild(sureButton);
container.removeChild(cancelButton);
container.appendChild(delButton);
}, false);
var sureButton = dom.createElement('button');
sureButton.textContent = 'Delete ' + noun;
sureButton.setAttribute('style', UI.style.buttonStyle);
container.appendChild(sureButton).addEventListener('click', function (e) {
container.removeChild(sureButton);
container.removeChild(cancelButton);
deleteFunction();
}, false);
}, false);
return delButton;
};
/* Make a button
*
* @param dom - the DOM document object
* @Param iconURI - the URI of theb icon to use
* @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
*/
buttons.button = function (dom, iconURI, text, handler) {
var button = dom.createElement('button');
button.setAttribute('type', 'button');
button.setAttribute('style', UI.style.buttonStyle); // button.innerHTML = text // later, user preferences may make text preferred for some
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;
if (handler) {
button.addEventListener('click', handler);
}
return button;
};
buttons.cancelButton = function (dom, handler) {
return buttons.button(dom, cancelIconURI, 'Cancel', handler);
};
buttons.continueButton = function (dom, handler) {
return buttons.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
* @returns: a promise of (a name or null if cancelled)
*/
buttons.askName = function (dom, kb, container, predicate, klass, noun) {
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 || UI.ns.foaf('name'); // eg 'name' in user's language
noun = noun || (klass ? utils.label(klass) : ' '); // 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', UI.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'));
var cancel = form.appendChild(buttons.cancelButton(dom));
cancel.addEventListener('click', function (e) {
form.parentNode.removeChild(form);
resolve(null);
}, false);
var continueButton = form.appendChild(buttons.continueButton(dom));
continueButton.addEventListener('click', function (e) {
gotName();
}, false);
namefield.focus();
}); // Promise
}; // ////////////////////////////////////////////////////////////////
// A little link icon
//
//
buttons.linkIcon = function (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 || UI.icons.originalIconBase + 'go-to-this.png');
img.setAttribute('style', 'margin: 0.3em;');
return anchor;
}; // A TR to repreent a draggable person, etc in a list
//
// pred is unused param at the moment
//
buttons.personTR = function (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')); // var image = td1.appendChild(dom.createElement('img'))
var 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;');
image.setAttribute('style', 'width: 3em; height: 3em; margin: 0.1em; border-radius: 1em;');
td1.appendChild(image); // buttons.setImage(image, obj)
buttons.setName(td2, obj);
if (options.deleteFunction) {
buttons.deleteButtonWithCheck(dom, td3, options.noun || 'one', options.deleteFunction);
}
if (obj.uri) {
// blank nodes need not apply
if (options.link !== false) {
var anchor = td3.appendChild(buttons.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
dragAndDrop.makeDraggable(tr, obj);
}
}
tr.subject = obj;
return tr;
}; // Refresh a DOM tree recursively
buttons.refreshTree = function refreshTree(root) {
if (root.refresh) {
root.refresh();
return;
}
for (var i = 0; i < root.children.length; i++) {
refreshTree(root.children[i]);
}
}; // List of attachments accepting drop
buttons.attachmentList = function (dom, subject, div, options) {
options = options || {};
var doc = options.doc || subject.doc();
if (options.modify === undefined) options.modify = true;
var modify = options.modify;
var promptIcon = options.promptIcon || UI.icons.iconBase + 'noun_748003.svg'; // target
// var promptIcon = options.promptIcon || (UI.icons.iconBase + 'noun_25830.svg') // paperclip
var predicate = options.predicate || UI.ns.wf('attachment');
var noun = options.noun || 'attachment';
var kb = UI.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
var deleteAttachment = function deleteAttachment(target) {
kb.updater.update($rdf.st(subject, predicate, target, doc), [], function (uri, ok, errorBody, xhr) {
if (ok) {
refresh();
} else {
complain('Error deleting one: ' + errorBody);
}
});
};
var createNewRow = function createNewRow(target) {
var theTarget = target;
var opt = {
noun: noun
};
if (modify) {
opt.deleteFunction = function () {
deleteAttachment(theTarget);
};
}
return buttons.personTR(dom, predicate, target, opt);
};
var refresh = attachmentTable.refresh = function () {
var things = kb.each(subject, predicate);
things.sort();
utils.syncTableToArray(attachmentTable, things, createNewRow);
};
attachmentOuter.refresh = refresh; // Participate in downstream changes
refresh();
var droppedURIHandler = function droppedURIHandler(uris) {
var ins = [];
uris.map(function (u) {
var target = $rdf.sym(u); // Attachment needs text label to disinguish I think not icon.
console.log('Dropped on attachemnt ' + u); // icon was: UI.icons.iconBase + 'noun_25830.svg'
ins.push($rdf.st(subject, predicate, target, doc));
});
kb.updater.update([], ins, function (uri, ok, errorBody, xhr) {
if (ok) {
refresh();
} else {
complain('Error adding one: ' + errorBody);
}
});
};
if (modify) {
var paperclip = attachmentLeft.appendChild(dom.createElement('img'));
paperclip.setAttribute('src', promptIcon);
paperclip.setAttribute('style', 'width; 2em; height: 2em; margin: 0.5em;');
paperclip.setAttribute('draggable', 'false');
dragAndDrop.makeDropTarget(attachmentLeft, droppedURIHandler);
}
return attachmentOuter;
}; // /////////////////////////////////////////////////////////////////////////////
// Event Handler for links within solid apps.
//
// Note that native links have consraints in Firefox, they
// don't work with local files for instance (2011)
//
buttons.openHrefInOutlineMode = function (e) {
e.preventDefault();
e.stopPropagation();
var target = utils.getTarget(e);
var uri = target.getAttribute('href');
if (!uri) return console.log('openHrefInOutlineMode: No href found!\n');
var dom = window.document;
if (dom.outlineManager) {
dom.outlineManager.GotoSubject(UI.store.sym(uri), true, undefined, true, undefined);
} else if (window && window.panes && window.panes.getOutliner) {
window.panes.getOutliner().GotoSubject(UI.store.sym(uri), true, undefined, true, undefined);
} else {
console.log("ERROR: Can't access outline manager in this config");
} // dom.outlineManager.GotoSubject(UI.store.sym(uri), true, undefined, true, undefined)
}; // We make a URI in the annotation store out of the URI of the thing to be annotated.
//
// @@ Todo: make it a personal preference.
//
buttons.defaultAnnotationStore = function (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 UI.store.sym('http://tabulator.org/wiki/annnotation/' + s);
};
buttons.allClassURIs = function () {
var set = {};
UI.store.statementsMatching(undefined, UI.ns.rdf('type'), undefined).map(function (st) {
if (st.object.uri) set[st.object.uri] = true;
});
UI.store.statementsMatching(undefined, UI.ns.rdfs('subClassOf'), undefined).map(function (st) {
if (st.object.uri) set[st.object.uri] = true;
if (st.subject.uri) set[st.subject.uri] = true;
});
UI.store.each(undefined, UI.ns.rdf('type'), UI.ns.rdfs('Class')).map(function (c) {
if (c.uri) set[c.uri] = true;
});
return set;
}; // Figuring which propertites could by be used
//
buttons.propertyTriage = function () {
var possibleProperties = {}; // if (possibleProperties === undefined) possibleProperties = {}
var kb = UI.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, UI.ns.rdf('type'), UI.ns.rdf('Property'));
for (var i = 0; i < ps.length; i++) {
p = ps[i].toNT();
UI.log.debug('propertyTriage: unknown: ' + p);
if (!op[p] && !dp[p]) {
dp[p] = true;
op[p] = true;
nu++;
}
}
possibleProperties.op = op;
possibleProperties.dp = dp;
UI.log.info('propertyTriage: ' + no + ' non-lit, ' + nd + ' literal. ' + nu + ' unknown.');
return possibleProperties;
};
/* General purpose widgets
**
*/
// A button for jumping
//
buttons.linkButton = function (dom, object) {
var b = dom.createElement('button');
b.setAttribute('type', 'button');
b.textContent = 'Goto ' + utils.label(object);
b.addEventListener('click', function (e) {
// b.parentNode.removeChild(b)
dom.outlineManager.GotoSubject(object, true, undefined, true, undefined);
}, true);
return b;
};
buttons.removeButton = function (dom, element) {
var b = dom.createElement('button');
b.setAttribute('type', 'button');
b.textContent = '✕'; // MULTIPLICATION X
b.addEventListener('click', function (e) {
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) {
var 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
//
buttons.selectorPanel = function (dom, kb, type, predicate, inverse, possible, options, callbackFunction, linkCallback) {
return buttons.selectorPanelRefresh(dom.createElement('div'), dom, kb, type, predicate, inverse, possible, options, callbackFunction, linkCallback);
};
buttons.selectorPanelRefresh = function (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, 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 || UI.icons.iconBase + 'noun_25830.svg');
image.setAttribute('title', already.length ? already.length : 'attach');
};
var f = buttons.index.twoLine.widgetForClass(type);
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
//
buttons.index = {};
buttons.index.line = {}; // Approx 80em
buttons.index.twoLine = {}; // Approx 40em * 2.4em
// ///////////////////////////////////////////////////////////////////////////
// We need these for anything which is a subject of an attachment.
//
// These should be moved to type-dependeent UI code. Related panes maybe
buttons.index.twoLine[''] = function (dom, x) {
// Default
var box = dom.createElement('div');
box.textContent = utils.label(x);
return box;
};
buttons.index.twoLine.widgetForClass = function (c) {
var widget = buttons.index.twoLine[c.uri];
var kb = UI.store;
if (widget) return widget;
var sup = kb.findSuperClassesNT(c);
for (var cl in sup) {
widget = buttons.index.twoLine[kb.fromNT(cl).uri];
if (widget) return widget;
}
return buttons.index.twoLine[''];
};
buttons.index.twoLine['http://www.w3.org/2000/10/swap/pim/qif#Transaction'] = function (dom, x) {
var failed = '';
var enc = function enc(p) {
var y = UI.store.any(x, UI.ns.qu(p));
if (!y) failed += '@@ No value for ' + p + '! ';
return y ? utils.escapeForXML(y.value) : '?'; // @@@@
};
var box = dom.createElement('table');
box.innerHTML = '<tr><td colspan="2">' + enc('payee') + '</td></tr>\n<tr><td><td>' + enc('date').slice(0, 10) + '</td><td style="text-align: right;">' + enc('amount') + '</td></tr>';
if (failed) {
box.innerHTML = '<tr><td><a href="' + utils.escapeForXML(x.uri) + '">' + utils.escapeForXML(failed) + '</a></td></tr>';
}
return box;
};
buttons.index.twoLine['http://www.w3.org/ns/pim/trip#Trip'] = function (dom, x) {
var enc = function enc(p) {
var y = UI.store.any(x, p);
return y ? utils.escapeForXML(y.value) : '?';
};
var box = dom.createElement('table');
box.innerHTML = '<tr><td colspan="2">' + enc(UI.ns.dc('title')) + '</td></tr>\n<tr style="color: #777"><td><td>' + enc(UI.ns.cal('dtstart')) + '</td><td>' + enc(UI.ns.cal('dtend')) + '</td></tr>';
return box;
}; // Stick a stylesheet link the document if not already there
buttons.addStyleSheet = function (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
//
buttons.isAudio = function (file) {
return buttons.isImage(file, 'audio');
};
buttons.isVideo = function (file) {
return buttons.isImage(file, 'video');
};
buttons.isImage = function (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';
var typeURIs = UI.store.findTypeURIs(file);
var prefix = $rdf.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 display:none - 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
buttons.fileUploadButtonDiv = 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) {
console.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 button = div.appendChild(buttons.button(dom, UI.icons.iconBase + 'noun_Upload_76574_000000.svg', 'Upload files', function (event) {
input.click();
}));
dragAndDrop.makeDropTarget(button, null, droppedFileHandler); // Can also just drop on button
return div;
};
module.exports = buttons;
//# sourceMappingURL=buttons.js.map