557 lines
21 KiB
Plaintext
557 lines
21 KiB
Plaintext
@namespace Oqtane.Modules.Controls
|
|
@using System.Threading
|
|
@inherits ModuleControlBase
|
|
@inject IFolderService FolderService
|
|
@inject IFileService FileService
|
|
@inject ISettingService SettingService
|
|
@inject IUserService UserService
|
|
@inject IStringLocalizer<FileManager> Localizer
|
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
|
|
|
@if (_initialized)
|
|
{
|
|
<div id="@Id" class="container-fluid px-0">
|
|
<div class="row">
|
|
<div class="col">
|
|
<div class="container-fluid px-0">
|
|
@if (ShowFolders)
|
|
{
|
|
<div class="row">
|
|
<div class="col">
|
|
<select class="form-select" value="@FolderId" @onchange="(e => FolderChanged(e))">
|
|
<option value="-1"><@Localizer["Folder.Select"]></option>
|
|
@foreach (Folder folder in _folders)
|
|
{
|
|
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
}
|
|
@if (ShowFiles)
|
|
{
|
|
<div class="row mt-1">
|
|
<div class="col">
|
|
<select class="form-select" value="@FileId" @onchange="(e => FileChanged(e))">
|
|
<option value="-1"><@Localizer["File.Select"]></option>
|
|
@foreach (File file in _files)
|
|
{
|
|
<option value="@(file.FileId)">@(file.Name)</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
if (FileId != -1 && _file != null && !UploadMultiple)
|
|
{
|
|
<input class="form-control" @bind="@_file.Name" disabled />
|
|
}
|
|
}
|
|
@if (ShowUpload && _haseditpermission)
|
|
{
|
|
<div class="row mt-2">
|
|
<div class="col">
|
|
@if (UploadMultiple)
|
|
{
|
|
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
|
|
}
|
|
else
|
|
{
|
|
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
|
}
|
|
</div>
|
|
<div class="col-auto">
|
|
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
|
|
@if (FileId != -1 && !UploadMultiple)
|
|
{
|
|
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
@if (ShowProgress)
|
|
{
|
|
<div class="row">
|
|
<div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
|
|
<div class="col mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
if (_uploading)
|
|
{
|
|
<div class="app-progress-indicator"></div>
|
|
}
|
|
}
|
|
}
|
|
@if (!string.IsNullOrEmpty(_message))
|
|
{
|
|
<div class="row mt-1">
|
|
<div class="col">
|
|
<ModuleMessage Message="@_message" Type="@_messagetype" />
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
@if (_image != string.Empty)
|
|
{
|
|
<div class="col-auto">
|
|
@((MarkupString) _image)
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@code {
|
|
private bool _initialized = false;
|
|
private List<Folder> _folders;
|
|
private List<File> _files = new List<File>();
|
|
private string _fileinputid = string.Empty;
|
|
private string _progressinfoid = string.Empty;
|
|
private string _progressbarid = string.Empty;
|
|
private string _filter = "*";
|
|
private bool _haseditpermission = false;
|
|
private string _image = string.Empty;
|
|
private File _file = null;
|
|
private string _guid;
|
|
private string _message = string.Empty;
|
|
private MessageType _messagetype;
|
|
private bool _uploading = false;
|
|
|
|
[Parameter]
|
|
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
|
|
|
|
[Parameter]
|
|
public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid
|
|
|
|
[Parameter]
|
|
public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path
|
|
|
|
[Parameter]
|
|
public int FileId { get; set; } = -1; // optional - for selecting a specific file by default
|
|
|
|
[Parameter]
|
|
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
|
|
|
|
[Parameter]
|
|
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
|
|
|
|
[Parameter]
|
|
public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true
|
|
|
|
[Parameter]
|
|
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
|
|
|
|
[Parameter]
|
|
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
|
|
|
|
[Parameter]
|
|
public bool ShowProgress { get; set; } = true; // optional - for indicating whether progress info should be displayed during upload - default is true
|
|
|
|
[Parameter]
|
|
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
|
|
|
|
[Parameter]
|
|
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
|
|
|
[Parameter]
|
|
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
|
|
|
[Parameter]
|
|
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
|
|
|
|
[Parameter]
|
|
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
// create unique id for component
|
|
_guid = Guid.NewGuid().ToString("N");
|
|
_fileinputid = "FileInput_" + _guid;
|
|
_progressinfoid = "ProgressInfo_" + _guid;
|
|
_progressbarid = "ProgressBar_" + _guid;
|
|
}
|
|
|
|
protected override async Task OnParametersSetAsync()
|
|
{
|
|
// packages folder is a framework folder for uploading installable nuget packages
|
|
if (Folder == Constants.PackagesFolder)
|
|
{
|
|
ShowFiles = false;
|
|
ShowFolders = false;
|
|
Filter = "nupkg";
|
|
ShowSuccess = true;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
|
|
{
|
|
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
|
|
if (folder != null)
|
|
{
|
|
FolderId = folder.FolderId;
|
|
}
|
|
else
|
|
{
|
|
FolderId = -1;
|
|
_message = "Folder Path " + Folder + " Does Not Exist";
|
|
_messagetype = MessageType.Error;
|
|
}
|
|
}
|
|
|
|
if (ShowFolders)
|
|
{
|
|
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
|
|
}
|
|
else
|
|
{
|
|
if (FolderId != -1)
|
|
{
|
|
var folder = await FolderService.GetFolderAsync(FolderId);
|
|
if (folder != null)
|
|
{
|
|
_folders = new List<Folder> { folder };
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FileId != -1)
|
|
{
|
|
File file = await FileService.GetFileAsync(FileId);
|
|
if (file != null)
|
|
{
|
|
FolderId = file.FolderId;
|
|
}
|
|
else
|
|
{
|
|
_message = "FileId " + FileId.ToString() + " Does Not Exist";
|
|
_messagetype = MessageType.Error;
|
|
FileId = -1; // file does not exist
|
|
}
|
|
}
|
|
|
|
await SetImage();
|
|
|
|
if (!string.IsNullOrEmpty(Filter))
|
|
{
|
|
_filter = "." + Filter.Replace(",", ",.");
|
|
}
|
|
|
|
await GetFiles();
|
|
|
|
_initialized = true;
|
|
}
|
|
|
|
private async Task GetFiles()
|
|
{
|
|
_haseditpermission = false;
|
|
if (Folder == Constants.PackagesFolder)
|
|
{
|
|
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
|
_files = new List<File>();
|
|
}
|
|
else
|
|
{
|
|
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
|
|
if (folder != null)
|
|
{
|
|
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
|
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
|
|
{
|
|
_files = await FileService.GetFilesAsync(FolderId);
|
|
}
|
|
else
|
|
{
|
|
_files = new List<File>();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_haseditpermission = false;
|
|
_files = new List<File>();
|
|
}
|
|
if (_filter != "*")
|
|
{
|
|
List<File> filtered = new List<File>();
|
|
foreach (File file in _files)
|
|
{
|
|
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
|
|
{
|
|
filtered.Add(file);
|
|
}
|
|
}
|
|
_files = filtered;
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task FolderChanged(ChangeEventArgs e)
|
|
{
|
|
_message = string.Empty;
|
|
try
|
|
{
|
|
FolderId = int.Parse((string)e.Value);
|
|
await GetFiles();
|
|
FileId = -1;
|
|
_file = null;
|
|
_image = string.Empty;
|
|
StateHasChanged();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
|
|
_message = Localizer["Error.File.Load"];
|
|
_messagetype = MessageType.Error;
|
|
}
|
|
}
|
|
|
|
private async Task FileChanged(ChangeEventArgs e)
|
|
{
|
|
_message = string.Empty;
|
|
FileId = int.Parse((string)e.Value);
|
|
await SetImage();
|
|
await OnSelect.InvokeAsync(FileId);
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task SetImage()
|
|
{
|
|
_image = string.Empty;
|
|
_file = null;
|
|
if (FileId != -1)
|
|
{
|
|
_file = await FileService.GetFileAsync(FileId);
|
|
if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
|
|
{
|
|
var maxwidth = 200;
|
|
var maxheight = 200;
|
|
|
|
var ratioX = (double)maxwidth / (double)_file.ImageWidth;
|
|
var ratioY = (double)maxheight / (double)_file.ImageHeight;
|
|
var ratio = ratioX < ratioY ? ratioX : ratioY;
|
|
|
|
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
|
|
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
|
|
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task UploadFiles()
|
|
{
|
|
_message = string.Empty;
|
|
var interop = new Interop(JSRuntime);
|
|
var uploads = await interop.GetFiles(_fileinputid);
|
|
|
|
if (uploads.Length > 0)
|
|
{
|
|
string restricted = "";
|
|
foreach (var upload in uploads)
|
|
{
|
|
var filename = upload.Split(':')[0];
|
|
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
|
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
|
{
|
|
restricted += (restricted == "" ? "" : ",") + extension;
|
|
}
|
|
}
|
|
if (restricted == "")
|
|
{
|
|
try
|
|
{
|
|
// upload the files
|
|
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
|
|
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
|
|
var jwt = "";
|
|
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
|
{
|
|
jwt = await UserService.GetTokenAsync();
|
|
if (string.IsNullOrEmpty(jwt))
|
|
{
|
|
await logger.LogInformation("File Upload Failed From .NET MAUI Due To Missing Security Token. Token Options Must Be Set In User Settings.");
|
|
_message = "Security Token Not Specified";
|
|
_messagetype = MessageType.Error;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!ShowProgress)
|
|
{
|
|
_uploading = true;
|
|
StateHasChanged();
|
|
}
|
|
|
|
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt);
|
|
|
|
// uploading is asynchronous so we need to poll to determine if uploads are completed
|
|
var success = true;
|
|
int upload = 0;
|
|
while (upload < uploads.Length && success)
|
|
{
|
|
success = false;
|
|
var filename = uploads[upload].Split(':')[0];
|
|
|
|
var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes
|
|
var megabits = (size / 1048576.0) * 8; // binary conversion
|
|
var uploadspeed = (PageState.Alias.Name.Contains("localhost")) ? 100 : 3; // 3 Mbps is FCC minimum for broadband upload
|
|
var uploadtime = (megabits / uploadspeed); // seconds
|
|
var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds)
|
|
var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds
|
|
|
|
int attempts = 0;
|
|
while (attempts < maxattempts && !success)
|
|
{
|
|
attempts += 1;
|
|
Thread.Sleep(sleep);
|
|
|
|
if (Folder == Constants.PackagesFolder)
|
|
{
|
|
var files = await FileService.GetFilesAsync(folder);
|
|
if (files != null && files.Any(item => item.Name == filename))
|
|
{
|
|
success = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var file = await FileService.GetFileAsync(int.Parse(folder), filename);
|
|
if (file != null)
|
|
{
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
if (success)
|
|
{
|
|
upload++;
|
|
}
|
|
}
|
|
|
|
// reset progress indicators
|
|
if (ShowProgress)
|
|
{
|
|
await interop.SetElementAttribute(_progressinfoid, "style", "display: none;");
|
|
await interop.SetElementAttribute(_progressbarid, "style", "display: none;");
|
|
}
|
|
else
|
|
{
|
|
_uploading = false;
|
|
StateHasChanged();
|
|
}
|
|
|
|
if (success)
|
|
{
|
|
await logger.LogInformation("File Upload Succeeded {Files}", uploads);
|
|
if (ShowSuccess)
|
|
{
|
|
_message = Localizer["Success.File.Upload"];
|
|
_messagetype = MessageType.Success;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
|
|
_message = Localizer["Error.File.Upload"];
|
|
_messagetype = MessageType.Error;
|
|
}
|
|
|
|
if (Folder == Constants.PackagesFolder)
|
|
{
|
|
await OnUpload.InvokeAsync(-1);
|
|
}
|
|
else
|
|
{
|
|
// set FileId to first file in upload collection
|
|
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
|
|
if (file != null)
|
|
{
|
|
FileId = file.FileId;
|
|
await SetImage();
|
|
await OnSelect.InvokeAsync(FileId);
|
|
await OnUpload.InvokeAsync(FileId);
|
|
}
|
|
await GetFiles();
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
|
|
_message = Localizer["Error.File.Upload"];
|
|
_messagetype = MessageType.Error;
|
|
_uploading = false;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
|
|
_messagetype = MessageType.Warning;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_message = Localizer["Message.File.NotSelected"];
|
|
_messagetype = MessageType.Warning;
|
|
}
|
|
}
|
|
|
|
private async Task DeleteFile()
|
|
{
|
|
_message = string.Empty;
|
|
try
|
|
{
|
|
await FileService.DeleteFileAsync(FileId);
|
|
await logger.LogInformation("File Deleted {File}", FileId);
|
|
await OnDelete.InvokeAsync(FileId);
|
|
|
|
if (ShowSuccess)
|
|
{
|
|
_message = Localizer["Success.File.Delete"];
|
|
_messagetype = MessageType.Success;
|
|
}
|
|
|
|
await GetFiles();
|
|
FileId = -1;
|
|
await SetImage();
|
|
await OnSelect.InvokeAsync(FileId);
|
|
StateHasChanged();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
|
|
|
|
_message = Localizer["Error.File.Delete"];
|
|
_messagetype = MessageType.Error;
|
|
}
|
|
}
|
|
|
|
public int GetFileId() => FileId;
|
|
|
|
public int GetFolderId() => FolderId;
|
|
|
|
public File GetFile() => _file;
|
|
|
|
public async Task Refresh()
|
|
{
|
|
await Refresh(-1);
|
|
}
|
|
|
|
public async Task Refresh(int fileId)
|
|
{
|
|
await GetFiles();
|
|
if (fileId != -1)
|
|
{
|
|
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
|
|
if (file != null)
|
|
{
|
|
FileId = file.FileId;
|
|
await SetImage();
|
|
}
|
|
}
|
|
StateHasChanged();
|
|
}
|
|
}
|