@inherits OwningComponentBase @using AntDesign.TableModels; @using LANCommander.SDK.Services @using Microsoft.Extensions.Logging @inject IArchiveClient ArchiveClient @inject IMessageService MessageService @inject ILogger Logger @namespace LANCommander.UI.Components
@if (Features.HasFlag(FileManagerFeatures.NavigationBack)) {
@code { [Parameter] public Guid? ArchiveId { get; set; } [Parameter] public string WorkingDirectory { get; set; } [Parameter] public bool SelectMultiple { get; set; } = true; [Parameter] public bool IncludeDirectories { get; set; } = true; [Parameter] public FileManagerDirectory CurrentPath { get; set; } [Parameter] public EventCallback CurrentPathChanged { get; set; } [Parameter] public FileManagerFeatures Features { get; set; } = FileManagerFeatures.NavigationBack | FileManagerFeatures.NavigationForward | FileManagerFeatures.UpALevel | FileManagerFeatures.Refresh | FileManagerFeatures.Breadcrumbs | FileManagerFeatures.NewFolder | FileManagerFeatures.UploadFile | FileManagerFeatures.Delete | FileManagerFeatures.ColumnPicker; [Parameter] public IEnumerable Selected { get; set; } = new List(); [Parameter] public EventCallback> SelectedChanged { get; set; } [Parameter] public Func EntrySelectable { get; set; } = _ => true; [Parameter] public Func EntryVisible { get; set; } = _ => true; [Parameter] public Dictionary ColumnVisibility { get; set; } [Parameter] public EventCallback> ColumnVisibilityChanged { get; set; } const string ColumnSize = "Size"; const string ColumnType = "Type"; const string ColumnModified = "Modified"; const string ColumnCreated = "Created"; const int PageSize = 100; IFileManagerSource FileSource; List Past { get; set; } = new(); List Future { get; set; } = new(); List Breadcrumbs = new(); List Entries { get; set; } = new(); HashSet Directories { get; set; } = new(); NewFolderModal NewFolderModal; UploadModal UploadModal; Dictionary OnRow(RowData row) => new() { ["data-path"] = row.Data.Path, ["ondblclick"] = ((System.Action)delegate { if (row.Data is FileManagerDirectory) ChangeDirectory((FileManagerDirectory)row.Data, true); }) }; protected override async Task OnInitializedAsync() { if (!String.IsNullOrWhiteSpace(WorkingDirectory)) FileSource = new FileManagerLocalDiskSource(WorkingDirectory); else if (ArchiveId != null && ArchiveId != Guid.Empty) FileSource = new FileManagerArchiveSource(ArchiveClient, ArchiveId.Value); Directories = FileSource.GetDirectoryTree().ToHashSet(); if (FileSource is FileManagerLocalDiskSource) { var target = BuildDirectoryChain(WorkingDirectory); await ChangeDirectory(target, true); } else if (FileSource is FileManagerArchiveSource) await ChangeDirectory(Directories.First(), true); await InvokeAsync(StateHasChanged); } async Task ChangeDirectory(FileManagerDirectory directory, bool clearFuture) { var currentPath = FileSource.GetCurrentPath(); if (currentPath != null && !String.IsNullOrWhiteSpace(currentPath.Path) && directory.Path != currentPath.Path && Past.LastOrDefault()?.Path != directory.Path) Past.Add(currentPath); CurrentPath = directory; if (CurrentPathChanged.HasDelegate) await CurrentPathChanged.InvokeAsync(CurrentPath); FileSource.SetCurrentPath(directory); await UpdateEntries(); UpdateBreadcrumbs(); if (clearFuture) Future.Clear(); StateHasChanged(); } async Task ExpandTree(TreeEventArgs args) { var directory = (FileManagerDirectory)args.Node.DataItem; directory = FileSource.ExpandNode(directory); } async Task UpdateEntries() { Entries = FileSource.GetEntries().ToList(); } void UpdateBreadcrumbs() { Breadcrumbs.Clear(); var currentPath = FileSource.GetCurrentPath(); while (currentPath != null) { Breadcrumbs.Add(currentPath); currentPath = currentPath.Parent; } Breadcrumbs.Reverse(); } async Task NavigateBack() { if (Past.Count > 0) { Future.Add(FileSource.GetCurrentPath()); await ChangeDirectory(Past.Last(), false); Past = Past.Take(Past.Count - 1).ToList(); } } async Task NavigateForward() { if (Future.Count > 0) { Past.Add(FileSource.GetCurrentPath()); await ChangeDirectory(Future.First(), false); Future = Future.Skip(1).ToList(); } } async Task NavigateUp() { var currentPath = FileSource.GetCurrentPath(); if (currentPath.Parent != null) await ChangeDirectory(currentPath.Parent, true); } async Task Refresh() { await ChangeDirectory(FileSource.GetCurrentPath(), false); StateHasChanged(); } async Task AddFolder(string name) { try { FileSource.CreateDirectory(System.IO.Path.Combine(FileSource.GetCurrentPath().Path, name)); await Refresh(); MessageService.Success("Folder created!"); } catch (Exception ex) { MessageService.Error("Error creating folder!"); Logger.LogError(ex, "Error creating folder!"); } } async Task Delete() { try { foreach (var entry in Selected) { FileSource.DeleteEntry(entry); } Selected = new List(); MessageService.Success("Deleted!"); } catch (Exception ex) { MessageService.Error("Error deleting file/folder!"); Logger.LogError(ex, "Error deleting file/folder!"); } await Refresh(); } FileManagerDirectory BuildDirectoryChain(string path) { var segments = new List(); var current = path; while (!string.IsNullOrEmpty(current)) { segments.Add(current); var parentPath = Path.GetDirectoryName(current); if (parentPath == current) break; current = parentPath; } segments.Reverse(); FileManagerDirectory parent = null; // Try to match the first segment to a tree root foreach (var root in Directories) { if (string.Equals(root.Path.TrimEnd(Path.DirectorySeparatorChar), segments[0].TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)) { parent = root; break; } } // If no tree root matched, create one if (parent == null) { parent = FileSource.GetDirectory(segments[0]); parent.Parent = null; } // Build the chain for remaining segments for (int i = 1; i < segments.Count; i++) { var dir = FileSource.GetDirectory(segments[i]); dir.Parent = parent; parent = dir; } return parent; } static readonly Dictionary ColumnDefaults = new() { { ColumnSize, true }, { ColumnType, false }, { ColumnModified, true }, { ColumnCreated, false }, }; bool IsColumnVisible(string column) { if (ColumnVisibility != null && ColumnVisibility.TryGetValue(column, out var visible)) return visible; return ColumnDefaults.GetValueOrDefault(column, true); } async Task ToggleColumn(string column) { ColumnVisibility ??= new Dictionary(); ColumnVisibility[column] = !IsColumnVisible(column); if (ColumnVisibilityChanged.HasDelegate) await ColumnVisibilityChanged.InvokeAsync(ColumnVisibility); StateHasChanged(); } }