Fix bucket object browser nested object action button; Improve UX

This commit is contained in:
2025-12-22 13:17:27 +08:00
parent 992d9eccd9
commit 86c04f85f6
3 changed files with 92 additions and 6 deletions

View File

@@ -1,7 +1,7 @@
"""Central location for the application version string."""
from __future__ import annotations
APP_VERSION = "0.1.7"
APP_VERSION = "0.1.8"
def get_version() -> str:

View File

@@ -517,6 +517,22 @@ code {
overflow-y: auto;
}
.objects-table-container thead {
position: sticky;
top: 0;
z-index: 10;
}
.objects-table-container thead th {
background-color: #f8f9fa;
border-bottom: 1px solid var(--myfsio-card-border);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
[data-theme='dark'] .objects-table-container thead th {
background-color: #1e293b;
}
.btn-group form { display: inline; }
.font-monospace { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; }

View File

@@ -86,7 +86,9 @@
</svg>
Upload
</button>
<input id="object-search" class="form-control form-control-sm" type="search" placeholder="Filter objects" style="max-width: 180px;" />
<div class="position-relative">
<input id="object-search" class="form-control form-control-sm" type="search" placeholder="Filter objects" style="max-width: 180px;" />
</div>
<button class="btn btn-outline-danger btn-sm d-none" type="button" data-bulk-delete-trigger disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" class="bi bi-check2-square me-1" viewBox="0 0 16 16">
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z" />
@@ -114,6 +116,14 @@
</li>
</ol>
</nav>
<div id="filter-warning" class="alert alert-warning fade show d-none py-1 px-2 mt-2 mb-0 small d-flex align-items-center" role="alert">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1 flex-shrink-0" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/>
</svg>
<span id="filter-warning-text" class="me-2">Filtering loaded objects only. Not all objects have been loaded yet.</span>
<button type="button" class="btn-close ms-auto flex-shrink-0" style="font-size: 0.5rem;" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
<div
class="table-responsive objects-table-container drop-zone"
@@ -158,6 +168,7 @@
</div>
</div>
<span id="load-more-status" class="text-muted"></span>
<span id="folder-view-status" class="text-muted d-none"></span>
<button id="load-more-btn" class="btn btn-link btn-sm p-0 d-none" style="font-size: 0.75rem;">Load more</button>
</div>
<div class="d-flex align-items-center gap-1">
@@ -2189,7 +2200,8 @@
};
const countObjectsInFolder = (folderPrefix) => {
return allObjects.filter(obj => obj.key.startsWith(folderPrefix)).length;
const count = allObjects.filter(obj => obj.key.startsWith(folderPrefix)).length;
return { count, mayHaveMore: hasMoreObjects };
};
const renderBreadcrumb = (prefix) => {
@@ -2265,7 +2277,8 @@
const createFolderRow = (folderPath) => {
const folderName = folderPath.slice(currentPrefix.length).replace(/\/$/, '');
const objectCount = countObjectsInFolder(folderPath);
const { count: objectCount, mayHaveMore } = countObjectsInFolder(folderPath);
const countDisplay = mayHaveMore ? `${objectCount}+` : objectCount;
const tr = document.createElement('tr');
tr.className = 'folder-row';
@@ -2283,7 +2296,7 @@
</svg>
<span>${escapeHtml(folderName)}/</span>
</div>
<div class="text-muted small ms-4 ps-2">${objectCount} object${objectCount !== 1 ? 's' : ''}</div>
<div class="text-muted small ms-4 ps-2">${countDisplay} object${objectCount !== 1 ? 's' : ''}</div>
</td>
<td class="text-end text-nowrap">
<span class="text-muted small">—</span>
@@ -2309,6 +2322,12 @@
toggleRowSelection(obj.element, checkbox.checked);
});
});
const folderBtn = tr.querySelector('button');
folderBtn?.addEventListener('click', (e) => {
e.stopPropagation();
navigateToFolder(folderPath);
});
tr.addEventListener('click', (e) => {
if (e.target.closest('[data-folder-select]') || e.target.closest('button')) return;
@@ -2329,6 +2348,14 @@
updateBulkDeleteState();
}
if (typeof updateFolderViewStatus === 'function') {
updateFolderViewStatus();
}
if (typeof updateFilterWarning === 'function') {
updateFilterWarning();
}
if (previewPanel) previewPanel.classList.add('d-none');
if (previewEmpty) previewEmpty.classList.remove('d-none');
activeRow = null;
@@ -3138,16 +3165,59 @@
function initFolderNavigation() {
if (hasFolders()) {
renderBreadcrumb('');
renderBreadcrumb(currentPrefix);
renderObjectsView();
}
if (typeof updateFolderViewStatus === 'function') {
updateFolderViewStatus();
}
if (typeof updateFilterWarning === 'function') {
updateFilterWarning();
}
}
bulkDeleteButton?.addEventListener('click', () => openBulkDeleteModal());
bulkDeleteConfirm?.addEventListener('click', () => performBulkDelete());
const filterWarning = document.getElementById('filter-warning');
const filterWarningText = document.getElementById('filter-warning-text');
const folderViewStatus = document.getElementById('folder-view-status');
const updateFilterWarning = () => {
if (!filterWarning) return;
const isFiltering = currentFilterTerm.length > 0;
if (isFiltering && hasMoreObjects) {
filterWarning.classList.remove('d-none');
} else {
filterWarning.classList.add('d-none');
}
};
const updateFolderViewStatus = () => {
if (!folderViewStatus || !loadMoreStatus) return;
if (currentPrefix) {
const { folders, files } = getFoldersAtPrefix(currentPrefix);
const visibleCount = folders.length + files.length;
const folderObjectCount = allObjects.filter(obj => obj.key.startsWith(currentPrefix)).length;
const folderMayHaveMore = hasMoreObjects && folderObjectCount > 0;
if (folderMayHaveMore) {
folderViewStatus.textContent = `Showing ${visibleCount} items in folder • more may be available`;
folderViewStatus.classList.remove('d-none');
loadMoreStatus.classList.add('d-none');
} else {
folderViewStatus.textContent = `${visibleCount} items in folder`;
folderViewStatus.classList.remove('d-none');
loadMoreStatus.classList.add('d-none');
}
} else {
folderViewStatus.classList.add('d-none');
loadMoreStatus.classList.remove('d-none');
}
};
document.getElementById('object-search')?.addEventListener('input', (event) => {
currentFilterTerm = event.target.value.toLowerCase();
updateFilterWarning();
if (hasFolders()) {
const { folders, files } = getFoldersAtPrefix(currentPrefix);