1(function (root, factory) { 2 if (typeof define === 'function' && define.amd) { 3 define([], factory); 4 } else if (typeof module === 'object' && module.exports) { 5 module.exports = factory(); 6 } else { 7 root.insight = factory(); 8 } 9} (this, function () { 10 'use strict'; 11 12 let document; 13 let strsData, mods, tagIds; 14 let domPathInput, domFuzzyMatch; 15 let domTBody; 16 let domPlaceholder = null; 17 let placeholderVisible = false; 18 19 //-------------------------------------------------------------------------- 20 // DOM Helper Functions 21 //-------------------------------------------------------------------------- 22 function domNewText(text) { 23 return document.createTextNode(text); 24 } 25 26 function domNewElem(type) { 27 let dom = document.createElement(type); 28 for (let i = 1; i < arguments.length; ++i) { 29 let arg = arguments[i]; 30 if (typeof(arg) == 'string' || typeof(arg) == 'number') { 31 arg = domNewText(arg) 32 } 33 dom.appendChild(arg); 34 } 35 return dom; 36 } 37 38 function domNewLink(text, onClick) { 39 let dom = domNewElem('a', text); 40 dom.setAttribute('href', '#'); 41 dom.addEventListener('click', onClick); 42 return dom; 43 } 44 45 //-------------------------------------------------------------------------- 46 // Module Row 47 //-------------------------------------------------------------------------- 48 function countDeps(deps) { 49 let direct = 0; 50 let indirect = 0; 51 if (deps.length > 0) { 52 direct = deps[0].length; 53 for (let i = 1; i < deps.length; ++i) { 54 indirect += deps[i].length; 55 } 56 } 57 return [direct, indirect]; 58 } 59 60 function Module(id, modData) { 61 this.id = id; 62 this.path = strsData[modData[0]]; 63 this.cls = modData[1]; 64 this.tagIds = new Set(modData[2]); 65 this.deps = modData[3]; 66 this.users = modData[4]; 67 this.srcDirs = modData[5].map(function (x) { return strsData[x]; }); 68 69 [this.numDirectDeps, this.numIndirectDeps] = countDeps(this.deps); 70 this.numUsers = this.users.length; 71 72 this.dom = null; 73 this.visible = false; 74 75 this.linkDoms = Object.create(null); 76 } 77 78 Module.prototype.isTagged = function (tagId) { 79 return this.tagIds.has(tagId); 80 } 81 82 Module.prototype.createModuleLinkDom = function (mod) { 83 let dom = domNewElem('a', mod.path); 84 dom.setAttribute('href', '#mod_' + mod.id); 85 dom.setAttribute('data-mod-id', mod.id); 86 dom.setAttribute('data-owner-id', this.id); 87 dom.addEventListener('click', onModuleLinkClicked); 88 dom.addEventListener('mouseover', onModuleLinkMouseOver); 89 dom.addEventListener('mouseout', onModuleLinkMouseOut); 90 91 this.linkDoms[mod.id] = dom; 92 93 return dom; 94 } 95 96 Module.prototype.createModuleRelationsDom = function (parent, label, 97 modIds) { 98 parent.appendChild(domNewElem('h2', label)); 99 100 let domOl = domNewElem('ol'); 101 parent.appendChild(domOl); 102 for (let modId of modIds) { 103 domOl.appendChild( 104 domNewElem('li', this.createModuleLinkDom(mods[modId]))); 105 } 106 } 107 108 Module.prototype.createModulePathTdDom = function (parent) { 109 let domTd = domNewElem('td'); 110 domTd.appendChild(domNewElem('p', this.createModuleLinkDom(this))); 111 for (let dir of this.srcDirs) { 112 let domP = domNewElem('p', 'source: ' + dir); 113 domP.setAttribute('class', 'module_src_dir'); 114 domTd.appendChild(domP); 115 } 116 parent.appendChild(domTd); 117 } 118 119 Module.prototype.createTagsTdDom = function (parent) { 120 let domTd = domNewElem('td'); 121 for (let tag of this.tagIds) { 122 domTd.appendChild(domNewElem('p', strsData[tag])); 123 } 124 parent.appendChild(domTd); 125 } 126 127 Module.prototype.createDepsTdDom = function (parent) { 128 let domTd = domNewElem( 129 'td', this.numDirectDeps + ' + ' + this.numIndirectDeps); 130 131 let deps = this.deps; 132 if (deps.length > 0) { 133 this.createModuleRelationsDom(domTd, 'Direct', deps[0]); 134 135 for (let i = 1; i < deps.length; ++i) { 136 this.createModuleRelationsDom(domTd, 'Indirect #' + i, deps[i]); 137 } 138 } 139 140 parent.appendChild(domTd); 141 } 142 143 Module.prototype.createUsersTdDom = function (parent) { 144 let domTd = domNewElem('td', this.numUsers); 145 146 let users = this.users; 147 if (users.length > 0) { 148 this.createModuleRelationsDom(domTd, 'Direct', users); 149 } 150 151 parent.appendChild(domTd); 152 } 153 154 Module.prototype.createDom = function () { 155 let dom = this.dom = domNewElem('tr'); 156 dom.setAttribute('id', 'mod_' + this.id); 157 158 this.createModulePathTdDom(dom); 159 this.createTagsTdDom(dom); 160 this.createDepsTdDom(dom); 161 this.createUsersTdDom(dom) 162 } 163 164 Module.prototype.showDom = function () { 165 hidePlaceholder(); 166 if (this.visible) { 167 return; 168 } 169 if (this.dom === null) { 170 this.createDom(); 171 } 172 domTBody.appendChild(this.dom); 173 this.visible = true; 174 } 175 176 Module.prototype.hideDom = function () { 177 if (!this.visible) { 178 return; 179 } 180 this.dom.parentNode.removeChild(this.dom); 181 this.visible = false; 182 } 183 184 function createModulesFromData(stringsData, modulesData) { 185 return modulesData.map(function (modData, id) { 186 return new Module(id, modData); 187 }); 188 } 189 190 function createTagIdsFromData(stringsData, mods) { 191 let tagIds = new Set(); 192 for (let mod of mods) { 193 for (let tag of mod.tagIds) { 194 tagIds.add(tag); 195 } 196 } 197 198 tagIds = Array.from(tagIds); 199 tagIds.sort(function (a, b) { 200 return strsData[a].localeCompare(strsData[b]); 201 }); 202 203 return tagIds; 204 } 205 206 //-------------------------------------------------------------------------- 207 // Data 208 //-------------------------------------------------------------------------- 209 function init(doc, stringsData, modulesData) { 210 document = doc; 211 strsData = stringsData; 212 213 mods = createModulesFromData(stringsData, modulesData); 214 tagIds = createTagIdsFromData(stringsData, mods); 215 216 document.addEventListener('DOMContentLoaded', function (evt) { 217 createControlDom(document.body); 218 createTableDom(document.body); 219 }); 220 } 221 222 //-------------------------------------------------------------------------- 223 // Control 224 //-------------------------------------------------------------------------- 225 function createControlDom(parent) { 226 let domTBody = domNewElem('tbody'); 227 228 createSelectionTrDom(domTBody); 229 createAddByTagsTrDom(domTBody); 230 createAddByPathTrDom(domTBody); 231 232 let domTable = domNewElem('table', domTBody); 233 domTable.id = 'control'; 234 235 let domFixedLink = domNewElem('a', 'Menu'); 236 domFixedLink.href = '#control'; 237 domFixedLink.id = 'control_menu'; 238 239 parent.appendChild(domFixedLink); 240 parent.appendChild(domTable); 241 } 242 243 function createControlMenuTr(parent, label, items) { 244 let domUl = domNewElem('ul'); 245 domUl.className = 'menu'; 246 for (let [txt, callback] of items) { 247 domUl.appendChild(domNewElem('li', domNewLink(txt, callback))); 248 } 249 250 let domTr = domNewElem('tr', 251 createControlLabelTdDom(label), 252 domNewElem('td', domUl)); 253 254 parent.appendChild(domTr); 255 } 256 257 function createSelectionTrDom(parent) { 258 const items = [ 259 ['All', onAddAll], 260 ['32-bit', onAddAll32], 261 ['64-bit', onAddAll64], 262 ['Clear', onClear], 263 ]; 264 265 createControlMenuTr(parent, 'Selection:', items); 266 } 267 268 function createAddByTagsTrDom(parent) { 269 if (tagIds.length == 0) { 270 return; 271 } 272 273 const items = tagIds.map(function (tagId) { 274 return [strsData[tagId], function (evt) { 275 evt.preventDefault(true); 276 showModulesByTagId(tagId); 277 }]; 278 }); 279 280 createControlMenuTr(parent, 'Add by Tags:', items); 281 } 282 283 function createAddByPathTrDom(parent) { 284 let domForm = domNewElem('form'); 285 domForm.addEventListener('submit', onAddModuleByPath); 286 287 domPathInput = domNewElem('input'); 288 domPathInput.type = 'text'; 289 domForm.appendChild(domPathInput); 290 291 let domBtn = domNewElem('input'); 292 domBtn.type = 'submit'; 293 domBtn.value = 'Add'; 294 domForm.appendChild(domBtn); 295 296 domFuzzyMatch = domNewElem('input'); 297 domFuzzyMatch.setAttribute('id', 'fuzzy_match'); 298 domFuzzyMatch.setAttribute('type', 'checkbox'); 299 domFuzzyMatch.setAttribute('checked', 'checked'); 300 domForm.appendChild(domFuzzyMatch); 301 302 let domFuzzyMatchLabel = domNewElem('label', 'Fuzzy Match'); 303 domFuzzyMatchLabel.setAttribute('for', 'fuzzy_match'); 304 domForm.appendChild(domFuzzyMatchLabel); 305 306 let domTr = domNewElem('tr', 307 createControlLabelTdDom('Add by Path:'), 308 domNewElem('td', domForm)); 309 310 parent.appendChild(domTr); 311 } 312 313 function createControlLabelTdDom(text) { 314 return domNewElem('td', domNewElem('strong', text)); 315 } 316 317 318 //-------------------------------------------------------------------------- 319 // Table 320 //-------------------------------------------------------------------------- 321 function createTableDom(parent) { 322 domTBody = domNewElem('tbody'); 323 domTBody.id = 'module_tbody'; 324 325 createTableHeaderDom(domTBody); 326 327 showPlaceholder(); 328 329 let domTable = domNewElem('table', domTBody); 330 domTable.id = 'module_table'; 331 332 parent.appendChild(domTable); 333 } 334 335 function createTableHeaderDom(parent) { 336 const labels = [ 337 'Name', 338 'Tags', 339 'Dependencies (Direct + Indirect)', 340 'Users', 341 ]; 342 343 let domTr = domNewElem('tr'); 344 for (let label of labels) { 345 domTr.appendChild(domNewElem('th', label)); 346 } 347 348 parent.appendChild(domTr); 349 } 350 351 function createPlaceholder() { 352 let domTd = domNewElem('td'); 353 domTd.setAttribute('colspan', 4); 354 domTd.setAttribute('id', 'no_module_placeholder'); 355 domTd.appendChild(domNewText( 356 'No modules are selected. Click the menu to select modules by ' + 357 'names or categories.')); 358 domPlaceholder = domNewElem('tr', domTd); 359 } 360 361 function showPlaceholder() { 362 if (placeholderVisible) { 363 return; 364 } 365 placeholderVisible = true; 366 if (domPlaceholder === null) { 367 createPlaceholder(); 368 } 369 domTBody.appendChild(domPlaceholder); 370 } 371 372 function hidePlaceholder() { 373 if (placeholderVisible) { 374 domTBody.removeChild(domPlaceholder); 375 placeholderVisible = false; 376 } 377 } 378 379 function hideAllModules() { 380 for (let mod of mods) { 381 mod.hideDom(); 382 } 383 showPlaceholder(); 384 } 385 386 function showAllModules() { 387 for (let mod of mods) { 388 mod.showDom(); 389 } 390 } 391 392 function showModulesByFilter(pred) { 393 let numMatched = 0; 394 for (let mod of mods) { 395 if (pred(mod)) { 396 mod.showDom(); 397 ++numMatched; 398 } 399 } 400 return numMatched; 401 } 402 403 function showModulesByTagId(tagId) { 404 showModulesByFilter(function (mod) { 405 return mod.isTagged(tagId); 406 }); 407 } 408 409 410 //-------------------------------------------------------------------------- 411 // Events 412 //-------------------------------------------------------------------------- 413 414 function onAddModuleByPath(evt) { 415 evt.preventDefault(); 416 417 let path = domPathInput.value; 418 domPathInput.value = ''; 419 420 function escapeRegExp(pattern) { 421 return pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); 422 } 423 424 function createFuzzyMatcher() { 425 let parts = path.split(/\/+/g); 426 let pattern = ''; 427 for (let part of parts) { 428 pattern += escapeRegExp(part) + '(?:/[^\/]*)*'; 429 } 430 pattern = RegExp(pattern); 431 432 return function (mod) { 433 return pattern.test(mod.path); 434 }; 435 } 436 437 function exactMatcher(mod) { 438 return mod.path == path; 439 } 440 441 let numMatched = showModulesByFilter( 442 domFuzzyMatch.checked ? createFuzzyMatcher() : exactMatcher); 443 444 if (numMatched == 0) { 445 alert('No matching modules: ' + path); 446 } 447 } 448 449 function onAddAll(evt) { 450 evt.preventDefault(true); 451 hideAllModules(); 452 showAllModules(); 453 } 454 455 function onAddAllClass(evt, cls) { 456 evt.preventDefault(true); 457 hideAllModules(); 458 showModulesByFilter(function (mod) { 459 return mod.cls == cls; 460 }); 461 } 462 463 function onAddAll32(evt) { 464 onAddAllClass(evt, 32); 465 } 466 467 function onAddAll64(evt) { 468 onAddAllClass(evt, 64); 469 } 470 471 function onClear(evt) { 472 evt.preventDefault(true); 473 hideAllModules(); 474 } 475 476 function onModuleLinkClicked(evt) { 477 let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10); 478 mods[modId].showDom(); 479 } 480 481 function setDirectDepBackgroundColor(modId, ownerId, color) { 482 let mod = mods[modId]; 483 let owner = mods[ownerId]; 484 let ownerLinkDoms = owner.linkDoms; 485 if (mod.deps.length > 0) { 486 for (let depId of mod.deps[0]) { 487 if (depId in ownerLinkDoms) { 488 ownerLinkDoms[depId].style.backgroundColor = color; 489 } 490 } 491 } 492 } 493 494 function onModuleLinkMouseOver(evt) { 495 let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10); 496 let ownerId = parseInt(evt.target.getAttribute('data-owner-id'), 10); 497 setDirectDepBackgroundColor(modId, ownerId, '#ffff00'); 498 } 499 500 function onModuleLinkMouseOut(evt) { 501 let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10); 502 let ownerId = parseInt(evt.target.getAttribute('data-owner-id'), 10); 503 setDirectDepBackgroundColor(modId, ownerId, 'transparent'); 504 } 505 506 return { 507 'init': init, 508 }; 509})); 510