Filter menus: None first, documented picks; Custom View drag preview

- Fix: sorting a software column stopped its filter listing apps
  individually (the sort arrow written into the header changed the label
  used to recognise list columns)
- "None" (nothing missing) is pinned to the top of the value list, so the
  easy devices are one click away
- Software columns get "Documented" / "Undocumented" quick picks that
  tick only apps with / without a knowledge-base article; documented apps
  are marked in the list
- Custom View: remove Reset Filters / Reset Sort (Clear filters and the
  column menus replace them); old saved filters are cleared since nothing
  else could clear them
- Custom View column drag: a ghost of the column follows the cursor, the
  column being moved is dimmed, and a line shows where it will land
This commit is contained in:
Johan Sandoval committed 2026-10-06 18:03:33 +00:00
1 parent 318c59e610
commit 973ff592a4
4 files changed
+149 -5

No files matched your search

+67
View File
@@ -142,6 +142,67 @@
});
}
// Custom View column drag: a ghost of the column follows the cursor, the column being
// moved is dimmed, and a line shows exactly where it will land. Adds to the dashboard's
// own drag-and-drop (attachColDragHandlers), which still performs the move.
function enhanceColumnDrag() {
const table = document.getElementById('table-custom');
const host = document.getElementById('custom-table-host');
if (!table || !host) return;
const headers = Array.from(table.querySelectorAll('thead th.custom-th'));
let from = null, marker = null;
const setDimmed = (idx, on) => {
for (const row of table.rows) { const c = row.cells[idx]; if (c) c.classList.toggle('col-moving', on); }
};
const clear = () => {
if (from !== null) setDimmed(from, false);
if (marker) marker.remove();
marker = null; from = null;
};
headers.forEach((th, idx) => {
const grip = th.querySelector('.col-grip');
if (!grip || grip.dataset.ghost) return;
grip.dataset.ghost = '1';
grip.addEventListener('dragstart', e => {
from = idx;
const ghost = document.createElement('div');
ghost.className = 'col-drag-ghost';
const head = document.createElement('div');
head.className = 'col-drag-ghost-head';
head.textContent = th.querySelector('.label-text') ? th.querySelector('.label-text').textContent : th.textContent.trim();
ghost.appendChild(head);
Array.from(table.tBodies[0].rows).filter(r => r.style.display !== 'none').slice(0, 7).forEach(r => {
const cell = document.createElement('div');
cell.className = 'col-drag-ghost-cell';
cell.textContent = r.cells[idx] ? r.cells[idx].textContent.trim() : '';
ghost.appendChild(cell);
});
ghost.style.width = Math.min(280, Math.max(140, th.getBoundingClientRect().width)) + 'px';
document.body.appendChild(ghost);
try { e.dataTransfer.setDragImage(ghost, 24, 18); } catch (_) {}
setTimeout(() => ghost.remove(), 0);
setDimmed(idx, true);
});
grip.addEventListener('dragend', clear);
th.addEventListener('dragover', () => {
if (from === null) return;
if (!marker) { marker = document.createElement('div'); marker.className = 'col-drop-marker'; host.appendChild(marker); }
// moveCustomColumn(from, to) lands the column after the target when moving right,
// before it when moving left
const hostBox = host.getBoundingClientRect(), box = th.getBoundingClientRect();
const x = (idx > from ? box.right : box.left) - hostBox.left;
marker.style.left = (x - 2) + 'px';
marker.style.height = table.getBoundingClientRect().height + 'px';
marker.hidden = idx === from;
});
th.addEventListener('drop', clear);
});
}
let queued = false;
function run() {
queued = false;
@@ -164,6 +225,12 @@
// Groups and pages are shown by toggling display (not a DOM change), so also
// re-measure as the user scrolls; writes only happen when a value changes.
window.addEventListener('scroll', schedule, { passive: true });
// Custom View rebuilds its header (and re-attaches drag handlers) on every render
if (typeof window.attachColDragHandlers === 'function') {
const origDrag = window.attachColDragHandlers;
window.attachColDragHandlers = function () { const r = origDrag.apply(this, arguments); enhanceColumnDrag(); return r; };
enhanceColumnDrag();
}
// Page switches show and hide pages without changing the DOM
if (typeof window.switchPage === 'function') {
const orig = window.switchPage;