Each widget is as tall as its content, never taller than its saved height (where it scrolls). The dashboard is capped at 2400px instead of 1400px. Past 12 columns' worth of width it adds columns (one per 110px, up to 24) and places widgets again in reading order at their saved widths, so more sit side by side instead of stretching. Edit mode shows and saves the 12-column layout, so a layout doesn't depend on the screen it was edited on. The share view uses the same layout code (static/dash-layout.js). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.7 KiB
JavaScript
93 lines
3.7 KiB
JavaScript
/* Dashboard layout (#5249): each widget as tall as its content, never taller
|
|
than the height saved for it, and extra grid columns on wide screens.
|
|
|
|
The layout is saved in 12 columns. A viewer on a wider screen gets more
|
|
columns (one per COL_PX of grid width, up to MAX_COLS), and the widgets are
|
|
placed again in reading order at their saved widths, so more of them sit
|
|
side by side; none is stretched. Edit mode always shows the saved 12-column
|
|
layout, so what is saved never depends on the screen it was edited on.
|
|
|
|
Used by the dashboard (Gridstack) and the share view (CSS grid). */
|
|
(function () {
|
|
var COL_PX = 110, MIN_COLS = 12, MAX_COLS = 24, NARROW_PX = 700, MIN_ROWS = 2;
|
|
|
|
function columnsFor(width) {
|
|
if (width < NARROW_PX) return 1;
|
|
return Math.max(MIN_COLS, Math.min(MAX_COLS, Math.floor(width / COL_PX)));
|
|
}
|
|
|
|
/* items: [{id, x, y, w, h}] from the saved layout; h is the height to place
|
|
(the content-fitted one). Returns {id: {x, y, w, h}}.
|
|
|
|
Reading order (y, then x) is kept: nothing is placed above the item before
|
|
it. At 12 columns an item keeps its saved column and floats up, which is the
|
|
saved layout minus the space short widgets no longer need. Narrower or wider,
|
|
it takes the first free spot. */
|
|
function pack(items, cols) {
|
|
var taken = [];
|
|
function free(x, y, w, h) {
|
|
for (var r = y; r < y + h; r++) {
|
|
if (!taken[r]) continue;
|
|
for (var c = x; c < x + w; c++) if (taken[r][c]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
function take(x, y, w, h) {
|
|
for (var r = y; r < y + h; r++) {
|
|
taken[r] = taken[r] || [];
|
|
for (var c = x; c < x + w; c++) taken[r][c] = true;
|
|
}
|
|
}
|
|
var out = {}, minY = 0;
|
|
items.slice().sort(function (a, b) { return a.y - b.y || a.x - b.x; }).forEach(function (it) {
|
|
var w = Math.min(it.w, cols), h = it.h, x = 0, y;
|
|
if (cols === MIN_COLS) {
|
|
x = Math.min(it.x, cols - w);
|
|
for (y = 0; !free(x, y, w, h); y++) { /* float up as far as it fits */ }
|
|
} else {
|
|
search:
|
|
for (y = minY; ; y++) {
|
|
for (x = 0; x + w <= cols; x++) if (free(x, y, w, h)) break search;
|
|
}
|
|
}
|
|
take(x, y, w, h);
|
|
minY = y;
|
|
out[it.id] = { x: x, y: y, w: w, h: h };
|
|
});
|
|
return out;
|
|
}
|
|
|
|
/* The height a widget's box needs to show everything in it: its padding and
|
|
border, plus each child (header, body) at its natural height. The body
|
|
normally fills the box and scrolls, so it is measured unstretched. */
|
|
function naturalHeight(box) {
|
|
var body = box.querySelector('.dash-cell-body');
|
|
var cs = getComputedStyle(box);
|
|
var h = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom)
|
|
+ parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
|
|
if (body) { body.style.flex = '0 0 auto'; body.style.overflow = 'visible'; }
|
|
Array.prototype.forEach.call(box.children, function (child) {
|
|
var s = getComputedStyle(child);
|
|
h += child.offsetHeight + parseFloat(s.marginTop) + parseFloat(s.marginBottom);
|
|
});
|
|
if (body) { body.style.flex = ''; body.style.overflow = ''; }
|
|
return h;
|
|
}
|
|
|
|
/* Grid rows for that height: rowPx is one row plus the gap that follows it,
|
|
gapPx the space a cell loses to its margins. Capped at the saved height,
|
|
where the body scrolls instead. */
|
|
function fitRows(box, rowPx, gapPx, maxRows) {
|
|
var rows = Math.ceil((naturalHeight(box) + gapPx) / rowPx);
|
|
return Math.max(MIN_ROWS, Math.min(maxRows, rows));
|
|
}
|
|
|
|
function saved(el) {
|
|
return { x: +el.dataset.x, y: +el.dataset.y, w: +el.dataset.w, h: +el.dataset.h };
|
|
}
|
|
|
|
window.StewardDashLayout = {
|
|
MIN_COLS: MIN_COLS, columnsFor: columnsFor, pack: pack, fitRows: fitRows, saved: saved,
|
|
};
|
|
})();
|