Polish sticky headers, Custom View, docs page and dialogs
- Status tables: the whole header (column names + filters) sticks as one block, tucked 1px under the group header, so no gap can open between the rows; offsets use fractional heights - Phones/tablets: the filter panel sticks under the group header too, and is opaque and compact (three filters per row on phones) - Custom View: fix the group header being pushed over the column header (it lives inside the view's own scroll box); the column header now pins to the top of that box on desktop, and the header strip sticks on phones - All Devices: group title is "All Devices (N)" instead of "Missing Software", matching the page title count - Documentation fits the window on desktop: only the article list and the article scroll; placeholder no longer says "from the left" - Dialogs sit above the terminal dock so it can't cover their last rows; page bottom padding follows the dock's measured height
This commit is contained in:
1 parent
a85c22b2b9
commit
d24be4a2d6
4 files changed
+73
-25
No files matched your search
+1
-2
@@ -5,8 +5,7 @@
|
|||||||
font-family: 'Segoe UI', sans-serif;
|
font-family: 'Segoe UI', sans-serif;
|
||||||
box-shadow: 0 -4px 16px rgba(0,0,0,0.35);
|
box-shadow: 0 -4px 16px rgba(0,0,0,0.35);
|
||||||
}
|
}
|
||||||
body { padding-bottom: 40px; }
|
/* Page bottom padding for the dock is set in layout.css from its measured height */
|
||||||
body.usmt-term-open { padding-bottom: calc(var(--usmt-term-h, 340px) + 72px); }
|
|
||||||
|
|
||||||
.usmt-term-resize { height: 5px; cursor: ns-resize; background: transparent; }
|
.usmt-term-resize { height: 5px; cursor: ns-resize; background: transparent; }
|
||||||
.usmt-term.collapsed .usmt-term-resize { display: none; }
|
.usmt-term.collapsed .usmt-term-resize { display: none; }
|
||||||
|
|||||||
+5
-3
@@ -459,7 +459,7 @@
|
|||||||
|
|
||||||
<div class="table-wrapper" style="overflow-x: auto; background: var(--bg-card); border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); transition: padding-bottom 0.2s;">
|
<div class="table-wrapper" style="overflow-x: auto; background: var(--bg-card); border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); transition: padding-bottom 0.2s;">
|
||||||
<div class="group-header" style="margin-top: 0; border-radius: 8px 8px 0 0;">
|
<div class="group-header" style="margin-top: 0; border-radius: 8px 8px 0 0;">
|
||||||
<span>Missing Software</span>
|
<span>All Devices <span id="softwareGroupCount"></span></span>
|
||||||
<div style="display: flex; gap: 8px;">
|
<div style="display: flex; gap: 8px;">
|
||||||
<button class="copy-table-btn no-copy" onclick="copyLocationInfo('software', this)">
|
<button class="copy-table-btn no-copy" onclick="copyLocationInfo('software', this)">
|
||||||
<svg class="icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/></svg> Copy Location Info
|
<svg class="icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/></svg> Copy Location Info
|
||||||
@@ -595,7 +595,7 @@
|
|||||||
<button class="nav-btn" id="btn-edit-mode" onclick="setDocMode('edit')" title="Switch to Edit Mode" style="padding: 6px 10px; border: 1px solid var(--border-color);"></button>
|
<button class="nav-btn" id="btn-edit-mode" onclick="setDocMode('edit')" title="Switch to Edit Mode" style="padding: 6px 10px; border: 1px solid var(--border-color);"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="docContent" style="flex: 1; overflow-y: auto; line-height: 1.6; min-height: 0; padding-right: 10px;">Select a document from the left...</div>
|
<div id="docContent" style="flex: 1; overflow-y: auto; line-height: 1.6; min-height: 0; padding-right: 10px;">Select an article from the list to read it.</div>
|
||||||
<textarea id="docEditor" style="display: none; flex: 1; width: 100%; background: var(--input-bg); color: var(--text-main); border: 1px solid var(--border-color); border-radius: 4px; padding: 15px; font-family: monospace; font-size: 1.05em; resize: none; min-height: 0; box-sizing: border-box;"></textarea>
|
<textarea id="docEditor" style="display: none; flex: 1; width: 100%; background: var(--input-bg); color: var(--text-main); border: 1px solid var(--border-color); border-radius: 4px; padding: 15px; font-family: monospace; font-size: 1.05em; resize: none; min-height: 0; box-sizing: border-box;"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -880,6 +880,8 @@
|
|||||||
let swVis = Array.from(swRows).filter(tr => tr.style.display !== 'none').length;
|
let swVis = Array.from(swRows).filter(tr => tr.style.display !== 'none').length;
|
||||||
let sc = document.getElementById('softwareCount');
|
let sc = document.getElementById('softwareCount');
|
||||||
if(sc) sc.innerText = `(${swVis})`;
|
if(sc) sc.innerText = `(${swVis})`;
|
||||||
|
let sgc = document.getElementById('softwareGroupCount');
|
||||||
|
if(sgc) sgc.innerText = `(${swVis})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let globalDataLastUpdated = "Never";
|
let globalDataLastUpdated = "Never";
|
||||||
@@ -2242,7 +2244,7 @@
|
|||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showToast("Article archived successfully!");
|
showToast("Article archived successfully!");
|
||||||
document.getElementById('docContent').innerHTML = "Select a document from the left...";
|
document.getElementById('docContent').innerHTML = "Select an article from the list to read it.";
|
||||||
document.getElementById('docToolbar').style.display = 'none';
|
document.getElementById('docToolbar').style.display = 'none';
|
||||||
document.getElementById('docEditor').style.display = 'none';
|
document.getElementById('docEditor').style.display = 'none';
|
||||||
currentDocTitle = "";
|
currentDocTitle = "";
|
||||||
|
|||||||
+49
-13
@@ -10,25 +10,45 @@
|
|||||||
* Sticky headers
|
* Sticky headers
|
||||||
*
|
*
|
||||||
* A status group's header ("Not Started (37)") sticks under the top nav while you scroll
|
* A status group's header ("Not Started (37)") sticks under the top nav while you scroll
|
||||||
* through that group, then hands over to the next group's header. On desktop the column
|
* through that group, then hands over to the next group's header. Beneath it the table's
|
||||||
* header and filter rows stick beneath it. layout.js sets --nav-h (top nav height) and,
|
* header (column names + filters on desktop, the filter panel on phones) sticks as one
|
||||||
* per table, --sticky-top (nav + group header) and --thead-r1 (header row height).
|
* block, so the rows inside it can never drift apart. layout.js sets --nav-h and, per
|
||||||
|
* table, --sticky-top (nav + group header).
|
||||||
* ------------------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------------------- */
|
||||||
.group-header { position: sticky; top: var(--nav-h, 60px); z-index: 30; }
|
.group-header { position: sticky; top: var(--nav-h, 60px); z-index: 30; }
|
||||||
|
|
||||||
@media (min-width: 1101px) {
|
.master-table:not(#table-custom) thead { position: sticky; top: var(--sticky-top, 112px); z-index: 20; }
|
||||||
/* An overflowing wrapper would capture the sticky rows; these tables never need to
|
|
||||||
scroll sideways at desktop widths (they're fixed-layout, 100% wide) */
|
|
||||||
#masterList .table-wrapper, #software .table-wrapper, .stack-box.table-wrapper { overflow: visible !important; }
|
|
||||||
|
|
||||||
.master-table:not(#table-custom) thead th {
|
|
||||||
position: sticky; top: var(--sticky-top, 112px); z-index: 20;
|
|
||||||
}
|
|
||||||
.master-table:not(#table-custom) thead tr.filter-row th {
|
.master-table:not(#table-custom) thead tr.filter-row th {
|
||||||
top: calc(var(--sticky-top, 112px) + var(--thead-r1, 38px) - 2px); z-index: 19; /* -2px: no seam from collapsed borders */
|
|
||||||
/* the original header tint, but opaque so rows don't show through */
|
/* the original header tint, but opaque so rows don't show through */
|
||||||
background: linear-gradient(rgba(0,0,0,0.2), rgba(0,0,0,0.2)), var(--bg-card);
|
background: linear-gradient(rgba(0,0,0,0.2), rgba(0,0,0,0.2)), var(--bg-card);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1101px) {
|
||||||
|
/* An overflowing wrapper would capture the sticky header; these tables never need to
|
||||||
|
scroll sideways at desktop widths (they're fixed-layout, 100% wide) */
|
||||||
|
#masterList .table-wrapper, #software .table-wrapper, .stack-box.table-wrapper { overflow: visible !important; }
|
||||||
|
|
||||||
|
/* Custom View scrolls inside its own box (it can be wider than the page), so its
|
||||||
|
group header stays put and its column header sticks to the top of that box */
|
||||||
|
#wrapper-custom .group-header { position: static; }
|
||||||
|
#scroll-custom thead th { position: sticky; top: 0; z-index: 20; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------------------
|
||||||
|
* Bottom terminal dock: keep page content and dialogs clear of it.
|
||||||
|
* layout.js sets --dock-h to the dock's current height (bar only, or bar + terminal).
|
||||||
|
* ------------------------------------------------------------------------------------- */
|
||||||
|
body { padding-bottom: var(--dock-h, 40px) !important; }
|
||||||
|
|
||||||
|
/* Dialogs sit above the dock (below the top nav), so it can't cover their bottom rows */
|
||||||
|
.modal-overlay { z-index: 1950 !important; }
|
||||||
|
#modal { z-index: 1960 !important; }
|
||||||
|
#syncModal { z-index: 1970 !important; }
|
||||||
|
#confirmModal, #promptModal { z-index: 1980 !important; }
|
||||||
|
|
||||||
|
/* Documentation fits the window on desktop: only the article list and the article scroll */
|
||||||
|
@media (min-width: 901px) {
|
||||||
|
#docs > div { height: calc(100vh - var(--nav-h, 60px) - 50px - var(--dock-h, 40px)) !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------------------
|
/* ---------------------------------------------------------------------------------------
|
||||||
@@ -56,7 +76,9 @@
|
|||||||
/* Filters: a wrapping panel of labeled controls */
|
/* Filters: a wrapping panel of labeled controls */
|
||||||
table.master-table thead tr.filter-row {
|
table.master-table thead tr.filter-row {
|
||||||
display: flex; flex-wrap: wrap; gap: 8px 10px; padding: 10px 12px;
|
display: flex; flex-wrap: wrap; gap: 8px 10px; padding: 10px 12px;
|
||||||
background: rgba(0,0,0,0.15); border-bottom: 1px solid var(--border-color);
|
/* opaque: the panel sticks over the cards while scrolling */
|
||||||
|
background: linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15)), var(--bg-card);
|
||||||
|
border-bottom: 1px solid var(--border-color); box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
table.master-table thead tr.filter-row th {
|
table.master-table thead tr.filter-row th {
|
||||||
display: block; flex: 1 1 150px; min-width: 0; width: auto !important;
|
display: block; flex: 1 1 150px; min-width: 0; width: auto !important;
|
||||||
@@ -69,6 +91,11 @@
|
|||||||
font-size: 0.78em; font-weight: 600; color: var(--text-muted);
|
font-size: 0.78em; font-weight: 600; color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom View: cards don't need the sideways-scrolling box, so it scrolls with the
|
||||||
|
page like the other tables and its header strip sticks under the group header */
|
||||||
|
#wrapper-custom, #scroll-custom { overflow: visible !important; max-height: none !important; }
|
||||||
|
#table-custom thead { position: sticky; top: var(--sticky-top, 112px); z-index: 20; }
|
||||||
|
|
||||||
/* Custom View keeps its header (sort / filter / remove buttons) as a strip of chips */
|
/* Custom View keeps its header (sort / filter / remove buttons) as a strip of chips */
|
||||||
#table-custom thead tr.card-label-row {
|
#table-custom thead tr.card-label-row {
|
||||||
display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 12px;
|
display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 12px;
|
||||||
@@ -134,6 +161,15 @@
|
|||||||
@media (min-width: 1101px) {
|
@media (min-width: 1101px) {
|
||||||
.card-filter-toggle { display: none; }
|
.card-filter-toggle { display: none; }
|
||||||
}
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
/* Compact filter panel on phones: three per row, shorter controls */
|
||||||
|
table.master-table thead tr.filter-row { gap: 6px 8px; padding: 8px 10px; }
|
||||||
|
table.master-table thead tr.filter-row th { flex: 1 1 96px; }
|
||||||
|
table.master-table thead tr.filter-row th::before { font-size: 0.7em; margin-bottom: 2px; }
|
||||||
|
table.master-table thead tr.filter-row input[type="text"],
|
||||||
|
table.master-table thead tr.filter-row .select-box { height: 32px; padding: 3px 6px; }
|
||||||
|
table.master-table thead tr.filter-row .select-box { font-size: 13px; }
|
||||||
|
}
|
||||||
@media (max-width: 420px) {
|
@media (max-width: 420px) {
|
||||||
table.master-table tbody td { padding-left: 7.8em; }
|
table.master-table tbody td { padding-left: 7.8em; }
|
||||||
table.master-table tbody td::before { width: 7.2em; }
|
table.master-table tbody td::before { width: 7.2em; }
|
||||||
|
|||||||
+18
-7
@@ -87,16 +87,19 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
function updateSticky() {
|
function updateSticky() {
|
||||||
|
const root = document.documentElement;
|
||||||
const nav = document.querySelector('.top-nav');
|
const nav = document.querySelector('.top-nav');
|
||||||
const navH = nav ? nav.offsetHeight : 60;
|
const navH = nav ? nav.getBoundingClientRect().height : 60;
|
||||||
setVar(document.documentElement, '--nav-h', navH + 'px');
|
setVar(root, '--nav-h', navH + 'px');
|
||||||
|
const dock = document.getElementById('usmt-term');
|
||||||
|
setVar(root, '--dock-h', (dock ? Math.ceil(dock.getBoundingClientRect().height) : 0) + 'px');
|
||||||
|
|
||||||
document.querySelectorAll('table.master-table').forEach(table => {
|
document.querySelectorAll('table.master-table').forEach(table => {
|
||||||
|
if (!table.offsetParent) return; // hidden page/group: keep old values
|
||||||
const gh = groupHeaderFor(table);
|
const gh = groupHeaderFor(table);
|
||||||
const ghH = gh ? gh.offsetHeight : 0;
|
const ghH = gh && getComputedStyle(gh).position === 'sticky' ? gh.getBoundingClientRect().height : 0;
|
||||||
const firstRow = table.tHead && table.tHead.rows[0];
|
// 1px overlap tucks the table header under the group header: no hairline gap
|
||||||
if (!firstRow || !firstRow.offsetHeight) return; // hidden page/group: keep old values
|
setVar(table, '--sticky-top', Math.max(0, navH + ghH - 1) + 'px');
|
||||||
setVar(table, '--sticky-top', (navH + ghH) + 'px');
|
|
||||||
setVar(table, '--thead-r1', firstRow.offsetHeight + 'px');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +124,14 @@
|
|||||||
// Groups and pages are shown by toggling display (not a DOM change), so also
|
// 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.
|
// re-measure as the user scrolls; writes only happen when a value changes.
|
||||||
window.addEventListener('scroll', schedule, { passive: true });
|
window.addEventListener('scroll', schedule, { passive: true });
|
||||||
|
// The terminal dock opens, closes and resizes without a page resize
|
||||||
|
if (window.ResizeObserver) {
|
||||||
|
const watchDock = () => {
|
||||||
|
const dock = document.getElementById('usmt-term');
|
||||||
|
if (dock) new ResizeObserver(schedule).observe(dock); else setTimeout(watchDock, 500);
|
||||||
|
};
|
||||||
|
watchDock();
|
||||||
|
}
|
||||||
schedule();
|
schedule();
|
||||||
}
|
}
|
||||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
|
||||||
|
|||||||
Reference in new issue
Block a user