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