Files
BQP/srcs/RobotNet10/Components/RobotNet10.ScriptEditor/ScriptEditor.razor
2026-07-13 09:25:40 +07:00

178 lines
6.3 KiB
Plaintext

@implements IAsyncDisposable
@using RobotNet10.ScriptEditor.Clients
@using RobotNet10.ScriptEditor.Models
@using RobotNet10.ScriptEditor.Dialogs
@using Microsoft.JSInterop
@using Microsoft.JSInterop.Implementation
@using Microsoft.AspNetCore.Components.Web
@using RobotNet10.ScriptEditor.Services
@using RobotNet10.ScriptEngine.Shared
@inject IJSRuntime JSRuntime
@inject IScriptEngineResource ScriptResource
@inject FileManagerHubClient FileManagerClient
@inject ScriptManagerHubClient ScriptManagerClient
@inject ScriptResourceResolver ResourceResolver
@inject ScriptWorkspace Workspace
@inject NavigationManager NavigationManager
@inject IDialogService DialogService
@using MudBlazor
<div class="script-editor-container">
<!-- Phần 1: Sidebar (có thể resize ngang) -->
<div class="sidebar-container" style="width: @(_sidebarWidth)px;">
<div class="sidebar-content">
<FileExplorer />
<VariableManager />
<TaskManager />
<MissionManager />
</div>
<div class="sidebar-resizer"></div>
</div>
<!-- Phần 2 và 3: Editor Area và Console Panel (sắp xếp dọc) -->
<div class="editor-console-container">
<!-- Phần 2: Editor Area -->
<div class="editor-area" id="editor-area">
<div class="editor-area-content">
<Editor />
</div>
</div>
<!-- Resizer giữa Editor và Console -->
<div class="vertical-resizer"></div>
<!-- Phần 3: Console Panel (có thể resize dọc) -->
<div class="console-panel" id="console-panel">
<Console />
</div>
</div>
</div>
<MudOverlay Visible="@(!Workspace.IsInitialized)" DarkBackground Modal Absolute AutoClose="false">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
</MudOverlay>
@code {
private int _sidebarWidth = 250; // Chiều rộng mặc định của sidebar (px)
private IJSObjectReference? _jsModule;
protected override void OnInitialized()
{
// Subscribe to edit permission revoked event
FileManagerClient.EditPermissionRevoked += OnEditPermissionRevoked;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
try
{
_jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>(
"import", "./_content/RobotNet10.ScriptEditor/scriptEditorResize.js");
if (_jsModule != null)
{
await _jsModule.InvokeVoidAsync("initializeLayout");
}
await FileManagerClient.StartAsync();
await ScriptManagerClient.StartAsync();
// Request edit permission before initializing workspace
// This will always succeed and force out any previous connection
try
{
await FileManagerClient.RequestEditPermissionAsync();
// Check if we actually have edit permission (depends on state)
var hasPermission = await FileManagerClient.HasEditPermissionAsync();
Workspace.IsReadOnly = !hasPermission;
}
catch (Exception)
{
// If request fails, set to read-only
Workspace.IsReadOnly = true;
}
var references = await ResourceResolver.GetMetadataReferences(ScriptResource.Modules.ToArray(), ScriptResource.DocModules.ToArray());
var rootFolder = await FileManagerClient.GetRootFolderAsync();
Workspace.Initialize(references, ScriptResource.UsingNamespaces.ToArray(), ScriptResource.AppGlobalType, rootFolder);
StateHasChanged();
}
catch (JSException)
{
// JavaScript module chưa được load, sẽ được xử lý sau
}
catch (Exception)
{
// Ignore other exceptions but don't crash the component
// Mono runtime assertions (e.g., debugger-agent) are non-fatal
}
}
}
private async void OnEditPermissionRevoked(string? userId)
{
// Lock the workspace when permission is revoked
Workspace.IsReadOnly = true;
StateHasChanged();
// Show dialog to inform user
// User can only close by clicking reload button, not by clicking outside or pressing Escape
var options = new DialogOptions
{
CloseButton = false,
CloseOnEscapeKey = false,
BackdropClick = false,
MaxWidth = MaxWidth.Small,
FullWidth = true
};
var parameters = new DialogParameters<PermissionRevokedDialog>
{
{ x => x.OnReload, new Action(() => NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true)) }
};
await DialogService.ShowAsync<PermissionRevokedDialog>("Edit Permission Revoked", parameters, options);
}
public async ValueTask DisposeAsync()
{
// Unsubscribe from events
FileManagerClient.EditPermissionRevoked -= OnEditPermissionRevoked;
if (_jsModule != null)
{
try
{
await _jsModule.InvokeVoidAsync("cleanupLayout");
await _jsModule.DisposeAsync();
// Revoke edit permission before stopping connection
if (FileManagerClient.IsConnected)
{
try
{
await FileManagerClient.RevokeEditPermissionAsync();
}
catch (Exception)
{
// Ignore errors when revoking permission during dispose
}
}
await FileManagerClient.StopAsync();
await ScriptManagerClient.StopAsync();
}
catch (JSDisconnectedException)
{
// Ignore khi JS context đã bị disconnect
}
}
}
}