// Solid-UI general Utilities // ========================== // // This must load AFTER the rdflib.js and log-ext.js (or log.js). // var utilsModule = module.exports = {} var UI = { utils: utilsModule, log: require('./log'), ns: require('./ns'), rdf: require('rdflib'), store: require('./store') } // Make pseudorandom color from a uri UI.utils.hashColor = function(who) { who = who.uri || who; var hash = function(x){ return x.split("").reduce(function(a,b){ a=((a<<5)-a)+b.charCodeAt(0);return a&a },0); } return '#' + ((hash(who) & 0xffffff) | 0xc0c0c0).toString(16); // c0c0c0 or 808080 forces pale } UI.utils.genUuid = function () { // http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0 var v = c === 'x' ? r : (r & 0x3 | 0x8) return v.toString(16) }) } // Sync a DOM table with an array of things // // table - will have a tr for each thing // things - ORDERED array of NamedNode objects // createNewRow(thing) returns a TR table row for that thing // UI.utils.syncTableToArray = function(table, things, createNewRow){ var foundOne, row for (i = 0; i < table.children.length; i++) { row = table.children[i] row.trashMe = true } for (var g = 0; g < things.length; g++) { var thing = things[g] foundOne = false for (var i = 0; i < table.children.length; i++) { var row = table.children[i] if (row.subject && row.subject.sameTerm(thing)) { row.trashMe = false foundOne = true break } } if (!foundOne) { var newRow = createNewRow(thing) table.appendChild(newRow) newRow.subject = thing // UI.widgets.makeDraggable(newRow, thing) } // if not foundOne } // loop g for (i = 0; i < table.children.length; i++) { var row = table.children[i] if (row.trashMe) { table.removeChild(row) } } } // syncTableToArray // http://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep // http://www.tsheffler.com/blog/2013/05/14/audiocontext-noteonnoteoff-and-time-units/ if (!UI.utils.audioContext) { if (typeof AudioContext !== 'undefined') { UI.utils.audioContext = AudioContext; } else if (typeof window !== 'undefined') { UI.utils.audioContext = window.AudioContext || window.webkitAudioContext; } } if (UI.utils.audioContext) { UI.utils.beep = (function () { var ctx = new(UI.utils.audioContext) return function (duration, frequency, type, finishedCallback) { duration = + (duration || 0.3) // Only 0-4 are valid types. type = type || 'sine'; // sine, square, sawtooth, triangle if (typeof finishedCallback !== "function") { finishedCallback = function () {} } var osc = ctx.createOscillator() osc.type = type osc.frequency.value = frequency || 256 osc.connect(ctx.destination) osc.start(0) osc.stop(duration) } })() } else { // Safari 2015 UI.utils.beep = function() {} } if (typeof UI.utils.nextVariable == 'undefined') UI.utils.nextVariable = 0 UI.utils.newVariableName = function () { return 'v' + UI.utils.nextVariable++ } UI.utils.clearVariableNames = function () { UI.utils.nextVariable = 0 } /* Error stack to string for better diagnotsics ** ** See http://snippets.dzone.com/posts/show/6632 */ UI.utils.stackString = function (e) { var str = '' + e + '\n' if (!e.stack) { return str + 'No stack available.\n' } var lines = e.stack.toString().split('\n') var toprint = [] for (var i = 0; i < lines.length; i++) { var line = lines[i] if (line.indexOf('ecmaunit.js') > -1) { // remove useless bit of traceback break } if (line.charAt(0) == '(') { line = 'function' + line } var chunks = line.split('@') toprint.push(chunks) } // toprint.reverse(); No - I prefer the latest at the top by the error message -tbl for (var i = 0; i < toprint.length; i++) { str += ' ' + toprint[i][1] + '\n ' + toprint[i][0] } return str } // @@ This shoud be in rdf.uri (?) UI.utils.getURIQueryParameters = function (uri) { var results = [] var getDataString = uri ? uri.toString() : new String(window.location) var questionMarkLocation = getDataString.indexOf('?') if (questionMarkLocation != -1) { getDataString = getDataString.substr(questionMarkLocation + 1) var getDataArray = getDataString.split(/&/g) for (var i = 0;i < getDataArray.length;i++) { var nameValuePair = getDataArray[i].split(/=/) results[decodeURIComponent(nameValuePair[0])] = decodeURIComponent(nameValuePair[1]) } } return results } UI.utils.emptyNode = function (node) { var nodes = node.childNodes, len = nodes.length, i for (i = len - 1; i >= 0; i--) node.removeChild(nodes[i]) return node } UI.utils.getTarget = function (e) { var target if (!e) var e = window.event if (e.target) target = e.target else if (e.srcElement) target = e.srcElement if (target.nodeType == 3) // defeat Safari bug [sic] target = target.parentNode // UI.log.debug("Click on: " + target.tagName) return target } UI.utils.ancestor = function (target, tagName) { var level for (level = target; level; level = level.parentNode) { // UI.log.debug("looking for "+tagName+" Level: "+level+" "+level.tagName) try { if (level.tagName == tagName) return level } catch(e) { // can hit "TypeError: can't access dead object" in ffox return undefined } } return undefined } UI.utils.getAbout = function (kb, target) { var level, aa for (level = target; level && (level.nodeType == 1); level = level.parentNode) { // UI.log.debug("Level "+level + ' '+level.nodeType + ': '+level.tagName) aa = level.getAttribute('about') if (aa) { // UI.log.debug("kb.fromNT(aa) = " + kb.fromNT(aa)) return kb.fromNT(aa) // } else { // if (level.tagName=='TR') return undefined//this is to prevent literals passing through } } UI.log.debug('getAbout: No about found') return undefined } UI.utils.getTerm = function (target) { var statementTr = target.parentNode var st = statementTr ? statementTr.AJAR_statement : undefined var className = st ? target.className : ''; // if no st then it's necessary to use getAbout switch (className) { case 'pred': case 'pred selected': return st.predicate break case 'obj': case 'obj selected': if (!statementTr.AJAR_inverse) return st.object else return st.subject break case '': case 'selected': // header TD return UI.utils.getAbout(UI.store, target) // kb to be changed case 'undetermined selected': return (target.nextSibling) ? st.predicate : ((!statementTr.AJAR_inverse) ? st.object : st.subject) } } UI.utils.include = function (document, linkstr) { var lnk = document.createElement('script') lnk.setAttribute('type', 'text/javascript') lnk.setAttribute('src', linkstr) // TODO:This needs to be fixed or no longer used. // document.getElementsByTagName('head')[0].appendChild(lnk) return lnk } UI.utils.addLoadEvent = function (func) { var oldonload = window.onload if (typeof window.onload != 'function') { window.onload = func } else { window.onload = function () { oldonload() func() } } } // addLoadEvent // Find the position of an object relative to the window // UI.utils.findPos = function (obj) { // C&P from http://www.quirksmode.org/js/findpos.html var myDocument = obj.ownerDocument var DocBox = myDocument.documentElement.getBoundingClientRect() var box = obj.getBoundingClientRect() return [box.left - DocBox.left, box.top - DocBox.top] } UI.utils.getEyeFocus = function (element, instantly, isBottom, myWindow) { if (!myWindow) myWindow = window var elementPosY = UI.utils.findPos(element)[1] var totalScroll = elementPosY - 52 - myWindow.scrollY; // magic number 52 for web-based version if (instantly) { if (isBottom) { myWindow.scrollBy(0, elementPosY + element.clientHeight - (myWindow.scrollY + myWindow.innerHeight)) return } myWindow.scrollBy(0, totalScroll) return } var id = myWindow.setInterval(scrollAmount, 50) var times = 0 function scrollAmount () { myWindow.scrollBy(0, totalScroll / 10) times++ if (times == 10) myWindow.clearInterval(id) } } UI.utils.AJARImage = function (src, alt, tt, doc) { if (!doc) { doc = document } //if (!tt && tabulator.Icon.tooltips[src]) tooltip system discontinued 2016 // tt = tabulator.Icon.tooltips[src] var image = doc.createElement('img') image.setAttribute('src', src) image.addEventListener('copy', function(e){ e.clipboardData.setData('text/plain', ''); e.clipboardData.setData('text/html', ''); e.preventDefault(); // We want no title data to be written to the clipboard }); // if (typeof alt != 'undefined') // Messes up cut-and-paste of text // image.setAttribute('alt', alt) if (typeof tt != 'undefined') image.setAttribute('title', tt) return image } UI.utils.parse_headers = function (headers) { var lines = headers.split('\n') var headers = {} for (var i = 0; i < lines.length; i++) { var line = lines[i].trim() if (line.length == 0) { continue } var chunks = line.split(':') var hkey = chunks.shift().trim().toLowerCase() var hval = chunks.join(':') if (headers[hkey] !== undefined) { headers[hkey].push(hval) } else { headers[hkey] = [hval] } } return headers } // Make short name for ontology UI.utils.shortName = function (uri) { var p = uri if ('#/'.indexOf(p[p.length - 1]) >= 0) p = p.slice(0, -1) var namespaces = [] for (var ns in this.prefixes) { namespaces[this.prefixes[ns]] = ns // reverse index } var pok function canUse(pp) { //if (!__Serializer.prototype.validPrefix.test(pp)) return false; // bad format if (pp === 'ns') return false; // boring // if (pp in this.namespaces) return false; // already used // this.prefixes[uri] = pp; // this.namespaces[pp] = uri; pok = pp; return true; } canUse = canUse.bind(this); var hash = p.lastIndexOf('#') if (hash >= 0) p = p.slice(hash - 1) // lop off localid for (;;) { var slash = p.lastIndexOf('/') if (slash >= 0) p = p.slice(slash + 1) var i = 0 while (i < p.length) if (this.prefixchars.indexOf(p[i])) i++; else break p = p.slice(0, i) if (p.length < 6 && canUse(p)) return pok // exact i sbest if (canUse(p.slice(0, 3))) return pok if (canUse(p.slice(0, 2))) return pok if (canUse(p.slice(0, 4))) return pok if (canUse(p.slice(0, 1))) return pok if (canUse(p.slice(0, 5))) return pok for (var i = 0;; i++) if (canUse(p.slice(0, 3) + i)) return pok } } // Short name for an ontology // UI.utils.ontologyLabel = function (term) { if (term.uri === undefined) return '??' var s = term.uri var namespaces = [] var i = s.lastIndexOf('#') var part if (i >= 0) { s = s.slice(0, i + 1) } else { i = s.lastIndexOf('/') if (i >= 0) { s = s.slice(0, i + 1) } else { return term.uri + '?!'; // strange should have # or / } } for (var ns in UI.ns) { namespaces[UI.ns[ns]] = ns // reverse index } try { return namespaces[s] } catch (e) {} s = s.slice(0, -1); // Chop off delimiter ... now have just while(s) { i = s.lastIndexOf('/') if (i >= 0) { part = s.slice(i + 1) s = s.slice(0, i) if ((part !== 'ns') && ('0123456789'.indexOf(part[0]) < 0)) return part } else { return term.uri + '!?' // strange should have a nice part } } } UI.utils.labelWithOntology = function (x, initialCap) { var t = UI.store.findTypeURIs(x) if (t[UI.ns.rdf('Predicate').uri] || t[UI.ns.rdfs('Class').uri]) { return UI.utils.label(x, initialCap) + ' (' + UI.utils.ontologyLabel(x) + ')' } return UI.utils.label(x, initialCap) } // This ubiquitous function returns the best label for a thing // // The hacks in this code make a major difference to the usability // // @returns string // UI.utils.label = function (x, initialCap) { // x is an object function doCap (s) { // s = s.toString() if (initialCap) return s.slice(0, 1).toUpperCase() + s.slice(1) return s } function cleanUp (s1) { var s2 = '' if (s1.slice(-1) === '/') s1 = s1.slice(0, -1) // chop trailing slash for (var i = 0; i < s1.length; i++) { if (s1[i] == '_' || s1[i] == '-') { s2 += ' ' continue } s2 += s1[i] if (i + 1 < s1.length && s1[i].toUpperCase() != s1[i] && s1[i + 1].toLowerCase() != s1[i + 1]) { s2 += ' ' } } if (s2.slice(0, 4) == 'has ') s2 = s2.slice(4) return doCap(s2) } // The tabulator labeler is more sophisticated if it exists // Todo: move it to a solid-ui option. var lab if (typeof tabulator !== 'undefined' && tabulator.lb){ lab = tabulator.lb.label(x) if (lab) { return doCap(lab.value) } } // Hard coded known label predicates // @@ TBD: Add subproperties of rdfs:label var kb = UI.store var lab1 = kb.any(x, UI.ns.link('message')) || kb.any(x, UI.ns.vcard('fn')) || kb.any(x, UI.ns.foaf('name')) || kb.any(x, UI.ns.dct('title')) || kb.any(x, UI.ns.dc('title')) || kb.any(x, UI.ns.rss('title')) || kb.any(x, UI.ns.contact('fullName')) || kb.any(x, kb.sym('http://www.w3.org/2001/04/roadmap/org#name')) || kb.any(x, UI.ns.cal('summary')) || kb.any(x, UI.ns.foaf('nick')) || kb.any(x, UI.ns.rdfs('label')) if (lab1){ return doCap(lab1.value) } // Default to label just generated from the URI if (x.termType == 'BlankNode') { return '...' } if (x.termType == 'Collection') { return '(' + x.elements.length + ')' } var s = x.uri if (typeof s == 'undefined') return x.toString(); // can't be a symbol // s = decodeURI(s) // This can crash is random valid @ signs are presentation // The idea was to clean up eg URIs encoded in query strings // Also encoded character in what was filenames like @ [] {} try{ s = s.split('/').map(decodeURIComponent).join('/') // If it is properly encoded } catch(e){ // try individual decoding of ASCII code points for (var i =s.length - 3; i > 0; i --) { const hex = '0123456789abcefABCDEF' // The while upacks multiple layers of encoding while (s[i] === '%' && hex.indexOf(s[i+1]) >=0 && hex.indexOf(s[i+2]) >=0 ) { s = s.slice(0, i) + String.fromCharCode(parseInt(s.slice(i+1, i+3), 16)) + s.slice(i+3) } } } if (s.slice(-5) === '#this') s = s.slice(0, -5) else if (s.slice(-3) === '#me') s = s.slice(0, -3) var hash = s.indexOf('#') if (hash >= 0) return cleanUp(s.slice(hash + 1)) if (s.slice(-9) === '/foaf.rdf') s = s.slice(0, -9) else if (s.slice(-5) === '/foaf') s = s.slice(0, -5) if (1) { // Eh? Why not do this? e.g. dc:title needs it only trim URIs, not rdfs:labels var slash = s.lastIndexOf('/', s.length - 2); // (len-2) excludes trailing slash if ((slash >= 0) && (slash < x.uri.length)) return cleanUp(s.slice(slash + 1)) } return doCap(decodeURIComponent(x.uri)) } UI.utils.escapeForXML = function (str) { return str.replace(/&/g, '&').replace(/