MyFSIO v0.1.8 Release #9

Merged
kqjy merged 13 commits from next into main 2025-12-23 06:01:32 +00:00
3 changed files with 92 additions and 6 deletions
Showing only changes of commit 86c04f85f6 - Show all commits

View File

@@ -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:

View File

@@ -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; }

View File

@@ -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);