namespace ScadaLink.CentralUI.Components.Shared; /// /// Default implementation. Holds the currently /// open dialog state in and notifies subscribers (the /// DialogHost component) via . Only a single /// dialog can be open at a time; attempting to open another while one is /// already active throws — there is /// no nested-dialog use case today and surfacing the bug is preferable to /// silently queuing. /// public class DialogService : IDialogService { /// /// Raised whenever changes (dialog opened or closed). /// The host component subscribes and calls StateHasChanged. /// public event Action? OnChange; /// /// The dialog currently being displayed, or null when no dialog is /// open. The host reads this to decide what (if anything) to render. /// public DialogState? Current { get; private set; } private TaskCompletionSource? _tcs; public Task ConfirmAsync(string title, string message, bool danger = false) { EnsureNoActiveDialog(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _tcs = tcs; Current = new DialogState(title, DialogKind.Confirm, message, danger, PromptInitial: string.Empty, Placeholder: null); OnChange?.Invoke(); return tcs.Task.ContinueWith(t => t.Result is bool b && b, TaskScheduler.Default); } public Task PromptAsync(string title, string label, string initialValue = "", string? placeholder = null) { EnsureNoActiveDialog(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _tcs = tcs; Current = new DialogState(title, DialogKind.Prompt, label, Danger: false, PromptInitial: initialValue, Placeholder: placeholder); OnChange?.Invoke(); return tcs.Task.ContinueWith(t => t.Result as string, TaskScheduler.Default); } /// /// Called by the host component when the user dismisses or confirms the /// dialog. must be a bool for confirms /// and a string? for prompts (null = cancel). /// internal void Resolve(object? result) { var tcs = _tcs; _tcs = null; Current = null; OnChange?.Invoke(); tcs?.TrySetResult(result); } private void EnsureNoActiveDialog() { if (Current is not null) { throw new InvalidOperationException( "A dialog is already open. IDialogService does not support nested dialogs."); } } } /// /// Snapshot of a dialog's display state, exposed read-only on /// for the host component to render. /// /// Modal title text. /// Discriminates between confirm and prompt rendering. /// For confirm: the message; for prompt: the input label. /// When true, the confirm button uses danger styling. /// Initial value for prompt-kind dialogs. /// Placeholder shown when the prompt input is empty. public record DialogState( string Title, DialogKind Kind, string Body, bool Danger, string PromptInitial, string? Placeholder); public enum DialogKind { Confirm, Prompt }