Merge branch 'file-manager'
commit
d869a5a763
|
@ -1,14 +0,0 @@
|
||||||
@inherits FeedbackComponent<ArchiveBrowserOptions, IEnumerable<ZipArchiveEntry>>
|
|
||||||
@using System.IO.Compression;
|
|
||||||
@using LANCommander.Models;
|
|
||||||
|
|
||||||
<ArchiveBrowser ArchiveId="Options.ArchiveId" @bind-SelectedFiles="SelectedFiles" Select="Options.Select" Multiple="Options.Multiple" AllowDirectories="Options.AllowDirectories" />
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private IEnumerable<ZipArchiveEntry> SelectedFiles { get; set; }
|
|
||||||
|
|
||||||
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
|
|
||||||
{
|
|
||||||
await base.OkCancelRefWithResult!.OnOk(SelectedFiles);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,509 @@
|
||||||
|
@using AntDesign.TableModels;
|
||||||
|
@using LANCommander.Components.FileManagerComponents
|
||||||
|
@inject ArchiveService ArchiveService
|
||||||
|
@inject IMessageService MessageService
|
||||||
|
@namespace LANCommander.Components
|
||||||
|
|
||||||
|
<div class="file-manager">
|
||||||
|
<GridRow Class="file-manager-nav">
|
||||||
|
<Space>
|
||||||
|
@if (Features.HasFlag(FileManagerFeatures.NavigationBack))
|
||||||
|
{
|
||||||
|
<SpaceItem>
|
||||||
|
<Tooltip Title="Back" MouseEnterDelay="2">
|
||||||
|
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.ArrowLeft" OnClick="NavigateBack" Disabled="@(Past.Count == 0)" />
|
||||||
|
</Tooltip>
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Features.HasFlag(FileManagerFeatures.NavigationForward))
|
||||||
|
{
|
||||||
|
<SpaceItem>
|
||||||
|
<Tooltip Title="Forward" MouseEnterDelay="2">
|
||||||
|
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.ArrowRight" OnClick="NavigateForward" Disabled="@(Future.Count == 0)" />
|
||||||
|
</Tooltip>
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Features.HasFlag(FileManagerFeatures.UpALevel))
|
||||||
|
{
|
||||||
|
<SpaceItem>
|
||||||
|
<Tooltip Title="Up a Level" MouseEnterDelay="2">
|
||||||
|
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.ArrowUp" OnClick="NavigateUp" Disabled="@(Path.Parent == null)" />
|
||||||
|
</Tooltip>
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Features.HasFlag(FileManagerFeatures.Refresh))
|
||||||
|
{
|
||||||
|
<SpaceItem>
|
||||||
|
<Tooltip Title="Refresh" MouseEnterDelay="2">
|
||||||
|
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Reload" OnClick="Refresh" />
|
||||||
|
</Tooltip>
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Features.HasFlag(FileManagerFeatures.Breadcrumbs))
|
||||||
|
{
|
||||||
|
<SpaceItem Class="file-manager-nav-breadcrumbs">
|
||||||
|
<Breadcrumb>
|
||||||
|
@foreach (var breadcrumb in Breadcrumbs)
|
||||||
|
{
|
||||||
|
<BreadcrumbItem OnClick="() => ChangeDirectory(breadcrumb, false)">@breadcrumb.Name</BreadcrumbItem>
|
||||||
|
}
|
||||||
|
</Breadcrumb>
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Features.HasFlag(FileManagerFeatures.NewFolder))
|
||||||
|
{
|
||||||
|
<SpaceItem>
|
||||||
|
<Tooltip Title="New Folder" MouseEnterDelay="2">
|
||||||
|
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.FolderAdd" OnClick="() => NewFolderModal.Open()" />
|
||||||
|
</Tooltip>
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Features.HasFlag(FileManagerFeatures.UploadFile))
|
||||||
|
{
|
||||||
|
<SpaceItem>
|
||||||
|
<Tooltip Title="Upload File" MouseEnterDelay="2">
|
||||||
|
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Upload" OnClick="() => UploadModal.Open()" />
|
||||||
|
</Tooltip>
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Features.HasFlag(FileManagerFeatures.Delete))
|
||||||
|
{
|
||||||
|
<SpaceItem>
|
||||||
|
<Tooltip Title="Delete" MouseEnterDelay="2">
|
||||||
|
<Popconfirm OnConfirm="Delete">
|
||||||
|
<TitleTemplate>
|
||||||
|
Are you sure you want to delete the selected file@(Selected?.Count() == 1 ? "" : "s")?
|
||||||
|
</TitleTemplate>
|
||||||
|
<ChildContent>
|
||||||
|
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Delete" Disabled="@(Selected?.Count() == 0)" />
|
||||||
|
</ChildContent>
|
||||||
|
</Popconfirm>
|
||||||
|
</Tooltip>
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
|
</Space>
|
||||||
|
</GridRow>
|
||||||
|
|
||||||
|
<GridRow Class="file-manager-body">
|
||||||
|
<GridCol Span="6" Class="file-manager-tree">
|
||||||
|
<Tree TItem="FileManagerDirectory"
|
||||||
|
DataSource="Directories"
|
||||||
|
SwitcherIcon="@IconType.Outline.Down"
|
||||||
|
TitleExpression="x => x.DataItem.Name"
|
||||||
|
ChildrenExpression="x => x.DataItem.Children"
|
||||||
|
IsLeafExpression="x => !x.DataItem.HasChildren"
|
||||||
|
IconExpression="x => x.Expanded ? IconType.Outline.FolderOpen : IconType.Outline.Folder"
|
||||||
|
DefaultExpandParent="true"
|
||||||
|
OnClick="(args) => ChangeDirectory(args.Node.DataItem, false)"
|
||||||
|
OnNodeLoadDelayAsync="ExpandTree">
|
||||||
|
<SwitcherIconTemplate>
|
||||||
|
<Icon Type="@IconType.Outline.Down" />
|
||||||
|
</SwitcherIconTemplate>
|
||||||
|
<TitleIconTemplate>
|
||||||
|
@if (context.Expanded)
|
||||||
|
{
|
||||||
|
<Icon Type="@IconType.Outline.FolderOpen" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Icon Type="@IconType.Outline.Folder" />
|
||||||
|
}
|
||||||
|
</TitleIconTemplate>
|
||||||
|
</Tree>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
|
<GridCol Span="18" Class="file-manager-list">
|
||||||
|
<Table TItem="IFileManagerEntry"
|
||||||
|
DataSource="Entries"
|
||||||
|
HidePagination="true"
|
||||||
|
Loading="Entries == null"
|
||||||
|
OnRow="OnRow"
|
||||||
|
SelectedRowsChanged="SelectedChanged"
|
||||||
|
RowSelectable="EntrySelectable"
|
||||||
|
Size="@TableSize.Small">
|
||||||
|
<Selection Key="@context.Path" Type="@(SelectMultiple ? "checkbox" : "radio")" Disabled="!EntrySelectable.Invoke(context)" Class="@(EntrySelectable.Invoke(context) ? "" : "file-manager-selector-hidden")" />
|
||||||
|
<Column TData="string" Width="32">
|
||||||
|
@if (context is FileManagerFile)
|
||||||
|
{
|
||||||
|
<Icon Type="@(((FileManagerFile)context).GetIcon())" Theme="outline" />
|
||||||
|
}
|
||||||
|
else if (context is FileManagerDirectory)
|
||||||
|
{
|
||||||
|
<Icon Type="@IconType.Outline.Folder" />
|
||||||
|
}
|
||||||
|
</Column>
|
||||||
|
<PropertyColumn Property="e => e.Path" Sortable Title="Name">
|
||||||
|
@GetEntryName(context)
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="e => e.Size" Sortable Title="Size">
|
||||||
|
@ByteSizeLib.ByteSize.FromBytes(context.Size)
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="e => e.ModifiedOn" Format="MM/dd/yyyy hh:mm tt" Sortable Title="Modified" />
|
||||||
|
</Table>
|
||||||
|
</GridCol>
|
||||||
|
</GridRow>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NewFolderModal @ref="NewFolderModal" OnFolderNameEntered="AddFolder" />
|
||||||
|
<UploadModal @ref="UploadModal" Path="@Path.Path" OnUploadCompleted="() => Refresh()" />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public Guid ArchiveId { get; set; }
|
||||||
|
[Parameter] public string WorkingDirectory { get; set; }
|
||||||
|
[Parameter] public bool SelectMultiple { get; set; } = true;
|
||||||
|
[Parameter] public FileManagerFeatures Features { get; set; } = FileManagerFeatures.NavigationBack | FileManagerFeatures.NavigationForward | FileManagerFeatures.UpALevel | FileManagerFeatures.Refresh | FileManagerFeatures.Breadcrumbs | FileManagerFeatures.NewFolder | FileManagerFeatures.UploadFile | FileManagerFeatures.Delete;
|
||||||
|
[Parameter] public IEnumerable<IFileManagerEntry> Selected { get; set; } = new List<IFileManagerEntry>();
|
||||||
|
[Parameter] public EventCallback<IEnumerable<IFileManagerEntry>> SelectedChanged { get; set; }
|
||||||
|
[Parameter] public Func<IFileManagerEntry, bool> EntrySelectable { get; set; } = _ => true;
|
||||||
|
[Parameter] public Func<IFileManagerEntry, bool> EntryVisible { get; set; } = _ => true;
|
||||||
|
|
||||||
|
FileManagerSource Source = FileManagerSource.FileSystem;
|
||||||
|
|
||||||
|
FileManagerDirectory Path { get; set; } = new FileManagerDirectory();
|
||||||
|
|
||||||
|
List<FileManagerDirectory> Past { get; set; } = new List<FileManagerDirectory>();
|
||||||
|
List<FileManagerDirectory> Future { get; set; } = new List<FileManagerDirectory>();
|
||||||
|
List<FileManagerDirectory> Breadcrumbs = new List<FileManagerDirectory>();
|
||||||
|
|
||||||
|
List<IFileManagerEntry> Entries { get; set; } = new List<IFileManagerEntry>();
|
||||||
|
HashSet<FileManagerDirectory> Directories { get; set; } = new HashSet<FileManagerDirectory>();
|
||||||
|
|
||||||
|
NewFolderModal NewFolderModal;
|
||||||
|
UploadModal UploadModal;
|
||||||
|
|
||||||
|
Dictionary<string, object> OnRow(RowData<IFileManagerEntry> 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))
|
||||||
|
Source = FileManagerSource.FileSystem;
|
||||||
|
else if (ArchiveId != Guid.Empty)
|
||||||
|
Source = FileManagerSource.Archive;
|
||||||
|
|
||||||
|
Directories = await GetDirectoriesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<HashSet<FileManagerDirectory>> GetDirectoriesAsync()
|
||||||
|
{
|
||||||
|
switch (Source)
|
||||||
|
{
|
||||||
|
case FileManagerSource.FileSystem:
|
||||||
|
return await GetFileSystemDirectoriesAsync(WorkingDirectory);
|
||||||
|
case FileManagerSource.Archive:
|
||||||
|
return await GetArchiveDirectoriesAsync(ArchiveId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HashSet<FileManagerDirectory>();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<HashSet<FileManagerDirectory>> GetFileSystemDirectoriesAsync(string path)
|
||||||
|
{
|
||||||
|
var paths = Directory.EnumerateDirectories(path, "*", new EnumerationOptions
|
||||||
|
{
|
||||||
|
IgnoreInaccessible = true,
|
||||||
|
RecurseSubdirectories = true,
|
||||||
|
MaxRecursionDepth = 1
|
||||||
|
});
|
||||||
|
|
||||||
|
var root = new FileManagerDirectory
|
||||||
|
{
|
||||||
|
Name = path,
|
||||||
|
Path = path,
|
||||||
|
IsExpanded = true
|
||||||
|
};
|
||||||
|
|
||||||
|
root.PopulateChildren(paths);
|
||||||
|
|
||||||
|
await ChangeDirectory(root, true);
|
||||||
|
|
||||||
|
return new HashSet<FileManagerDirectory>
|
||||||
|
{
|
||||||
|
root
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<HashSet<FileManagerDirectory>> GetArchiveDirectoriesAsync(Guid archiveId)
|
||||||
|
{
|
||||||
|
var entries = await ArchiveService.GetContents(archiveId);
|
||||||
|
var directories = new HashSet<FileManagerDirectory>();
|
||||||
|
|
||||||
|
var root = new FileManagerDirectory
|
||||||
|
{
|
||||||
|
Name = "Root",
|
||||||
|
Path = "",
|
||||||
|
IsExpanded = true
|
||||||
|
};
|
||||||
|
|
||||||
|
root.PopulateChildren(entries);
|
||||||
|
|
||||||
|
await ChangeDirectory(root, true);
|
||||||
|
|
||||||
|
return new HashSet<FileManagerDirectory>
|
||||||
|
{
|
||||||
|
root
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetEntryName(IFileManagerEntry entry)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrWhiteSpace(entry.Name) && entry.Size == 0)
|
||||||
|
{
|
||||||
|
return entry.Path.TrimEnd('/').Split('/').Last();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return entry.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ChangeDirectory(FileManagerDirectory directory, bool clearFuture)
|
||||||
|
{
|
||||||
|
if (Path != null && !String.IsNullOrWhiteSpace(Path.Path) && directory.Path != Path.Path && Past.LastOrDefault()?.Path != directory.Path)
|
||||||
|
Past.Add(Path);
|
||||||
|
|
||||||
|
Path = directory;
|
||||||
|
|
||||||
|
await UpdateEntries();
|
||||||
|
UpdateBreadcrumbs();
|
||||||
|
|
||||||
|
if (clearFuture)
|
||||||
|
Future.Clear();
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ExpandTree(TreeEventArgs<FileManagerDirectory> args)
|
||||||
|
{
|
||||||
|
if (Source == FileManagerSource.FileSystem)
|
||||||
|
{
|
||||||
|
var directory = (FileManagerDirectory)args.Node.DataItem;
|
||||||
|
|
||||||
|
foreach (var child in directory.Children)
|
||||||
|
{
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
var paths = Directory.EnumerateDirectories(child.Path, "*", new EnumerationOptions
|
||||||
|
{
|
||||||
|
IgnoreInaccessible = true,
|
||||||
|
RecurseSubdirectories = true,
|
||||||
|
MaxRecursionDepth = 1
|
||||||
|
});
|
||||||
|
|
||||||
|
child.PopulateChildren(paths);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task UpdateEntries()
|
||||||
|
{
|
||||||
|
Entries.Clear();
|
||||||
|
|
||||||
|
switch (Source)
|
||||||
|
{
|
||||||
|
case FileManagerSource.FileSystem:
|
||||||
|
await Task.Run(UpdateFileSystemEntries);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FileManagerSource.Archive:
|
||||||
|
await UpdateArchiveEntries();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateFileSystemEntries()
|
||||||
|
{
|
||||||
|
var entries = Directory.EnumerateFileSystemEntries(Path.Path);
|
||||||
|
var separator = System.IO.Path.DirectorySeparatorChar;
|
||||||
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(entry))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var info = new DirectoryInfo(entry);
|
||||||
|
var directory = new FileManagerDirectory
|
||||||
|
{
|
||||||
|
Path = entry,
|
||||||
|
Name = entry.Substring(Path.Path.Length).TrimStart(separator),
|
||||||
|
ModifiedOn = info.LastWriteTime,
|
||||||
|
CreatedOn = info.CreationTime,
|
||||||
|
Parent = Path
|
||||||
|
};
|
||||||
|
|
||||||
|
if (EntryVisible.Invoke(directory))
|
||||||
|
Entries.Add(directory);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var info = new FileInfo(entry);
|
||||||
|
var file = new FileManagerFile
|
||||||
|
{
|
||||||
|
Path = entry,
|
||||||
|
Name = System.IO.Path.GetFileName(entry),
|
||||||
|
ModifiedOn = info.LastWriteTime,
|
||||||
|
CreatedOn = info.CreationTime,
|
||||||
|
Size = info.Length,
|
||||||
|
Parent = Path
|
||||||
|
};
|
||||||
|
|
||||||
|
if (EntryVisible.Invoke(file))
|
||||||
|
Entries.Add(file);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task UpdateArchiveEntries()
|
||||||
|
{
|
||||||
|
var entries = await ArchiveService.GetContents(ArchiveId);
|
||||||
|
var separator = '/';
|
||||||
|
|
||||||
|
foreach (var entry in entries.Where(e => e.FullName != Path.Path && e.FullName.StartsWith(Path.Path) && !e.FullName.Substring(Path.Path.Length).TrimEnd(separator).Contains(separator)))
|
||||||
|
{
|
||||||
|
if (entry.FullName.EndsWith(separator))
|
||||||
|
{
|
||||||
|
var directory = new FileManagerDirectory
|
||||||
|
{
|
||||||
|
Path = entry.FullName,
|
||||||
|
Name = entry.Name,
|
||||||
|
ModifiedOn = entry.LastWriteTime.UtcDateTime.ToLocalTime(),
|
||||||
|
CreatedOn = entry.LastWriteTime.UtcDateTime.ToLocalTime(),
|
||||||
|
Size = entry.Length,
|
||||||
|
Parent = Path
|
||||||
|
};
|
||||||
|
|
||||||
|
if (EntryVisible.Invoke(directory))
|
||||||
|
Entries.Add(directory);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var file = new FileManagerFile
|
||||||
|
{
|
||||||
|
Path = entry.FullName,
|
||||||
|
Name = entry.Name,
|
||||||
|
ModifiedOn = entry.LastWriteTime.UtcDateTime.ToLocalTime(),
|
||||||
|
CreatedOn = entry.LastWriteTime.UtcDateTime.ToLocalTime(),
|
||||||
|
Size = entry.Length,
|
||||||
|
Parent = Path
|
||||||
|
};
|
||||||
|
|
||||||
|
if (EntryVisible.Invoke(file))
|
||||||
|
Entries.Add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateBreadcrumbs()
|
||||||
|
{
|
||||||
|
Breadcrumbs.Clear();
|
||||||
|
|
||||||
|
var currentPath = Path;
|
||||||
|
|
||||||
|
while (currentPath != null)
|
||||||
|
{
|
||||||
|
Breadcrumbs.Add(currentPath);
|
||||||
|
|
||||||
|
currentPath = currentPath.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
Breadcrumbs.Reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task NavigateBack()
|
||||||
|
{
|
||||||
|
if (Past.Count > 0)
|
||||||
|
{
|
||||||
|
Future.Add(Path);
|
||||||
|
await ChangeDirectory(Past.Last(), false);
|
||||||
|
Past = Past.Take(Past.Count - 1).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task NavigateForward()
|
||||||
|
{
|
||||||
|
if (Future.Count > 0)
|
||||||
|
{
|
||||||
|
Past.Add(Path);
|
||||||
|
await ChangeDirectory(Future.First(), false);
|
||||||
|
Future = Future.Skip(1).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task NavigateUp()
|
||||||
|
{
|
||||||
|
if (Path.Parent != null)
|
||||||
|
await ChangeDirectory(Path.Parent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Refresh()
|
||||||
|
{
|
||||||
|
await ChangeDirectory(Path, false);
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task AddFolder(string name)
|
||||||
|
{
|
||||||
|
if (Source == FileManagerSource.Archive)
|
||||||
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(System.IO.Path.Combine(Path.Path, name));
|
||||||
|
|
||||||
|
await Refresh();
|
||||||
|
|
||||||
|
await MessageService.Success("Folder created!");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await MessageService.Error("Error creating folder!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Delete()
|
||||||
|
{
|
||||||
|
if (Source == FileManagerSource.Archive)
|
||||||
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var entry in Selected)
|
||||||
|
{
|
||||||
|
if (entry is FileManagerDirectory)
|
||||||
|
Directory.Delete(entry.Path);
|
||||||
|
else if (entry is FileManagerFile)
|
||||||
|
File.Delete(entry.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Selected = new List<IFileManagerEntry>();
|
||||||
|
MessageService.Success("Deleted!");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
MessageService.Error("Error deleting!");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Refresh();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
using System.IO.Compression;
|
||||||
|
|
||||||
|
namespace LANCommander.Components.FileManagerComponents
|
||||||
|
{
|
||||||
|
public class FileManagerDirectory : FileManagerEntry
|
||||||
|
{
|
||||||
|
public bool IsExpanded { get; set; } = false;
|
||||||
|
public bool HasChildren => Children != null && Children.Count > 0;
|
||||||
|
public HashSet<FileManagerDirectory> Children { get; set; } = new HashSet<FileManagerDirectory>();
|
||||||
|
|
||||||
|
public void PopulateChildren(IEnumerable<ZipArchiveEntry> entries)
|
||||||
|
{
|
||||||
|
var path = Path == "/" ? "" : Path;
|
||||||
|
var childPaths = entries.Where(e => e.FullName.EndsWith('/'));
|
||||||
|
var directChildren = childPaths.Where(p => p.FullName != path && p.FullName.StartsWith(path) && p.FullName.Substring(path.Length).TrimEnd('/').Split('/').Length == 1);
|
||||||
|
|
||||||
|
foreach (var directChild in directChildren)
|
||||||
|
{
|
||||||
|
var child = new FileManagerDirectory()
|
||||||
|
{
|
||||||
|
Path = directChild.FullName,
|
||||||
|
Name = directChild.FullName.Substring(path.Length).TrimEnd('/'),
|
||||||
|
Parent = this
|
||||||
|
};
|
||||||
|
|
||||||
|
child.PopulateChildren(entries);
|
||||||
|
|
||||||
|
Children.Add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopulateChildren(IEnumerable<string> entries)
|
||||||
|
{
|
||||||
|
var separator = System.IO.Path.DirectorySeparatorChar;
|
||||||
|
var childPaths = entries.Where(e => e.StartsWith(Path));
|
||||||
|
var directChildren = childPaths.Where(p => p != Path && p.Substring(Path.Length + 1).Split(separator).Length == 1);
|
||||||
|
|
||||||
|
foreach (var directChild in directChildren)
|
||||||
|
{
|
||||||
|
if (!Children.Any(c => c.Path == directChild))
|
||||||
|
{
|
||||||
|
var child = new FileManagerDirectory()
|
||||||
|
{
|
||||||
|
Path = directChild,
|
||||||
|
Name = directChild.Substring(Path.Length).TrimStart(separator),
|
||||||
|
Parent = this
|
||||||
|
};
|
||||||
|
|
||||||
|
child.PopulateChildren(entries);
|
||||||
|
|
||||||
|
Children.Add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace LANCommander.Components.FileManagerComponents
|
||||||
|
{
|
||||||
|
public abstract class FileManagerEntry : IFileManagerEntry
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public FileManagerDirectory Parent { get; set; }
|
||||||
|
public DateTime ModifiedOn { get; set; }
|
||||||
|
public DateTime CreatedOn { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace LANCommander.Components.FileManagerComponents
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum FileManagerFeatures
|
||||||
|
{
|
||||||
|
NavigationBack = 0,
|
||||||
|
NavigationForward = 1,
|
||||||
|
UpALevel = 2,
|
||||||
|
Refresh = 4,
|
||||||
|
Breadcrumbs = 8,
|
||||||
|
NewFolder = 16,
|
||||||
|
UploadFile = 32,
|
||||||
|
Delete = 64,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
namespace LANCommander.Components.FileManagerComponents
|
||||||
|
{
|
||||||
|
public class FileManagerFile : FileManagerEntry
|
||||||
|
{
|
||||||
|
public string Extension => Name.Contains('.') ? Name.Split('.').Last() : Name;
|
||||||
|
|
||||||
|
public string GetIcon()
|
||||||
|
{
|
||||||
|
switch (Extension)
|
||||||
|
{
|
||||||
|
case "":
|
||||||
|
return "folder";
|
||||||
|
|
||||||
|
case "exe":
|
||||||
|
return "code";
|
||||||
|
|
||||||
|
case "zip":
|
||||||
|
case "rar":
|
||||||
|
case "7z":
|
||||||
|
case "gz":
|
||||||
|
case "tar":
|
||||||
|
return "file-zip";
|
||||||
|
|
||||||
|
case "wad":
|
||||||
|
case "pk3":
|
||||||
|
case "pak":
|
||||||
|
case "cab":
|
||||||
|
return "file-zip";
|
||||||
|
|
||||||
|
case "txt":
|
||||||
|
case "cfg":
|
||||||
|
case "config":
|
||||||
|
case "ini":
|
||||||
|
case "yml":
|
||||||
|
case "yaml":
|
||||||
|
case "log":
|
||||||
|
case "doc":
|
||||||
|
case "nfo":
|
||||||
|
return "file-text";
|
||||||
|
|
||||||
|
case "bat":
|
||||||
|
case "ps1":
|
||||||
|
case "json":
|
||||||
|
return "code";
|
||||||
|
|
||||||
|
case "bik":
|
||||||
|
case "avi":
|
||||||
|
case "mov":
|
||||||
|
case "mp4":
|
||||||
|
case "m4v":
|
||||||
|
case "mkv":
|
||||||
|
case "wmv":
|
||||||
|
case "mpg":
|
||||||
|
case "mpeg":
|
||||||
|
case "flv":
|
||||||
|
return "video-camera";
|
||||||
|
|
||||||
|
case "dll":
|
||||||
|
return "api";
|
||||||
|
|
||||||
|
case "hlp":
|
||||||
|
return "file-unknown";
|
||||||
|
|
||||||
|
case "png":
|
||||||
|
case "bmp":
|
||||||
|
case "jpeg":
|
||||||
|
case "jpg":
|
||||||
|
case "gif":
|
||||||
|
return "file-image";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "file";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace LANCommander.Components.FileManagerComponents
|
||||||
|
{
|
||||||
|
public enum FileManagerSource
|
||||||
|
{
|
||||||
|
FileSystem,
|
||||||
|
Archive
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace LANCommander.Components.FileManagerComponents
|
||||||
|
{
|
||||||
|
public interface IFileManagerEntry
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public FileManagerDirectory Parent { get; set; }
|
||||||
|
public DateTime ModifiedOn { get; set; }
|
||||||
|
public DateTime CreatedOn { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<Modal Title="New Folder" Visible="@Visible" Draggable="true" DragInViewport="false" OnOk="OnOk" OnCancel="Close">
|
||||||
|
<Input @bind-Value="@Name" />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public EventCallback<string> OnFolderNameEntered { get; set; }
|
||||||
|
|
||||||
|
bool Visible { get; set; } = false;
|
||||||
|
string Name { get; set; }
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
Name = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Open()
|
||||||
|
{
|
||||||
|
Name = "";
|
||||||
|
Visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task OnOk(MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (OnFolderNameEntered.HasDelegate)
|
||||||
|
await OnFolderNameEntered.InvokeAsync(Name);
|
||||||
|
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<Modal Title="Upload Files" Visible="@Visible" Draggable="true" DragInViewport="false" OnCancel="Close">
|
||||||
|
<Upload Action="/Upload/File" Name="file" Drag Multiple Data="Data" OnCompleted="OnCompleted">
|
||||||
|
<p class="ant-upload-drag-icon">
|
||||||
|
<Icon Type="@IconType.Outline.Upload" />
|
||||||
|
</p>
|
||||||
|
<p class="ant-upload-text">Click or Drag Files</p>
|
||||||
|
</Upload>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public string Path { get; set; }
|
||||||
|
[Parameter] public EventCallback OnUploadCompleted { get; set; }
|
||||||
|
|
||||||
|
bool Visible = false;
|
||||||
|
|
||||||
|
Dictionary<string, object> Data = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
Data["Path"] = Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Open()
|
||||||
|
{
|
||||||
|
Visible = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
Visible = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task OnCompleted()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
|
||||||
|
if (OnUploadCompleted.HasDelegate)
|
||||||
|
await OnUploadCompleted.InvokeAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
@using LANCommander.Components.FileManagerComponents;
|
||||||
|
@using LANCommander.Models;
|
||||||
|
@using System.IO.Compression;
|
||||||
|
@inject ModalService ModalService
|
||||||
|
@inject ArchiveService ArchiveService
|
||||||
|
|
||||||
|
<Space Style="display: flex">
|
||||||
|
<SpaceItem Style="flex-grow: 1">
|
||||||
|
<Input Type="text" @bind-Value="Value" OnChange="ValueChanged" />
|
||||||
|
</SpaceItem>
|
||||||
|
@if (ArchiveId != Guid.Empty) {
|
||||||
|
<SpaceItem>
|
||||||
|
<Button OnClick="BrowseForFile" Type="@ButtonType.Primary" Icon="@IconType.Outline.FolderOpen" Disabled="!ArchiveExists" />
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
|
else if (!String.IsNullOrWhiteSpace(Root))
|
||||||
|
{
|
||||||
|
<SpaceItem>
|
||||||
|
<Button OnClick="BrowseForFile" Type="@ButtonType.Primary" Icon="@IconType.Outline.FolderOpen" />
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public string Value { get; set; }
|
||||||
|
[Parameter] public EventCallback<string> ValueChanged { get; set; }
|
||||||
|
[Parameter] public Guid ArchiveId { get; set; }
|
||||||
|
[Parameter] public string Title { get; set; } = "Choose File";
|
||||||
|
[Parameter] public string OkText { get; set; } = "Select File";
|
||||||
|
[Parameter] public bool AllowDirectories { get; set; } = false;
|
||||||
|
[Parameter] public string Prefix { get; set; }
|
||||||
|
[Parameter] public string Root { get; set; }
|
||||||
|
[Parameter] public Func<IFileManagerEntry, bool> EntrySelectable { get; set; } = _ => true;
|
||||||
|
[Parameter] public Func<IFileManagerEntry, bool> EntryVisible { get; set; } = _ => true;
|
||||||
|
|
||||||
|
bool ArchiveExists { get; set; } = false;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (ArchiveId != Guid.Empty)
|
||||||
|
ArchiveExists = await ArchiveService.Exists(ArchiveId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BrowseForFile()
|
||||||
|
{
|
||||||
|
var modalOptions = new ModalOptions()
|
||||||
|
{
|
||||||
|
Title = Title,
|
||||||
|
Maximizable = false,
|
||||||
|
DefaultMaximized = true,
|
||||||
|
Closable = true,
|
||||||
|
OkText = OkText,
|
||||||
|
WrapClassName = "file-picker-dialog"
|
||||||
|
};
|
||||||
|
|
||||||
|
var browserOptions = new FilePickerOptions()
|
||||||
|
{
|
||||||
|
ArchiveId = ArchiveId,
|
||||||
|
Root = Root,
|
||||||
|
Select = true,
|
||||||
|
Multiple = false,
|
||||||
|
EntrySelectable = EntrySelectable,
|
||||||
|
EntryVisible = EntryVisible
|
||||||
|
};
|
||||||
|
|
||||||
|
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
|
||||||
|
|
||||||
|
modalRef.OnOk = async (results) =>
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrWhiteSpace(Prefix))
|
||||||
|
Value = Prefix + results?.FirstOrDefault()?.Path;
|
||||||
|
else
|
||||||
|
Value = results?.FirstOrDefault()?.Path;
|
||||||
|
|
||||||
|
if (ValueChanged.HasDelegate)
|
||||||
|
await ValueChanged.InvokeAsync(Value);
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
@inherits FeedbackComponent<FilePickerOptions, IEnumerable<IFileManagerEntry>>
|
||||||
|
@using System.IO.Compression;
|
||||||
|
@using LANCommander.Components.FileManagerComponents;
|
||||||
|
@using LANCommander.Models;
|
||||||
|
|
||||||
|
<FileManager ArchiveId="@Options.ArchiveId" WorkingDirectory="@Options.Root" @bind-Selected="SelectedFiles" EntrySelectable="Options.EntrySelectable" EntryVisible="Options.EntryVisible" SelectMultiple="Options.Multiple" Features="@(FileManagerFeatures.NavigationBack | FileManagerFeatures.NavigationForward | FileManagerFeatures.UpALevel | FileManagerFeatures.Breadcrumbs)" />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
private IEnumerable<IFileManagerEntry> SelectedFiles { get; set; }
|
||||||
|
|
||||||
|
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
|
||||||
|
{
|
||||||
|
await base.OkCancelRefWithResult!.OnOk(SelectedFiles);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,63 +0,0 @@
|
||||||
@using LANCommander.Models;
|
|
||||||
@using System.IO.Compression;
|
|
||||||
@inject ModalService ModalService
|
|
||||||
@inject ArchiveService ArchiveService
|
|
||||||
|
|
||||||
<Space Style="display: flex">
|
|
||||||
<SpaceItem Style="flex-grow: 1">
|
|
||||||
<Input Type="text" @bind-Value="Value" OnChange="ValueChanged" />
|
|
||||||
</SpaceItem>
|
|
||||||
@if (ArchiveId != Guid.Empty) {
|
|
||||||
<SpaceItem>
|
|
||||||
<Button OnClick="() => BrowseForFile()" Type="@ButtonType.Primary" Icon="@IconType.Outline.FolderOpen" Disabled="!ArchiveExists" />
|
|
||||||
</SpaceItem>
|
|
||||||
}
|
|
||||||
</Space>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public string Value { get; set; }
|
|
||||||
[Parameter] public EventCallback<string> ValueChanged { get; set; }
|
|
||||||
[Parameter] public Guid ArchiveId { get; set; }
|
|
||||||
[Parameter] public string ArchiveBrowserTitle { get; set; } = "Choose File";
|
|
||||||
[Parameter] public bool AllowDirectories { get; set; } = false;
|
|
||||||
|
|
||||||
bool ArchiveExists { get; set; } = false;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
if (ArchiveId != Guid.Empty)
|
|
||||||
ArchiveExists = await ArchiveService.Exists(ArchiveId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void BrowseForFile()
|
|
||||||
{
|
|
||||||
var modalOptions = new ModalOptions()
|
|
||||||
{
|
|
||||||
Title = ArchiveBrowserTitle,
|
|
||||||
Maximizable = false,
|
|
||||||
DefaultMaximized = true,
|
|
||||||
Closable = true,
|
|
||||||
OkText = "Select File"
|
|
||||||
};
|
|
||||||
|
|
||||||
var browserOptions = new ArchiveBrowserOptions()
|
|
||||||
{
|
|
||||||
ArchiveId = ArchiveId,
|
|
||||||
Select = true,
|
|
||||||
Multiple = false,
|
|
||||||
AllowDirectories = AllowDirectories
|
|
||||||
};
|
|
||||||
|
|
||||||
var modalRef = await ModalService.CreateModalAsync<ArchiveBrowserDialog, ArchiveBrowserOptions, IEnumerable<ZipArchiveEntry>>(modalOptions, browserOptions);
|
|
||||||
|
|
||||||
modalRef.OnOk = async (results) =>
|
|
||||||
{
|
|
||||||
Value = "{InstallDir}/" + results.FirstOrDefault().FullName;
|
|
||||||
|
|
||||||
if (ValueChanged.HasDelegate)
|
|
||||||
await ValueChanged.InvokeAsync(Value);
|
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,12 +2,15 @@
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
namespace LANCommander.Controllers
|
namespace LANCommander.Controllers
|
||||||
{
|
{
|
||||||
[Authorize(Roles = "Administrator")]
|
[Authorize(Roles = "Administrator")]
|
||||||
public class UploadController : Controller
|
public class UploadController : Controller
|
||||||
{
|
{
|
||||||
|
protected readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private const string UploadDirectory = "Upload";
|
private const string UploadDirectory = "Upload";
|
||||||
|
|
||||||
public JsonResult Init()
|
public JsonResult Init()
|
||||||
|
@ -52,5 +55,30 @@ namespace LANCommander.Controllers
|
||||||
|
|
||||||
return Json("Done!");
|
return Json("Done!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> File(IFormFile file, string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
return BadRequest("Destination path does not exist.");
|
||||||
|
|
||||||
|
path = Path.Combine(path, file.FileName);
|
||||||
|
|
||||||
|
using (var fileStream = System.IO.File.OpenWrite(path))
|
||||||
|
{
|
||||||
|
await file.CopyToAsync(fileStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error(ex, "An error occurred while uploading the file");
|
||||||
|
|
||||||
|
return BadRequest("An error occurred while uploading the file.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
namespace LANCommander.Models
|
|
||||||
{
|
|
||||||
public class ArchiveBrowserOptions
|
|
||||||
{
|
|
||||||
public Guid ArchiveId { get; set; }
|
|
||||||
public bool Select { get; set; }
|
|
||||||
public bool Multiple { get; set; } = false;
|
|
||||||
public bool AllowDirectories { get; set; } = false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
using LANCommander.Components.FileManagerComponents;
|
||||||
|
|
||||||
|
namespace LANCommander.Models
|
||||||
|
{
|
||||||
|
public class FilePickerOptions
|
||||||
|
{
|
||||||
|
public Guid ArchiveId { get; set; }
|
||||||
|
public string Root { get; set; }
|
||||||
|
public bool Select { get; set; }
|
||||||
|
public bool Multiple { get; set; } = false;
|
||||||
|
public Func<IFileManagerEntry, bool> EntryVisible { get; set; } = _ => true;
|
||||||
|
public Func<IFileManagerEntry, bool> EntrySelectable { get; set; } = _ => true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
@page "/Files"
|
||||||
|
@using LANCommander.Components.FileManagerComponents
|
||||||
|
|
||||||
|
<FileManager WorkingDirectory="@RootPath" />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
string RootPath = Path.GetPathRoot(Directory.GetCurrentDirectory());
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
@using LANCommander.Data.Models
|
@using LANCommander.Components.FileManagerComponents;
|
||||||
|
@using LANCommander.Data.Models
|
||||||
@using LANCommander.Extensions
|
@using LANCommander.Extensions
|
||||||
@using LANCommander.Models;
|
@using LANCommander.Models;
|
||||||
@using System.IO.Compression;
|
@using System.IO.Compression;
|
||||||
|
@ -11,7 +12,7 @@
|
||||||
<Input Type="text" @bind-Value="context.Name" />
|
<Input Type="text" @bind-Value="context.Name" />
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="a => a.Path">
|
<PropertyColumn Property="a => a.Path">
|
||||||
<InputArchiveFile @bind-Value="context.Path" ArchiveId="@ArchiveId" AllowDirectories="true" />
|
<FilePicker @bind-Value="context.Path" ArchiveId="@ArchiveId" AllowDirectories="true" />
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="a => a.Arguments">
|
<PropertyColumn Property="a => a.Arguments">
|
||||||
<Input Type="text" @bind-Value="context.Arguments" />
|
<Input Type="text" @bind-Value="context.Arguments" />
|
||||||
|
@ -100,18 +101,18 @@
|
||||||
OkText = "Select File"
|
OkText = "Select File"
|
||||||
};
|
};
|
||||||
|
|
||||||
var browserOptions = new ArchiveBrowserOptions()
|
var browserOptions = new FilePickerOptions()
|
||||||
{
|
{
|
||||||
ArchiveId = ArchiveId,
|
ArchiveId = ArchiveId,
|
||||||
Select = true,
|
Select = true,
|
||||||
Multiple = false
|
Multiple = false
|
||||||
};
|
};
|
||||||
|
|
||||||
var modalRef = await ModalService.CreateModalAsync<ArchiveBrowserDialog, ArchiveBrowserOptions, IEnumerable<ZipArchiveEntry>>(modalOptions, browserOptions);
|
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
|
||||||
|
|
||||||
modalRef.OnOk = (results) =>
|
modalRef.OnOk = (results) =>
|
||||||
{
|
{
|
||||||
action.Path = results.FirstOrDefault().FullName;
|
action.Path = results.FirstOrDefault().Path;
|
||||||
|
|
||||||
var parts = action.Path.Split('/');
|
var parts = action.Path.Split('/');
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<InputArchiveFile @bind-Value="context.Path" ArchiveId="@ArchiveId" AllowDirectories="true" />
|
<FilePicker @bind-Value="context.Path" ArchiveId="@ArchiveId" AllowDirectories="true" />
|
||||||
}
|
}
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<ActionColumn>
|
<ActionColumn>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@using LANCommander.Data.Enums;
|
@using LANCommander.Components.FileManagerComponents;
|
||||||
|
@using LANCommander.Data.Enums;
|
||||||
@using LANCommander.Extensions;
|
@using LANCommander.Extensions;
|
||||||
@using LANCommander.Models
|
@using LANCommander.Models
|
||||||
@using LANCommander.Services
|
@using LANCommander.Services
|
||||||
|
@ -213,18 +214,18 @@
|
||||||
OkText = "Insert File Path"
|
OkText = "Insert File Path"
|
||||||
};
|
};
|
||||||
|
|
||||||
var browserOptions = new ArchiveBrowserOptions()
|
var browserOptions = new FilePickerOptions()
|
||||||
{
|
{
|
||||||
ArchiveId = ArchiveId,
|
ArchiveId = ArchiveId,
|
||||||
Select = true,
|
Select = true,
|
||||||
Multiple = false
|
Multiple = false
|
||||||
};
|
};
|
||||||
|
|
||||||
var modalRef = await ModalService.CreateModalAsync<ArchiveBrowserDialog, ArchiveBrowserOptions, IEnumerable<ZipArchiveEntry>>(modalOptions, browserOptions);
|
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
|
||||||
|
|
||||||
modalRef.OnOk = (results) =>
|
modalRef.OnOk = (results) =>
|
||||||
{
|
{
|
||||||
var path = results.FirstOrDefault().FullName;
|
var path = results.FirstOrDefault().Path;
|
||||||
|
|
||||||
Editor.Trigger("keyboard", "type", new
|
Editor.Trigger("keyboard", "type", new
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
@page "/Games/{id:guid}"
|
@page "/Games/{id:guid}"
|
||||||
@page "/Games/{id:guid}/{panel}"
|
@page "/Games/{id:guid}/{panel}"
|
||||||
@page "/Games/Add"
|
@page "/Games/Add"
|
||||||
|
@using LANCommander.Components.FileManagerComponents;
|
||||||
@using LANCommander.Models;
|
@using LANCommander.Models;
|
||||||
@using LANCommander.Pages.Games.Components
|
@using LANCommander.Pages.Games.Components
|
||||||
@using System.IO.Compression;
|
@using System.IO.Compression;
|
||||||
|
@ -70,7 +71,7 @@
|
||||||
<Input @bind-Value="@context.SortTitle" />
|
<Input @bind-Value="@context.SortTitle" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="Icon">
|
<FormItem Label="Icon">
|
||||||
<InputArchiveFile @bind-Value="context.Icon" ArchiveId="@LatestArchiveId" AllowDirectories="true" />
|
<FilePicker @bind-Value="context.Icon" ArchiveId="@LatestArchiveId" AllowDirectories="true" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="Notes">
|
<FormItem Label="Notes">
|
||||||
<TextArea @bind-Value="@context.Notes" MaxLength=2000 ShowCount />
|
<TextArea @bind-Value="@context.Notes" MaxLength=2000 ShowCount />
|
||||||
|
@ -160,7 +161,7 @@ else
|
||||||
IEnumerable<Genre> Genres;
|
IEnumerable<Genre> Genres;
|
||||||
IEnumerable<Data.Models.Tag> Tags;
|
IEnumerable<Data.Models.Tag> Tags;
|
||||||
|
|
||||||
ArchiveBrowserDialog ArchiveBrowserDialog;
|
FilePickerDialog ArchiveFilePickerDialog;
|
||||||
|
|
||||||
Modal FileSelectorModal;
|
Modal FileSelectorModal;
|
||||||
|
|
||||||
|
@ -250,18 +251,18 @@ else
|
||||||
OkText = "Select File"
|
OkText = "Select File"
|
||||||
};
|
};
|
||||||
|
|
||||||
var browserOptions = new ArchiveBrowserOptions()
|
var browserOptions = new FilePickerOptions()
|
||||||
{
|
{
|
||||||
ArchiveId = Game.Archives.FirstOrDefault().Id,
|
ArchiveId = Game.Archives.FirstOrDefault().Id,
|
||||||
Select = true,
|
Select = true,
|
||||||
Multiple = false
|
Multiple = false
|
||||||
};
|
};
|
||||||
|
|
||||||
var modalRef = await ModalService.CreateModalAsync<ArchiveBrowserDialog, ArchiveBrowserOptions, IEnumerable<ZipArchiveEntry>>(modalOptions, browserOptions);
|
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
|
||||||
|
|
||||||
modalRef.OnOk = (results) =>
|
modalRef.OnOk = (results) =>
|
||||||
{
|
{
|
||||||
Game.Icon = results.FirstOrDefault().FullName;
|
Game.Icon = results.FirstOrDefault().Path;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
@page "/Servers/{id:guid}/{panel}"
|
@page "/Servers/{id:guid}/{panel}"
|
||||||
@page "/Servers/{id:guid}/{panel}/{logId}"
|
@page "/Servers/{id:guid}/{panel}/{logId}"
|
||||||
@page "/Servers/Add"
|
@page "/Servers/Add"
|
||||||
|
@using LANCommander.Components.FileManagerComponents;
|
||||||
@using LANCommander.Pages.Servers.Components
|
@using LANCommander.Pages.Servers.Components
|
||||||
@inject GameService GameService
|
@inject GameService GameService
|
||||||
@inject ServerService ServerService
|
@inject ServerService ServerService
|
||||||
|
@ -72,13 +73,13 @@
|
||||||
</Select>
|
</Select>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="Path">
|
<FormItem Label="Path">
|
||||||
<Input @bind-Value="@context.Path" />
|
<FilePicker Root="@RootPath" EntrySelectable="x => x is FileManagerFile" @bind-Value="@context.Path" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="Arguments">
|
<FormItem Label="Arguments">
|
||||||
<Input @bind-Value="@context.Arguments" />
|
<Input @bind-Value="@context.Arguments" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="Working Directory">
|
<FormItem Label="Working Directory">
|
||||||
<Input @bind-Value="@context.WorkingDirectory" />
|
<FilePicker Root="@RootPath" Title="Choose Working Directory" OkText="Select Directory" EntrySelectable="x => x is FileManagerDirectory" @bind-Value="@context.WorkingDirectory" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<LabelTemplate>
|
<LabelTemplate>
|
||||||
|
@ -147,6 +148,8 @@
|
||||||
|
|
||||||
IEnumerable<Game> Games = new List<Game>();
|
IEnumerable<Game> Games = new List<Game>();
|
||||||
|
|
||||||
|
string RootPath = Path.GetPathRoot(Directory.GetCurrentDirectory());
|
||||||
|
|
||||||
Server Server;
|
Server Server;
|
||||||
Guid GameId;
|
Guid GameId;
|
||||||
|
|
||||||
|
@ -157,9 +160,6 @@
|
||||||
else
|
else
|
||||||
Server = await ServerService.Get(Id);
|
Server = await ServerService.Get(Id);
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(Server.WorkingDirectory))
|
|
||||||
Server.WorkingDirectory = "C:\\";
|
|
||||||
|
|
||||||
if (Server.GameId.HasValue)
|
if (Server.GameId.HasValue)
|
||||||
GameId = Server.GameId.Value;
|
GameId = Server.GameId.Value;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<MenuItem RouterLink="/Dashboard">Dashboard</MenuItem>
|
<MenuItem RouterLink="/Dashboard">Dashboard</MenuItem>
|
||||||
<MenuItem RouterLink="/Games">Games</MenuItem>
|
<MenuItem RouterLink="/Games">Games</MenuItem>
|
||||||
<MenuItem RouterLink="/Servers">Servers</MenuItem>
|
<MenuItem RouterLink="/Servers">Servers</MenuItem>
|
||||||
|
<MenuItem RouterLink="/Files">Files</MenuItem>
|
||||||
<MenuItem RouterLink="/Settings">Settings</MenuItem>
|
<MenuItem RouterLink="/Settings">Settings</MenuItem>
|
||||||
</MainMenu>
|
</MainMenu>
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,109 @@
|
||||||
right: 16px;
|
right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-manager {
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager-nav {
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
height: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager-nav .ant-space {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager-nav-breadcrumbs {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager-nav .ant-breadcrumb {
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 8px;
|
||||||
|
background: rgba(0, 0, 0, 0.018);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager-nav .ant-breadcrumb-link {
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager-nav .ant-breadcrumb-link:hover,
|
||||||
|
.file-manager-nav .ant-breadcrumb-link:active {
|
||||||
|
background: rgba(0, 0, 0, 0.018);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager .file-manager-body {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager .ant-tree-treenode {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager .ant-tree-treenode:hover,
|
||||||
|
.file-manager .ant-tree-treenode:active {
|
||||||
|
background: rgba(0, 0, 0, 0.018);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager-tree {
|
||||||
|
padding-top: 8px;
|
||||||
|
border-right: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager .ant-tree-node-content-wrapper {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager .ant-tree-node-content-wrapper:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager .ant-tree-switcher .ant-tree-switcher-icon {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-picker-dialog .ant-modal-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-picker-dialog .file-manager {
|
||||||
|
height: calc(100vh - 55px - 53px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-manager-selector-hidden .ant-radio-wrapper {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="Dark"] .file-manager,
|
||||||
|
[data-theme="Dark"] .file-manager-list,
|
||||||
|
[data-theme="Dark"] .file-picker-dialog .file-manager tbody tr {
|
||||||
|
background: #141414;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="Dark"] .file-manager-tree,
|
||||||
|
[data-theme="Dark"] .file-manager-nav {
|
||||||
|
border-color: #303030;
|
||||||
|
background: #141414;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="Dark"] .file-manager .ant-tree-treenode:hover,
|
||||||
|
[data-theme="Dark"] .file-manager .ant-tree-treenode:active,
|
||||||
|
[data-theme="Dark"] .file-manager-nav .ant-breadcrumb-link:hover,
|
||||||
|
[data-theme="Dark"] .file-manager-nav .ant-breadcrumb-link:active {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.mobile-menu {
|
.mobile-menu {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
Loading…
Reference in New Issue