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