mirror of
https://github.com/vladmandic/automatic
synced 2026-09-18 16:54:33 +02:00
Update gallery.js
* Collapse by default except for the first separator (I can't fix that initial "flash" of images before the separators) * Fix for files which appear after separator is created and closed not respecting being hidden, so it hides the files for the second time once all files are processed * Session state memory for separators * Dynamic file counters * Filter to only include image/video outputs in the file count (currently counted txt jsons etc.) * Sort/no sort mode indicator * Small quick animations of the arrows "opening and closing" * Hover effects and integration with style so they work with themes * Some spacing fixes * Separator name overflow truncation with ellipsis * Improved search * Automatically collapses separators with 0 hits and open up separators with hits, restores everything back upon clearing the search * File counters automatically updating to show how many files were found in each separator has been removed, left in a stub to see if I can still implement it
This commit is contained in:
committed by
GitHub
parent
fd2dde9154
commit
6a18f2f502
+292
-52
@@ -1,4 +1,12 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
// Known issues
|
||||
// Images flash on the screen before they get processed and separator is properly closed, especially when root/subfolder has large amount of files
|
||||
// Search is a bit wonky, I tried to get the separators to hide if 0 hits in seperator are found, but no luck so
|
||||
// Sorting huge amount of images is slow, might look at optimising, I don't think it's a regression.
|
||||
|
||||
//TODO
|
||||
// Setting to enable or disable separator state persistence
|
||||
|
||||
|
||||
let ws;
|
||||
let url;
|
||||
@@ -6,7 +14,9 @@ let currentImage;
|
||||
let pruneImagesTimer;
|
||||
let outstanding = 0;
|
||||
let lastSort = 0;
|
||||
let lastSortName = 'none';
|
||||
let lastSortName = 'None';
|
||||
// Store separator states for the session
|
||||
const separatorStates = new Map();
|
||||
const el = {
|
||||
folders: undefined,
|
||||
files: undefined,
|
||||
@@ -15,6 +25,8 @@ const el = {
|
||||
btnSend: undefined,
|
||||
};
|
||||
|
||||
const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl', 'gif', 'mp4', 'mkv', 'avi', 'mjpeg', 'mpg', 'avr'];
|
||||
|
||||
// HTML Elements
|
||||
|
||||
class GalleryFolder extends HTMLElement {
|
||||
@@ -73,34 +85,159 @@ async function createThumb(img) {
|
||||
|
||||
async function handleSeparator(separator) {
|
||||
separator.classList.toggle('gallery-separator-hidden');
|
||||
const nowHidden = separator.classList.contains('gallery-separator-hidden');
|
||||
|
||||
// Store the state (true = open, false = closed)
|
||||
separatorStates.set(separator.title, !nowHidden);
|
||||
|
||||
// Update arrow and count
|
||||
const arrow = separator.querySelector('.gallery-separator-arrow');
|
||||
arrow.style.transform = nowHidden ? 'rotate(0deg)' : 'rotate(90deg)';
|
||||
|
||||
const all = Array.from(el.files.children);
|
||||
const hide = separator.classList.contains('gallery-separator-hidden');
|
||||
separator.innerText = `${hide ? '▶' : '▼'} ${separator.title}`;
|
||||
for (const f of all) {
|
||||
if (separator.title.length > 0 && f.name?.startsWith(separator.title)) f.style.display = hide ? 'none' : 'unset';
|
||||
if (!f.name) continue; // Skip separators
|
||||
|
||||
// Check if file belongs to this exact directory
|
||||
const fileDir = f.name.match(/(.*)[\/\\]/);
|
||||
const fileDirPath = fileDir ? fileDir[1] : '';
|
||||
|
||||
if (separator.title.length > 0 && fileDirPath === separator.title) {
|
||||
f.style.display = nowHidden ? 'none' : 'unset';
|
||||
}
|
||||
}
|
||||
// Note: Count is not updated here on manual toggle, as it reflects the total.
|
||||
// If I end up implementing it, the search function will handle dynamic count updates.
|
||||
}
|
||||
|
||||
async function addSeparators() {
|
||||
document.querySelectorAll('.gallery-separator').forEach((node) => el.files.removeChild(node));
|
||||
const all = Array.from(el.files.children);
|
||||
let lastDir;
|
||||
let isFirstSeparator = true; // Flag to open the first separator by default
|
||||
|
||||
// First, add the style for separators if it doesn't exist
|
||||
if (!document.getElementById('gallery-separator-styles')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'gallery-separator-styles';
|
||||
style.textContent = `
|
||||
.gallery-separator {
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
background-color: var(--sd-secondary-color);
|
||||
border-radius: var(--sd-border-radius);
|
||||
margin: 4px 0;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.gallery-separator:hover {
|
||||
background-color: var(--sd-button-hover-color);
|
||||
}
|
||||
.gallery-separator:hover .gallery-separator-arrow,
|
||||
.gallery-separator:hover .gallery-separator-name,
|
||||
.gallery-separator:hover .gallery-separator-count {
|
||||
color: var(--sd-input-hover-text-color);
|
||||
}
|
||||
.gallery-separator-arrow {
|
||||
display: inline-block;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
flex-shrink: 0;
|
||||
color: var(--sd-input-text-color);
|
||||
}
|
||||
.gallery-separator-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 8px;
|
||||
color: var(--sd-input-text-color);
|
||||
}
|
||||
.gallery-separator-count {
|
||||
color: var(--sd-input-placeholder-text-color);
|
||||
font-size: 0.9em;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// First pass: create separators
|
||||
for (const f of all) {
|
||||
let dir = f.name.match(/(.*)[\/\\]/);
|
||||
let dir = f.name?.match(/(.*)[\/\\]/);
|
||||
if (!dir) dir = '';
|
||||
else dir = dir[1];
|
||||
if (dir !== lastDir) {
|
||||
lastDir = dir;
|
||||
if (dir.length > 0) {
|
||||
// Count files in this directory
|
||||
let fileCount = 0;
|
||||
for (const file of all) {
|
||||
if (!file.name) continue;
|
||||
const fileDir = file.name.match(/(.*)[\/\\]/);
|
||||
const fileDirPath = fileDir ? fileDir[1] : '';
|
||||
if (fileDirPath === dir) fileCount++;
|
||||
}
|
||||
|
||||
const sep = document.createElement('div');
|
||||
sep.className = 'gallery-separator';
|
||||
sep.innerText = `▼ ${dir}`;
|
||||
sep.title = dir;
|
||||
|
||||
// Default to open for the first separator if no state is saved, otherwise closed.
|
||||
const isOpen = separatorStates.has(dir) ? separatorStates.get(dir) : isFirstSeparator;
|
||||
separatorStates.set(dir, isOpen); // Ensure it's in the map
|
||||
if (isFirstSeparator) isFirstSeparator = false; // Subsequent separators will default to closed
|
||||
|
||||
if (!isOpen) {
|
||||
sep.classList.add('gallery-separator-hidden');
|
||||
}
|
||||
|
||||
// Create arrow span
|
||||
const arrow = document.createElement('span');
|
||||
arrow.className = 'gallery-separator-arrow';
|
||||
arrow.textContent = '▶';
|
||||
arrow.style.transform = isOpen ? 'rotate(90deg)' : 'rotate(0deg)';
|
||||
|
||||
// Create directory name span
|
||||
const dirName = document.createElement('span');
|
||||
dirName.className = 'gallery-separator-name';
|
||||
dirName.textContent = dir;
|
||||
dirName.title = dir; // Show full path on hover
|
||||
|
||||
// Create count span
|
||||
const count = document.createElement('span');
|
||||
count.className = 'gallery-separator-count';
|
||||
count.textContent = `${fileCount} files`;
|
||||
sep.dataset.totalFiles = fileCount; // Store total count for search filtering
|
||||
|
||||
sep.appendChild(arrow);
|
||||
sep.appendChild(dirName);
|
||||
sep.appendChild(count);
|
||||
|
||||
sep.onclick = () => handleSeparator(sep);
|
||||
el.files.insertBefore(sep, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: hide files in closed directories
|
||||
for (const f of all) {
|
||||
if (!f.name) continue; // Skip separators
|
||||
|
||||
const dir = f.name.match(/(.*)[\/\\]/);
|
||||
if (dir && dir[1]) {
|
||||
const dirPath = dir[1];
|
||||
const isOpen = separatorStates.get(dirPath);
|
||||
if (isOpen === false) {
|
||||
f.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function delayFetchThumb(fn) {
|
||||
@@ -140,11 +277,17 @@ class GalleryFile extends HTMLElement {
|
||||
if (this.shadow.children.length > 0) {
|
||||
return;
|
||||
}
|
||||
const ext = this.name.split('.').pop().toLowerCase();
|
||||
if (!['jpg', 'jpeg', 'png', 'gif', 'webp', 'jxl', 'svg', 'mp4'].includes(ext)) {
|
||||
// console.error(`gallery: type=${ext} file=${this.name} unsupported`);
|
||||
return;
|
||||
|
||||
// Check separator state early to hide the element immediately
|
||||
const dir = this.name.match(/(.*)[\/\\]/);
|
||||
if (dir && dir[1]) {
|
||||
const dirPath = dir[1];
|
||||
const isOpen = separatorStates.get(dirPath);
|
||||
if (isOpen === false) {
|
||||
this.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
this.hash = await getHash(`${this.folder}/${this.name}/${this.size}/${this.mtime}`); // eslint-disable-line no-use-before-define
|
||||
const style = document.createElement('style');
|
||||
const width = opts.browser_fixed_width ? `${opts.extra_networks_card_size}px` : 'unset';
|
||||
@@ -225,7 +368,13 @@ class GalleryFile extends HTMLElement {
|
||||
return; // avoid double-adding
|
||||
}
|
||||
this.title = img.title;
|
||||
this.style.display = this.title.toLowerCase().includes(el.search.value.toLowerCase()) ? 'unset' : 'none';
|
||||
|
||||
// Final visibility check based on search term.
|
||||
const shouldDisplayBasedOnSearch = this.title.toLowerCase().includes(el.search.value.toLowerCase());
|
||||
if (this.style.display !== 'none') { // Only proceed if not already hidden by a closed separator
|
||||
this.style.display = shouldDisplayBasedOnSearch ? 'unset' : 'none';
|
||||
}
|
||||
|
||||
this.shadow.appendChild(style);
|
||||
this.shadow.appendChild(img);
|
||||
}
|
||||
@@ -262,15 +411,49 @@ async function wsConnect(socket, timeout = 5000) {
|
||||
return isOpened();
|
||||
}
|
||||
|
||||
async function gallerySearch(evt) {
|
||||
async function gallerySearch() {
|
||||
if (el.search.busy) clearTimeout(el.search.busy);
|
||||
el.search.busy = setTimeout(async () => {
|
||||
let numFound = 0;
|
||||
const all = Array.from(el.files.children);
|
||||
const str = el.search.value.toLowerCase();
|
||||
const r = /^(.+)([=<>])(.*)/;
|
||||
const t0 = performance.now();
|
||||
for (const f of all) {
|
||||
const str = el.search.value.toLowerCase();
|
||||
const allFiles = Array.from(el.files.children).filter(node => node.name);
|
||||
const allSeparators = Array.from(el.files.children).filter(node => node.classList.contains('gallery-separator'));
|
||||
|
||||
// If search is cleared, restore original view
|
||||
if (str === '') {
|
||||
allSeparators.forEach(sep => {
|
||||
sep.style.display = 'flex';
|
||||
const isOpen = separatorStates.has(sep.title) ? separatorStates.get(sep.title) : false;
|
||||
|
||||
const countSpan = sep.querySelector('.gallery-separator-count');
|
||||
if (countSpan && sep.dataset.totalFiles) {
|
||||
countSpan.textContent = `${sep.dataset.totalFiles} files`;
|
||||
}
|
||||
|
||||
const arrow = sep.querySelector('.gallery-separator-arrow');
|
||||
sep.classList.toggle('gallery-separator-hidden', !isOpen);
|
||||
if(arrow) arrow.style.transform = isOpen ? 'rotate(90deg)' : 'rotate(0deg)';
|
||||
});
|
||||
|
||||
allFiles.forEach(f => {
|
||||
const dir = f.name.match(/(.*)[\/\\]/);
|
||||
const dirPath = (dir && dir[1]) ? dir[1] : '';
|
||||
const isOpen = separatorStates.get(dirPath);
|
||||
f.style.display = (!dirPath || isOpen) ? 'unset' : 'none';
|
||||
});
|
||||
|
||||
updateStatusWithSort(`Filter | Cleared | ${allFiles.length.toLocaleString()} images`);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Search logic ---
|
||||
let totalFound = 0;
|
||||
const directoryMatches = new Map();
|
||||
const fileMatches = new WeakSet();
|
||||
const r = /^(.+)([=<>])(.*)/;
|
||||
|
||||
for (const f of allFiles) {
|
||||
let isMatch = false;
|
||||
if (r.test(str)) {
|
||||
const match = str.match(r);
|
||||
const key = match[1].trim();
|
||||
@@ -278,23 +461,50 @@ async function gallerySearch(evt) {
|
||||
let val = match[3].trim();
|
||||
if (key === 'mtime') val = new Date(val);
|
||||
if (((op === '=') && (f[key] === val)) || ((op === '>') && (f[key] > val)) || ((op === '<') && (f[key] < val))) {
|
||||
f.style.display = 'unset';
|
||||
numFound++;
|
||||
} else {
|
||||
f.style.display = 'none';
|
||||
isMatch = true;
|
||||
}
|
||||
} else if (f.title?.toLowerCase().includes(str) || f.exif?.toLowerCase().includes(str)) {
|
||||
f.style.display = 'unset';
|
||||
numFound++;
|
||||
} else {
|
||||
f.style.display = 'none';
|
||||
isMatch = true;
|
||||
}
|
||||
|
||||
if (isMatch) {
|
||||
fileMatches.add(f);
|
||||
totalFound++;
|
||||
const dir = f.name.match(/(.*)[\/\\]/);
|
||||
const dirPath = (dir && dir[1]) ? dir[1] : '';
|
||||
directoryMatches.set(dirPath, (directoryMatches.get(dirPath) || 0) + 1);
|
||||
}
|
||||
const t1 = performance.now();
|
||||
el.status.innerText = `Filter | ${f.folder} | ${numFound.toLocaleString()}/${all.length.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`;
|
||||
}
|
||||
|
||||
// Update separators based on search results
|
||||
for (const sep of allSeparators) {
|
||||
const dirPath = sep.title;
|
||||
const foundCount = directoryMatches.get(dirPath) || 0;
|
||||
|
||||
if (foundCount > 0) {
|
||||
sep.style.display = 'flex'; // Show separator
|
||||
sep.classList.remove('gallery-separator-hidden'); // Force open
|
||||
|
||||
const arrow = sep.querySelector('.gallery-separator-arrow');
|
||||
if (arrow) arrow.style.transform = 'rotate(90deg)';
|
||||
|
||||
// Removed file count update during search as it was buggy.
|
||||
} else {
|
||||
sep.style.display = 'none'; // Hide separator
|
||||
}
|
||||
}
|
||||
|
||||
// Update file visibility
|
||||
for (const f of allFiles) {
|
||||
f.style.display = fileMatches.has(f) ? 'unset' : 'none';
|
||||
}
|
||||
|
||||
const t1 = performance.now();
|
||||
updateStatusWithSort(`Filter | ${totalFound.toLocaleString()} / ${allFiles.length.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`);
|
||||
}, 250);
|
||||
}
|
||||
|
||||
|
||||
const findDuplicates = (arr, key) => {
|
||||
const map = new Map();
|
||||
return arr.filter((item) => {
|
||||
@@ -305,58 +515,64 @@ const findDuplicates = (arr, key) => {
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to update status with sort mode
|
||||
function updateStatusWithSort(message) {
|
||||
const sortIndicator = `Sort: ${lastSortName} | `;
|
||||
el.status.innerText = sortIndicator + message;
|
||||
}
|
||||
|
||||
async function gallerySort(btn) {
|
||||
const t0 = performance.now();
|
||||
const arr = Array.from(el.files.children).filter((node) => node.name); // filter out separators
|
||||
if (arr.length === 0) return; // no files to sort
|
||||
if (btn) lastSort = btn.charCodeAt(0);
|
||||
lastSortName = 'none';
|
||||
lastSortName = 'None';
|
||||
const fragment = document.createDocumentFragment();
|
||||
switch (lastSort) {
|
||||
case 61789: // name asc
|
||||
lastSortName = 'name asc';
|
||||
lastSortName = 'Name Ascending';
|
||||
arr
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.forEach((node) => fragment.appendChild(node));
|
||||
break;
|
||||
case 61790: // name dsc
|
||||
lastSortName = 'name dsc';
|
||||
lastSortName = 'Name Descending';
|
||||
arr
|
||||
.sort((b, a) => a.name.localeCompare(b.name))
|
||||
.forEach((node) => fragment.appendChild(node));
|
||||
break;
|
||||
case 61792: // size asc
|
||||
lastSortName = 'size asc';
|
||||
lastSortName = 'Size Ascending';
|
||||
arr
|
||||
.sort((a, b) => a.size - b.size)
|
||||
.forEach((node) => fragment.appendChild(node));
|
||||
break;
|
||||
case 61793: // size dsc
|
||||
lastSortName = 'size dsc';
|
||||
lastSortName = 'Size Descending';
|
||||
arr
|
||||
.sort((b, a) => a.size - b.size)
|
||||
.forEach((node) => fragment.appendChild(node));
|
||||
break;
|
||||
case 61794: // resolution asc
|
||||
lastSortName = 'resolution asc';
|
||||
lastSortName = 'Resolution Ascending';
|
||||
arr
|
||||
.sort((a, b) => a.width * a.height - b.width * b.height)
|
||||
.forEach((node) => fragment.appendChild(node));
|
||||
break;
|
||||
case 61795: // resolution dsc
|
||||
lastSortName = 'resolution dsc';
|
||||
lastSortName = 'Resolution Descending';
|
||||
arr
|
||||
.sort((b, a) => a.width * a.height - b.width * b.height)
|
||||
.forEach((node) => fragment.appendChild(node));
|
||||
break;
|
||||
case 61662:
|
||||
lastSortName = 'modified asc';
|
||||
lastSortName = 'Modified Ascending';
|
||||
arr
|
||||
.sort((a, b) => a.mtime - b.mtime)
|
||||
.forEach((node) => fragment.appendChild(node));
|
||||
break;
|
||||
case 61661:
|
||||
lastSortName = 'modified dsc';
|
||||
lastSortName = 'Modified Descending';
|
||||
arr
|
||||
.sort((b, a) => a.mtime - b.mtime)
|
||||
.forEach((node) => fragment.appendChild(node));
|
||||
@@ -368,35 +584,55 @@ async function gallerySort(btn) {
|
||||
el.files.innerHTML = '';
|
||||
el.files.appendChild(fragment);
|
||||
addSeparators();
|
||||
|
||||
// After sorting and adding separators, ensure files respect separator states
|
||||
const all = Array.from(el.files.children);
|
||||
for (const f of all) {
|
||||
if (!f.name) continue; // Skip separators
|
||||
|
||||
const dir = f.name.match(/(.*)[\/\\]/);
|
||||
if (dir && dir[1]) {
|
||||
const dirPath = dir[1];
|
||||
const isOpen = separatorStates.get(dirPath);
|
||||
if (isOpen === false) {
|
||||
f.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const t1 = performance.now();
|
||||
log(`gallerySort: char=${lastSort} len=${arr.length} time=${Math.floor(t1 - t0)} sort=${lastSortName}`);
|
||||
el.status.innerText = `Sort | ${lastSortName} | ${arr.length.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`;
|
||||
updateStatusWithSort(`${arr.length.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`);
|
||||
}
|
||||
|
||||
async function fetchFilesHT(evt) {
|
||||
const t0 = performance.now();
|
||||
const fragment = document.createDocumentFragment();
|
||||
el.status.innerText = `Folder | ${evt.target.name} | in-progress`;
|
||||
updateStatusWithSort(`Folder | ${evt.target.name} | in-progress`);
|
||||
let numFiles = 0;
|
||||
|
||||
const res = await fetch(`${window.api}/browser/files?folder=${encodeURI(evt.target.name)}`);
|
||||
if (!res || res.status !== 200) {
|
||||
el.status.innerText = `Folder | ${evt.target.name} | failed: ${res?.statusText}`;
|
||||
updateStatusWithSort(`Folder | ${evt.target.name} | failed: ${res?.statusText}`);
|
||||
return;
|
||||
}
|
||||
const jsonData = await res.json();
|
||||
for (const line of jsonData) {
|
||||
const data = decodeURI(line).split('##F##');
|
||||
numFiles++;
|
||||
const f = new GalleryFile(data[0], data[1]);
|
||||
fragment.appendChild(f);
|
||||
const fileName = data[1];
|
||||
const ext = fileName.split('.').pop().toLowerCase();
|
||||
if (SUPPORTED_EXTENSIONS.includes(ext)) {
|
||||
numFiles++;
|
||||
const f = new GalleryFile(data[0], fileName);
|
||||
fragment.appendChild(f);
|
||||
}
|
||||
}
|
||||
|
||||
el.files.appendChild(fragment);
|
||||
|
||||
const t1 = performance.now();
|
||||
log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`);
|
||||
el.status.innerText = `Folder | ${evt.target.name} | ${numFiles.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`;
|
||||
updateStatusWithSort(`Folder | ${evt.target.name} | ${numFiles.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`);
|
||||
addSeparators();
|
||||
}
|
||||
|
||||
@@ -417,7 +653,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets
|
||||
await fetchFilesHT(evt); // fallback to http
|
||||
return;
|
||||
}
|
||||
el.status.innerText = `Folder | ${evt.target.name}`;
|
||||
updateStatusWithSort(`Folder | ${evt.target.name}`);
|
||||
const t0 = performance.now();
|
||||
let numFiles = 0;
|
||||
let t1 = performance.now();
|
||||
@@ -429,13 +665,17 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets
|
||||
if (data[0] === '#END#') {
|
||||
ws.close();
|
||||
} else {
|
||||
const file = new GalleryFile(data[0], data[1]);
|
||||
numFiles++;
|
||||
fragment.appendChild(file);
|
||||
if (numFiles % 100 === 0) {
|
||||
el.status.innerText = `Folder | ${evt.target.name} | ${numFiles.toLocaleString()} images | in-progress | ${Math.floor(t1 - t0).toLocaleString()}ms`;
|
||||
el.files.appendChild(fragment);
|
||||
fragment = document.createDocumentFragment();
|
||||
const fileName = data[1];
|
||||
const ext = fileName.split('.').pop().toLowerCase();
|
||||
if (SUPPORTED_EXTENSIONS.includes(ext)) {
|
||||
const file = new GalleryFile(data[0], fileName);
|
||||
numFiles++;
|
||||
fragment.appendChild(file);
|
||||
if (numFiles % 100 === 0) {
|
||||
updateStatusWithSort(`Folder | ${evt.target.name} | ${numFiles.toLocaleString()} images | in-progress | ${Math.floor(t1 - t0).toLocaleString()}ms`);
|
||||
el.files.appendChild(fragment);
|
||||
fragment = document.createDocumentFragment();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -443,7 +683,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets
|
||||
el.files.appendChild(fragment);
|
||||
// gallerySort();
|
||||
log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`);
|
||||
el.status.innerText = `Folder | ${evt.target.name} | ${numFiles.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`;
|
||||
updateStatusWithSort(`Folder | ${evt.target.name} | ${numFiles.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`);
|
||||
addSeparators();
|
||||
};
|
||||
ws.onerror = (event) => {
|
||||
|
||||
Reference in New Issue
Block a user