@using System.Collections.Concurrent @using Microsoft.Extensions.Logging @using MoonCore.Blazor.FlyonUi.Modals @using MoonCore.Helpers @using XtermBlazor @using MoonCore.Blazor.FlyonUi.Components @inject IJSRuntime JsRuntime @inject ModalService ModalService @inject ILogger Logger @implements IAsyncDisposable
@if (IsInitialized) { }
@if (IsPaused) { } else { }
@code { [Parameter] public Func? OnAfterInitialized { get; set; } [Parameter] public Func? OnFirstRender { get; set; } [Parameter] public bool ShowActions { get; set; } = true; [Parameter] public bool ShowInput { get; set; } = false; [Parameter] public int MaxOutputCacheSize { get; set; } = 250; [Parameter] public Func? OnCommand { get; set; } [Parameter] public IList CommandHistory { get; set; } = new ConcurrentList(); public event Func? OnWrite; private Xterm Terminal; public HashSet Addons { get; } = ["addon-fit"]; public TerminalOptions Options { get; } = new() { CursorBlink = false, CursorStyle = CursorStyle.Bar, CursorWidth = 1, FontFamily = "Space Mono, monospace", DisableStdin = true, CursorInactiveStyle = CursorInactiveStyle.None, Theme = { Background = "#000000" }, }; public ConcurrentList OutputCache { get; private set; } = new(); public ConcurrentList WriteQueue { get; private set; } = new(); private int CommandIndex = -1; private bool IsReadyToWrite = false; private bool IsPaused = false; private bool IsInitialized = false; private string CommandInput = ""; protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) return; try { await JsRuntime.InvokeVoidAsync("moonlightServers.loadAddons"); } catch (Exception e) { Logger.LogError("An error occured while initializing addons: {e}", e); } IsInitialized = true; await InvokeAsync(StateHasChanged); if (OnAfterInitialized != null) await OnAfterInitialized.Invoke(); } private async Task HandleFirstRender() { try { await Terminal.Addon("addon-fit").InvokeVoidAsync("fit"); } catch (Exception e) { Logger.LogError("An error occured while calling addons: {e}", e); } IsReadyToWrite = true; // Write queued content since initialisation started var queueContent = string.Concat(WriteQueue); WriteQueue.Clear(); await Terminal.Write(queueContent); if (OnFirstRender != null) await OnFirstRender.Invoke(); } public async Task Write(string content) { // We cache messages here as there is the chance that the console isn't ready for input while receiving write tasks if (IsReadyToWrite && !IsPaused) await HandleWrite(content); else WriteQueue.Add(content); } private async Task HandleWrite(string content) { // Update output cache and prune it if required if (OutputCache.Count > MaxOutputCacheSize) { for (var i = 0; i < 50; i++) OutputCache.RemoveAt(i); } OutputCache.Add(content); // Trigger events if (OnWrite != null) await OnWrite.Invoke(content); // Write in own terminal await Terminal.Write(content); } private async Task OpenFullscreen() { await ModalService.Launch(parameters => { parameters["Parent"] = this; }, size: "max-w-none"); } private async Task TogglePause() { IsPaused = !IsPaused; await InvokeAsync(StateHasChanged); if (IsPaused) return; var queueContent = string.Concat(WriteQueue); WriteQueue.Clear(); await HandleWrite(queueContent); } private async Task SubmitCommand() { CommandHistory.Add(CommandInput); if (OnCommand != null) await OnCommand.Invoke(CommandInput); CommandIndex = -1; CommandInput = ""; await InvokeAsync(StateHasChanged); } private async Task HandleKey(KeyboardEventArgs keyboard) { switch (keyboard.Code) { case "Enter": await SubmitCommand(); break; case "ArrowUp" or "ArrowDown": { var highestIndex = CommandHistory.Count - 1; if (CommandIndex == -1) CommandIndex = highestIndex; else { if (keyboard.Code is "ArrowUp") CommandIndex++; else if (keyboard.Code is "ArrowDown") CommandIndex--; } if (CommandIndex > highestIndex) CommandIndex = highestIndex; if (CommandIndex < 0) CommandIndex = 0; if (CommandIndex <= highestIndex || CommandHistory.Count > 0) CommandInput = CommandHistory[CommandIndex]; await InvokeAsync(StateHasChanged); break; } } } public async ValueTask DisposeAsync() { await Terminal.DisposeAsync(); } }