feat: SQLite preview read straight from the file, and sorting of the file table by column, kept per folder
This commit is contained in:
1 parent
1a90af7800
commit
c911143eb2
2 files changed
+191
-10
No files matched your search
@@ -45,6 +45,10 @@
|
||||
.ic { display:inline-block; width:18px; text-align:center; margin-right:6px; }
|
||||
.sz { color:var(--muted); font-variant-numeric:tabular-nums; }
|
||||
.act { display:flex; gap:6px; justify-content:flex-end; }
|
||||
/* Sortable headers: a click sorts by the column, a second one turns it round. */
|
||||
table.fm th.srt { cursor:pointer; user-select:none; white-space:nowrap; }
|
||||
table.fm th.srt:hover { color:var(--text); }
|
||||
table.fm th.srt i { font-style:normal; display:inline-block; width:12px; margin-left:4px; color:var(--accent); font-size:10px; }
|
||||
table.fm .c-sz { width:120px; }
|
||||
table.fm .c-mt { width:160px; }
|
||||
table.fm .c-act { width:160px; }
|
||||
@@ -208,8 +212,8 @@
|
||||
<table class="fm">
|
||||
<thead><tr>
|
||||
<th style="width:28px"><input type="checkbox" id="all"></th>
|
||||
<th>Имя</th><th class="sz c-sz">Размер</th>
|
||||
<th class="sz c-mt">Изменён</th><th class="c-act"></th>
|
||||
<th class="srt" data-sort="name">Имя<i></i></th><th class="sz c-sz srt" data-sort="size">Размер<i></i></th>
|
||||
<th class="sz c-mt srt" data-sort="mtime">Изменён<i></i></th><th class="c-act"></th>
|
||||
</tr></thead>
|
||||
<tbody id="list"></tbody>
|
||||
</table>
|
||||
@@ -326,10 +330,49 @@
|
||||
$('path').value = cwd; render();
|
||||
}
|
||||
|
||||
// Sorting: by name, size or time, folders always ahead of files. Kept in
|
||||
// this browser for each folder of each machine.
|
||||
const SORT_DEF = {by: 'name', dir: 1};
|
||||
const sortKey = () => 'lpra.sort:' + agentID + ':' + cwd;
|
||||
function getSort() {
|
||||
try { const v = JSON.parse(localStorage.getItem(sortKey())); if (v && ['name', 'size', 'mtime'].includes(v.by)) return v; } catch (e) {}
|
||||
return SORT_DEF;
|
||||
}
|
||||
function setSort(v) {
|
||||
try {
|
||||
if (v.by === SORT_DEF.by && v.dir === SORT_DEF.dir) localStorage.removeItem(sortKey());
|
||||
else localStorage.setItem(sortKey(), JSON.stringify(v));
|
||||
} catch (e) {}
|
||||
}
|
||||
const byName = (a, b) => a.name.localeCompare(b.name, undefined, {numeric: true, sensitivity: 'base'}) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
||||
function sorted() {
|
||||
const s = getSort();
|
||||
const key = s.by === 'size' ? e => e.size || 0 : s.by === 'mtime' ? e => e.mtime || 0 : null;
|
||||
return entries.slice().sort((a, b) => {
|
||||
if (a.is_dir !== b.is_dir) return a.is_dir ? -1 : 1;
|
||||
const d = key ? key(a) - key(b) : 0;
|
||||
return (d || byName(a, b)) * s.dir;
|
||||
});
|
||||
}
|
||||
function showSort() {
|
||||
const s = getSort();
|
||||
document.querySelectorAll('table.fm th.srt').forEach(th => {
|
||||
th.querySelector('i').textContent = th.dataset.sort === s.by ? (s.dir > 0 ? '▲' : '▼') : '';
|
||||
});
|
||||
}
|
||||
document.querySelectorAll('table.fm th.srt').forEach(th => th.onclick = () => {
|
||||
const s = getSort(), by = th.dataset.sort;
|
||||
// A new column starts the way it is most often wanted: names A to Z,
|
||||
// sizes and times biggest and newest first.
|
||||
setSort(s.by === by ? {by, dir: -s.dir} : {by, dir: by === 'name' ? 1 : -1});
|
||||
render();
|
||||
});
|
||||
|
||||
function render() {
|
||||
showSort();
|
||||
const tb = $('list'); tb.innerHTML = '';
|
||||
updateSel();
|
||||
for (const e of entries) {
|
||||
for (const e of sorted()) {
|
||||
const tr = document.createElement('tr');
|
||||
const isZip = !e.is_dir && /\.zip$/i.test(e.name);
|
||||
const dlHref = '/api/panel/agents/' + encodeURIComponent(agentID) + '/fs/download?path=' + encodeURIComponent(join(cwd, e.name));
|
||||
@@ -381,7 +424,7 @@
|
||||
|
||||
const IMG = /\.(png|jpe?g|gif|webp|bmp|ico|avif|svg)$/i, VID = /\.(mp4|m4v|webm|ogv|mov)$/i, AUD = /\.(mp3|wav|ogg|oga|m4a|aac|flac|opus)$/i;
|
||||
const HEX_CAP = 64 * 1024 * 1024; // what the hex editor takes in memory
|
||||
const DOC = /\.(pdf|docx?|odt|xlsx|ods|csv|tsv|html?)$/i;
|
||||
const DOC = /\.(pdf|docx?|odt|xlsx|ods|csv|tsv|html?|db|sqlite3?|db3)$/i;
|
||||
const DOC_CAP = 32 * 1024 * 1024; // the biggest document drawn here
|
||||
|
||||
// Highlighting by the file's name, then its extension, then its #! line.
|
||||
@@ -564,6 +607,7 @@
|
||||
: ext === 'xlsx' ? await DocView.xlsx(buf)
|
||||
: ext === 'ods' ? await DocView.ods(buf)
|
||||
: ext === 'doc' ? DocView.doc(buf)
|
||||
: /^(db|sqlite3?|db3)$/.test(ext) ? DocView.sqlite(buf)
|
||||
: DocView.csv(new TextDecoder().decode(buf), ext === 'tsv');
|
||||
} catch (err) {
|
||||
html = '<p class="dv-note">не удалось показать: ' + esc(err.message) + '</p>';
|
||||
|
||||
Reference in new issue
Block a user