Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- TTF font support on ESP32-S3 devices. Whole-point sizes from 8pt to 22pt will be automatically available.
- The OPDS browser shows how many books a category holds, in parentheses next to its arrow, when the catalog provides a count (a `thr:count` link attribute or a "12713 books" summary). Folder titles that start with a 📁 emoji show as "/name" instead, since the device fonts have no folder emoji.
- On devices with PSRAM (Sticky, X4 Pro), the OPDS browser downloads the next page of a catalog in the background while you browse, and keeps pages you have visited in memory, so Next page, Previous page and Back open without waiting on the server.
- Sort the web file manager by name, size or modified date by clicking the column header. Click again to reverse the order. Folders always stay at the top, and the chosen order is kept while you browse other folders in the same tab.
- The web file manager's image preview has previous and next buttons, and the left and right arrow keys, to step through the images in the current folder. Other file types are skipped, and stepping wraps around at either end. The preview also shows the image's position in the folder, its pixel dimensions, its file size and, when the file list has one, its modified date.
- The Settings > System footer shows the firmware's branch (the batch number, such as `b11`, for combined test builds) and commit, with `*` when built from uncommitted changes, under the version. The System list stops above the footer instead of running under it. The web status API reports the full branch, build number (the commit count unless the build sets `CROSSINK_BUILD_NUMBER`; left out for shallow checkouts) and UTC build time.
- In-reader menu for X3/X4/X4 Classic have been updated to a modified version of the in-reader menu for touch devices
Expand Down
2 changes: 2 additions & 0 deletions web/pages/files.css
Original file line number Diff line number Diff line change
Expand Up @@ -1516,3 +1516,5 @@ h2 {
.log-summary-table .increased {
color: #f39c12;
}

th.sortable { cursor: pointer; user-select: none; }
56 changes: 46 additions & 10 deletions web/pages/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,23 +201,56 @@ async function hydrate() {
document.getElementById("folder-summary").innerHTML =
`${folderCount} ${folderLabel}, ${fileCount} ${fileLabel}, ${formatFileSize(totalSize)}`;

listedFiles = files;
renderFileTable();
}

// Column sort; null key keeps the default folders, EPUBs, then name order
let listedFiles = [];
let fileSort = { key: null, dir: 1 };
try {
fileSort = JSON.parse(sessionStorage.getItem("fileSort")) || fileSort;
} catch (e) {}

function compareFiles(a, b) {
// Folders always first; they have no size or date, so they sort by name
if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;
const key = fileSort.key;
if (a.isDirectory && key !== "name") return a.name.localeCompare(b.name);
if (!key) {
if (a.isEpub !== b.isEpub) return a.isEpub ? -1 : 1;
return a.name.localeCompare(b.name);
}
const diff = key === "name" ? a.name.localeCompare(b.name) : (a[key] || 0) - (b[key] || 0);
return (diff || a.name.localeCompare(b.name)) * fileSort.dir;
}

function sortHeader(key, label, cls) {
const arrow = fileSort.key === key ? (fileSort.dir > 0 ? " ▲" : " ▼") : "";
return `<th class="sortable ${cls}" data-sort="${key}">${label}${arrow}</th>`;
}

function setFileSort(key) {
fileSort = { key, dir: fileSort.key === key ? -fileSort.dir : 1 };
try {
sessionStorage.setItem("fileSort", JSON.stringify(fileSort));
} catch (e) {}
renderFileTable();
}

function renderFileTable() {
const files = listedFiles;
const fileTable = document.getElementById("file-table");
if (files.length === 0) {
fileTable.innerHTML = '<div class="no-files">This folder is empty</div>';
} else {
let fileTableContent = '<table class="file-table">';

// Add select-all checkbox column
fileTableContent +=
'<tr><th style="width:40px"><input type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll(this)"></th><th>Name</th><th>Type</th><th>Size</th><th class="modified-col">Modified</th><th class="actions-col">Actions</th></tr>';

const sortedFiles = files.sort((a, b) => {
// Directories first, then epub files, then other files, alphabetically within each group
if (a.isDirectory && !b.isDirectory) return -1;
if (!a.isDirectory && b.isDirectory) return 1;
if (a.isEpub && !b.isEpub) return -1;
if (!a.isEpub && b.isEpub) return 1;
return a.name.localeCompare(b.name);
});
`<tr><th style="width:40px"><input type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll(this)"></th>${sortHeader("name", "Name", "")}<th>Type</th>${sortHeader("size", "Size", "")}${sortHeader("mtime", "Modified", "modified-col")}<th class="actions-col">Actions</th></tr>`;

const sortedFiles = files.sort(compareFiles);

sortedFiles.forEach((file) => {
if (file.isDirectory) {
Expand Down Expand Up @@ -266,6 +299,9 @@ async function hydrate() {

fileTableContent += "</table>";
fileTable.innerHTML = fileTableContent;
fileTable.querySelectorAll("th[data-sort]").forEach((th) => {
th.addEventListener("click", () => setFileSort(th.dataset.sort));
});
fileTable.querySelectorAll(".file-action-btn").forEach((button) => {
button.addEventListener("click", handleFileActionClick);
});
Expand Down
Loading