(function (root, factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof module === 'object' && module.exports) { module.exports = factory(); } else { root.insight = factory(); } } (this, function () { 'use strict'; let document; let strsData, mods, tagIds; let domPathInput, domFuzzyMatch; let domTBody; let domPlaceholder = null; let placeholderVisible = false; //-------------------------------------------------------------------------- // DOM Helper Functions //-------------------------------------------------------------------------- function domNewText(text) { return document.createTextNode(text); } function domNewElem(type) { let dom = document.createElement(type); for (let i = 1; i < arguments.length; ++i) { let arg = arguments[i]; if (typeof(arg) == 'string' || typeof(arg) == 'number') { arg = domNewText(arg) } dom.appendChild(arg); } return dom; } function domNewLink(text, onClick) { let dom = domNewElem('a', text); dom.setAttribute('href', '#'); dom.addEventListener('click', onClick); return dom; } //-------------------------------------------------------------------------- // Module Row //-------------------------------------------------------------------------- function countDeps(deps) { let direct = 0; let indirect = 0; if (deps.length > 0) { direct = deps[0].length; for (let i = 1; i < deps.length; ++i) { indirect += deps[i].length; } } return [direct, indirect]; } function Module(id, modData) { this.id = id; this.path = strsData[modData[0]]; this.cls = modData[1]; this.tagIds = new Set(modData[2]); this.deps = modData[3]; this.users = modData[4]; this.srcDirs = modData[5].map(function (x) { return strsData[x]; }); [this.numDirectDeps, this.numIndirectDeps] = countDeps(this.deps); this.numUsers = this.users.length; this.dom = null; this.visible = false; this.linkDoms = Object.create(null); } Module.prototype.isTagged = function (tagId) { return this.tagIds.has(tagId); } Module.prototype.createModuleLinkDom = function (mod) { let dom = domNewElem('a', mod.path); dom.setAttribute('href', '#mod_' + mod.id); dom.setAttribute('data-mod-id', mod.id); dom.setAttribute('data-owner-id', this.id); dom.addEventListener('click', onModuleLinkClicked); dom.addEventListener('mouseover', onModuleLinkMouseOver); dom.addEventListener('mouseout', onModuleLinkMouseOut); this.linkDoms[mod.id] = dom; return dom; } Module.prototype.createModuleRelationsDom = function (parent, label, modIds) { parent.appendChild(domNewElem('h2', label)); let domOl = domNewElem('ol'); parent.appendChild(domOl); for (let modId of modIds) { domOl.appendChild( domNewElem('li', this.createModuleLinkDom(mods[modId]))); } } Module.prototype.createModulePathTdDom = function (parent) { let domTd = domNewElem('td'); domTd.appendChild(domNewElem('p', this.createModuleLinkDom(this))); for (let dir of this.srcDirs) { let domP = domNewElem('p', 'source: ' + dir); domP.setAttribute('class', 'module_src_dir'); domTd.appendChild(domP); } parent.appendChild(domTd); } Module.prototype.createTagsTdDom = function (parent) { let domTd = domNewElem('td'); for (let tag of this.tagIds) { domTd.appendChild(domNewElem('p', strsData[tag])); } parent.appendChild(domTd); } Module.prototype.createDepsTdDom = function (parent) { let domTd = domNewElem( 'td', this.numDirectDeps + ' + ' + this.numIndirectDeps); let deps = this.deps; if (deps.length > 0) { this.createModuleRelationsDom(domTd, 'Direct', deps[0]); for (let i = 1; i < deps.length; ++i) { this.createModuleRelationsDom(domTd, 'Indirect #' + i, deps[i]); } } parent.appendChild(domTd); } Module.prototype.createUsersTdDom = function (parent) { let domTd = domNewElem('td', this.numUsers); let users = this.users; if (users.length > 0) { this.createModuleRelationsDom(domTd, 'Direct', users); } parent.appendChild(domTd); } Module.prototype.createDom = function () { let dom = this.dom = domNewElem('tr'); dom.setAttribute('id', 'mod_' + this.id); this.createModulePathTdDom(dom); this.createTagsTdDom(dom); this.createDepsTdDom(dom); this.createUsersTdDom(dom) } Module.prototype.showDom = function () { hidePlaceholder(); if (this.visible) { return; } if (this.dom === null) { this.createDom(); } domTBody.appendChild(this.dom); this.visible = true; } Module.prototype.hideDom = function () { if (!this.visible) { return; } this.dom.parentNode.removeChild(this.dom); this.visible = false; } function createModulesFromData(stringsData, modulesData) { return modulesData.map(function (modData, id) { return new Module(id, modData); }); } function createTagIdsFromData(stringsData, mods) { let tagIds = new Set(); for (let mod of mods) { for (let tag of mod.tagIds) { tagIds.add(tag); } } tagIds = Array.from(tagIds); tagIds.sort(function (a, b) { return strsData[a].localeCompare(strsData[b]); }); return tagIds; } //-------------------------------------------------------------------------- // Data //-------------------------------------------------------------------------- function init(doc, stringsData, modulesData) { document = doc; strsData = stringsData; mods = createModulesFromData(stringsData, modulesData); tagIds = createTagIdsFromData(stringsData, mods); document.addEventListener('DOMContentLoaded', function (evt) { createControlDom(document.body); createTableDom(document.body); }); } //-------------------------------------------------------------------------- // Control //-------------------------------------------------------------------------- function createControlDom(parent) { let domTBody = domNewElem('tbody'); createSelectionTrDom(domTBody); createAddByTagsTrDom(domTBody); createAddByPathTrDom(domTBody); let domTable = domNewElem('table', domTBody); domTable.id = 'control'; let domFixedLink = domNewElem('a', 'Menu'); domFixedLink.href = '#control'; domFixedLink.id = 'control_menu'; parent.appendChild(domFixedLink); parent.appendChild(domTable); } function createControlMenuTr(parent, label, items) { let domUl = domNewElem('ul'); domUl.className = 'menu'; for (let [txt, callback] of items) { domUl.appendChild(domNewElem('li', domNewLink(txt, callback))); } let domTr = domNewElem('tr', createControlLabelTdDom(label), domNewElem('td', domUl)); parent.appendChild(domTr); } function createSelectionTrDom(parent) { const items = [ ['All', onAddAll], ['32-bit', onAddAll32], ['64-bit', onAddAll64], ['Clear', onClear], ]; createControlMenuTr(parent, 'Selection:', items); } function createAddByTagsTrDom(parent) { if (tagIds.length == 0) { return; } const items = tagIds.map(function (tagId) { return [strsData[tagId], function (evt) { evt.preventDefault(true); showModulesByTagId(tagId); }]; }); createControlMenuTr(parent, 'Add by Tags:', items); } function createAddByPathTrDom(parent) { let domForm = domNewElem('form'); domForm.addEventListener('submit', onAddModuleByPath); domPathInput = domNewElem('input'); domPathInput.type = 'text'; domForm.appendChild(domPathInput); let domBtn = domNewElem('input'); domBtn.type = 'submit'; domBtn.value = 'Add'; domForm.appendChild(domBtn); domFuzzyMatch = domNewElem('input'); domFuzzyMatch.setAttribute('id', 'fuzzy_match'); domFuzzyMatch.setAttribute('type', 'checkbox'); domFuzzyMatch.setAttribute('checked', 'checked'); domForm.appendChild(domFuzzyMatch); let domFuzzyMatchLabel = domNewElem('label', 'Fuzzy Match'); domFuzzyMatchLabel.setAttribute('for', 'fuzzy_match'); domForm.appendChild(domFuzzyMatchLabel); let domTr = domNewElem('tr', createControlLabelTdDom('Add by Path:'), domNewElem('td', domForm)); parent.appendChild(domTr); } function createControlLabelTdDom(text) { return domNewElem('td', domNewElem('strong', text)); } //-------------------------------------------------------------------------- // Table //-------------------------------------------------------------------------- function createTableDom(parent) { domTBody = domNewElem('tbody'); domTBody.id = 'module_tbody'; createTableHeaderDom(domTBody); showPlaceholder(); let domTable = domNewElem('table', domTBody); domTable.id = 'module_table'; parent.appendChild(domTable); } function createTableHeaderDom(parent) { const labels = [ 'Name', 'Tags', 'Dependencies (Direct + Indirect)', 'Users', ]; let domTr = domNewElem('tr'); for (let label of labels) { domTr.appendChild(domNewElem('th', label)); } parent.appendChild(domTr); } function createPlaceholder() { let domTd = domNewElem('td'); domTd.setAttribute('colspan', 4); domTd.setAttribute('id', 'no_module_placeholder'); domTd.appendChild(domNewText( 'No modules are selected. Click the menu to select modules by ' + 'names or categories.')); domPlaceholder = domNewElem('tr', domTd); } function showPlaceholder() { if (placeholderVisible) { return; } placeholderVisible = true; if (domPlaceholder === null) { createPlaceholder(); } domTBody.appendChild(domPlaceholder); } function hidePlaceholder() { if (placeholderVisible) { domTBody.removeChild(domPlaceholder); placeholderVisible = false; } } function hideAllModules() { for (let mod of mods) { mod.hideDom(); } showPlaceholder(); } function showAllModules() { for (let mod of mods) { mod.showDom(); } } function showModulesByFilter(pred) { let numMatched = 0; for (let mod of mods) { if (pred(mod)) { mod.showDom(); ++numMatched; } } return numMatched; } function showModulesByTagId(tagId) { showModulesByFilter(function (mod) { return mod.isTagged(tagId); }); } //-------------------------------------------------------------------------- // Events //-------------------------------------------------------------------------- function onAddModuleByPath(evt) { evt.preventDefault(); let path = domPathInput.value; domPathInput.value = ''; function escapeRegExp(pattern) { return pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); } function createFuzzyMatcher() { let parts = path.split(/\/+/g); let pattern = ''; for (let part of parts) { pattern += escapeRegExp(part) + '(?:/[^\/]*)*'; } pattern = RegExp(pattern); return function (mod) { return pattern.test(mod.path); }; } function exactMatcher(mod) { return mod.path == path; } let numMatched = showModulesByFilter( domFuzzyMatch.checked ? createFuzzyMatcher() : exactMatcher); if (numMatched == 0) { alert('No matching modules: ' + path); } } function onAddAll(evt) { evt.preventDefault(true); hideAllModules(); showAllModules(); } function onAddAllClass(evt, cls) { evt.preventDefault(true); hideAllModules(); showModulesByFilter(function (mod) { return mod.cls == cls; }); } function onAddAll32(evt) { onAddAllClass(evt, 32); } function onAddAll64(evt) { onAddAllClass(evt, 64); } function onClear(evt) { evt.preventDefault(true); hideAllModules(); } function onModuleLinkClicked(evt) { let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10); mods[modId].showDom(); } function setDirectDepBackgroundColor(modId, ownerId, color) { let mod = mods[modId]; let owner = mods[ownerId]; let ownerLinkDoms = owner.linkDoms; if (mod.deps.length > 0) { for (let depId of mod.deps[0]) { if (depId in ownerLinkDoms) { ownerLinkDoms[depId].style.backgroundColor = color; } } } } function onModuleLinkMouseOver(evt) { let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10); let ownerId = parseInt(evt.target.getAttribute('data-owner-id'), 10); setDirectDepBackgroundColor(modId, ownerId, '#ffff00'); } function onModuleLinkMouseOut(evt) { let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10); let ownerId = parseInt(evt.target.getAttribute('data-owner-id'), 10); setDirectDepBackgroundColor(modId, ownerId, 'transparent'); } return { 'init': init, }; }));