208 lines
8.2 KiB
TypeScript
208 lines
8.2 KiB
TypeScript
// Table sorting and styling utilities
|
|
|
|
// Add table styling helper
|
|
export function applyTableStyles(table: HTMLTableElement) {
|
|
table.style.cssText = `
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 12px;
|
|
background: var(--comfy-input-bg);
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
margin: 12px 0;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
`;
|
|
|
|
// Style header cells
|
|
table.querySelectorAll('thead th').forEach(th => {
|
|
const header = th as HTMLTableHeaderCellElement;
|
|
header.style.cssText = `
|
|
padding: 12px 8px;
|
|
text-align: left;
|
|
border-bottom: 2px solid var(--border-color);
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
color: var(--descrip-text);
|
|
background: rgba(0, 0, 0, 0.2);
|
|
`;
|
|
});
|
|
|
|
// Style body cells
|
|
table.querySelectorAll('tbody td').forEach(td => {
|
|
const cell = td as HTMLTableCellElement;
|
|
cell.style.cssText = `
|
|
padding: 8px;
|
|
border-bottom: 1px solid var(--border-color);
|
|
transition: background-color 0.2s;
|
|
`;
|
|
});
|
|
|
|
// Add hover effect to rows
|
|
table.querySelectorAll('tbody tr').forEach(tr => {
|
|
const row = tr as HTMLTableRowElement;
|
|
row.style.transition = 'background-color 0.2s';
|
|
|
|
row.addEventListener('mouseenter', () => {
|
|
row.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
|
|
});
|
|
|
|
row.addEventListener('mouseleave', () => {
|
|
row.style.backgroundColor = '';
|
|
});
|
|
});
|
|
|
|
// Add alternating row colors
|
|
table.querySelectorAll('tbody tr:nth-child(even)').forEach(tr => {
|
|
const row = tr as HTMLTableRowElement;
|
|
row.style.backgroundColor = 'rgba(0, 0, 0, 0.1)';
|
|
});
|
|
}
|
|
|
|
// Add utility functions for sorting
|
|
export function makeSortable(table: HTMLTableElement) {
|
|
const headers = table.querySelectorAll('th');
|
|
const tbody = table.querySelector('tbody') as HTMLTableSectionElement;
|
|
|
|
headers.forEach((header, index) => {
|
|
const th = header as HTMLTableHeaderCellElement;
|
|
if (th.classList.contains('no-sort')) return;
|
|
|
|
th.style.cssText = `
|
|
cursor: pointer;
|
|
user-select: none;
|
|
position: relative;
|
|
padding: 8px 24px 8px 8px;
|
|
text-align: left;
|
|
border-bottom: 2px solid var(--border-color);
|
|
transition: background-color 0.2s;
|
|
font-weight: 600;
|
|
color: var(--descrip-text);
|
|
`;
|
|
|
|
// Add sort indicator
|
|
const indicator = document.createElement('span');
|
|
indicator.className = 'sort-indicator';
|
|
indicator.style.cssText = `
|
|
position: absolute;
|
|
right: 8px;
|
|
opacity: 0.5;
|
|
`;
|
|
indicator.textContent = '⇅';
|
|
th.appendChild(indicator);
|
|
|
|
th.addEventListener('mouseenter', () => {
|
|
th.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
|
|
});
|
|
|
|
th.addEventListener('mouseleave', () => {
|
|
if (!th.classList.contains('sort-asc') && !th.classList.contains('sort-desc')) {
|
|
th.style.backgroundColor = '';
|
|
}
|
|
});
|
|
|
|
th.addEventListener('click', () => {
|
|
if (!tbody) return;
|
|
|
|
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
const isAscending = th.classList.contains('sort-asc');
|
|
|
|
// Clear previous sort indicators
|
|
headers.forEach(h => {
|
|
const header = h as HTMLTableHeaderCellElement;
|
|
header.style.backgroundColor = '';
|
|
header.classList.remove('sort-asc', 'sort-desc');
|
|
const ind = header.querySelector('.sort-indicator');
|
|
if (ind) ind.textContent = '⇅';
|
|
});
|
|
|
|
// Sort rows
|
|
rows.sort((a, b) => {
|
|
const aCell = a.children[index] as HTMLTableCellElement;
|
|
const bCell = b.children[index] as HTMLTableCellElement;
|
|
|
|
if (!aCell || !bCell) return 0;
|
|
|
|
let aValue: string | number;
|
|
let bValue: string | number;
|
|
|
|
// Handle different column types
|
|
if (th.classList.contains('sort-numeric')) {
|
|
// Extract numeric value, handling units like GB, MB, s, ms
|
|
const aText = aCell.textContent?.trim() || '0';
|
|
const bText = bCell.textContent?.trim() || '0';
|
|
|
|
// Convert to base unit (bytes for memory, ms for time)
|
|
const convertToBaseUnit = (text: string): number => {
|
|
const num = parseFloat(text.replace(/[^\d.-]/g, '')) || 0;
|
|
if (text.includes('GB')) return num * 1024 * 1024 * 1024;
|
|
if (text.includes('MB')) return num * 1024 * 1024;
|
|
if (text.includes('KB')) return num * 1024;
|
|
if (text.includes('s') && !text.includes('ms')) return num * 1000;
|
|
return num;
|
|
};
|
|
|
|
aValue = convertToBaseUnit(aText);
|
|
bValue = convertToBaseUnit(bText);
|
|
} else if (th.classList.contains('sort-time')) {
|
|
// Special handling for time values
|
|
const parseTime = (cell: HTMLTableCellElement): number => {
|
|
// First try to get timestamp from data attribute
|
|
const timestamp = cell.getAttribute('data-timestamp');
|
|
if (timestamp) {
|
|
return parseInt(timestamp);
|
|
}
|
|
|
|
// Fallback to parsing time string
|
|
const timeStr = cell.textContent?.trim() || '';
|
|
const timeParts = timeStr.split(':');
|
|
if (timeParts.length === 3) {
|
|
const [hours, minutes, seconds] = timeParts.map(Number);
|
|
return hours * 3600000 + minutes * 60000 + seconds * 1000;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
aValue = parseTime(aCell);
|
|
bValue = parseTime(bCell);
|
|
} else {
|
|
// Text sorting - handle nested content
|
|
const getMainContent = (cell: HTMLTableCellElement): string => {
|
|
// For cells with nested divs (like Node Type column), get only the first div's content
|
|
const firstDiv = cell.querySelector('div');
|
|
if (firstDiv) {
|
|
return firstDiv.textContent || '';
|
|
}
|
|
return cell.textContent || '';
|
|
};
|
|
|
|
aValue = getMainContent(aCell).trim().toLowerCase();
|
|
bValue = getMainContent(bCell).trim().toLowerCase();
|
|
}
|
|
|
|
if (typeof aValue === 'string' && typeof bValue === 'string') {
|
|
return isAscending ?
|
|
aValue.localeCompare(bValue) :
|
|
bValue.localeCompare(aValue);
|
|
} else {
|
|
const numA = Number(aValue);
|
|
const numB = Number(bValue);
|
|
return isAscending ? numA - numB : numB - numA;
|
|
}
|
|
});
|
|
|
|
// Update sort indicator
|
|
th.classList.toggle('sort-asc', !isAscending);
|
|
th.classList.toggle('sort-desc', isAscending);
|
|
th.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
|
|
|
|
const indicator = th.querySelector('.sort-indicator') as HTMLSpanElement;
|
|
if (indicator) {
|
|
indicator.textContent = isAscending ? '⇓' : '⇑';
|
|
indicator.style.opacity = '1';
|
|
}
|
|
|
|
// Reorder rows
|
|
rows.forEach(row => tbody.appendChild(row));
|
|
});
|
|
});
|
|
}
|