-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathclasses.js
More file actions
198 lines (172 loc) · 7.27 KB
/
classes.js
File metadata and controls
198 lines (172 loc) · 7.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/**
* classes.js — Searchable, virtually-scrolled classes table
*
* Strategy (same wrapping pattern as compare.js):
* 1. Let the original createDivClasses render its <table> into #propertyClasses
* (which is inside a hidden wrapper so the user never sees it).
* 2. Parse every <tr> from that table into a plain array of {id, label} objects.
* 3. Re-render into our own virtual-scroll container (#classes-scroll-viewport)
* with live client-side filtering.
*
* Virtual scroll keeps the live DOM to ~25 rows regardless of dataset size,
* making 2 000+ row tables scroll at 60 fps with zero jank.
*/
(function () {
// ── state ──
var allRows = []; // full dataset parsed from original table
var filtered = []; // subset after filter applied
var ROW_HEIGHT = 44; // px — must match CSS .vst-row height
var BUFFER = 5; // rows rendered above/below the visible window
// ── DOM refs (resolved once after DOM is ready) ──
var scrollEl, viewportEl, containerEl, searchEl, countEl, hiddenDiv;
function resolveRefs() {
scrollEl = document.getElementById('classes-scroll');
viewportEl = document.getElementById('classes-scroll-viewport');
containerEl = document.getElementById('classes-scroll-container');
searchEl = document.getElementById('classes-search');
countEl = document.getElementById('classes-count');
hiddenDiv = document.getElementById('propertyClasses');
}
// ── Parse the table that the original callback rendered ──
function parseOriginalTable() {
var table = hiddenDiv.querySelector('table');
if (!table) return [];
var rows = table.querySelectorAll('tbody tr, tr'); // covers both with/without tbody
var data = [];
rows.forEach(function (tr) {
var tds = tr.querySelectorAll('td');
if (tds.length < 2) return; // skip header row (th)
var idCell = tds[0];
var lblCell = tds[1];
var a = idCell.querySelector('a');
if (!a) return;
var href = a.getAttribute('href') || '';
var id = a.textContent.trim();
var label = lblCell.textContent.trim();
data.push({ id: id, label: label, href: href });
});
return data;
}
// ── Filter ──
function applyFilter() {
var q = searchEl ? searchEl.value.toLowerCase().trim() : '';
if (q === '') {
filtered = allRows;
} else {
filtered = allRows.filter(function (r) {
return r.id.toLowerCase().indexOf(q) !== -1 ||
r.label.toLowerCase().indexOf(q) !== -1;
});
}
updateCount();
renderVisibleRows();
}
function updateCount() {
if (countEl) {
countEl.innerHTML = '<strong>' + filtered.length.toLocaleString() + '</strong>' +
(filtered.length !== allRows.length
? ' of ' + allRows.length.toLocaleString()
: '');
}
}
// ── Virtual scroll rendering ──
function renderVisibleRows() {
if (!scrollEl || !viewportEl || !containerEl) return;
var totalHeight = filtered.length * ROW_HEIGHT;
viewportEl.style.height = totalHeight + 'px';
var scrollTop = scrollEl.scrollTop;
var clientH = scrollEl.clientHeight;
var startIdx = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - BUFFER);
var endIdx = Math.min(filtered.length, Math.ceil((scrollTop + clientH) / ROW_HEIGHT) + BUFFER);
var offsetY = startIdx * ROW_HEIGHT;
containerEl.style.transform = 'translateY(' + offsetY + 'px)';
// Recycle: reuse existing row elements where possible
var existingRows = containerEl.children;
var needed = endIdx - startIdx;
// Remove excess rows
while (existingRows.length > needed) {
containerEl.removeChild(containerEl.lastChild);
}
for (var i = 0; i < needed; i++) {
var dataIdx = startIdx + i;
var item = filtered[dataIdx];
var row;
if (i < existingRows.length) {
row = existingRows[i]; // recycle
} else {
row = document.createElement('div');
row.className = 'vst-row';
// id cell
var idCell = document.createElement('div');
idCell.className = 'vst-cell vst-cell-id';
var a = document.createElement('a');
a.className = 'vst-id-link';
idCell.appendChild(a);
row.appendChild(idCell);
// label cell
var lblCell = document.createElement('div');
lblCell.className = 'vst-cell vst-cell-label';
row.appendChild(lblCell);
containerEl.appendChild(row);
}
// update content
var link = row.querySelector('.vst-id-link');
var label = row.querySelector('.vst-cell-label');
link.setAttribute('href', item.href);
link.textContent = item.id;
label.textContent = item.label || item.id;
}
}
// ── Scroll listener (throttled via rAF) ──
var rafPending = false;
function onScroll() {
if (!rafPending) {
rafPending = true;
requestAnimationFrame(function () {
renderVisibleRows();
rafPending = false;
});
}
}
// ── Hook: wrap createDivClasses ──
function installHook() {
if (typeof createDivClasses === 'undefined') {
setTimeout(installHook, 50);
return;
}
var originalCreateDivClasses = createDivClasses;
window.createDivClasses = function (divId, json) {
// Let original render its table into the hidden div
originalCreateDivClasses(divId, json);
// Only intercept the main classes page target
if (divId !== 'propertyClasses') return;
resolveRefs();
allRows = parseOriginalTable();
filtered = allRows;
// Show the modern UI, hide the original
hiddenDiv.style.display = 'none';
var modernUI = document.getElementById('classes-modern');
if (modernUI) modernUI.style.display = 'block';
updateCount();
renderVisibleRows();
// Wire up search
if (searchEl) {
searchEl.addEventListener('input', applyFilter);
}
// Wire up scroll
if (scrollEl) {
scrollEl.addEventListener('scroll', onScroll);
// Also re-render on resize
window.addEventListener('resize', function () { renderVisibleRows(); });
}
// Initial render after a frame (ensures scroll container has measured height)
requestAnimationFrame(renderVisibleRows);
};
}
// Install hook as soon as DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', installHook);
} else {
installHook();
}
})();