Files
Moonlight/Moonlight/Shared/Components/FileManagerPartials/FileManager.razor

379 lines
12 KiB
Plaintext

@using Moonlight.App.Helpers.Files
@using Moonlight.App.Helpers
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using BlazorDownloadFile
@inject ToastService ToastService
@inject NavigationManager NavigationManager
@inject AlertService AlertService
@inject SmartTranslateService SmartTranslateService
@inject IBlazorDownloadFileService FileService
@if (Editing)
{
<FileEditor @ref="Editor"
InitialData="@EditorInitialData"
Language="@EditorLanguage"
OnCancel="() => Cancel()"
OnSubmit="(_) => Save()"
HideControls="false"
ShowHeader="true"
Header="@(EditingFile.Name)">
</FileEditor>
}
else
{
<div class="card mb-7">
<div class="card-header border-0 my-2">
<div class="card-title">
<div class="d-flex flex-stack">
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged"/>
</div>
</div>
<div class="card-toolbar">
<div class="d-flex justify-content-end align-items-center">
@if (View != null && View.SelectedFiles.Any())
{
<div class="fw-bold me-5">
<span class="me-2">
@(View.SelectedFiles.Length) <TL>selected</TL>
</span>
</div>
<WButton Text="@(SmartTranslateService.Translate("Move"))"
WorkingText="@(SmartTranslateService.Translate("Moving"))"
CssClasses="btn-primary me-3"
OnClick="StartMoveFiles">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Compress"))"
WorkingText="@(SmartTranslateService.Translate("Compressing"))"
CssClasses="btn-primary me-3"
OnClick="CompressMultiple">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-danger"
OnClick="DeleteMultiple">
</WButton>
}
else
{
<div class="btn-group me-3">
<button type="button" @onclick="Launch" class="btn btn-light-primary">
<TL>Launch WinSCP</TL>
</button>
<button type="button" class="btn btn-light-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item btn" target="_blank" href="https://winscp.net/eng/downloads.php">
<TL>Download WinSCP</TL>
</a>
</li>
<li>
<button class="dropdown-item btn" @onclick="() => ConnectionDetailsModal.Show()">
<TL>Show connection details</TL>
</button>
</li>
</ul>
</div>
<div class="btn-group me-3">
<button type="button" class="btn btn-light-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<TL>New</TL>&nbsp;
</button>
<ul class="dropdown-menu">
<li>
<button @onclick="CreateFile" class="dropdown-item btn">
<TL>New file</TL>
</button>
</li>
<li>
<button @onclick="CreateFolder" class="dropdown-item btn">
<TL>New folder</TL>
</button>
</li>
</ul>
</div>
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged"/>
}
</div>
</div>
</div>
</div>
<div class="card card-body ps-9">
<FileView @ref="View"
Access="Access"
ContextActions="Actions"
OnSelectionChanged="OnSelectionChanged"
OnElementClicked="OnElementClicked"
DisableScrolling="true">
</FileView>
</div>
<FileSelectModal @ref="FileSelectModal"
OnlyFolder="true"
Title="@(SmartTranslateService.Translate("Select folder to move the file(s) to"))"
Access="MoveAccess"
OnSubmit="OnFileMoveSubmit">
</FileSelectModal>
<ConnectionDetailsModal @ref="ConnectionDetailsModal" Access="Access"/>
}
@code
{
[Parameter]
public FileAccess Access { get; set; }
// File Editor
private bool Editing = false;
private string EditorInitialData = "";
private string EditorLanguage = "";
private FileData EditingFile;
private FileEditor Editor;
// File View
private FileView? View;
// File Move
private FileAccess MoveAccess;
private FileSelectModal FileSelectModal;
private FileData? SingleMoveFile = null;
// Config
private ContextAction[] Actions = Array.Empty<ContextAction>();
// Connection details
private ConnectionDetailsModal ConnectionDetailsModal;
protected override void OnInitialized()
{
MoveAccess = (FileAccess)Access.Clone();
List<ContextAction> actions = new();
actions.Add(new()
{
Id = "rename",
Name = "Rename",
Action = async (x) =>
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Rename"),
SmartTranslateService.Translate("Enter a new name"),
x.Name
);
if (name != x.Name)
{
await Access.Move(x, Access.CurrentPath + name);
}
await View!.Refresh();
}
});
actions.Add(new()
{
Id = "download",
Name = "Download",
Action = async (x) =>
{
if (x.IsFile)
{
try
{
var stream = await Access.DownloadStream(x);
await ToastService.Info(SmartTranslateService.Translate("Starting download"));
stream.Position = 0;
await FileService.DownloadFile(fileName: x.Name, stream: stream, contentType: "application/octet-stream");
}
catch (NotImplementedException)
{
var url = await Access.DownloadUrl(x);
NavigationManager.NavigateTo(url, true);
await ToastService.Info(SmartTranslateService.Translate("Starting download"));
}
}
else
await ToastService.Error(SmartTranslateService.Translate("You are not able to download folders using the moonlight file manager"));
}
});
actions.Add(new()
{
Id = "compress",
Name = "Compress",
Action = async (x) =>
{
await Access.Compress(x);
await View!.Refresh();
}
});
actions.Add(new()
{
Id = "decompress",
Name = "Decompress",
Action = async (x) =>
{
await Access.Decompress(x);
await View!.Refresh();
}
});
actions.Add(new()
{
Id = "move",
Name = "Move",
Action = async (x) =>
{
SingleMoveFile = x;
await StartMoveFiles();
}
});
actions.Add(new()
{
Id = "delete",
Name = "Delete",
Action = async (x) =>
{
await Access.Delete(x);
await View!.Refresh();
}
});
Actions = actions.ToArray();
}
private async Task<bool> OnElementClicked(FileData fileData)
{
if (fileData.IsFile)
{
EditorInitialData = await Access.Read(fileData);
EditorLanguage = MonacoTypeHelper.GetEditorType(fileData.Name);
EditingFile = fileData;
Editing = true;
await InvokeAsync(StateHasChanged);
return true;
}
await InvokeAsync(StateHasChanged);
return false;
}
private async void Save()
{
var data = await Editor.GetData();
await Access.Write(EditingFile, data);
await ToastService.Success(SmartTranslateService.Translate("Successfully saved file"));
}
private async void Cancel(bool save = false)
{
if (save)
{
var data = await Editor.GetData();
await Access.Write(EditingFile, data);
}
Editing = false;
await InvokeAsync(StateHasChanged);
}
private async Task Launch()
{
var url = await Access.GetLaunchUrl();
NavigationManager.NavigateTo(url, true);
}
private async Task CreateFolder()
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Create a new folder"),
SmartTranslateService.Translate("Enter a name"),
""
);
if (string.IsNullOrEmpty(name))
return;
await Access.MkDir(name);
await View!.Refresh();
}
private async Task CreateFile()
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Create a new file"),
SmartTranslateService.Translate("Enter a name"),
""
);
if (string.IsNullOrEmpty(name))
return;
await Access.Write(new FileData { IsFile = true, Name = name }, "");
await View!.Refresh();
}
private async Task OnSelectionChanged()
{
await InvokeAsync(StateHasChanged);
}
private async Task StartMoveFiles()
{
await FileSelectModal.Show();
}
private async Task DeleteMultiple()
{
foreach (var data in View!.SelectedFiles)
{
await Access.Delete(data);
}
await View!.Refresh();
}
private async Task CompressMultiple()
{
await Access.Compress(View!.SelectedFiles);
await View!.Refresh();
}
private async Task OnFileMoveSubmit(string path)
{
foreach (var sFile in View!.SelectedFiles)
{
await Access.Move(sFile, path + sFile.Name);
}
if (SingleMoveFile != null)
{
await Access.Move(SingleMoveFile, path + SingleMoveFile.Name);
SingleMoveFile = null;
}
await View.Refresh();
}
// This method can be called by every component to refresh the view
private async Task OnComponentStateChanged()
{
await View!.Refresh();
await InvokeAsync(StateHasChanged);
}
}