From b9e19e9a8a4bea9f829b5f39b794b369022c549a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bianchi Date: Wed, 22 Jan 2025 16:26:44 +0100 Subject: [PATCH] fix(Dashboard): fixed "editor not found" errors Aligned Blazor application state with DOM state to prevent issues like 'Couldn't find the editor with id...' or 'Editor with ID "..." not found'. Signed-off-by: Jean-Baptiste Bianchi --- .../Components/DocumentDetails/Store.cs | 26 +++++++--- .../Components/MonacoEditor/Store.cs | 32 +++++++----- .../Pages/Functions/Create/Store.cs | 37 ++++++++------ .../Pages/Functions/Create/View.razor | 2 +- .../Pages/Workflows/Create/Store.cs | 49 ++++++++++--------- .../Pages/Workflows/Create/View.razor | 10 ++-- .../Pages/Workflows/Details/Store.cs | 24 ++++++--- .../Services/SpecificationSchemaManager.cs | 4 +- 8 files changed, 113 insertions(+), 71 deletions(-) diff --git a/src/dashboard/Synapse.Dashboard/Components/DocumentDetails/Store.cs b/src/dashboard/Synapse.Dashboard/Components/DocumentDetails/Store.cs index a577475a8..bd3297431 100644 --- a/src/dashboard/Synapse.Dashboard/Components/DocumentDetails/Store.cs +++ b/src/dashboard/Synapse.Dashboard/Components/DocumentDetails/Store.cs @@ -33,6 +33,7 @@ public class DocumentDetailsStore(ILogger logger, ISynapse TextModel? _textModel; readonly string _textModelUri = monacoEditorHelper.GetResourceUri(); + private bool _hasTextEditorInitialized = false; /// /// Gets the service used to perform logging @@ -291,7 +292,7 @@ public async Task LoadReferencedDocumentAsync() /// public async Task ToggleTextBasedEditorLanguageAsync(string _) { - await this.OnTextBasedEditorInitAsync(); + await this.InitializeTextBasedEditorAsync(); } /// @@ -300,6 +301,17 @@ public async Task ToggleTextBasedEditorLanguageAsync(string _) /// public async Task OnTextBasedEditorInitAsync() { + this._hasTextEditorInitialized = true; + await this.InitializeTextBasedEditorAsync(); + } + + /// + /// Initializes the text editor + /// + /// + public async Task InitializeTextBasedEditorAsync() + { + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; await this.SetTextBasedEditorLanguageAsync(); await this.SetTextEditorValueAsync(); } @@ -313,10 +325,10 @@ public async Task SetTextBasedEditorLanguageAsync() try { var language = this.MonacoEditorHelper.PreferredLanguage; - if (this.TextEditor != null) + if (this.TextEditor != null && this._hasTextEditorInitialized) { this._textModel = await Global.GetModel(this.JSRuntime, this._textModelUri); - this._textModel ??= await Global.CreateModel(this.JSRuntime, "", language, this._textModelUri); + this._textModel ??= await Global.CreateModel(this.JSRuntime, " ", language, this._textModelUri); await Global.SetModelLanguage(this.JSRuntime, this._textModel, language); await this.TextEditor!.SetModel(this._textModel); } @@ -335,7 +347,7 @@ async Task SetTextEditorValueAsync() { var document = this.Get(state => state.DocumentJson); var language = this.MonacoEditorHelper.PreferredLanguage; - if (this.TextEditor != null && !string.IsNullOrWhiteSpace(document)) + if (this.TextEditor != null && !string.IsNullOrWhiteSpace(document) && this._hasTextEditorInitialized) { try { @@ -359,7 +371,7 @@ async Task SetTextEditorValueAsync() /// A awaitable task public async Task OnCopyToClipboard() { - if (this.TextEditor == null) return; + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; var text = await this.TextEditor.GetValue(); if (string.IsNullOrWhiteSpace(text)) return; try @@ -408,7 +420,7 @@ public override Task InitializeAsync() /// protected async Task OnPreferredThemeChangedAsync(string newTheme) { - if (this.TextEditor != null) + if (this.TextEditor != null && this._hasTextEditorInitialized) { await this.TextEditor.UpdateOptions(new EditorUpdateOptions() { Theme = newTheme }); } @@ -430,7 +442,7 @@ protected override void Dispose(bool disposing) this._textModel.DisposeModel(); this._textModel = null; } - if (this.TextEditor != null) + if (this.TextEditor != null && this._hasTextEditorInitialized) { this.TextEditor.Dispose(); this.TextEditor = null; diff --git a/src/dashboard/Synapse.Dashboard/Components/MonacoEditor/Store.cs b/src/dashboard/Synapse.Dashboard/Components/MonacoEditor/Store.cs index 2c2a8e578..455fba17e 100644 --- a/src/dashboard/Synapse.Dashboard/Components/MonacoEditor/Store.cs +++ b/src/dashboard/Synapse.Dashboard/Components/MonacoEditor/Store.cs @@ -32,6 +32,7 @@ public class MonacoEditorStore(ILogger logger, ISynapseApiCli TextModel? _textModel; string _textModelUri = monacoEditorHelper.GetResourceUri(); + private bool _hasTextEditorInitialized = false; /// /// Gets the service used to perform logging @@ -204,18 +205,12 @@ public void SetTexModelName(string modelName) /// public async Task ToggleTextBasedEditorLanguageAsync(string _) { - if (this.TextEditor == null) - { - return; - } + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; var language = this.MonacoEditorHelper.PreferredLanguage; try { var document = await this.TextEditor.GetValue(); - if (document == null) - { - return; - } + if (document == null) return; document = language == PreferredLanguage.YAML ? this.YamlSerializer.ConvertFromJson(document) : this.YamlSerializer.ConvertToJson(document); @@ -223,7 +218,7 @@ public async Task ToggleTextBasedEditorLanguageAsync(string _) { DocumentText = document }); - await this.OnTextBasedEditorInitAsync(); + await this.InitializeTextBasedEditorAsync(); } catch (Exception ex) { @@ -238,6 +233,17 @@ public async Task ToggleTextBasedEditorLanguageAsync(string _) /// public async Task OnTextBasedEditorInitAsync() { + this._hasTextEditorInitialized = true; + await this.InitializeTextBasedEditorAsync(); + } + + /// + /// Initializes the text editor + /// + /// + public async Task InitializeTextBasedEditorAsync() + { + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; await this.SetTextBasedEditorLanguageAsync(); await this.SetTextEditorValueAsync(); } @@ -251,7 +257,7 @@ public async Task SetTextBasedEditorLanguageAsync() try { var language = this.MonacoEditorHelper.PreferredLanguage; - if (this.TextEditor != null) + if (this.TextEditor != null && this._hasTextEditorInitialized) { this._textModel = await Global.GetModel(this.JSRuntime, this._textModelUri); this._textModel ??= await Global.CreateModel(this.JSRuntime, "", language, this._textModelUri); @@ -272,7 +278,7 @@ public async Task SetTextBasedEditorLanguageAsync() async Task SetTextEditorValueAsync() { var document = this.Get(state => state.DocumentText); - if (this.TextEditor != null && !string.IsNullOrWhiteSpace(document)) + if (this.TextEditor != null && !string.IsNullOrWhiteSpace(document) && this._hasTextEditorInitialized) { await this.TextEditor.SetValue(document); } @@ -284,7 +290,7 @@ async Task SetTextEditorValueAsync() /// A awaitable task public async Task OnCopyToClipboard() { - if (this.TextEditor == null) return; + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; var text = await this.TextEditor.GetValue(); if (string.IsNullOrWhiteSpace(text)) return; try @@ -321,7 +327,7 @@ public override Task InitializeAsync() /// protected async Task OnPreferredThemeChangedAsync(string newTheme) { - if (this.TextEditor != null) + if (this.TextEditor != null && this._hasTextEditorInitialized) { await this.TextEditor.UpdateOptions(new EditorUpdateOptions() { Theme = newTheme }); } diff --git a/src/dashboard/Synapse.Dashboard/Pages/Functions/Create/Store.cs b/src/dashboard/Synapse.Dashboard/Pages/Functions/Create/Store.cs index 254d13fff..bbab2ce80 100644 --- a/src/dashboard/Synapse.Dashboard/Pages/Functions/Create/Store.cs +++ b/src/dashboard/Synapse.Dashboard/Pages/Functions/Create/Store.cs @@ -16,7 +16,6 @@ using Semver; using ServerlessWorkflow.Sdk.Models; using Synapse.Api.Client.Services; -using Synapse.Dashboard.Pages.Workflows.Create; using Synapse.Resources; namespace Synapse.Dashboard.Pages.Functions.Create; @@ -51,6 +50,7 @@ MonacoInterop monacoInterop private string _textModelUri = string.Empty; private bool _disposed = false; private bool _processingVersion = false; + private bool _hasTextEditorInitialized = false; /// /// Gets the service used to perform logging @@ -292,7 +292,7 @@ public async Task ToggleTextBasedEditorLanguageAsync(string _) { FunctionText = document }); - await this.OnTextBasedEditorInitAsync(); + await this.InitializeTextBasedEditorAsync(); } catch (Exception ex) { @@ -307,6 +307,17 @@ public async Task ToggleTextBasedEditorLanguageAsync(string _) /// public async Task OnTextBasedEditorInitAsync() { + this._hasTextEditorInitialized = true; + await this.InitializeTextBasedEditorAsync(); + } + + /// + /// Initializes the text editor + /// + /// + public async Task InitializeTextBasedEditorAsync() + { + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; await this.SetTextBasedEditorLanguageAsync(); await this.SetTextEditorValueAsync(); } @@ -317,10 +328,7 @@ public async Task OnTextBasedEditorInitAsync() /// public async Task SetTextBasedEditorLanguageAsync() { - if (this.TextEditor == null) - { - return; - } + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; try { var language = this.MonacoEditorHelper.PreferredLanguage; @@ -342,10 +350,7 @@ public async Task SetTextBasedEditorLanguageAsync() async Task SetTextEditorValueAsync() { var document = this.Get(state => state.FunctionText); - if (this.TextEditor == null || string.IsNullOrWhiteSpace(document)) - { - return; - } + if (this.TextEditor == null || string.IsNullOrWhiteSpace(document) || !this._hasTextEditorInitialized) return; try { await this.TextEditor.SetValue(document); @@ -365,7 +370,7 @@ async Task SetTextEditorValueAsync() /// An awaitable task public async Task OnDidChangeModelContent(ModelContentChangedEvent e) { - if (this.TextEditor == null) return; + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; var document = await this.TextEditor.GetValue(); this.Reduce(state => state with { @@ -379,7 +384,7 @@ public async Task OnDidChangeModelContent(ModelContentChangedEvent e) /// A new awaitable public async Task SaveCustomFunctionAsync() { - if (this.TextEditor == null) + if (this.TextEditor == null || !this._hasTextEditorInitialized) { this.Reduce(state => state with { @@ -513,6 +518,10 @@ public async Task SaveCustomFunctionAsync() /// public override async Task InitializeAsync() { + this.Loading.Where(loading => loading == true).Subscribe(loading => + { + this._hasTextEditorInitialized = false; // reset text editor state when loading + }, token: this.CancellationTokenSource.Token); this.Function.SubscribeAsync(async definition => { string document = string.Empty; if (definition != null) @@ -543,7 +552,7 @@ await this.GetCustomFunctionAsync(name!), /// protected async Task OnPreferredThemeChangedAsync(string newTheme) { - if (this.TextEditor != null) + if (this.TextEditor != null && this._hasTextEditorInitialized) { await this.TextEditor.UpdateOptions(new EditorUpdateOptions() { Theme = newTheme }); } @@ -565,7 +574,7 @@ protected async Task SetValidationSchema(string? version = null) this._processingVersion = true; try { - var schema = $"https://raw.githubusercontent.com/serverlessworkflow/serverlessworkflow.github.io/main/static/schemas/{version}/workflow.yaml#/$defs/task"; + var schema = $"https://raw.githubusercontent.com/serverlessworkflow/serverlessworkflow.github.io/main/public/schemas/{version}/workflow.yaml#/$defs/task"; var type = $"create_{typeof(CustomFunction).Name.ToLower()}_{version}_schema"; await this.MonacoInterop.AddValidationSchemaAsync(schema, $"https://synapse.io/schemas/{type}.json", $"{type}*").ConfigureAwait(false); this._textModelUri = this.MonacoEditorHelper.GetResourceUri(type); diff --git a/src/dashboard/Synapse.Dashboard/Pages/Functions/Create/View.razor b/src/dashboard/Synapse.Dashboard/Pages/Functions/Create/View.razor index a1618b613..bb06dc9f5 100644 --- a/src/dashboard/Synapse.Dashboard/Pages/Functions/Create/View.razor +++ b/src/dashboard/Synapse.Dashboard/Pages/Functions/Create/View.razor @@ -92,7 +92,7 @@ else string? version; string? chosenName; string? nameInputValue; - bool loading; + bool loading = true; bool saving; ProblemDetails? problemDetails = null; diff --git a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/Store.cs b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/Store.cs index 4161800e4..d849a137e 100644 --- a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/Store.cs +++ b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/Store.cs @@ -54,6 +54,7 @@ IWorkflowDefinitionValidator workflowDefinitionValidator private string _textModelUri = string.Empty; private bool _disposed = false; private bool _processingVersion = false; + private bool _hasTextEditorInitialized = false; /// /// Gets the service used to perform logging @@ -108,7 +109,7 @@ IWorkflowDefinitionValidator workflowDefinitionValidator /// /// The provider function /// - public Func StandaloneEditorConstructionOptions = monacoEditorHelper.GetStandaloneEditorConstructionOptions(string.Empty, false, monacoEditorHelper.PreferredLanguage); + public Func StandaloneEditorConstructionOptions = monacoEditorHelper.GetStandaloneEditorConstructionOptions(" ", false, monacoEditorHelper.PreferredLanguage); /// /// The reference @@ -276,18 +277,12 @@ public async Task GetWorkflowDefinitionAsync(string @namespace, string name) /// public async Task ToggleTextBasedEditorLanguageAsync(string _) { - if (this.TextEditor == null) - { - return; - } + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; var language = this.MonacoEditorHelper.PreferredLanguage; try { var document = await this.TextEditor.GetValue(); - if (document == null) - { - return; - } + if (document == null) return; document = language == PreferredLanguage.YAML ? this.YamlSerializer.ConvertFromJson(document) : this.YamlSerializer.ConvertToJson(document); @@ -295,7 +290,7 @@ public async Task ToggleTextBasedEditorLanguageAsync(string _) { WorkflowDefinitionText = document }); - await this.OnTextBasedEditorInitAsync(); + await this.InitializeTextBasedEditorAsync(); } catch (Exception ex) { @@ -310,6 +305,17 @@ public async Task ToggleTextBasedEditorLanguageAsync(string _) /// public async Task OnTextBasedEditorInitAsync() { + this._hasTextEditorInitialized = true; + await this.InitializeTextBasedEditorAsync(); + } + + /// + /// Initializes the text editor + /// + /// + public async Task InitializeTextBasedEditorAsync() + { + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; await this.SetTextBasedEditorLanguageAsync(); await this.SetTextEditorValueAsync(); } @@ -320,10 +326,7 @@ public async Task OnTextBasedEditorInitAsync() /// public async Task SetTextBasedEditorLanguageAsync() { - if (this.TextEditor == null) - { - return; - } + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; try { var language = this.MonacoEditorHelper.PreferredLanguage; @@ -345,10 +348,7 @@ public async Task SetTextBasedEditorLanguageAsync() async Task SetTextEditorValueAsync() { var document = this.Get(state => state.WorkflowDefinitionText); - if (this.TextEditor == null || string.IsNullOrWhiteSpace(document)) - { - return; - } + if (this.TextEditor == null || string.IsNullOrWhiteSpace(document) || !this._hasTextEditorInitialized) return; try { await this.TextEditor.SetValue(document); @@ -368,8 +368,7 @@ async Task SetTextEditorValueAsync() /// An awaitable task public async Task OnDidChangeModelContent(ModelContentChangedEvent e) { - if (this.TextEditor == null) return; - + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; var document = await this.TextEditor.GetValue(); this.Reduce(state => state with { @@ -383,7 +382,7 @@ public async Task OnDidChangeModelContent(ModelContentChangedEvent e) /// A new awaitable public async Task SaveWorkflowDefinitionAsync() { - if (this.TextEditor == null) + if (this.TextEditor == null || !this._hasTextEditorInitialized) { this.Reduce(state => state with { @@ -535,6 +534,10 @@ public async Task SaveWorkflowDefinitionAsync() /// public override async Task InitializeAsync() { + this.Loading.Where(loading => loading == true).Subscribe(loading => + { + this._hasTextEditorInitialized = false; // reset text editor state when loading + }, token: this.CancellationTokenSource.Token); this.WorkflowDefinition.SubscribeAsync(async definition => { string document = ""; if (definition != null) @@ -547,7 +550,7 @@ public override async Task InitializeAsync() { WorkflowDefinitionText = document }); - await this.OnTextBasedEditorInitAsync(); + await this.InitializeTextBasedEditorAsync(); }, cancellationToken: this.CancellationTokenSource.Token); Observable.CombineLatest( this.Namespace.Where(ns => !string.IsNullOrWhiteSpace(ns)), @@ -617,7 +620,7 @@ protected async Task SetValidationSchema(string? version = null) /// protected async Task OnPreferredThemeChangedAsync(string newTheme) { - if (this.TextEditor != null) + if (this.TextEditor != null && this._hasTextEditorInitialized) { await this.TextEditor.UpdateOptions(new EditorUpdateOptions() { Theme = newTheme }); } diff --git a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/View.razor b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/View.razor index adca02fed..72454b1de 100644 --- a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/View.razor +++ b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/View.razor @@ -37,10 +37,10 @@ else + ConstructionOptions="Store.StandaloneEditorConstructionOptions" + OnDidInit="Store.OnTextBasedEditorInitAsync" + OnDidChangeModelContent="Store.OnDidChangeModelContent" + CssClass="h-100" /> @if (problemDetails != null) {
@@ -80,7 +80,7 @@ else string? ns; string? name; - bool loading; + bool loading = true; bool saving; private ProblemDetails? problemDetails = null; diff --git a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/Store.cs b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/Store.cs index 33f012efc..b0b1c3727 100644 --- a/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/Store.cs +++ b/src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/Store.cs @@ -47,6 +47,7 @@ ToastService toastService private TextModel? _textModel = null; private bool _disposed; + private bool _hasTextEditorInitialized = false; /// /// Gets the service used for JS interop @@ -275,15 +276,26 @@ public async Task GetWorkflowAsync(string ns, string name) /// A awaitable task public async Task ToggleTextBasedEditorLanguageAsync(string _) { - await this.OnTextBasedEditorInitAsync(); + await this.InitializeTextBasedEditorAsync(); } /// /// Handles initialization of the text editor /// - /// A awaitable task + /// public async Task OnTextBasedEditorInitAsync() { + this._hasTextEditorInitialized = true; + await this.InitializeTextBasedEditorAsync(); + } + + /// + /// Initializes the text editor + /// + /// + public async Task InitializeTextBasedEditorAsync() + { + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; await this.SetTextBasedEditorLanguageAsync(); await this.SetTextEditorValueAsync(); } @@ -297,7 +309,7 @@ public async Task SetTextBasedEditorLanguageAsync() try { var language = this.MonacoEditorHelper.PreferredLanguage; - if (this.TextEditor != null) + if (this.TextEditor != null && this._hasTextEditorInitialized) { if (this._textModel != null) { @@ -325,7 +337,7 @@ public async Task SetTextBasedEditorLanguageAsync() /// A awaitable task public async Task OnCopyToClipboard() { - if (this.TextEditor == null) return; + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; var text = await this.TextEditor.GetValue(); if (string.IsNullOrWhiteSpace(text)) return; try @@ -510,7 +522,7 @@ public async Task DeleteWorkflowInstanceAsync(WorkflowInstance workflowInstance) public async Task SelectNodeInEditor(GraphEventArgs e) { if (e.GraphElement == null) return; - if (this.TextEditor == null) return; + if (this.TextEditor == null || !this._hasTextEditorInitialized) return; var source = await this.TextEditor.GetValue(); var pointer = e.GraphElement.Id; var language = this.MonacoEditorHelper.PreferredLanguage; @@ -564,7 +576,7 @@ public override async Task InitializeAsync() /// protected async Task OnPreferredThemeChangedAsync(string newTheme) { - if (this.TextEditor != null) + if (this.TextEditor != null && this._hasTextEditorInitialized) { await this.TextEditor.UpdateOptions(new EditorUpdateOptions() { Theme = newTheme }); } diff --git a/src/dashboard/Synapse.Dashboard/Services/SpecificationSchemaManager.cs b/src/dashboard/Synapse.Dashboard/Services/SpecificationSchemaManager.cs index f14267c8b..9160b54c7 100644 --- a/src/dashboard/Synapse.Dashboard/Services/SpecificationSchemaManager.cs +++ b/src/dashboard/Synapse.Dashboard/Services/SpecificationSchemaManager.cs @@ -50,14 +50,14 @@ public async Task GetLatestVersion() } /// - /// Gets the specification's JSON schema for the specificed version + /// Gets the specification's JSON schema for the specified version /// /// The version to get the schema for /// A awaitable task public async Task GetSchema(string version) { if (_knownSchemas.TryGetValue(version, out string? value)) return value; - var address = $"https://raw.githubusercontent.com/serverlessworkflow/serverlessworkflow.github.io/main/static/schemas/{version}/workflow.yaml"; + var address = $"https://raw.githubusercontent.com/serverlessworkflow/serverlessworkflow.github.io/main/public/schemas/{version}/workflow.yaml"; var yamlSchema = await this.HttpClient.GetStringAsync(address); this._knownSchemas.Add(version, this.YamlSerializer.ConvertToJson(yamlSchema)); return this._knownSchemas[version];