@implements IAsyncDisposable @using Microsoft.AspNetCore.Components @using Microsoft.CodeAnalysis @using Microsoft.CodeAnalysis.Text @using Microsoft.JSInterop @using RobotNet.WebApp.Scripts.Monaco @using RobotNet.WebApp.Scripts.Monaco.Editor @using RobotNet.WebApp.Scripts.Monaco.Languages @using System.Timers @inject IJSRuntime JsRuntime @inject ISnackbar Snackbar
@NameFile
@code { [CascadingParameter] private ScriptWorkspace Workspace { get; set; } = null!; private object dotNetHelper = default!; private IJSInProcessObjectReference? editor; private IJSInProcessObjectReference? editorModel; private IJSInProcessObjectReference? disposableCompletionItemProvider; private IJSInProcessObjectReference? disposableSignatureHelpProvider; private IJSInProcessObjectReference? disposableHoverProvider; private bool IsEditorInitialized; private string Code = ""; private string NameFile = ""; public ProcessorState State { get; private set; } = ProcessorState.Idle; private bool IsReadOnly = true; private ElementReference EditorContainer; protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); if (firstRender) { dotNetHelper = DotNetObjectReference.Create(this); editor = await JsRuntime.InvokeAsync("monaco.editor.create", EditorContainer, new { Value = "", Language = "csharp", Theme = "vs-dark", GlyphMargin = true, AutomaticLayout = true, ReadOnly = IsReadOnly, }); editorModel = await editor.InvokeAsync("getModel"); await JsRuntime.InvokeVoidAsync("MonacoRuntime.OnCodeEditorDidChangeModelContent", editor, dotNetHelper, nameof(CodeEditorDidChangeModelContent)); disposableCompletionItemProvider = await JsRuntime.InvokeAsync("MonacoRuntime.CSharpLanguageRegisterCompletionItemProvider", dotNetHelper, nameof(GetCompletionItems)); disposableSignatureHelpProvider = await JsRuntime.InvokeAsync("MonacoRuntime.CSharpLanguageRegisterSignatureHelpProvider", dotNetHelper, nameof(GetSignatureHelp)); disposableHoverProvider = await JsRuntime.InvokeAsync("MonacoRuntime.CSharpLanguageRegisterHoverProvider", dotNetHelper, nameof(GetQuickInfo)); IsEditorInitialized = true; Workspace.OnFileChanged += FileChanged; Workspace.OnDiagnoticChanged += DiagnoticChanged; } } public void OnProcessStateChanged(ProcessorState state) { State = state; var isReadOnly = State != ProcessorState.Ready && State != ProcessorState.Idle && State != ProcessorState.BuildError; if (isReadOnly != IsReadOnly) { IsReadOnly = isReadOnly; if (editor != null) { _ = InvokeAsync(async Task () => { await editor.InvokeVoidAsync("updateOptions", new { ReadOnly = IsReadOnly, }); }); } } } private async Task FileChanged(ScriptFileModel? file) { await this.InvokeAsync(async Task () => { if (file is null) { NameFile = ""; Code = ""; } else { NameFile = file.Name; Code = file.EditCode; } if (IsEditorInitialized && editor is not null) { await editor.InvokeVoidAsync("setValue", Code); await JsRuntime.InvokeVoidAsync("monaco.editor.setModelMarkers", editorModel, "default", file?.Diagnostics.Select(ToMonacoDiagnostic) ?? []); // await editor.InvokeVoidAsync("updateOptions", new // { // ReadOnly = IsReadOnly, // }); } StateHasChanged(); }); } private async Task DiagnoticChanged(IEnumerable diagnostics) { await this.InvokeAsync(async Task () => { if (IsEditorInitialized && editor is not null) { await JsRuntime.InvokeVoidAsync("monaco.editor.setModelMarkers", editorModel, "default", diagnostics.Select(ToMonacoDiagnostic) ?? []); } }); } [JSInvokable] public async Task GetCompletionItems(int line, int column, int kind, char? triggerCharacter) { return new() { Suggestions = [.. await Workspace.GetCompletionCurrentFileAsync(line, column, kind, triggerCharacter)], }; } [JSInvokable] public async Task GetQuickInfo(int line, int column) { return await Workspace.GetQuickInfoCurrentFile(line, column); } [JSInvokable] public async Task GetSignatureHelp(int line, int column) { return await Workspace.GetSignatureHelpCurrentFile(line, column); } [JSInvokable] public async Task CodeEditorDidChangeModelContent(ModelContentChangedEvent e) { if (e.IsFlush) return; if (e.Changes.Length != 0) { if (editor is not null) { var code = await editor.InvokeAsync("getValue"); if (code != Code) { Code = code; Workspace.WriteDocument(Code); } } } } bool isSaving = false; private async Task OnKeyPress(KeyboardEventArgs e) { if ((e.Key == "s" || e.Key == "S") && e.CtrlKey) { if(IsReadOnly) { Snackbar.Add($"Hệ thống đang ở trạng thái {State}, Không thể thay đổi script", Severity.Warning); return; } if (isSaving) return; isSaving = true; var result = await Workspace.SaveCurrentFileAsync(); if (!result.IsSuccess) { Snackbar.Add(result.Message, Severity.Error); } isSaving = false; } } public async ValueTask DisposeAsync() { if (disposableCompletionItemProvider != null) { await disposableCompletionItemProvider.InvokeVoidAsync("dispose"); await disposableCompletionItemProvider.DisposeAsync(); disposableCompletionItemProvider = null; } if (disposableSignatureHelpProvider != null) { await disposableSignatureHelpProvider.InvokeVoidAsync("dispose"); await disposableSignatureHelpProvider.DisposeAsync(); disposableSignatureHelpProvider = null; } if (disposableHoverProvider != null) { await disposableHoverProvider.InvokeVoidAsync("dispose"); await disposableHoverProvider.DisposeAsync(); disposableHoverProvider = null; } if (editorModel != null) { await editorModel.InvokeVoidAsync("dispose"); await editorModel.DisposeAsync(); editorModel = null; } if (editor != null) { await editor.InvokeVoidAsync("dispose"); await editor.DisposeAsync(); editor = null; } if (IsEditorInitialized) { Workspace.OnFileChanged -= FileChanged; Workspace.OnDiagnoticChanged -= DiagnoticChanged; } GC.SuppressFinalize(this); } private static MarkerData ToMonacoDiagnostic(Diagnostic diagnostic) { var lineSpan = diagnostic.Location.GetLineSpan(); return new() { StartLineNumber = lineSpan.StartLinePosition.Line + 1, StartColumn = lineSpan.StartLinePosition.Character + 1, EndLineNumber = lineSpan.EndLinePosition.Line + 1, EndColumn = lineSpan.EndLinePosition.Character + 1, Message = diagnostic.GetMessage(), Severity = diagnostic.Severity switch { DiagnosticSeverity.Info => MarkerSeverity.Info, DiagnosticSeverity.Warning => MarkerSeverity.Warning, DiagnosticSeverity.Error => MarkerSeverity.Error, _ => MarkerSeverity.Hint, }, }; } }