diff options
| author | Liam Mitchell <[email protected]> | 2026-03-09 19:06:36 -0700 |
|---|---|---|
| committer | Liam Mitchell <[email protected]> | 2026-03-09 19:06:36 -0700 |
| commit | d1abc50ee9d4fb72efc646e17decafea741caa34 (patch) | |
| tree | e4288e00f2f7ca0391b83d986efcb69d3ba66a83 /src/zenserver/frontend/html/util | |
| parent | Allow requests with invalid content-types unless specified in command line or... (diff) | |
| parent | updated chunk–block analyser (#818) (diff) | |
| download | zen-d1abc50ee9d4fb72efc646e17decafea741caa34.tar.xz zen-d1abc50ee9d4fb72efc646e17decafea741caa34.zip | |
Merge branch 'main' into lm/restrict-content-type
Diffstat (limited to 'src/zenserver/frontend/html/util')
| -rw-r--r-- | src/zenserver/frontend/html/util/compactbinary.js | 4 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/util/friendly.js | 21 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/util/widgets.js | 138 |
3 files changed, 160 insertions, 3 deletions
diff --git a/src/zenserver/frontend/html/util/compactbinary.js b/src/zenserver/frontend/html/util/compactbinary.js index 90e4249f6..415fa4be8 100644 --- a/src/zenserver/frontend/html/util/compactbinary.js +++ b/src/zenserver/frontend/html/util/compactbinary.js @@ -310,8 +310,8 @@ CbFieldView.prototype.as_value = function(int_type=BigInt) case CbFieldType.IntegerPositive: return VarInt.read_uint(this._data_view, int_type)[0]; case CbFieldType.IntegerNegative: return VarInt.read_int(this._data_view, int_type)[0]; - case CbFieldType.Float32: return new DataView(this._data_view.subarray(0, 4)).getFloat32(0, false); - case CbFieldType.Float64: return new DataView(this._data_view.subarray(0, 8)).getFloat64(0, false); + case CbFieldType.Float32: { const s = this._data_view; return new DataView(s.buffer, s.byteOffset, 4).getFloat32(0, false); } + case CbFieldType.Float64: { const s = this._data_view; return new DataView(s.buffer, s.byteOffset, 8).getFloat64(0, false); } case CbFieldType.BoolFalse: return false; case CbFieldType.BoolTrue: return true; diff --git a/src/zenserver/frontend/html/util/friendly.js b/src/zenserver/frontend/html/util/friendly.js index a15252faf..5d4586165 100644 --- a/src/zenserver/frontend/html/util/friendly.js +++ b/src/zenserver/frontend/html/util/friendly.js @@ -20,4 +20,25 @@ export class Friendly static kib(x, p=0) { return Friendly.sep((BigInt(x) + 1023n) / (1n << 10n)|0n, p) + " KiB"; } static mib(x, p=1) { return Friendly.sep( BigInt(x) / (1n << 20n), p) + " MiB"; } static gib(x, p=2) { return Friendly.sep( BigInt(x) / (1n << 30n), p) + " GiB"; } + + static duration(s) + { + const v = Number(s); + if (v >= 1) return Friendly.sep(v, 2) + " s"; + if (v >= 0.001) return Friendly.sep(v * 1000, 2) + " ms"; + if (v >= 0.000001) return Friendly.sep(v * 1000000, 1) + " µs"; + return Friendly.sep(v * 1000000000, 0) + " ns"; + } + + static bytes(x) + { + const v = BigInt(Math.trunc(Number(x))); + if (v >= (1n << 60n)) return Friendly.sep(Number(v) / Number(1n << 60n), 2) + " EiB"; + if (v >= (1n << 50n)) return Friendly.sep(Number(v) / Number(1n << 50n), 2) + " PiB"; + if (v >= (1n << 40n)) return Friendly.sep(Number(v) / Number(1n << 40n), 2) + " TiB"; + if (v >= (1n << 30n)) return Friendly.sep(Number(v) / Number(1n << 30n), 2) + " GiB"; + if (v >= (1n << 20n)) return Friendly.sep(Number(v) / Number(1n << 20n), 1) + " MiB"; + if (v >= (1n << 10n)) return Friendly.sep(Number(v) / Number(1n << 10n), 0) + " KiB"; + return Friendly.sep(Number(v), 0) + " B"; + } } diff --git a/src/zenserver/frontend/html/util/widgets.js b/src/zenserver/frontend/html/util/widgets.js index 32a3f4d28..2964f92f2 100644 --- a/src/zenserver/frontend/html/util/widgets.js +++ b/src/zenserver/frontend/html/util/widgets.js @@ -54,6 +54,8 @@ export class Table extends Widget static Flag_PackRight = 1 << 1; static Flag_BiasLeft = 1 << 2; static Flag_FitLeft = 1 << 3; + static Flag_Sortable = 1 << 4; + static Flag_AlignNumeric = 1 << 5; constructor(parent, column_names, flags=Table.Flag_EvenSpacing, index_base=0) { @@ -81,11 +83,108 @@ export class Table extends Widget root.style("gridTemplateColumns", column_style); - this._add_row(column_names, false); + this._header_row = this._add_row(column_names, false); this._index = index_base; this._num_columns = column_names.length; this._rows = []; + this._flags = flags; + this._sort_column = -1; + this._sort_ascending = true; + + if (flags & Table.Flag_Sortable) + { + this._init_sortable(); + } + } + + _init_sortable() + { + const header_elem = this._element.firstElementChild; + if (!header_elem) + { + return; + } + + const cells = header_elem.children; + for (let i = 0; i < cells.length; i++) + { + const cell = cells[i]; + cell.style.cursor = "pointer"; + cell.style.userSelect = "none"; + cell.addEventListener("click", () => this._sort_by(i)); + } + } + + _sort_by(column_index) + { + if (this._sort_column === column_index) + { + this._sort_ascending = !this._sort_ascending; + } + else + { + this._sort_column = column_index; + this._sort_ascending = true; + } + + // Update header indicators + const header_elem = this._element.firstElementChild; + for (const cell of header_elem.children) + { + const text = cell.textContent.replace(/ [▲▼]$/, ""); + cell.textContent = text; + } + const active_cell = header_elem.children[column_index]; + active_cell.textContent += this._sort_ascending ? " ▲" : " ▼"; + + // Sort rows by comparing cell text content + const dir = this._sort_ascending ? 1 : -1; + const unit_multipliers = { "B": 1, "KiB": 1024, "MiB": 1048576, "GiB": 1073741824, "TiB": 1099511627776, "PiB": 1125899906842624, "EiB": 1152921504606846976 }; + const parse_sortable = (text) => { + // Try byte units first (e.g. "1,234 KiB", "1.5 GiB") + const byte_match = text.match(/^([\d,.]+)\s*(B|[KMGTPE]iB)/); + if (byte_match) + { + const num = parseFloat(byte_match[1].replace(/,/g, "")); + const mult = unit_multipliers[byte_match[2]] || 1; + return num * mult; + } + // Try percentage (e.g. "95.5%") + const pct_match = text.match(/^([\d,.]+)%/); + if (pct_match) + { + return parseFloat(pct_match[1].replace(/,/g, "")); + } + // Try plain number (possibly with commas/separators) + const num = parseFloat(text.replace(/,/g, "")); + if (!isNaN(num)) + { + return num; + } + return null; + }; + this._rows.sort((a, b) => { + const aElem = a.inner().children[column_index]; + const bElem = b.inner().children[column_index]; + const aText = aElem ? aElem.textContent : ""; + const bText = bElem ? bElem.textContent : ""; + + const aNum = parse_sortable(aText); + const bNum = parse_sortable(bText); + + if (aNum !== null && bNum !== null) + { + return (aNum - bNum) * dir; + } + return aText.localeCompare(bText) * dir; + }); + + // Re-order DOM elements + for (const row of this._rows) + { + this._element.appendChild(row.inner()); + } } *[Symbol.iterator]() @@ -121,6 +220,18 @@ export class Table extends Widget ret.push(new TableCell(leaf, row)); } + if ((this._flags & Table.Flag_AlignNumeric) && indexed) + { + for (const c of ret) + { + const t = c.inner().textContent; + if (t && /^\d/.test(t)) + { + c.style("textAlign", "right"); + } + } + } + if (this._index >= 0) ret.shift(); @@ -131,9 +242,34 @@ export class Table extends Widget { var row = this._add_row(args); this._rows.push(row); + + if ((this._flags & Table.Flag_AlignNumeric) && this._rows.length === 1) + { + this._align_header(); + } + return row; } + _align_header() + { + const first_row = this._rows[0]; + if (!first_row) + { + return; + } + const header_elem = this._element.firstElementChild; + const header_cells = header_elem.children; + const data_cells = first_row.inner().children; + for (let i = 0; i < data_cells.length && i < header_cells.length; i++) + { + if (data_cells[i].style.textAlign === "right") + { + header_cells[i].style.textAlign = "right"; + } + } + } + clear(index=0) { const elem = this._element; |