diff --git a/src/framework/Elsa.Studio.DomInterop/ClientLib/src/dom/element/get-visible-height.ts b/src/framework/Elsa.Studio.DomInterop/ClientLib/src/dom/element/get-visible-height.ts index 0fffdfa4..51a4e2c8 100644 --- a/src/framework/Elsa.Studio.DomInterop/ClientLib/src/dom/element/get-visible-height.ts +++ b/src/framework/Elsa.Studio.DomInterop/ClientLib/src/dom/element/get-visible-height.ts @@ -2,6 +2,10 @@ import {getElement} from "./get-element"; export function getVisibleHeight(elementOrQuerySelector: Element | string): number { const element = getElement(elementOrQuerySelector); + + if(!element) + return 0; + const rect = element.getBoundingClientRect(); const windowHeight = window.innerHeight; diff --git a/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/calculate-activity-size.ts b/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/calculate-activity-size.ts index f6fa3e7c..8d2240bb 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/calculate-activity-size.ts +++ b/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/calculate-activity-size.ts @@ -16,6 +16,12 @@ export function calculateActivitySize(activity: Activity): Promise { return new Promise((resolve, reject) => { const checkSize = () => { const activityElement: Element = wrapper.getElementsByTagName(activityTagName)[0]; + + if(activityElement == null) { + reject('Activity element not found.'); + return; + } + const activityElementRect = activityElement.getBoundingClientRect(); // If the custom element has no width or height yet, it means it has not yet rendered. diff --git a/src/modules/Elsa.Studio.Workflows.Designer/Components/FlowchartDesigner.razor.cs b/src/modules/Elsa.Studio.Workflows.Designer/Components/FlowchartDesigner.razor.cs index 98e48347..610ab9cb 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/Components/FlowchartDesigner.razor.cs +++ b/src/modules/Elsa.Studio.Workflows.Designer/Components/FlowchartDesigner.razor.cs @@ -15,6 +15,7 @@ using Elsa.Studio.Workflows.UI.Models; using Humanizer; using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Logging; using Microsoft.JSInterop; using MudBlazor.Utilities; using ThrottleDebounce; @@ -28,9 +29,9 @@ public partial class FlowchartDesigner : IDisposable, IAsyncDisposable { private readonly string _containerId = $"container-{Guid.NewGuid():N}"; private DotNetObjectReference? _componentRef; - private IFlowchartMapper? _flowchartMapper = default!; - private IActivityMapper? _activityMapper = default!; - private X6GraphApi _graphApi = default!; + private IFlowchartMapper? _flowchartMapper = null!; + private IActivityMapper? _activityMapper = null!; + private X6GraphApi _graphApi = null!; private readonly PendingActionsQueue _pendingGraphActions; private RateLimitedFunc _rateLimitedLoadFlowchartAction; private IDictionary? _activityStats; @@ -39,12 +40,12 @@ public partial class FlowchartDesigner : IDisposable, IAsyncDisposable /// public FlowchartDesigner() { - _pendingGraphActions = new PendingActionsQueue(() => new(_graphApi != null!)); + _pendingGraphActions = new PendingActionsQueue(() => new(_graphApi != null!), () => Logger); _rateLimitedLoadFlowchartAction = Debouncer.Debounce(async () => { await InvokeAsync(async () => await LoadFlowchartAsync(Flowchart, ActivityStats)); }, TimeSpan.FromMilliseconds(100)); } /// The flowchart to render. - [Parameter] public JsonObject Flowchart { get; set; } = default!; + [Parameter] public JsonObject Flowchart { get; set; } = null!; /// The activity stats to render. [Parameter] public IDictionary? ActivityStats { get; set; } @@ -67,12 +68,13 @@ public FlowchartDesigner() /// An event raised when the graph is updated. [Parameter] public EventCallback GraphUpdated { get; set; } - [Inject] private DesignerJsInterop DesignerJsInterop { get; set; } = default!; - [Inject] private IThemeService ThemeService { get; set; } = default!; - [Inject] private IActivityRegistry ActivityRegistry { get; set; } = default!; - [Inject] private IMapperFactory MapperFactory { get; set; } = default!; - [Inject] private IIdentityGenerator IdentityGenerator { get; set; } = default!; - [Inject] private IActivityNameGenerator ActivityNameGenerator { get; set; } = default!; + [Inject] private DesignerJsInterop DesignerJsInterop { get; set; } = null!; + [Inject] private IThemeService ThemeService { get; set; } = null!; + [Inject] private IActivityRegistry ActivityRegistry { get; set; } = null!; + [Inject] private IMapperFactory MapperFactory { get; set; } = null!; + [Inject] private IIdentityGenerator IdentityGenerator { get; set; } = null!; + [Inject] private IActivityNameGenerator ActivityNameGenerator { get; set; } = null!; + [Inject] private ILogger Logger { get; set; } = null!; /// /// Invoked from JavaScript when an activity is selected. diff --git a/src/modules/Elsa.Studio.Workflows.Designer/Interop/DesignerJsInterop.cs b/src/modules/Elsa.Studio.Workflows.Designer/Interop/DesignerJsInterop.cs index 5c05ecb5..ad2e50fa 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/Interop/DesignerJsInterop.cs +++ b/src/modules/Elsa.Studio.Workflows.Designer/Interop/DesignerJsInterop.cs @@ -24,7 +24,7 @@ internal class DesignerJsInterop(IJSRuntime jsRuntime, IServiceProvider serviceP /// The ID of the graph. public async ValueTask CreateGraphAsync(string containerId, DotNetObjectReference componentRef, bool isReadOnly = false) { - return await InvokeAsync(async module => + return await TryInvokeAsync(async module => { await module.InvokeAsync("createGraph", containerId, componentRef, isReadOnly); return new X6GraphApi(module, serviceProvider, containerId); diff --git a/src/modules/Elsa.Studio.Workflows.Designer/Interop/JsInteropBase.cs b/src/modules/Elsa.Studio.Workflows.Designer/Interop/JsInteropBase.cs index 1f410702..903f4de8 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/Interop/JsInteropBase.cs +++ b/src/modules/Elsa.Studio.Workflows.Designer/Interop/JsInteropBase.cs @@ -71,6 +71,10 @@ protected async Task TryInvokeAsync(Func> { // Ignore. } + catch(ObjectDisposedException) + { + // Ignore. + } return default!; } diff --git a/src/modules/Elsa.Studio.Workflows.Designer/Services/PendingActionsQueue.cs b/src/modules/Elsa.Studio.Workflows.Designer/Services/PendingActionsQueue.cs index 10f95eee..4adc5333 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/Services/PendingActionsQueue.cs +++ b/src/modules/Elsa.Studio.Workflows.Designer/Services/PendingActionsQueue.cs @@ -1,23 +1,34 @@ +using Microsoft.Extensions.Logging; + namespace Elsa.Studio.Workflows.Designer.Services; -public class PendingActionsQueue(Func> shortCircuit) +/// +/// A queue for pending actions that can be processed in a FIFO manner. +/// +public class PendingActionsQueue(Func> shortCircuit, Func logger) { private readonly Queue> _pendingActions = new(); + /// + /// Processes all pending actions. + /// public async Task ProcessAsync() { while (_pendingActions.Any()) { var action = _pendingActions.Dequeue(); - await action(); + await TryExecuteActionAsync(action); } } + /// + /// Enqueues an action to be executed. + /// public async Task EnqueueAsync(Func action) { if(await shortCircuit()) { - await action(); + await TryExecuteActionAsync(action); return; } @@ -30,6 +41,9 @@ public async Task EnqueueAsync(Func action) await tsc.Task; } + /// + /// Enqueues an action to be executed. + /// public async Task EnqueueAsync(Func> action) { if(await shortCircuit()) @@ -43,4 +57,16 @@ public async Task EnqueueAsync(Func> action) }); return await tsc.Task; } + + private async Task TryExecuteActionAsync(Func action) + { + try + { + await action(); + } + catch(Exception ex) + { + logger().LogWarning(ex, "An error occurred while executing a pending action."); + } + } } \ No newline at end of file