215 lines
6.5 KiB
JavaScript
215 lines
6.5 KiB
JavaScript
/**
|
|
* Sort Module
|
|
* Handles multi-column sorting for the spreadsheet
|
|
*/
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Sort state: array of {column, direction}
|
|
let sortState = [];
|
|
|
|
/**
|
|
* Initialize sorting
|
|
*/
|
|
function init() {
|
|
const table = window.app.dom.spreadsheet;
|
|
const headers = table.querySelectorAll('thead th');
|
|
|
|
headers.forEach((th, index) => {
|
|
// Skip row number column
|
|
if (th.classList.contains('col-row-num')) return;
|
|
|
|
// Skip if already initialized
|
|
if (th.querySelector('.sort-indicator')) return;
|
|
|
|
// Make header clickable
|
|
th.style.cursor = 'pointer';
|
|
th.style.userSelect = 'none';
|
|
|
|
// Add sort indicator container
|
|
const sortIndicator = document.createElement('span');
|
|
sortIndicator.className = 'sort-indicator';
|
|
|
|
// Insert after the text node (before any br or filter)
|
|
const firstChild = th.firstChild;
|
|
if (firstChild && firstChild.nodeType === Node.TEXT_NODE) {
|
|
// Insert after text node
|
|
firstChild.after(sortIndicator);
|
|
} else {
|
|
// Prepend to header
|
|
th.insertBefore(sortIndicator, firstChild);
|
|
}
|
|
|
|
// Click to sort (only add once)
|
|
const handleClick = (e) => {
|
|
// Don't sort if clicking on resizer or filter input
|
|
if (e.target.classList.contains('column-resizer')) return;
|
|
if (e.target.classList.contains('column-filter')) return;
|
|
|
|
const columnName = th.className.replace('col-', '');
|
|
handleSort(columnName, e.ctrlKey || e.metaKey);
|
|
};
|
|
|
|
th.addEventListener('click', handleClick);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Apply default sort (called after initial render)
|
|
*/
|
|
function applyDefaultSort() {
|
|
const files = window.app.modules.store.getDisplayFiles();
|
|
if (files.length > 0) {
|
|
sortState = [{ column: 'original', direction: 'asc' }];
|
|
applySorts();
|
|
updateSortIndicators();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle sort click
|
|
*/
|
|
function handleSort(columnName, multiSort) {
|
|
// Use store to toggle sort
|
|
window.app.modules.store.toggleSort(columnName, multiSort);
|
|
}
|
|
|
|
/**
|
|
* Apply sort to files array (pure function - doesn't mutate)
|
|
*/
|
|
function applySortToFiles(files) {
|
|
if (sortState.length === 0) {
|
|
return files;
|
|
}
|
|
|
|
return [...files].sort((a, b) => {
|
|
for (const sort of sortState) {
|
|
const result = compareValues(a, b, sort.column, sort.direction);
|
|
if (result !== 0) return result;
|
|
}
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Apply all sorts (legacy - triggers render)
|
|
*/
|
|
function applySorts() {
|
|
window.app.modules.spreadsheet.render();
|
|
}
|
|
|
|
/**
|
|
* Apply default sort
|
|
*/
|
|
function applyDefaultSort() {
|
|
const files = window.app.modules.store.getDisplayFiles();
|
|
if (files.length > 0 && sortState.length === 0) {
|
|
sortState = [{ column: 'original', direction: 'asc' }];
|
|
window.app.modules.spreadsheet.render();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current sort state
|
|
*/
|
|
function getSortState() {
|
|
return sortState;
|
|
}
|
|
|
|
/**
|
|
* Update sort indicators in headers
|
|
*/
|
|
function updateIndicators() {
|
|
const table = window.app.dom.spreadsheet;
|
|
const headers = table.querySelectorAll('thead th');
|
|
|
|
headers.forEach(th => {
|
|
const indicator = th.querySelector('.sort-indicator');
|
|
if (!indicator) return;
|
|
|
|
const columnName = th.className.replace('col-', '');
|
|
const sortIndex = sortState.findIndex(s => s.column === columnName);
|
|
|
|
if (sortIndex >= 0) {
|
|
const sort = sortState[sortIndex];
|
|
const arrow = sort.direction === 'asc' ? '▲' : '▼';
|
|
const priority = sortState.length > 1 ? (sortIndex + 1) : '';
|
|
indicator.textContent = ` ${arrow}${priority}`;
|
|
indicator.style.display = 'inline';
|
|
} else {
|
|
indicator.textContent = '';
|
|
indicator.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Compare two values for sorting
|
|
*/
|
|
function compareValues(a, b, columnName, direction) {
|
|
let aVal, bVal;
|
|
|
|
// Get values based on column
|
|
switch (columnName) {
|
|
case 'original':
|
|
aVal = a.originalFilename || '';
|
|
bVal = b.originalFilename || '';
|
|
break;
|
|
case 'extension':
|
|
aVal = a.extension || '';
|
|
bVal = b.extension || '';
|
|
break;
|
|
case 'new':
|
|
case 'newFilename':
|
|
aVal = a.manualFilename || window.app.modules.spreadsheet.computeNewFilename(a, 0);
|
|
bVal = b.manualFilename || window.app.modules.spreadsheet.computeNewFilename(b, 0);
|
|
break;
|
|
case 'trackingNumber':
|
|
aVal = a.trackingNumber || '';
|
|
bVal = b.trackingNumber || '';
|
|
break;
|
|
case 'revision':
|
|
aVal = a.revision || '';
|
|
bVal = b.revision || '';
|
|
break;
|
|
case 'status':
|
|
aVal = a.status || '';
|
|
bVal = b.status || '';
|
|
break;
|
|
case 'title':
|
|
aVal = a.title || '';
|
|
bVal = b.title || '';
|
|
break;
|
|
case 'sha256':
|
|
aVal = a.sha256 || '';
|
|
bVal = b.sha256 || '';
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
// Natural sort for strings (handles numbers within strings)
|
|
const comparison = aVal.localeCompare(bVal, undefined, { numeric: true, sensitivity: 'base' });
|
|
|
|
return direction === 'asc' ? comparison : -comparison;
|
|
}
|
|
|
|
/**
|
|
* Clear all sorts
|
|
*/
|
|
function clearSorts() {
|
|
sortState = [];
|
|
applySorts();
|
|
}
|
|
|
|
// Export module
|
|
window.app.modules.sort = {
|
|
init,
|
|
applyDefaultSort,
|
|
applySorts,
|
|
applySortToFiles,
|
|
updateIndicators,
|
|
getSortState,
|
|
clearSorts
|
|
};
|
|
})();
|