From 7242f8f8afcfa3e8dcc1509484617ebe7d77b499 Mon Sep 17 00:00:00 2001 From: Johan Sandoval Date: Tue, 6 Oct 2026 21:09:33 +0000 Subject: [PATCH] Wider details dialog, Card fields dialog like device details, no sideways chip scroll - Details dialog is wider so the device name is never cut off. - Overview list rows use the USMT Status colors (row color, ping color). - Workbench on phones: the stack header no longer sits over the device list. - Card fields uses the device-details dialog frame; bolder arrows; x to remove. - Filter chips wrap onto more lines instead of scrolling sideways. --- web/excel-filter.js | 32 +++++++++++++++++++------------- web/index.html | 2 +- web/layout.css | 33 ++++++++++++++++++++++++++------- web/overview.js | 11 ++++++++--- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/web/excel-filter.js b/web/excel-filter.js index c39878f..00e843b 100644 --- a/web/excel-filter.js +++ b/web/excel-filter.js @@ -657,8 +657,10 @@ document.addEventListener('mousedown', e => { if (menu && !menu.contains(e.target) && !(e.target.closest && e.target.closest('.xf-btn, .xf-chip, .col-filter-btn'))) closeMenu(); }, true); - window.addEventListener('scroll', () => { if (menu && !menu.classList.contains('xf-sheet')) closeMenu(); }, { passive: true }); - window.addEventListener('resize', closeMenu); + // the card-field dialog stays open through scrolling and the phone keyboard resizing the page + const keepOpen = () => menuCtx && menuCtx.fields; + window.addEventListener('scroll', () => { if (menu && !keepOpen() && !menu.classList.contains('xf-sheet')) closeMenu(); }, { passive: true }); + window.addEventListener('resize', () => { if (!keepOpen()) closeMenu(); }); // ------------------------------------------------------------------------------------ // Custom View card fields (phones/tablets): choose and order what each card shows @@ -666,11 +668,14 @@ function openFieldEditor() { closeMenu(); if (typeof customColumns === 'undefined') return; - backdrop = el('div', 'xf-backdrop'); backdrop.onclick = closeMenu; - menu = el('div', 'xf-menu xf-sheet xf-fields-sheet'); + // Same frame as the device details dialog: under the nav, full width on phones + backdrop = el('div', 'modal-overlay xf-fields-overlay'); + backdrop.style.display = 'block'; + backdrop.onclick = e => { if (e.target === backdrop) closeMenu(); }; + menu = el('div', 'modal-content xf-fields-sheet'); menuCtx = { fields: true }; + backdrop.appendChild(menu); document.body.appendChild(backdrop); - document.body.appendChild(menu); document.addEventListener('keydown', onMenuKey, true); renderFieldEditor(); } @@ -679,10 +684,10 @@ if (!menu) return; const label = id => { try { return getCustomColDef(id).label; } catch (e) { return id; } }; menu.innerHTML = ''; - const head = el('div', 'xf-head'); - head.appendChild(el('span', 'xf-title', 'Card fields')); - const x = el('button', 'xf-x', '×'); x.type = 'button'; x.onclick = closeMenu; + const head = el('div', 'modal-header'); + const x = el('span', 'close-modal', '\u00d7'); x.title = 'Close'; x.onclick = closeMenu; head.appendChild(x); + head.appendChild(el('h2', 'xf-fields-title', 'Card fields')); menu.appendChild(head); menu.appendChild(el('div', 'xf-hint', 'Choose what each card shows and in what order. The first field is the card title.')); @@ -691,14 +696,15 @@ const row = el('div', 'xf-field'); row.appendChild(el('span', 'xf-field-name', label(id))); if (i === 0) row.appendChild(el('span', 'xf-badge', 'Title')); - const btn = (txt, title, fn, disabled) => { - const b = el('button', 'xf-mini', txt); b.type = 'button'; b.title = title; b.disabled = !!disabled; + const btn = (svg, title, fn, disabled, cls) => { + const b = el('button', 'xf-mini' + (cls ? ' ' + cls : '')); b.type = 'button'; b.title = title; b.disabled = !!disabled; + b.innerHTML = svg; b.setAttribute('aria-label', title); b.onclick = () => { fn(); renderFieldEditor(); }; row.appendChild(b); }; - btn('↑', 'Move up', () => moveCustomColumn(i, i - 1), i === 0); - btn('↓', 'Move down', () => moveCustomColumn(i, i + 1), i === customColumns.length - 1); - btn('−', 'Remove from cards', () => toggleCustomColumn(id), customColumns.length <= 1); + btn('', 'Move up', () => moveCustomColumn(i, i - 1), i === 0); + btn('', 'Move down', () => moveCustomColumn(i, i + 1), i === customColumns.length - 1); + btn('', 'Remove from cards', () => toggleCustomColumn(id), customColumns.length <= 1, 'xf-mini-x'); shown.appendChild(row); }); menu.appendChild(shown); diff --git a/web/index.html b/web/index.html index 20dc053..2683d59 100755 --- a/web/index.html +++ b/web/index.html @@ -2877,7 +2877,7 @@ detailsBody.style.minHeight = '0'; detailsBody.style.padding = '0'; } else { - modalContent.style.maxWidth = '550px'; + modalContent.style.maxWidth = '680px'; modalContent.style.width = '85%'; modalContent.style.height = 'auto'; modalContent.style.maxHeight = 'calc(100vh - 100px)'; diff --git a/web/layout.css b/web/layout.css index 912bc6a..6ee274e 100644 --- a/web/layout.css +++ b/web/layout.css @@ -23,6 +23,10 @@ table.xf-table thead tr.filter-row { display: none !important; } .master-table:not(#table-custom) thead { position: sticky; top: var(--sticky-top, 112px); z-index: 20; } +/* A Workbench stack never needs to scroll sideways, and a sideways-scrolling box captures its + sticky header (which then sits over the device list on phones) */ +.stack-box.table-wrapper { overflow: visible !important; } + @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) */ @@ -223,9 +227,6 @@ body { padding-bottom: var(--dock-h, 40px) !important; } .modal-header { padding: 14px 14px 8px; } .modal-header h2 { font-size: 1.2em; word-break: break-all; } - /* Details title: the Advanced/Simple View button gets its own line under the device - name instead of floating over the content */ - #detailsTitle button { float: none !important; display: block; margin: 10px 0 0 !important; font-size: 0.65em !important; } /* Device details: Advanced View's source / overview / destination columns stack, and the whole dialog scrolls instead of each column scrolling separately */ @@ -332,7 +333,7 @@ table.xf-table thead th:has(> .xf-btn):not(.sortable-th) { position: relative; p .xf-bar { display: none; } @media (max-width: 1100px) { .xf-bar { - display: flex; gap: 6px; padding: 8px 10px; overflow-x: auto; scrollbar-width: none; + display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 10px; overflow: visible; /* chips wrap: no sideways scrolling */ position: sticky; top: var(--sticky-top, 112px); z-index: 25; 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.25); @@ -360,6 +361,23 @@ table.xf-table thead th:has(> .xf-btn):not(.sortable-th) { position: relative; p background: transparent; color: inherit; border: 1px solid var(--border-color); border-radius: 6px; } .xf-mini:disabled { opacity: 0.3; cursor: default; } +.xf-mini { display: inline-flex; align-items: center; justify-content: center; padding: 0; } +.xf-mini svg { width: 17px; height: 17px; } +.xf-mini:hover:not(:disabled) { border-color: #3498db; color: #3498db; } +.xf-mini.xf-mini-x:hover:not(:disabled) { border-color: #e74c3c; color: #e74c3c; } + +/* Card fields: the same dialog frame as the device details */ +.xf-fields-overlay .modal-content.xf-fields-sheet { + width: 85%; max-width: 550px; margin: 80px auto 20px; max-height: calc(100vh - 100px); max-height: calc(100dvh - 100px); + display: flex; flex-direction: column; overflow: hidden; font-size: 15px; +} +.xf-fields-sheet .modal-header { flex: none; } +.xf-fields-sheet .xf-fields-title { margin: 0; } +.xf-fields-sheet .xf-hint { padding: 12px 20px 8px; } +.xf-fields-sheet .xf-fields { padding: 0 20px; } +.xf-fields-sheet .xf-add { padding: 0 20px; } +.xf-fields-sheet .xf-actions { padding: 12px 20px 16px; } +.xf-fields-sheet .xf-actions button { flex: 1; padding: 11px; } .xf-add { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; margin-top: 8px; } .xf-fields-sheet .xf-list { flex: 1 1 auto; min-height: 110px; } .xf-fields-sheet .xf-add .xf-search { flex: none; } @@ -476,9 +494,10 @@ a.nav-brand:hover h2 { color: #d6eaf8; } a.nav-brand:focus-visible { outline: 2px solid #3498db; outline-offset: 3px; } /* Device details title: name on the left, Simple / Advanced View toggle on the right, one line */ -#detailsTitle { display: flex; align-items: center; gap: 12px; flex-wrap: nowrap; padding-right: 10px; min-width: 0; } -/* the name gives way (ellipsis) so the toggle never drops to a second line */ -#detailsTitle .dt-name { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +/* The dialog is wide enough for name + toggle on one line; on a phone that can't fit both, + the toggle moves under the name rather than cutting the name off */ +#detailsTitle { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding-right: 10px; min-width: 0; } +#detailsTitle .dt-name { flex: 0 1 auto; min-width: 0; white-space: nowrap; } #detailsTitle .dt-name .copy-row-btn { vertical-align: middle; } #detailsTitle .dt-toggle { flex: 0 0 auto; } #detailsTitle .dt-toggle { diff --git a/web/overview.js b/web/overview.js index d4c6708..b5969b1 100644 --- a/web/overview.js +++ b/web/overview.js @@ -568,9 +568,14 @@ const td = (t, bold) => { const c = el('td'); if (bold) c.appendChild(el('b', null, t)); else c.textContent = t; c.title = t; return c; }; row.appendChild(td(r['Source Device'], true)); row.appendChild(td(r['Destination Device'] || 'N/A')); - row.appendChild(td(statusOf(r))); - row.appendChild(td(String(detail || ''))); + row.appendChild(td(statusOf(r), true)); + const dc = td(String(detail || '')); + // ping values colored like the USMT Status page + if (/^(Online|Timed Out|Not Found|DNS Issue)$/.test(String(detail || '')) && typeof getPingColor === 'function') { dc.style.color = getPingColor(detail); dc.style.fontWeight = 'bold'; } + row.appendChild(dc); row.appendChild(td(r.Tags || 'None')); + // same row colors as the USMT Status page + try { const bg = themes[currentTheme].groupColors[r['Backup Status']]; if (bg) row.style.backgroundColor = bg; } catch (e) {} row.onclick = () => showDeviceDetails(r['Source Device']); tb.appendChild(row); }); @@ -641,7 +646,7 @@ function signature() { const a = g('apiData'), sc = g('softwareCache') || {}, docs = g('availableDocs') || []; return [Array.isArray(a) ? a.length : 0, JSON.stringify(a || []).length, Object.keys(g('advancedDataCache') || {}).length, - Object.keys(sc).length, docs.length, g('globalDataLastUpdated'), Math.floor(Date.now() / 300000)].join('|'); + Object.keys(sc).length, docs.length, g('globalDataLastUpdated'), g('currentTheme'), Math.floor(Date.now() / 300000)].join('|'); } function maybeRender(force) { if (!visible()) return;