510 lines
15 KiB
JavaScript
510 lines
15 KiB
JavaScript
(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,
|
|
};
|
|
}));
|