filebrowsers working
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
currentPath = $bindable("ROOT"),
|
||||
searchQuery = $bindable(""),
|
||||
files = [],
|
||||
selectedPaths = $bindable(new Set<string>()),
|
||||
onNavigate = (path: string) => {},
|
||||
onToggleTrack = (item: FileItem) => {},
|
||||
onSelect = (item: FileItem) => {},
|
||||
@@ -44,6 +45,7 @@
|
||||
currentPath?: string;
|
||||
searchQuery?: string;
|
||||
files?: FileItem[];
|
||||
selectedPaths?: Set<string>;
|
||||
onNavigate?: (path: string) => void;
|
||||
onToggleTrack?: (item: FileItem) => void;
|
||||
onSelect?: (item: FileItem) => void;
|
||||
@@ -54,7 +56,6 @@
|
||||
pendingChanges?: Map<string, boolean>;
|
||||
}>();
|
||||
|
||||
let selectedPaths = $state<Set<string>>(new Set());
|
||||
let lastSelectedPath = $state<string | null>(null);
|
||||
let sortColumn = $state<"name" | "size" | "mtime" | "type">("name");
|
||||
let sortDirection = $state<"asc" | "desc">("asc");
|
||||
@@ -284,6 +285,13 @@
|
||||
}
|
||||
|
||||
function handleRowClick(e: MouseEvent, item: FileItem) {
|
||||
// For discrepancies mode, row click navigates (dirs) or shows metadata (files)
|
||||
// Checkbox handles selection
|
||||
if (mode === 'discrepancies') {
|
||||
onSelect(item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.shiftKey && lastSelectedPath) {
|
||||
const lastIndex = filteredFiles.findIndex((f: FileItem) => f.path === lastSelectedPath);
|
||||
const currentIndex = filteredFiles.findIndex((f: FileItem) => f.path === item.path);
|
||||
@@ -321,14 +329,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively add directory and all its children to selection
|
||||
function addItemAndChildren(path: string, newSelection: Set<string>) {
|
||||
newSelection.add(path);
|
||||
// Find all files/dirs that are children of this path
|
||||
for (const f of (files as FileItem[])) {
|
||||
if (f.path.startsWith(path + "/") || f.path === path) {
|
||||
newSelection.add(f.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively remove directory and all its children from selection
|
||||
function removeItemAndChildren(path: string, newSelection: Set<string>) {
|
||||
newSelection.delete(path);
|
||||
for (const f of (files as FileItem[])) {
|
||||
if (f.path.startsWith(path + "/")) {
|
||||
newSelection.delete(f.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectAll(checked: boolean | "indeterminate") {
|
||||
if (checked === true) {
|
||||
selectedPaths = new Set(filteredFiles.map((f: FileItem) => f.path));
|
||||
const newSelection = new Set<string>();
|
||||
for (const f of (filteredFiles as FileItem[])) {
|
||||
addItemAndChildren(f.path, newSelection);
|
||||
}
|
||||
selectedPaths = newSelection;
|
||||
} else {
|
||||
selectedPaths = new Set();
|
||||
}
|
||||
}
|
||||
|
||||
function handleToggleItem(item: FileItem) {
|
||||
const newSelection = new Set(selectedPaths as Set<string>);
|
||||
if (newSelection.has(item.path)) {
|
||||
// Deselecting - remove item and all children if it's a directory
|
||||
removeItemAndChildren(item.path, newSelection);
|
||||
} else {
|
||||
// Selecting - add item and all children if it's a directory
|
||||
addItemAndChildren(item.path, newSelection);
|
||||
}
|
||||
selectedPaths = newSelection;
|
||||
}
|
||||
|
||||
let isEditingPath = $state(false);
|
||||
let pathInputValue = $state("");
|
||||
|
||||
@@ -605,6 +650,7 @@
|
||||
onClick={(e) => handleRowClick(e, item)}
|
||||
onDoubleClick={() => handleRowDoubleClick(item)}
|
||||
onToggleTrack={() => onToggleTrack(item)}
|
||||
onToggleSelect={() => handleToggleItem(item)}
|
||||
onAddToCart={() => onAddToCart(item)}
|
||||
onDelete={() => onDelete(item)}
|
||||
/>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
onClick = (e: MouseEvent) => {},
|
||||
onDoubleClick = () => {},
|
||||
onToggleTrack = () => {},
|
||||
onToggleSelect = () => {},
|
||||
onAddToCart = () => {},
|
||||
onDelete = () => {},
|
||||
mode = "host",
|
||||
@@ -39,6 +40,7 @@
|
||||
onClick?: (e: MouseEvent) => void;
|
||||
onDoubleClick?: () => void;
|
||||
onToggleTrack?: () => void;
|
||||
onToggleSelect?: () => void;
|
||||
onAddToCart?: () => void;
|
||||
onDelete?: () => void;
|
||||
mode?: "host" | "index" | "live" | "cart" | "discrepancies";
|
||||
@@ -122,12 +124,16 @@
|
||||
ondblclick={(e) => { e.stopPropagation(); onDoubleClick(); }}
|
||||
onkeydown={(e) => e.key === "Enter" && onDoubleClick()}
|
||||
>
|
||||
<!-- TRACKING STATUS / SELECTION -->
|
||||
<!-- SELECTION CHECKBOX -->
|
||||
<div
|
||||
class="flex h-10 w-12 shrink-0 items-center justify-center border-r border-border-color/10"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleTrack();
|
||||
if (mode === 'discrepancies') {
|
||||
onToggleSelect();
|
||||
} else {
|
||||
onToggleTrack();
|
||||
}
|
||||
}}
|
||||
onkeydown={(e) => e.key === " " && e.stopPropagation()}
|
||||
role="none"
|
||||
@@ -150,6 +156,11 @@
|
||||
<Square size={16} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else if mode === 'discrepancies'}
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onCheckedChange={onToggleSelect}
|
||||
/>
|
||||
{:else}
|
||||
<Checkbox
|
||||
checked={item.selected}
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
let files = $state<FileItem[]>([]);
|
||||
let loading = $state(true);
|
||||
let currentPath = $state("ROOT");
|
||||
let selectedIds = $state<Set<number>>(new Set());
|
||||
let batchAction = $state<'acknowledge' | 'recover' | null>(null);
|
||||
let selectedPaths = $state<Set<string>>(new Set());
|
||||
let batchLoading = $state(false);
|
||||
|
||||
async function loadDiscrepancies() {
|
||||
@@ -105,6 +104,78 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function batchDismiss() {
|
||||
const ids = getDiscrepancyIdsFromPaths(selectedPaths);
|
||||
if (ids.length === 0) {
|
||||
toast.error("No files selected");
|
||||
return;
|
||||
}
|
||||
batchLoading = true;
|
||||
try {
|
||||
await batchDismissSystemDiscrepanciesBatchDismissPost({
|
||||
body: { ids }
|
||||
});
|
||||
toast.success(`Dismissed ${ids.length} files`);
|
||||
selectedPaths = new Set();
|
||||
await loadDiscrepancies();
|
||||
} catch (error: any) {
|
||||
toast.error(error.body?.detail || "Failed to dismiss files");
|
||||
} finally {
|
||||
batchLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function batchDelete() {
|
||||
const ids = getDiscrepancyIdsFromPaths(selectedPaths);
|
||||
if (ids.length === 0) {
|
||||
toast.error("No files selected");
|
||||
return;
|
||||
}
|
||||
batchLoading = true;
|
||||
try {
|
||||
await batchHardDeleteSystemDiscrepanciesBatchDeletePost({
|
||||
body: { ids }
|
||||
});
|
||||
toast.success(`Deleted ${ids.length} file records`);
|
||||
selectedPaths = new Set();
|
||||
await loadDiscrepancies();
|
||||
} catch (error: any) {
|
||||
toast.error(error.body?.detail || "Failed to delete files");
|
||||
} finally {
|
||||
batchLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function batchAddToCart() {
|
||||
const ids = getDiscrepancyIdsFromPaths(selectedPaths);
|
||||
if (ids.length === 0) {
|
||||
toast.error("No files selected");
|
||||
return;
|
||||
}
|
||||
batchLoading = true;
|
||||
try {
|
||||
await batchAddToRecoveryQueueRestoresQueueBatchPost({
|
||||
body: { ids }
|
||||
});
|
||||
toast.success(`Added ${ids.length} files to restore cart`);
|
||||
selectedPaths = new Set();
|
||||
} catch (error: any) {
|
||||
toast.error(error.body?.detail || "Failed to add files to cart");
|
||||
} finally {
|
||||
batchLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getDiscrepancyIdsFromPaths(paths: Set<string>): number[] {
|
||||
const ids: number[] = [];
|
||||
for (const item of files) {
|
||||
if (paths.has(item.path) && item.discrepancy_id) {
|
||||
ids.push(item.discrepancy_id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
function navigateTo(path: string) {
|
||||
currentPath = path;
|
||||
loadFiles(path);
|
||||
@@ -176,10 +247,32 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Batch Actions Bar -->
|
||||
{#if selectedPaths.size > 0}
|
||||
<div class="flex items-center gap-3 p-3 bg-bg-tertiary/50 rounded-lg border border-border-color">
|
||||
<span class="text-sm text-text-secondary">
|
||||
{selectedPaths.size} file(s) selected
|
||||
</span>
|
||||
<div class="flex gap-2 ml-auto">
|
||||
<Button size="sm" variant="outline" onclick={batchDismiss} disabled={batchLoading}>
|
||||
Dismiss Selected
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onclick={batchAddToCart} disabled={batchLoading}>
|
||||
<HardDriveDownload size={14} class="mr-1" />
|
||||
Add to Cart
|
||||
</Button>
|
||||
<Button size="sm" variant="destructive" onclick={batchDelete} disabled={batchLoading}>
|
||||
Delete Records
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- FileBrowser Component in discrepancies mode -->
|
||||
<div class="flex-1 min-h-[600px] bg-bg-secondary border border-border-color shadow-2xl rounded-lg flex flex-col relative overflow-hidden">
|
||||
<FileBrowser
|
||||
bind:currentPath={currentPath}
|
||||
bind:selectedPaths={selectedPaths}
|
||||
files={files}
|
||||
mode="discrepancies"
|
||||
onNavigate={navigateTo}
|
||||
|
||||
Reference in New Issue
Block a user