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