Fix bucket object browser nested object action button; Improve UX
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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>
|
||||
@@ -2310,6 +2323,12 @@
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
navigateToFolder(folderPath);
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user