<html> <head> <meta charset="utf-8"> <title>Tree</title> <style> #container { width: 400; height: 600; border: 1px solid black; } .monaco-scrollable-element>.scrollbar>.slider { background: rgba(100, 100, 100, .4); } .tl-contents { flex: 1; } .monaco-list-row:hover:not(.selected):not(.focused) { background: gainsboro !important; } </style> </head> <body> <input type="text" id="filter" /> <button id="expandall">Expand All</button> <button id="collapseall">Collapse All</button> <button id="renderwidth">Render Width</button> <button id="refresh">Refresh</button> <div id="container"></div> <script src="/static/vs/loader.js"></script> <script> function perf(name, fn) { performance.mark('before ' + name); const start = performance.now(); fn(); console.log(name + ' took', performance.now() - start); performance.mark('after ' + name); } require.config({ baseUrl: '/static' }); require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/objectTree', 'vs/base/browser/ui/tree/asyncDataTree', 'vs/base/browser/ui/tree/dataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { CompressibleObjectTree }, { AsyncDataTree }, { DataTree }, { TreeVisibility }, { iter }) => { function createIndexTree(opts) { opts = opts || {}; const delegate = { getHeight() { return 22; }, getTemplateId() { return 'template'; }, hasDynamicHeight() { return true; } }; const renderer = { templateId: 'template', renderTemplate(container) { return container; }, renderElement(element, index, container) { if (opts.supportDynamicHeights) { let v = []; for (let i = 1; i <= 5; i++) { v.push(element.element); } container.innerHTML = v.join('<br />'); } else { container.innerHTML = element.element; } }, disposeElement() { }, disposeTemplate() { } }; const treeFilter = new class { constructor() { this.pattern = null; let timeout; filter.oninput = () => { clearTimeout(timeout); timeout = setTimeout(() => this.updatePattern(), 300); }; } updatePattern() { if (!filter.value) { this.pattern = null; } else { this.pattern = new RegExp(filter.value, 'i'); } perf('refilter', () => tree.refilter()); } filter(el) { return (this.pattern ? this.pattern.test(el) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse; } }; const tree = new IndexTree('test', container, delegate, [renderer], null, { ...opts, filter: treeFilter, setRowLineHeight: false }); return { tree, treeFilter }; } function createCompressedObjectTree(opts) { opts = opts || {}; const delegate = { getHeight() { return 22; }, getTemplateId() { return 'template'; }, hasDynamicHeight() { return true; } }; const renderer = { templateId: 'template', renderTemplate(container) { return container; }, renderElement(element, index, container) { container.innerHTML = element.element.name; }, renderCompressedElements(node, index, container, height) { container.innerHTML = `🙈 ${node.element.elements.map(el => el.name).join('/')}`; }, disposeElement() { }, disposeTemplate() { } }; const treeFilter = new class { constructor() { this.pattern = null; let timeout; filter.oninput = () => { clearTimeout(timeout); timeout = setTimeout(() => this.updatePattern(), 300); }; } updatePattern() { if (!filter.value) { this.pattern = null; } else { this.pattern = new RegExp(filter.value, 'i'); } perf('refilter', () => tree.refilter()); } filter(el) { return (this.pattern ? this.pattern.test(el) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse; } }; const tree = new CompressibleObjectTree('test', container, delegate, [renderer], { ...opts, filter: treeFilter, setRowLineHeight: false, collapseByDefault: true, setRowLineHeight: true }); return { tree, treeFilter }; } function createAsyncDataTree() { const delegate = { getHeight() { return 22; }, getTemplateId() { return 'template'; } }; const renderer = { templateId: 'template', renderTemplate(container) { return container; }, renderElement(node, index, container) { container.textContent = node.element.element.name; }, disposeElement() { }, disposeTemplate() { } }; const treeFilter = new class { constructor() { this.pattern = null; let timeout; filter.oninput = () => { clearTimeout(timeout); timeout = setTimeout(() => this.updatePattern(), 300); }; } updatePattern() { if (!filter.value) { this.pattern = null; } else { this.pattern = new RegExp(filter.value, 'i'); } perf('refilter', () => tree.refilter()); } filter(el) { return (this.pattern ? this.pattern.test(el.name) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse; } }; const sorter = new class { compare(a, b) { if (a.collapsible === b.collapsible) { return a.name < b.name ? -1 : 1; } return a.collapsible ? -1 : 1; } }; const dataSource = new class { hasChildren(element) { return element === null || element.element.type === 'dir'; } getChildren(element) { return new Promise((c, e) => { const xhr = new XMLHttpRequest(); xhr.open('GET', element ? `/api/readdir?path=${element.element.path}` : '/api/readdir'); xhr.send(); xhr.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { const els = JSON.parse(this.responseText).map(element => ({ element, collapsible: element.type === 'dir' })); c(els); } }; }); } } const identityProvider = { getId(node) { return node.element.path; } }; const tree = new AsyncDataTree('test', container, delegate, [renderer], dataSource, { filter: treeFilter, sorter, identityProvider }); return { tree, treeFilter }; } function createDataTree() { const delegate = { getHeight() { return 22; }, getTemplateId() { return 'template'; } }; const renderer = { templateId: 'template', renderTemplate(container) { return container; }, renderElement(node, index, container) { container.textContent = node.element.name; }, disposeElement() { }, disposeTemplate() { } }; const treeFilter = new class { constructor() { this.pattern = null; let timeout; filter.oninput = () => { clearTimeout(timeout); timeout = setTimeout(() => this.updatePattern(), 300); }; } updatePattern() { if (!filter.value) { this.pattern = null; } else { this.pattern = new RegExp(filter.value, 'i'); } perf('refilter', () => tree.refilter()); } filter(el) { return (this.pattern ? this.pattern.test(el.name) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse; } }; const dataSource = new class { getChildren(element) { return element.children || []; } }; const identityProvider = { getId(node) { return node.name; } }; const tree = new DataTree('test', container, delegate, [renderer], dataSource, { filter: treeFilter, identityProvider }); tree.setInput({ children: [ { name: 'A', children: [{ name: 'AA' }, { name: 'AB' }] }, { name: 'B', children: [{ name: 'BA', children: [{ name: 'BAA' }] }, { name: 'BB' }] }, { name: 'C' } ] }); return { tree, treeFilter }; } switch (location.search) { case '?problems': { const { tree, treeFilter } = createIndexTree(); expandall.onclick = () => perf('expand all', () => tree.expandAll()); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); const files = []; for (let i = 0; i < 100000; i++) { const errors = []; for (let j = 1; j <= 3; j++) { errors.push({ element: `error #${j} ` }); } files.push({ element: `file #${i}`, children: errors }); } perf('splice', () => tree.splice([0], 0, files)); break; } case '?data': { const { tree, treeFilter } = createAsyncDataTree(); expandall.onclick = () => perf('expand all', () => tree.expandAll()); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); refresh.onclick = () => perf('refresh', () => tree.updateChildren()); tree.setInput(null); break; } case '?objectdata': { const { tree, treeFilter } = createDataTree(); expandall.onclick = () => perf('expand all', () => tree.expandAll()); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); refresh.onclick = () => perf('refresh', () => tree.updateChildren()); break; } case '?compressed': { const { tree, treeFilter } = createCompressedObjectTree(); expandall.onclick = () => perf('expand all', () => tree.expandAll()); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); const xhr = new XMLHttpRequest(); xhr.open('GET', '/compressed.json'); xhr.send(); xhr.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { tree.setChildren(null, JSON.parse(this.responseText)); } }; break; } case '?height': { const { tree, treeFilter } = createIndexTree({ supportDynamicHeights: true }); expandall.onclick = () => perf('expand all', () => tree.expandAll()); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); const xhr = new XMLHttpRequest(); xhr.open('GET', '/api/ls?path='); xhr.send(); xhr.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { perf('splice', () => tree.splice([0], 0, [JSON.parse(this.responseText)])); treeFilter.updatePattern(); } }; // container. break; } default: { const { tree, treeFilter } = createIndexTree(); expandall.onclick = () => perf('expand all', () => tree.expandAll()); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); const xhr = new XMLHttpRequest(); xhr.open('GET', '/api/ls?path='); xhr.send(); xhr.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { perf('splice', () => tree.splice([0], 0, [JSON.parse(this.responseText)])); treeFilter.updatePattern(); } }; } } }); </script> </body> </html>