271 lines
9.4 KiB
Plaintext
271 lines
9.4 KiB
Plaintext
@using Microsoft.AspNetCore.Components.Web
|
|
@using Microsoft.JSInterop
|
|
@using MudBlazor
|
|
@using RobotNet10.ScriptEditor.Models
|
|
@using RobotNet10.ScriptEditor.Clients
|
|
@using RobotNet10.ScriptEditor.Services
|
|
@using RobotNet10.ScriptEditor.Dialogs
|
|
@inject FileManagerHubClient FileManagerClient
|
|
@inject ScriptWorkspace Workspace
|
|
@inject IDialogService DialogService
|
|
@inject IJSRuntime JSRuntime
|
|
@inject ISnackbar Snackbar
|
|
@implements IDisposable
|
|
|
|
<MudMenu Class="w-100" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopLeft" Size="@Size.Small" PositionAtCursor Dense ActivationEvent="@MouseEvent.RightClick" @bind-Open="ShowContextMenu">
|
|
<ActivatorContent>
|
|
<div class="file-explorer-item"
|
|
data-file-path="@File.Path"
|
|
@onclick:stopPropagation="true"
|
|
@onclick:preventDefault="true"
|
|
@onclick="HandleClick"
|
|
@oncontextmenu="HandleClick">
|
|
<input id="@ItemId" type="radio" name="@RadioName" hidden />
|
|
<div class="file-item-content" data-file-id="@File.Id" data-level="@File.Level">
|
|
@for (int i = 0; i < File.Level - 1; i++)
|
|
{
|
|
<div class="file-indent-guide"></div>
|
|
}
|
|
<span class="file-icon-spacer"></span>
|
|
<span class="file-icon mdi mdi-language-csharp"></span>
|
|
<span class="file-name" data-file-id="@File.Id" data-name="name">@File.Name</span>
|
|
<span class="file-badge warning" data-file-id="@File.Id" data-badge="warning" data-count="@File.WarningCount" title="@File.WarningCount warning(s)">@File.WarningCount</span>
|
|
<span class="file-badge error" data-file-id="@File.Id" data-badge="error" data-count="@File.ErrorCount" title="@File.ErrorCount error(s)">@File.ErrorCount</span>
|
|
<span class="file-badge modified" data-file-id="@File.Id" data-badge="modified" title="Modified">●</span>
|
|
</div>
|
|
</div>
|
|
</ActivatorContent>
|
|
<ChildContent>
|
|
<MudMenuItem Icon="@Icons.Material.Filled.Edit" OnClick="HandleRename">
|
|
Rename
|
|
</MudMenuItem>
|
|
<MudMenuItem Icon="@Icons.Material.Filled.Save" OnClick="HandleSave" Disabled="@(!File.IsModified)">
|
|
Save
|
|
</MudMenuItem>
|
|
<MudDivider />
|
|
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="HandleDelete">
|
|
<MudText Color="Color.Error">Delete</MudText>
|
|
</MudMenuItem>
|
|
</ChildContent>
|
|
</MudMenu>
|
|
|
|
@code {
|
|
[CascadingParameter(Name = "FileExplorerRadioName")]
|
|
protected string RadioName { get; set; } = "script-explorer-item";
|
|
|
|
[Parameter, EditorRequired]
|
|
public ScriptFile File { get; set; } = null!;
|
|
|
|
private Guid ItemId = Guid.NewGuid();
|
|
private bool ShowContextMenu { get; set; }
|
|
private ScriptFile? _previousFile;
|
|
|
|
public override async Task SetParametersAsync(ParameterView parameters)
|
|
{
|
|
// Try to get the File parameter using nameof for type safety
|
|
if (parameters.TryGetValue<ScriptFile>(nameof(File), out var fileParameter))
|
|
{
|
|
// Unsubscribe from old file events if file changed
|
|
if (_previousFile != null && _previousFile != fileParameter)
|
|
{
|
|
_previousFile.Modified -= OnFileModified;
|
|
_previousFile.NameChanged -= OnFileNameChanged;
|
|
_previousFile.DiagnosticsChanged -= OnFileDiagnosticsChanged;
|
|
}
|
|
}
|
|
|
|
// Set parameters first
|
|
await base.SetParametersAsync(parameters);
|
|
|
|
// Subscribe to new file events using the parameter from TryGetValue
|
|
if (parameters.TryGetValue<ScriptFile>(nameof(File), out var newFile) && newFile != null)
|
|
{
|
|
// Only subscribe if this is a different file
|
|
if (_previousFile != newFile)
|
|
{
|
|
newFile.Modified += OnFileModified;
|
|
newFile.NameChanged += OnFileNameChanged;
|
|
newFile.DiagnosticsChanged += OnFileDiagnosticsChanged;
|
|
_previousFile = newFile;
|
|
|
|
// Update UI via JavaScript
|
|
await UpdateBadgesVisibility();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
// Only update badges on first render or when explicitly needed
|
|
if (firstRender && File != null)
|
|
{
|
|
await UpdateBadgesVisibility();
|
|
}
|
|
|
|
// Radio button state is managed via CheckRadioById and UncheckRadioByName
|
|
}
|
|
|
|
private async Task UpdateBadgesVisibility()
|
|
{
|
|
try
|
|
{
|
|
var fileId = File.Id.ToString();
|
|
await JSRuntime.InvokeVoidAsync("fileExplorer.updateFileBadges",
|
|
fileId,
|
|
File.WarningCount > 0,
|
|
File.ErrorCount > 0,
|
|
File.IsModified,
|
|
File.WarningCount.ToString(),
|
|
File.ErrorCount.ToString(),
|
|
File.Name);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore JS errors
|
|
}
|
|
}
|
|
|
|
|
|
private async Task HandleClick(MouseEventArgs e)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("fileExplorer.UncheckRadioByName", RadioName);
|
|
await JSRuntime.InvokeVoidAsync("fileExplorer.CheckRadioById", ItemId);
|
|
Workspace.SelectedFile = File;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private void OnFileModified()
|
|
{
|
|
InvokeAsync(async () =>
|
|
{
|
|
await UpdateBadgesVisibility();
|
|
StateHasChanged();
|
|
});
|
|
}
|
|
|
|
private void OnFileNameChanged()
|
|
{
|
|
InvokeAsync(async () =>
|
|
{
|
|
await UpdateBadgesVisibility();
|
|
StateHasChanged();
|
|
});
|
|
}
|
|
|
|
private void OnFileDiagnosticsChanged(int warningCount, int errorCount)
|
|
{
|
|
InvokeAsync(async () =>
|
|
{
|
|
await UpdateBadgesVisibility();
|
|
StateHasChanged();
|
|
});
|
|
}
|
|
|
|
private async Task HandleRename()
|
|
{
|
|
ShowContextMenu = false;
|
|
|
|
var parameters = new DialogParameters<RenameDialog>
|
|
{
|
|
{ x => x.CurrentName, File.Name },
|
|
{ x => x.ItemType, "File" },
|
|
{ x => x.RequireCsExtension, true }
|
|
};
|
|
|
|
var options = new DialogOptions
|
|
{
|
|
CloseOnEscapeKey = true,
|
|
MaxWidth = MaxWidth.Small,
|
|
FullWidth = true
|
|
};
|
|
|
|
var dialog = await DialogService.ShowAsync<RenameDialog>("Rename File", parameters, options);
|
|
var result = await dialog.Result;
|
|
|
|
if (result is not null && !result.Canceled && result.Data is string newName)
|
|
{
|
|
try
|
|
{
|
|
var parentPath = File.Parent?.Path ?? "";
|
|
var newPath = string.IsNullOrEmpty(parentPath) ? newName : System.IO.Path.Combine(parentPath, newName);
|
|
|
|
await FileManagerClient.CreateFileAsync(newPath, File.Code);
|
|
await FileManagerClient.DeleteFileAsync(File.Path);
|
|
|
|
File.Name = newName;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"Failed to rename file: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task HandleSave()
|
|
{
|
|
if (!File.IsModified) return;
|
|
|
|
ShowContextMenu = false;
|
|
try
|
|
{
|
|
await FileManagerClient.SaveFileAsync(File.Path, File.Code);
|
|
File.Saved();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"Failed to save file: {ex.Message}", Severity.Error);
|
|
}
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task HandleDelete()
|
|
{
|
|
ShowContextMenu = false;
|
|
|
|
var parameters = new DialogParameters<ConfirmDialog>
|
|
{
|
|
{ x => x.Title, "Delete File" },
|
|
{ x => x.Message, $"Are you sure you want to delete '{File.Name}'?" },
|
|
{ x => x.ConfirmText, "Delete" },
|
|
{ x => x.ConfirmColor, Color.Error }
|
|
};
|
|
|
|
var options = new DialogOptions
|
|
{
|
|
CloseOnEscapeKey = true,
|
|
MaxWidth = MaxWidth.Small,
|
|
FullWidth = true
|
|
};
|
|
|
|
var dialog = await DialogService.ShowAsync<ConfirmDialog>("Delete File", parameters, options);
|
|
var result = await dialog.Result;
|
|
|
|
if (result is not null && !result.Canceled && result.Data is bool confirmed && confirmed)
|
|
{
|
|
try
|
|
{
|
|
await FileManagerClient.DeleteFileAsync(File.Path);
|
|
|
|
// Update workspace immediately after successful deletion
|
|
// (Server may not send FileDeleted event to the client that performed the deletion)
|
|
Workspace.RemoveFile(File);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"Failed to delete file: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (File != null)
|
|
{
|
|
File.Modified -= OnFileModified;
|
|
File.NameChanged -= OnFileNameChanged;
|
|
File.DiagnosticsChanged -= OnFileDiagnosticsChanged;
|
|
}
|
|
}
|
|
}
|
|
|