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."""
|
"""Central location for the application version string."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
APP_VERSION = "0.1.7"
|
APP_VERSION = "0.1.8"
|
||||||
|
|
||||||
|
|
||||||
def get_version() -> str:
|
def get_version() -> str:
|
||||||
|
|||||||
@@ -517,6 +517,22 @@ code {
|
|||||||
overflow-y: auto;
|
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; }
|
.btn-group form { display: inline; }
|
||||||
|
|
||||||
.font-monospace { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; }
|
.font-monospace { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; }
|
||||||
|
|||||||
@@ -86,7 +86,9 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Upload
|
Upload
|
||||||
</button>
|
</button>
|
||||||
|
<div class="position-relative">
|
||||||
<input id="object-search" class="form-control form-control-sm" type="search" placeholder="Filter objects" style="max-width: 180px;" />
|
<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>
|
<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">
|
<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" />
|
<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>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</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>
|
||||||
<div
|
<div
|
||||||
class="table-responsive objects-table-container drop-zone"
|
class="table-responsive objects-table-container drop-zone"
|
||||||
@@ -158,6 +168,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span id="load-more-status" class="text-muted"></span>
|
<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>
|
<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>
|
||||||
<div class="d-flex align-items-center gap-1">
|
<div class="d-flex align-items-center gap-1">
|
||||||
@@ -2189,7 +2200,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const countObjectsInFolder = (folderPrefix) => {
|
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) => {
|
const renderBreadcrumb = (prefix) => {
|
||||||
@@ -2265,7 +2277,8 @@
|
|||||||
|
|
||||||
const createFolderRow = (folderPath) => {
|
const createFolderRow = (folderPath) => {
|
||||||
const folderName = folderPath.slice(currentPrefix.length).replace(/\/$/, '');
|
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');
|
const tr = document.createElement('tr');
|
||||||
tr.className = 'folder-row';
|
tr.className = 'folder-row';
|
||||||
@@ -2283,7 +2296,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span>${escapeHtml(folderName)}/</span>
|
<span>${escapeHtml(folderName)}/</span>
|
||||||
</div>
|
</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>
|
||||||
<td class="text-end text-nowrap">
|
<td class="text-end text-nowrap">
|
||||||
<span class="text-muted small">—</span>
|
<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) => {
|
tr.addEventListener('click', (e) => {
|
||||||
if (e.target.closest('[data-folder-select]') || e.target.closest('button')) return;
|
if (e.target.closest('[data-folder-select]') || e.target.closest('button')) return;
|
||||||
navigateToFolder(folderPath);
|
navigateToFolder(folderPath);
|
||||||
@@ -2329,6 +2348,14 @@
|
|||||||
updateBulkDeleteState();
|
updateBulkDeleteState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof updateFolderViewStatus === 'function') {
|
||||||
|
updateFolderViewStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof updateFilterWarning === 'function') {
|
||||||
|
updateFilterWarning();
|
||||||
|
}
|
||||||
|
|
||||||
if (previewPanel) previewPanel.classList.add('d-none');
|
if (previewPanel) previewPanel.classList.add('d-none');
|
||||||
if (previewEmpty) previewEmpty.classList.remove('d-none');
|
if (previewEmpty) previewEmpty.classList.remove('d-none');
|
||||||
activeRow = null;
|
activeRow = null;
|
||||||
@@ -3138,16 +3165,59 @@
|
|||||||
|
|
||||||
function initFolderNavigation() {
|
function initFolderNavigation() {
|
||||||
if (hasFolders()) {
|
if (hasFolders()) {
|
||||||
renderBreadcrumb('');
|
renderBreadcrumb(currentPrefix);
|
||||||
renderObjectsView();
|
renderObjectsView();
|
||||||
}
|
}
|
||||||
|
if (typeof updateFolderViewStatus === 'function') {
|
||||||
|
updateFolderViewStatus();
|
||||||
|
}
|
||||||
|
if (typeof updateFilterWarning === 'function') {
|
||||||
|
updateFilterWarning();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bulkDeleteButton?.addEventListener('click', () => openBulkDeleteModal());
|
bulkDeleteButton?.addEventListener('click', () => openBulkDeleteModal());
|
||||||
bulkDeleteConfirm?.addEventListener('click', () => performBulkDelete());
|
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) => {
|
document.getElementById('object-search')?.addEventListener('input', (event) => {
|
||||||
currentFilterTerm = event.target.value.toLowerCase();
|
currentFilterTerm = event.target.value.toLowerCase();
|
||||||
|
updateFilterWarning();
|
||||||
|
|
||||||
if (hasFolders()) {
|
if (hasFolders()) {
|
||||||
const { folders, files } = getFoldersAtPrefix(currentPrefix);
|
const { folders, files } = getFoldersAtPrefix(currentPrefix);
|
||||||
|
|||||||
Reference in New Issue
Block a user