fix table with handling, icons implemented
@@ -1,4 +1,4 @@
|
||||
:root{font-family:Arial,sans-serif;color:#222;background:#f3f5f7}*{box-sizing:border-box}body{margin:0}header{height:58px;background:#036;color:#fff;display:flex;align-items:center;padding:0 24px;gap:36px}.brand{font-size:22px;font-weight:bold}nav{display:flex;gap:20px}header a{color:#fff;text-decoration:none}main{width:100%;max-width:none;margin:24px 0;padding:0 24px}.toolbar{display:flex;justify-content:space-between;align-items:center}.button,button{background:#0878bd;color:#fff;border:0;border-radius:4px;padding:10px 15px;text-decoration:none;cursor:pointer}.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:16px}.card{display:flex;gap:14px;background:#fff;border:1px solid #d8dde2;border-radius:6px;padding:14px;text-decoration:none;color:#222}.card img,.placeholder{width:58px;height:58px;object-fit:cover;border-radius:5px}.placeholder{display:grid;place-items:center;background:#dbeaf3;font-size:26px}.card small{display:block;color:#667;margin-top:8px}table{width:100%;border-collapse:collapse;background:#fff}th,td{text-align:left;border-bottom:1px solid #ddd;padding:10px}th{background:#e8edf1}a{color:#056ca8}.filters{display:flex;gap:10px;margin:12px 0 18px;flex-wrap:wrap}.filters a{background:#fff;border:1px solid #ccd4da;padding:7px 10px;border-radius:4px;text-decoration:none}form{background:#fff;padding:20px;border:1px solid #d8dde2;border-radius:6px}label{display:block;margin-bottom:14px;font-weight:bold}input,select,textarea{display:block;width:100%;padding:9px;margin-top:5px;border:1px solid #bbb;border-radius:4px;font:inherit}textarea{min-height:100px}.detail{display:grid;grid-template-columns:280px 1fr;gap:24px;background:#fff;padding:20px}.asset-image{max-width:100%;max-height:260px;object-fit:contain}dl{display:grid;grid-template-columns:220px 1fr;margin:0}dt,dd{padding:9px;border-bottom:1px solid #eee;margin:0}dt{font-weight:bold}@media(max-width:700px){.detail{grid-template-columns:1fr}dl{grid-template-columns:1fr}header{padding:0 10px;gap:12px}nav{gap:9px}}
|
||||
:root{font-family:Arial,sans-serif;color:#222;background:#f3f5f7}*{box-sizing:border-box}body{margin:0}header{height:58px;background:#036;color:#fff;display:flex;align-items:center;padding:0 24px;gap:36px}.brand{font-size:22px;font-weight:bold}nav{display:flex;gap:20px}header a{color:#fff;text-decoration:none}main{width:100%;max-width:none;margin:24px 0;padding:0 24px}.toolbar{display:flex;justify-content:space-between;align-items:center}.button,button{background:#0878bd;color:#fff;border:0;border-radius:4px;padding:10px 15px;text-decoration:none;cursor:pointer}.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:16px}.card{display:flex;gap:14px;background:#fff;border:1px solid #d8dde2;border-radius:6px;padding:14px;text-decoration:none;color:#222}.card img,.placeholder{width:58px;height:58px;object-fit:contain;border-radius:5px}.card img{flex:0 0 58px;padding:2px;background:transparent}.placeholder{display:grid;place-items:center;background:#dbeaf3;font-size:26px}.card small{display:block;color:#667;margin-top:8px}table{width:100%;border-collapse:collapse;background:#fff}th,td{text-align:left;border-bottom:1px solid #ddd;padding:10px}th{background:#e8edf1}a{color:#056ca8}.filters{display:flex;gap:10px;margin:12px 0 18px;flex-wrap:wrap}.filters a{background:#fff;border:1px solid #ccd4da;padding:7px 10px;border-radius:4px;text-decoration:none}form{background:#fff;padding:20px;border:1px solid #d8dde2;border-radius:6px}label{display:block;margin-bottom:14px;font-weight:bold}input,select,textarea{display:block;width:100%;padding:9px;margin-top:5px;border:1px solid #bbb;border-radius:4px;font:inherit}textarea{min-height:100px}.detail{display:grid;grid-template-columns:280px 1fr;gap:24px;background:#fff;padding:20px}.asset-image{max-width:100%;max-height:260px;object-fit:contain}dl{display:grid;grid-template-columns:220px 1fr;margin:0}dt,dd{padding:9px;border-bottom:1px solid #eee;margin:0}dt{font-weight:bold}@media(max-width:700px){.detail{grid-template-columns:1fr}dl{grid-template-columns:1fr}header{padding:0 10px;gap:12px}nav{gap:9px}}
|
||||
.panel{background:#fff;border:1px solid #ccd4dc;border-radius:4px;padding:18px;margin-top:18px;overflow:auto}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.card{background:#fff;border:1px solid #ccd4dc;padding:16px;display:flex;flex-direction:column}.card strong{font-size:28px}.config-list{display:grid;grid-template-columns:minmax(130px,220px) 1fr;gap:8px 16px}.sync-status{display:inline-block;padding:3px 8px;border-radius:12px;background:#e5e7eb}.status-success,.status-synced,.status-created,.status-updated{background:#d9f2df}.status-conflict,.status-partial{background:#fff0c2}.status-error,.status-missing{background:#ffd9d9}code{word-break:break-all}
|
||||
.log-header{display:flex;align-items:center;justify-content:space-between;gap:16px}.log-header h2{margin:0}.sync-log{height:340px;overflow:auto;background:#111827;color:#e5e7eb;border-radius:5px;padding:12px;font-family:Consolas,"Courier New",monospace;font-size:13px;line-height:1.55;white-space:pre-wrap}.log-line{padding:1px 0}.log-debug{color:#9ca3af}.log-info{color:#dbeafe}.log-success{color:#86efac}.log-warning{color:#fde68a}.log-error{color:#fca5a5}button:disabled{opacity:.65;cursor:wait}
|
||||
.toolbar-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.inline-form{display:inline;margin:0;padding:0;background:transparent;border:0}.button-danger{background:#b42318}.button-danger:hover{background:#912018}.empty-state{text-align:center;color:#667;padding:24px}
|
||||
@@ -2934,12 +2934,12 @@ html.table-column-resizing * {
|
||||
|
||||
|
||||
/* User-resizable widths for every data-table column. */
|
||||
.data-table th.table-resizable-column-header {
|
||||
table th.table-resizable-column-header {
|
||||
position: relative;
|
||||
padding-right: 14px;
|
||||
overflow: visible;
|
||||
}
|
||||
.data-table .table-column-resizer {
|
||||
table .table-column-resizer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -4px;
|
||||
@@ -2949,7 +2949,7 @@ html.table-column-resizing * {
|
||||
z-index: 4;
|
||||
touch-action: none;
|
||||
}
|
||||
.data-table .table-column-resizer::after {
|
||||
table .table-column-resizer::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
@@ -2957,7 +2957,121 @@ html.table-column-resizing * {
|
||||
left: 4px;
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
.data-table .table-column-resizer:hover::after,
|
||||
.data-table .table-column-resizer:focus-visible::after {
|
||||
table .table-column-resizer:hover::after,
|
||||
table .table-column-resizer:focus-visible::after {
|
||||
border-left-color: currentColor;
|
||||
}
|
||||
|
||||
|
||||
/* Asset list device preview: first real column after the generated row number. */
|
||||
.asset-visual-column {
|
||||
width: 64px;
|
||||
min-width: 64px;
|
||||
max-width: 64px;
|
||||
text-align: center;
|
||||
padding-left: 6px !important;
|
||||
padding-right: 6px !important;
|
||||
}
|
||||
.asset-visual-link { display:inline-flex; align-items:center; justify-content:center; width:48px; height:42px; }
|
||||
.asset-list-device-image { max-width:46px; max-height:38px; width:auto; height:auto; object-fit:contain; border-radius:4px; }
|
||||
.asset-list-device-placeholder { font-size:24px; opacity:.35; line-height:1; }
|
||||
.mesh-icon-category-label { align-items:stretch; }
|
||||
.mesh-icon-category-title { display:flex; align-items:center; gap:8px; min-height:38px; }
|
||||
.mesh-icon-preview { width:38px; height:38px; object-fit:contain; flex:0 0 38px; }
|
||||
|
||||
/* v0.5.5.55 asset/category image management */
|
||||
.asset-edit-image-preview{display:flex;align-items:center;gap:14px;margin:8px 0 10px;padding:10px;border:1px solid #d8dde2;border-radius:6px;background:var(--am-surface-soft,#f8fafc);max-width:520px}
|
||||
.asset-edit-preview-image{width:110px;height:90px;object-fit:contain;border-radius:5px;background:transparent;border:1px solid #e1e5e9}
|
||||
.asset-edit-image-preview-actions{display:flex;flex-direction:column;align-items:flex-start;gap:8px}
|
||||
.button-small{padding:7px 10px;font-size:.9rem}
|
||||
.category-image-cell{text-align:center}
|
||||
.category-table-image{width:52px;height:42px;object-fit:contain;border-radius:4px;display:inline-block;background:transparent}
|
||||
html[data-theme="dark"] .asset-edit-image-preview{border-color:#46515d;background:#252b31}
|
||||
html[data-theme="dark"] .asset-edit-preview-image{background:transparent;border-color:#4a5561}
|
||||
|
||||
/* v0.5.5.56: consistent transparent image presentation and reliable table resize handles. */
|
||||
.asset-list-device-image,
|
||||
.category-table-image,
|
||||
.asset-edit-preview-image,
|
||||
.card img {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.card img,
|
||||
.asset-list-device-image,
|
||||
.category-table-image,
|
||||
.asset-edit-preview-image {
|
||||
object-fit: contain !important;
|
||||
object-position: center center;
|
||||
}
|
||||
|
||||
table th.table-resizable-column-header {
|
||||
position: relative;
|
||||
}
|
||||
table th.table-resizable-column-header > .table-column-resizer {
|
||||
right: 0;
|
||||
width: 12px;
|
||||
z-index: 30;
|
||||
}
|
||||
table th.table-resizable-column-header > .table-column-resizer::after {
|
||||
left: auto;
|
||||
right: 0;
|
||||
border-left-color: color-mix(in srgb, currentColor 28%, transparent);
|
||||
}
|
||||
table th.table-resizable-column-header > .table-column-resizer:hover::after,
|
||||
table th.table-resizable-column-header > .table-column-resizer:focus-visible::after {
|
||||
border-left-color: currentColor;
|
||||
}
|
||||
|
||||
/* v0.5.5.57: universal table resize visibility and reliable image editor state. */
|
||||
.asset-edit-image-preview[hidden],
|
||||
.image-remove-pending[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
.image-remove-pending {
|
||||
margin: 8px 0 10px;
|
||||
padding: 9px 11px;
|
||||
max-width: 520px;
|
||||
border: 1px solid var(--am-warning, #d97706);
|
||||
border-radius: 6px;
|
||||
background: color-mix(in srgb, var(--am-warning, #d97706) 10%, transparent);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
/* Every resizable header has a permanent separator; the transparent hit area
|
||||
remains wider than the visible line for easy mouse/touch dragging. */
|
||||
table th.table-resizable-column-header {
|
||||
position: relative !important;
|
||||
box-shadow: inset -1px 0 0 color-mix(in srgb, currentColor 28%, transparent);
|
||||
}
|
||||
table th.table-resizable-column-header > .table-column-resizer {
|
||||
display: block !important;
|
||||
pointer-events: auto !important;
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
right: -5px !important;
|
||||
bottom: 0 !important;
|
||||
width: 11px !important;
|
||||
z-index: 100 !important;
|
||||
cursor: col-resize !important;
|
||||
touch-action: none;
|
||||
background: transparent;
|
||||
}
|
||||
table th.table-resizable-column-header:hover > .table-column-resizer::after,
|
||||
table th.table-resizable-column-header > .table-column-resizer:focus-visible::after {
|
||||
border-left-color: currentColor !important;
|
||||
}
|
||||
|
||||
/* Reusable standard image library */
|
||||
.standard-image-library{margin-top:14px;padding-top:12px;border-top:1px solid var(--border,#d8dde2);max-width:760px}
|
||||
.image-library-help,.image-library-empty{margin:4px 0 9px}
|
||||
.standard-image-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px;margin:8px 0 12px;max-height:330px;overflow:auto;padding:2px}
|
||||
.standard-image-choice{position:relative;display:flex;flex-direction:column;align-items:center;gap:5px;min-width:0;padding:7px;border:1px solid var(--border,#d8dde2);border-radius:7px;cursor:pointer;background:transparent}
|
||||
.standard-image-choice:hover{border-color:var(--primary,#2583d8)}
|
||||
.standard-image-choice:has(input:checked){outline:2px solid var(--primary,#2583d8);outline-offset:-2px}
|
||||
.standard-image-choice input{position:absolute;top:6px;left:6px;margin:0}
|
||||
.standard-image-choice img{width:72px;height:58px;object-fit:contain;background:transparent}
|
||||
.standard-image-choice span{display:block;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center;font-size:.78rem}
|
||||
.image-upload-label{display:flex;flex-direction:column;align-items:flex-start;gap:5px;margin-top:8px}
|
||||
html[data-theme="dark"] .standard-image-library{border-color:#46515d}
|
||||
html[data-theme="dark"] .standard-image-choice{border-color:#46515d}
|
||||
|
||||
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 57 KiB |
@@ -0,0 +1,133 @@
|
||||
(() => {
|
||||
function initImageEditor(editor) {
|
||||
if (!editor || editor.dataset.imageEditorReady === '1') return;
|
||||
|
||||
const removeButton = editor.querySelector('[data-image-remove]');
|
||||
const removeInput = editor.querySelector('[data-image-remove-input]');
|
||||
let preview = editor.querySelector('[data-image-preview]');
|
||||
let previewImage = preview?.querySelector('img');
|
||||
const imageInput = editor.querySelector('input[type="file"][data-image-input]');
|
||||
const libraryUpload = editor.querySelector('input[type="file"][data-image-library-upload]');
|
||||
const libraryChoices = Array.from(editor.querySelectorAll('[data-image-library-choice]'));
|
||||
const pending = editor.querySelector('[data-image-remove-pending]');
|
||||
|
||||
if (!removeInput) return;
|
||||
|
||||
const originalPreviewSrc = previewImage?.getAttribute('src') || '';
|
||||
let objectUrl = null;
|
||||
|
||||
const releaseObjectUrl = () => {
|
||||
if (objectUrl) {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
objectUrl = null;
|
||||
}
|
||||
};
|
||||
|
||||
const ensurePreview = () => {
|
||||
if (preview && previewImage) return;
|
||||
preview = document.createElement('div');
|
||||
preview.className = 'asset-edit-image-preview';
|
||||
preview.dataset.imagePreview = '';
|
||||
preview.hidden = true;
|
||||
preview.style.display = 'none';
|
||||
|
||||
previewImage = document.createElement('img');
|
||||
previewImage.className = 'asset-edit-preview-image';
|
||||
previewImage.alt = '';
|
||||
preview.appendChild(previewImage);
|
||||
|
||||
const firstControl = editor.querySelector('[data-image-remove-input]');
|
||||
editor.insertBefore(preview, firstControl);
|
||||
};
|
||||
|
||||
const showPreview = src => {
|
||||
ensurePreview();
|
||||
if (src) previewImage.src = src;
|
||||
preview.hidden = false;
|
||||
preview.style.display = '';
|
||||
if (removeButton) {
|
||||
removeButton.hidden = false;
|
||||
removeButton.disabled = false;
|
||||
}
|
||||
};
|
||||
|
||||
const hidePreview = () => {
|
||||
if (!preview) return;
|
||||
preview.hidden = true;
|
||||
preview.style.display = 'none';
|
||||
if (removeButton) { removeButton.hidden = true; removeButton.disabled = true; }
|
||||
};
|
||||
|
||||
const clearLibraryChoices = () => {
|
||||
libraryChoices.forEach(choice => { choice.checked = false; });
|
||||
};
|
||||
|
||||
const clearRemoveState = () => {
|
||||
removeInput.value = '0';
|
||||
if (pending) pending.hidden = true;
|
||||
if (removeButton) {
|
||||
removeButton.disabled = false;
|
||||
removeButton.setAttribute('aria-pressed', 'false');
|
||||
}
|
||||
};
|
||||
|
||||
const setRemoveState = remove => {
|
||||
removeInput.value = remove ? '1' : '0';
|
||||
if (pending) pending.hidden = !remove;
|
||||
if (removeButton) {
|
||||
removeButton.disabled = remove;
|
||||
removeButton.setAttribute('aria-pressed', remove ? 'true' : 'false');
|
||||
}
|
||||
if (remove) {
|
||||
releaseObjectUrl();
|
||||
if (imageInput) imageInput.value = '';
|
||||
if (libraryUpload) libraryUpload.value = '';
|
||||
clearLibraryChoices();
|
||||
hidePreview();
|
||||
}
|
||||
};
|
||||
|
||||
removeButton?.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setRemoveState(true);
|
||||
});
|
||||
|
||||
imageInput?.addEventListener('change', () => {
|
||||
if (!imageInput.files || !imageInput.files.length) return;
|
||||
releaseObjectUrl();
|
||||
objectUrl = URL.createObjectURL(imageInput.files[0]);
|
||||
if (libraryUpload) libraryUpload.value = '';
|
||||
clearLibraryChoices();
|
||||
clearRemoveState();
|
||||
showPreview(objectUrl);
|
||||
});
|
||||
|
||||
libraryUpload?.addEventListener('change', () => {
|
||||
if (!libraryUpload.files || !libraryUpload.files.length) return;
|
||||
releaseObjectUrl();
|
||||
objectUrl = URL.createObjectURL(libraryUpload.files[0]);
|
||||
if (imageInput) imageInput.value = '';
|
||||
clearLibraryChoices();
|
||||
clearRemoveState();
|
||||
showPreview(objectUrl);
|
||||
});
|
||||
|
||||
libraryChoices.forEach(choice => {
|
||||
choice.addEventListener('change', () => {
|
||||
if (!choice.checked) return;
|
||||
releaseObjectUrl();
|
||||
if (imageInput) imageInput.value = '';
|
||||
if (libraryUpload) libraryUpload.value = '';
|
||||
clearRemoveState();
|
||||
showPreview(choice.dataset.imageSrc || choice.value);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', releaseObjectUrl, { once: true });
|
||||
editor.dataset.imageEditorReady = '1';
|
||||
editor.dataset.originalImageSrc = originalPreviewSrc;
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-image-editor]').forEach(initImageEditor);
|
||||
})();
|
||||
@@ -302,18 +302,48 @@
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function freezeTableGeometry() {
|
||||
if (table.id === 'asset-table') return;
|
||||
const widths = [...headerRow.cells].map(cell => Math.max(minimumColumnWidth, Math.round(cell.getBoundingClientRect().width || minimumColumnWidth)));
|
||||
const total = widths.reduce((sum, width) => sum + width, 0);
|
||||
widths.forEach((width, columnIndex) => {
|
||||
if (headerRow.cells[columnIndex]?.dataset.rowNumberColumn === '1') return;
|
||||
applyColumnWidth(columnIndex, width);
|
||||
});
|
||||
table.style.tableLayout = 'fixed';
|
||||
table.style.width = `${total}px`;
|
||||
table.style.minWidth = `${total}px`;
|
||||
}
|
||||
|
||||
function synchronizeTableWidth() {
|
||||
if (table.id === 'asset-table') return;
|
||||
const total = [...headerRow.cells].reduce((sum, cell) => sum + Math.round(cell.getBoundingClientRect().width || 0), 0);
|
||||
if (total > 0) {
|
||||
table.style.width = `${total}px`;
|
||||
table.style.minWidth = `${total}px`;
|
||||
}
|
||||
}
|
||||
|
||||
function applyStoredColumnWidths() {
|
||||
let restored = false;
|
||||
[...headerRow.cells].forEach((header, columnIndex) => {
|
||||
if (header.dataset.rowNumberColumn === '1') return;
|
||||
const stored = readStoredColumnWidth(header, columnIndex);
|
||||
if (stored !== null) applyColumnWidth(columnIndex, stored);
|
||||
if (stored !== null) {
|
||||
applyColumnWidth(columnIndex, stored);
|
||||
restored = true;
|
||||
}
|
||||
});
|
||||
if (restored && table.id !== 'asset-table') {
|
||||
table.style.tableLayout = 'fixed';
|
||||
requestAnimationFrame(synchronizeTableWidth);
|
||||
}
|
||||
}
|
||||
|
||||
function installColumnResizers() {
|
||||
[...headerRow.cells].forEach((header, columnIndex) => {
|
||||
if (header.dataset.rowNumberColumn === '1') return;
|
||||
if (header.dataset.noResize === '1') return;
|
||||
if (header.dataset.noResize === '1' || header.dataset.rowNumberColumn === '1') return;
|
||||
if (header.querySelector(':scope > .table-column-resizer')) return;
|
||||
|
||||
header.classList.add('table-resizable-column-header');
|
||||
@@ -334,34 +364,72 @@
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const startX = event.clientX;
|
||||
const measured = header.getBoundingClientRect().width;
|
||||
const startWidth = measured > 0 ? measured : minimumColumnWidth;
|
||||
handle.setPointerCapture?.(event.pointerId);
|
||||
freezeTableGeometry();
|
||||
|
||||
const headers = [...headerRow.cells];
|
||||
const rightmostIndex = headers.length - 1;
|
||||
const startWidth = header.getBoundingClientRect().width || minimumColumnWidth;
|
||||
const rightmostHeader = headers[rightmostIndex];
|
||||
const rightmostStartWidth = rightmostHeader?.getBoundingClientRect().width || minimumColumnWidth;
|
||||
const keepTableWidth = columnIndex !== rightmostIndex;
|
||||
|
||||
document.documentElement.classList.add('table-column-resizing');
|
||||
|
||||
const move = moveEvent => {
|
||||
applyColumnWidth(columnIndex, startWidth + moveEvent.clientX - startX);
|
||||
const resizePair = delta => {
|
||||
if (!keepTableWidth) {
|
||||
applyColumnWidth(columnIndex, startWidth + delta);
|
||||
synchronizeTableWidth();
|
||||
return;
|
||||
}
|
||||
|
||||
// All columns between the dragged column and the far-right column
|
||||
// stay unchanged. The far-right column alone absorbs the delta.
|
||||
const minDelta = minimumColumnWidth - startWidth;
|
||||
const maxDelta = rightmostStartWidth - minimumColumnWidth;
|
||||
const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, delta));
|
||||
applyColumnWidth(columnIndex, startWidth + effectiveDelta);
|
||||
applyColumnWidth(rightmostIndex, rightmostStartWidth - effectiveDelta);
|
||||
};
|
||||
const finish = finishEvent => {
|
||||
const current = header.getBoundingClientRect().width || startWidth;
|
||||
saveColumnWidth(header, columnIndex, current);
|
||||
handle.releasePointerCapture?.(finishEvent.pointerId);
|
||||
|
||||
const move = moveEvent => resizePair(moveEvent.clientX - startX);
|
||||
const finish = () => {
|
||||
saveColumnWidth(header, columnIndex, header.getBoundingClientRect().width || startWidth);
|
||||
if (keepTableWidth && rightmostHeader) {
|
||||
saveColumnWidth(rightmostHeader, rightmostIndex, rightmostHeader.getBoundingClientRect().width || rightmostStartWidth);
|
||||
} else {
|
||||
synchronizeTableWidth();
|
||||
}
|
||||
document.documentElement.classList.remove('table-column-resizing');
|
||||
handle.removeEventListener('pointermove', move);
|
||||
handle.removeEventListener('pointerup', finish);
|
||||
handle.removeEventListener('pointercancel', finish);
|
||||
window.removeEventListener('pointermove', move, true);
|
||||
window.removeEventListener('pointerup', finish, true);
|
||||
window.removeEventListener('pointercancel', finish, true);
|
||||
};
|
||||
handle.addEventListener('pointermove', move);
|
||||
handle.addEventListener('pointerup', finish);
|
||||
handle.addEventListener('pointercancel', finish);
|
||||
window.addEventListener('pointermove', move, true);
|
||||
window.addEventListener('pointerup', finish, true);
|
||||
window.addEventListener('pointercancel', finish, true);
|
||||
});
|
||||
|
||||
handle.addEventListener('keydown', event => {
|
||||
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
freezeTableGeometry();
|
||||
const headers = [...headerRow.cells];
|
||||
const rightmostIndex = headers.length - 1;
|
||||
const step = event.key === 'ArrowRight' ? 12 : -12;
|
||||
const current = header.getBoundingClientRect().width || minimumColumnWidth;
|
||||
saveColumnWidth(header, columnIndex, current + (event.key === 'ArrowRight' ? 12 : -12));
|
||||
if (columnIndex === rightmostIndex) {
|
||||
saveColumnWidth(header, columnIndex, current + step);
|
||||
synchronizeTableWidth();
|
||||
return;
|
||||
}
|
||||
const rightmostHeader = headers[rightmostIndex];
|
||||
const rightmostWidth = rightmostHeader?.getBoundingClientRect().width || minimumColumnWidth;
|
||||
const minDelta = minimumColumnWidth - current;
|
||||
const maxDelta = rightmostWidth - minimumColumnWidth;
|
||||
const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, step));
|
||||
saveColumnWidth(header, columnIndex, current + effectiveDelta);
|
||||
if (rightmostHeader) saveColumnWidth(rightmostHeader, rightmostIndex, rightmostWidth - effectiveDelta);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -796,8 +864,10 @@
|
||||
});
|
||||
|
||||
thead.appendChild(filterRow);
|
||||
installColumnResizers();
|
||||
applyStoredColumnWidths();
|
||||
if (isAssetTable) {
|
||||
installColumnResizers();
|
||||
applyStoredColumnWidths();
|
||||
}
|
||||
|
||||
// Editable cells are always read live, so changed values immediately
|
||||
// participate in filtering without a stale cache.
|
||||
@@ -840,7 +910,147 @@
|
||||
}));
|
||||
}
|
||||
|
||||
function initUniversalTableResize(table) {
|
||||
if (!table || table.id === 'asset-table' || table.dataset.columnResizeReady === '1') return;
|
||||
const thead = table.tHead;
|
||||
const tbody = table.tBodies?.[0];
|
||||
if (!thead || !tbody || !thead.rows.length) return;
|
||||
|
||||
const headerRow = thead.rows[0];
|
||||
const storagePart = table.dataset.storageKey || table.id || `${window.location.pathname}:table-${[...document.querySelectorAll('main table')].indexOf(table)}`;
|
||||
const storagePrefix = `assetmanager:table:${storagePart}:column-width:`;
|
||||
const minWidth = 56;
|
||||
const maxWidth = 1200;
|
||||
const normalize = width => Math.min(maxWidth, Math.max(minWidth, Math.round(width)));
|
||||
|
||||
const columnKey = (header, index) => header.dataset.field || `index-${index}`;
|
||||
const applyWidth = (index, width) => {
|
||||
const normalized = normalize(width);
|
||||
[...thead.rows, ...tbody.rows].forEach(row => {
|
||||
const cell = row.cells?.[index];
|
||||
if (!cell) return;
|
||||
cell.style.width = `${normalized}px`;
|
||||
cell.style.minWidth = `${normalized}px`;
|
||||
cell.style.maxWidth = `${normalized}px`;
|
||||
});
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const freezeGeometry = () => {
|
||||
const widths = [...headerRow.cells].map(cell => normalize(cell.getBoundingClientRect().width || minWidth));
|
||||
const total = widths.reduce((sum, width) => sum + width, 0);
|
||||
widths.forEach((width, index) => applyWidth(index, width));
|
||||
table.style.tableLayout = 'fixed';
|
||||
table.style.width = `${total}px`;
|
||||
table.style.minWidth = `${total}px`;
|
||||
};
|
||||
|
||||
const syncTableWidth = () => {
|
||||
const total = [...headerRow.cells].reduce((sum, cell) => sum + Math.round(cell.getBoundingClientRect().width || 0), 0);
|
||||
if (total > 0) {
|
||||
table.style.width = `${total}px`;
|
||||
table.style.minWidth = `${total}px`;
|
||||
}
|
||||
};
|
||||
|
||||
[...headerRow.cells].forEach((header, index) => {
|
||||
if (header.dataset.noResize === '1' || header.dataset.rowNumberColumn === '1') return;
|
||||
const key = `${storagePrefix}${columnKey(header, index)}`;
|
||||
try {
|
||||
const stored = Number.parseInt(localStorage.getItem(key) || '', 10);
|
||||
if (Number.isFinite(stored)) applyWidth(index, stored);
|
||||
} catch (_) {}
|
||||
|
||||
if (header.querySelector(':scope > .table-column-resizer')) return;
|
||||
header.classList.add('table-resizable-column-header');
|
||||
const handle = document.createElement('span');
|
||||
handle.className = 'table-column-resizer';
|
||||
handle.setAttribute('role', 'separator');
|
||||
handle.setAttribute('aria-orientation', 'vertical');
|
||||
handle.tabIndex = 0;
|
||||
handle.title = document.body.dataset.columnResizeLabel || 'Drag to change the column width';
|
||||
header.appendChild(handle);
|
||||
|
||||
const save = width => {
|
||||
const normalized = applyWidth(index, width);
|
||||
try { localStorage.setItem(key, String(normalized)); } catch (_) {}
|
||||
};
|
||||
handle.addEventListener('click', event => { event.preventDefault(); event.stopPropagation(); });
|
||||
handle.addEventListener('pointerdown', event => {
|
||||
event.preventDefault(); event.stopPropagation();
|
||||
const startX = event.clientX;
|
||||
freezeGeometry();
|
||||
const headers = [...headerRow.cells];
|
||||
const rightmostIndex = headers.length - 1;
|
||||
const startWidth = header.getBoundingClientRect().width || minWidth;
|
||||
const rightmostHeader = headers[rightmostIndex];
|
||||
const rightmostStartWidth = rightmostHeader?.getBoundingClientRect().width || minWidth;
|
||||
const keepTableWidth = index !== rightmostIndex;
|
||||
document.documentElement.classList.add('table-column-resizing');
|
||||
|
||||
const resizePair = delta => {
|
||||
if (!keepTableWidth) {
|
||||
applyWidth(index, startWidth + delta);
|
||||
syncTableWidth();
|
||||
return;
|
||||
}
|
||||
const minDelta = minWidth - startWidth;
|
||||
const maxDelta = rightmostStartWidth - minWidth;
|
||||
const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, delta));
|
||||
applyWidth(index, startWidth + effectiveDelta);
|
||||
applyWidth(rightmostIndex, rightmostStartWidth - effectiveDelta);
|
||||
};
|
||||
|
||||
const move = e => resizePair(e.clientX - startX);
|
||||
const finish = () => {
|
||||
save(header.getBoundingClientRect().width || startWidth);
|
||||
if (keepTableWidth && rightmostHeader) {
|
||||
const rightKey = `${storagePrefix}${columnKey(rightmostHeader, rightmostIndex)}`;
|
||||
const rightWidth = applyWidth(rightmostIndex, rightmostHeader.getBoundingClientRect().width || rightmostStartWidth);
|
||||
try { localStorage.setItem(rightKey, String(rightWidth)); } catch (_) {}
|
||||
} else {
|
||||
syncTableWidth();
|
||||
}
|
||||
document.documentElement.classList.remove('table-column-resizing');
|
||||
window.removeEventListener('pointermove', move, true);
|
||||
window.removeEventListener('pointerup', finish, true);
|
||||
window.removeEventListener('pointercancel', finish, true);
|
||||
};
|
||||
window.addEventListener('pointermove', move, true);
|
||||
window.addEventListener('pointerup', finish, true);
|
||||
window.addEventListener('pointercancel', finish, true);
|
||||
});
|
||||
handle.addEventListener('keydown', event => {
|
||||
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
|
||||
event.preventDefault(); event.stopPropagation();
|
||||
freezeGeometry();
|
||||
const headers = [...headerRow.cells];
|
||||
const rightmostIndex = headers.length - 1;
|
||||
const step = event.key === 'ArrowRight' ? 12 : -12;
|
||||
const current = header.getBoundingClientRect().width || minWidth;
|
||||
if (index === rightmostIndex) {
|
||||
save(current + step);
|
||||
syncTableWidth();
|
||||
return;
|
||||
}
|
||||
const rightmostHeader = headers[rightmostIndex];
|
||||
const rightmostWidth = rightmostHeader?.getBoundingClientRect().width || minWidth;
|
||||
const minDelta = minWidth - current;
|
||||
const maxDelta = rightmostWidth - minWidth;
|
||||
const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, step));
|
||||
save(current + effectiveDelta);
|
||||
if (rightmostHeader) {
|
||||
const rightKey = `${storagePrefix}${columnKey(rightmostHeader, rightmostIndex)}`;
|
||||
const rightWidth = applyWidth(rightmostIndex, rightmostWidth - effectiveDelta);
|
||||
try { localStorage.setItem(rightKey, String(rightWidth)); } catch (_) {}
|
||||
}
|
||||
});
|
||||
});
|
||||
table.dataset.columnResizeReady = '1';
|
||||
}
|
||||
|
||||
document.querySelectorAll('table.data-table').forEach(initTable);
|
||||
document.querySelectorAll('main table:not(#asset-table):not([data-column-resize="off"])').forEach(initUniversalTableResize);
|
||||
|
||||
window.addEventListener('asset-table-content-reloaded', event => {
|
||||
const table = event.detail?.table;
|
||||
|
||||