397 lines
13 KiB
Plaintext
397 lines
13 KiB
Plaintext
@using MoonCore.Helpers
|
|
@using Moonlight.Features.FileManager.Models.Abstractions.FileAccess
|
|
@using BlazorContextMenu
|
|
|
|
@inject IJSRuntime JsRuntime
|
|
|
|
<div class="@(IsLoading ? "table-loading" : "")">
|
|
@if (IsLoading)
|
|
{
|
|
<div class="table-loading-message table-loading-message fs-3 fw-bold text-white">
|
|
Loading...
|
|
</div>
|
|
}
|
|
<table class="w-100 table table-row-bordered @(IsLoading ? "blur" : "table-hover") fs-6">
|
|
<tbody>
|
|
<tr class="text-muted">
|
|
@if (ShowSelect)
|
|
{
|
|
<td class="w-10px align-middle">
|
|
<div class="form-check">
|
|
@if (IsAllSelected)
|
|
{
|
|
<input class="form-check-input" type="checkbox" value="1" checked="checked" @oninput="() => ChangeAllSelection(false)">
|
|
}
|
|
else
|
|
{
|
|
<input class="form-check-input" type="checkbox" value="0" @oninput="() => ChangeAllSelection(true)">
|
|
}
|
|
</div>
|
|
</td>
|
|
}
|
|
<td class="w-10px"></td>
|
|
<td>Name</td>
|
|
@if (ShowSize)
|
|
{
|
|
<td class="d-none d-md-table-cell">Size</td>
|
|
}
|
|
@if (ShowDate)
|
|
{
|
|
<td class="d-none d-md-table-cell">Last modified</td>
|
|
}
|
|
@if (EnableContextMenu)
|
|
{
|
|
<td></td>
|
|
}
|
|
@if (AdditionTemplate != null)
|
|
{
|
|
<td></td>
|
|
}
|
|
</tr>
|
|
|
|
@if (Path != "/" && ShowNavigateUp)
|
|
{
|
|
<tr class="fw-semibold">
|
|
@if (ShowSelect)
|
|
{
|
|
<td class="align-middle w-10px"></td>
|
|
}
|
|
<td class="w-10px">
|
|
<i class="bx bx-sm bx-chevrons-left"></i>
|
|
</td>
|
|
<td>
|
|
<a href="#" @onclick:preventDefault @onclick="NavigateUp">
|
|
Back to parent folder
|
|
</a>
|
|
</td>
|
|
@if (ShowSize)
|
|
{
|
|
<td></td>
|
|
}
|
|
@if (ShowDate)
|
|
{
|
|
<td></td>
|
|
}
|
|
@if (EnableContextMenu)
|
|
{
|
|
<td></td>
|
|
}
|
|
@if (AdditionTemplate != null)
|
|
{
|
|
<td></td>
|
|
}
|
|
</tr>
|
|
}
|
|
|
|
@foreach (var entry in Entries)
|
|
{
|
|
if (EnableContextMenu)
|
|
{
|
|
<ContextMenuTrigger MenuId="@ContextMenuId" WrapperTag="tr" Data="entry">
|
|
@if (ShowSelect)
|
|
{
|
|
<td class="w-10px align-middle">
|
|
<div class="form-check">
|
|
@if (SelectionCache.ContainsKey(entry) && SelectionCache[entry])
|
|
{
|
|
<input class="form-check-input" type="checkbox" value="1" checked="checked" @oninput="() => ChangeSelection(entry, false)">
|
|
}
|
|
else
|
|
{
|
|
<input class="form-check-input" type="checkbox" value="0" @oninput="() => ChangeSelection(entry, true)">
|
|
}
|
|
</div>
|
|
</td>
|
|
}
|
|
<td class="align-middle w-10px">
|
|
@if (entry.IsFile)
|
|
{
|
|
<i class="bx bx-md bxs-file-blank text-white"></i>
|
|
}
|
|
else
|
|
{
|
|
<i class="bx bx-md bxs-folder text-primary"></i>
|
|
}
|
|
</td>
|
|
<td class="align-middle">
|
|
<a href="#" @onclick:preventDefault @onclick="() => HandleEntryClick(entry)">
|
|
@entry.Name
|
|
</a>
|
|
</td>
|
|
@if (ShowSize)
|
|
{
|
|
<td class="align-middle d-none d-md-table-cell">
|
|
@if (entry.IsFile)
|
|
{
|
|
@Formatter.FormatSize(entry.Size)
|
|
}
|
|
</td>
|
|
}
|
|
@if (ShowDate)
|
|
{
|
|
<td class="align-middle d-none d-md-table-cell">
|
|
@Formatter.FormatDate(entry.LastModifiedAt)
|
|
</td>
|
|
}
|
|
<td class="d-table-cell d-md-none">
|
|
<div class="dropstart">
|
|
<button class="btn btn-icon btn-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
|
<i class="bx bx-sm bx-dots-horizontal"></i>
|
|
</button>
|
|
<div class="dropdown-menu fs-6">
|
|
@if (ContextMenuTemplate != null)
|
|
{
|
|
@ContextMenuTemplate.Invoke(entry)
|
|
}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
@if (AdditionTemplate != null)
|
|
{
|
|
@AdditionTemplate.Invoke(entry)
|
|
}
|
|
</ContextMenuTrigger>
|
|
}
|
|
else
|
|
{
|
|
<tr>
|
|
@if (ShowSelect)
|
|
{
|
|
<td class="w-10px align-middle">
|
|
<div class="form-check">
|
|
@if (SelectionCache.ContainsKey(entry) && SelectionCache[entry])
|
|
{
|
|
<input class="form-check-input" type="checkbox" value="1" checked="checked" @oninput="() => ChangeSelection(entry, false)">
|
|
}
|
|
else
|
|
{
|
|
<input class="form-check-input" type="checkbox" value="0" @oninput="() => ChangeSelection(entry, true)">
|
|
}
|
|
</div>
|
|
</td>
|
|
}
|
|
<td class="align-middle w-10px">
|
|
@if (entry.IsFile)
|
|
{
|
|
<i class="bx bx-md bxs-file-blank text-white"></i>
|
|
}
|
|
else
|
|
{
|
|
<i class="bx bx-md bxs-folder text-primary"></i>
|
|
}
|
|
</td>
|
|
<td class="align-middle">
|
|
<a href="#" @onclick:preventDefault @onclick="() => HandleEntryClick(entry)">
|
|
@entry.Name
|
|
</a>
|
|
</td>
|
|
@if (ShowSize)
|
|
{
|
|
<td class="align-middle d-none d-md-table-cell">
|
|
@if (entry.IsFile)
|
|
{
|
|
@Formatter.FormatSize(entry.Size)
|
|
}
|
|
</td>
|
|
}
|
|
@if (ShowDate)
|
|
{
|
|
<td class="align-middle d-none d-md-table-cell">
|
|
@Formatter.FormatDate(entry.LastModifiedAt)
|
|
</td>
|
|
}
|
|
@if (AdditionTemplate != null)
|
|
{
|
|
@AdditionTemplate.Invoke(entry)
|
|
}
|
|
</tr>
|
|
}
|
|
}
|
|
</tbody>
|
|
</table>
|
|
|
|
@if (Entries.Length == 0 && ShowUploadPrompt)
|
|
{
|
|
<div class="py-4">
|
|
<IconAlert Color="primary" Title="No files and folders found" Icon="bx-cloud-upload">
|
|
Drag and drop files and folders here to start uploading them or click on the upload button on the top
|
|
</IconAlert>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@if (EnableContextMenu && ContextMenuTemplate != null)
|
|
{
|
|
<ContextMenu @ref="CurrentContextMenu" Id="@ContextMenuId" OnAppearing="OnContextMenuAppear" OnHiding="OnContextMenuHide">
|
|
@if (ShowContextMenu)
|
|
{
|
|
<div class="dropdown-menu show fs-6">
|
|
@ContextMenuTemplate.Invoke(ContextMenuItem)
|
|
</div>
|
|
}
|
|
</ContextMenu>
|
|
}
|
|
|
|
@code
|
|
{
|
|
[Parameter] public RenderFragment<FileEntry>? AdditionTemplate { get; set; }
|
|
|
|
[Parameter] public bool ShowSize { get; set; } = true;
|
|
[Parameter] public bool ShowDate { get; set; } = true;
|
|
[Parameter] public bool ShowSelect { get; set; } = true;
|
|
[Parameter] public bool ShowNavigateUp { get; set; } = true;
|
|
[Parameter] public bool ShowUploadPrompt { get; set; } = false;
|
|
|
|
[Parameter] public RenderFragment<FileEntry>? ContextMenuTemplate { get; set; }
|
|
[Parameter] public bool EnableContextMenu { get; set; } = false;
|
|
private bool ShowContextMenu = false;
|
|
private FileEntry ContextMenuItem;
|
|
private string ContextMenuId = "fileManagerContextMenu";
|
|
private ContextMenu? CurrentContextMenu;
|
|
|
|
[Parameter] public BaseFileAccess FileAccess { get; set; }
|
|
[Parameter] public Func<FileEntry, bool>? Filter { get; set; }
|
|
|
|
[Parameter] public Func<FileEntry, Task>? OnEntryClicked { get; set; }
|
|
[Parameter] public Func<FileEntry[], Task>? OnSelectionChanged { get; set; }
|
|
|
|
[Parameter] public Func<Task>? OnNavigateUpClicked { get; set; }
|
|
|
|
private bool IsLoading = false;
|
|
private string LoadingText = "";
|
|
|
|
private FileEntry[] Entries = Array.Empty<FileEntry>();
|
|
private string Path = "/";
|
|
|
|
private Dictionary<FileEntry, bool> SelectionCache = new();
|
|
public FileEntry[] Selection => SelectionCache.Where(x => x.Value).Select(x => x.Key).ToArray();
|
|
private bool IsAllSelected => Entries.Length != 0 && SelectionCache.Count(x => x.Value) == Entries.Length;
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender)
|
|
await Refresh();
|
|
}
|
|
|
|
public async Task Refresh()
|
|
{
|
|
IsLoading = true;
|
|
LoadingText = "Loading";
|
|
await InvokeAsync(StateHasChanged);
|
|
|
|
// Load current directory
|
|
Path = await FileAccess.GetCurrentDirectory();
|
|
|
|
// Load entries
|
|
LoadingText = "Loading files and folders";
|
|
await InvokeAsync(StateHasChanged);
|
|
|
|
Entries = await FileAccess.List();
|
|
|
|
// Sort entries
|
|
LoadingText = "Sorting files and folders";
|
|
await InvokeAsync(StateHasChanged);
|
|
|
|
if (Filter != null)
|
|
{
|
|
Entries = Entries
|
|
.Where(x => Filter.Invoke(x))
|
|
.ToArray();
|
|
}
|
|
|
|
Entries = Entries
|
|
.GroupBy(x => x.IsFile)
|
|
.OrderBy(x => x.Key)
|
|
.SelectMany(x => x.OrderBy(y => y.Name))
|
|
.ToArray();
|
|
|
|
// Build selection cache
|
|
SelectionCache.Clear();
|
|
|
|
foreach (var entry in Entries)
|
|
SelectionCache.Add(entry, false);
|
|
|
|
if (OnSelectionChanged != null)
|
|
await OnSelectionChanged.Invoke(Array.Empty<FileEntry>());
|
|
|
|
IsLoading = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
private async Task HandleEntryClick(FileEntry entry)
|
|
{
|
|
if (OnEntryClicked == null)
|
|
return;
|
|
|
|
await OnEntryClicked.Invoke(entry);
|
|
}
|
|
|
|
private async Task NavigateUp()
|
|
{
|
|
if (OnNavigateUpClicked == null)
|
|
return;
|
|
|
|
await OnNavigateUpClicked.Invoke();
|
|
}
|
|
|
|
#region Selection
|
|
|
|
private async Task ChangeSelection(FileEntry entry, bool selectionState)
|
|
{
|
|
SelectionCache[entry] = selectionState;
|
|
await InvokeAsync(StateHasChanged);
|
|
|
|
if (OnSelectionChanged != null)
|
|
{
|
|
await OnSelectionChanged.Invoke(SelectionCache
|
|
.Where(x => x.Value)
|
|
.Select(x => x.Key)
|
|
.ToArray()
|
|
);
|
|
}
|
|
}
|
|
|
|
private async Task ChangeAllSelection(bool toggle)
|
|
{
|
|
foreach (var key in SelectionCache.Keys)
|
|
SelectionCache[key] = toggle;
|
|
|
|
await InvokeAsync(StateHasChanged);
|
|
|
|
if (OnSelectionChanged != null)
|
|
{
|
|
await OnSelectionChanged.Invoke(SelectionCache
|
|
.Where(x => x.Value)
|
|
.Select(x => x.Key)
|
|
.ToArray()
|
|
);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Context Menu
|
|
|
|
private async Task OnContextMenuAppear(MenuAppearingEventArgs data)
|
|
{
|
|
ContextMenuItem = (data.Data as FileEntry)!;
|
|
|
|
ShowContextMenu = true;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
private async Task OnContextMenuHide()
|
|
{
|
|
ShowContextMenu = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
public async Task HideContextMenu()
|
|
{
|
|
ShowContextMenu = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
#endregion
|
|
|
|
} |