From a6fd782f317ad5bbdf19ea700ee072ad73968736 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 Jan 2026 00:33:15 +0000 Subject: [PATCH] Add export/capture feature to Flowchart Designer - Restore #693 onto Main instead. Introduces a "Capture" button allowing users to export flowcharts as PNG, JPEG, or SVG. Adds a dialog for export options (file name, format, padding) with validation. Integrates the X6 export plugin and implements JS interop for exporting. Refactors designer wrappers and updates UI to support the new export functionality. Includes minor code cleanups and dependency updates. --- .../UI/Contracts/IDiagramDesigner.cs | 1 - .../ClientLib/package.json | 1 + .../src/designer/api/create-graph.ts | 5 +- .../src/designer/api/export-content.ts | 30 ++++++ .../ClientLib/src/designer/api/index.ts | 1 + .../Components/FlowchartDesigner.razor.cs | 6 ++ .../Elsa.Studio.Workflows.Designer.csproj | 17 +--- .../Interop/X6GraphApi.cs | 4 + .../Options/CaptureOptions.cs | 28 ++++++ .../CaptureFlowChartDialog.razor.cs | 48 +++++++++ .../Flowcharts/CaptureFlowchartDialog.razor | 28 ++++++ .../FlowchartDesignerWrapper.razor.cs | 17 ++-- .../Flowcharts/FlowchartDiagramDesigner.cs | 69 ++++++++----- .../FlowchartDiagramDesignerProvider.cs | 15 ++- .../DiagramDesignerWrapper.razor.cs | 97 ++++++------------- .../Validators/CaptureOptionsValidator.cs | 17 ++++ 16 files changed, 264 insertions(+), 120 deletions(-) create mode 100644 src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/export-content.ts create mode 100644 src/modules/Elsa.Studio.Workflows.Designer/Options/CaptureOptions.cs create mode 100644 src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/CaptureFlowChartDialog.razor.cs create mode 100644 src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/CaptureFlowchartDialog.razor create mode 100644 src/modules/Elsa.Studio.Workflows/Validators/CaptureOptionsValidator.cs diff --git a/src/modules/Elsa.Studio.Workflows.Core/UI/Contracts/IDiagramDesigner.cs b/src/modules/Elsa.Studio.Workflows.Core/UI/Contracts/IDiagramDesigner.cs index e77f26fe6..df0d984f2 100644 --- a/src/modules/Elsa.Studio.Workflows.Core/UI/Contracts/IDiagramDesigner.cs +++ b/src/modules/Elsa.Studio.Workflows.Core/UI/Contracts/IDiagramDesigner.cs @@ -1,7 +1,6 @@ using System.Text.Json.Nodes; using Elsa.Studio.Workflows.Domain.Models; using Elsa.Studio.Workflows.UI.Contexts; -using Elsa.Studio.Workflows.UI.Models; using Microsoft.AspNetCore.Components; namespace Elsa.Studio.Workflows.UI.Contracts; diff --git a/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/package.json b/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/package.json index 414289dea..b618ee26e 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/package.json +++ b/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/package.json @@ -8,6 +8,7 @@ "@antv/layout": "^0.3.25", "@antv/x6": "^2.18.1", "@antv/x6-plugin-clipboard": "^2.1.6", + "@antv/x6-plugin-export": "^2.1.6", "@antv/x6-plugin-history": "^2.2.4", "@antv/x6-plugin-keyboard": "^2.2.3", "@antv/x6-plugin-scroller": "^2.0.10", diff --git a/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/create-graph.ts b/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/create-graph.ts index 09b6d97fe..10816b8e6 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/create-graph.ts +++ b/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/create-graph.ts @@ -4,7 +4,8 @@ import {Snapline} from "@antv/x6-plugin-snapline"; import {Transform} from "@antv/x6-plugin-transform"; import {Keyboard} from "@antv/x6-plugin-keyboard"; import {Clipboard} from '@antv/x6-plugin-clipboard'; -import {History} from '@antv/x6-plugin-history'; +import { History } from '@antv/x6-plugin-history'; +import { Export } from "@antv/x6-plugin-export"; import {DotNetComponentRef, graphBindings} from "./graph-bindings"; import {DotNetFlowchartDesigner} from "./dotnet-flowchart-designer"; import {Activity} from "../models"; @@ -109,6 +110,8 @@ export async function createGraph(containerId: string, componentRef: DotNetCompo } }); + graph.use(new Export()); + graph.use( new History({ enabled: true, diff --git a/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/export-content.ts b/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/export-content.ts new file mode 100644 index 000000000..e6d70276a --- /dev/null +++ b/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/export-content.ts @@ -0,0 +1,30 @@ +import { graphBindings } from "./graph-bindings"; + +export interface CaptureOptions { + fileName?: string; + padding?: number; + format?: string; +} + +export function exportContentToJPEG(graphId: string, options: CaptureOptions) { + const { graph } = graphBindings[graphId]; + if (!graph) return; + + graph.exportJPEG(options.fileName + ".jpeg", { + padding: options.padding, + }); +} +export function exportContentToPNG(graphId: string, options: CaptureOptions) { + const { graph } = graphBindings[graphId]; + if (!graph) return; + + graph.exportPNG(options.fileName + ".png", { + padding: options.padding, + }); +} +export function exportContentToSVG(graphId: string, options: CaptureOptions) { + const { graph } = graphBindings[graphId]; + if (!graph) return; + + graph.exportSVG(options.fileName + ".svg", {}); +} \ No newline at end of file diff --git a/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/index.ts b/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/index.ts index 6fc148758..a7ba4b364 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/index.ts +++ b/src/modules/Elsa.Studio.Workflows.Designer/ClientLib/src/designer/api/index.ts @@ -3,6 +3,7 @@ export * from './calculate-activity-size'; export * from './center-content'; export * from './create-graph'; export * from './dispose-graph'; +export * from './export-content'; export * from './graph-bindings'; export * from './load-graph'; export * from './paste-cells'; 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 43a45b277..6d855508b 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/Components/FlowchartDesigner.razor.cs +++ b/src/modules/Elsa.Studio.Workflows.Designer/Components/FlowchartDesigner.razor.cs @@ -327,6 +327,12 @@ public async Task SelectActivityAsync(string id) /// public async Task CenterContentAsync() => await ScheduleGraphActionAsync(() => _graphApi.CenterContentAsync()); + /// + /// Exports the graphs content to a supplied format. + /// + /// The capture options + public async Task ExportContentToFormatAsync(CaptureOptions captureOptions) => await ScheduleGraphActionAsync(() => _graphApi.ExportContentToFormatAsync(captureOptions)); + /// Update the Graph Layout. public async Task AutoLayoutAsync( JsonObject activity, diff --git a/src/modules/Elsa.Studio.Workflows.Designer/Elsa.Studio.Workflows.Designer.csproj b/src/modules/Elsa.Studio.Workflows.Designer/Elsa.Studio.Workflows.Designer.csproj index 721bdb1c8..0412f23eb 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/Elsa.Studio.Workflows.Designer.csproj +++ b/src/modules/Elsa.Studio.Workflows.Designer/Elsa.Studio.Workflows.Designer.csproj @@ -6,25 +6,16 @@ - + - + - - - - - - - - - - - + + diff --git a/src/modules/Elsa.Studio.Workflows.Designer/Interop/X6GraphApi.cs b/src/modules/Elsa.Studio.Workflows.Designer/Interop/X6GraphApi.cs index c28d41fea..63b12aea0 100644 --- a/src/modules/Elsa.Studio.Workflows.Designer/Interop/X6GraphApi.cs +++ b/src/modules/Elsa.Studio.Workflows.Designer/Interop/X6GraphApi.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using Elsa.Studio.Workflows.Designer.Contracts; using Elsa.Studio.Workflows.Designer.Models; +using Elsa.Studio.Workflows.Designer.Options; using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; @@ -93,6 +94,9 @@ public async Task LoadGraphAsync(X6Graph graph) /// Center the canvas content. public async Task CenterContentAsync() => await InvokeAsync(module => module.InvokeVoidAsync("centerContent", _containerId)); + /// Exports the graphs content to a supplied + public async Task ExportContentToFormatAsync(CaptureOptions captureOptions) => await InvokeAsync(module => module.InvokeVoidAsync($"exportContentTo{captureOptions.Format}", _containerId, captureOptions)); + /// Adjusts the graph layout. public async Task AutoLayoutAsync(X6Graph graph) { diff --git a/src/modules/Elsa.Studio.Workflows.Designer/Options/CaptureOptions.cs b/src/modules/Elsa.Studio.Workflows.Designer/Options/CaptureOptions.cs new file mode 100644 index 000000000..d6ecfc603 --- /dev/null +++ b/src/modules/Elsa.Studio.Workflows.Designer/Options/CaptureOptions.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; + +namespace Elsa.Studio.Workflows.Designer.Options; + +/// +/// Represents the workflow capture options +/// +public class CaptureOptions +{ + /// + /// Gets or sets the export format. + /// Supported formats are JPEG, SVG and PNG. + /// Default: PNG. + /// + [Required] public string? Format { get; set; } = "PNG"; + + /// + /// Gets or sets the filename. + /// Default: Flowchart + /// + [Required] public string? FileName { get; set; } = "Flowchart"; + + /// + /// Gets or sets the padding around the workflow. + /// Default: 150. + /// + public int Padding { get; set; } = 150; +} \ No newline at end of file diff --git a/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/CaptureFlowChartDialog.razor.cs b/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/CaptureFlowChartDialog.razor.cs new file mode 100644 index 000000000..d6b21308d --- /dev/null +++ b/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/CaptureFlowChartDialog.razor.cs @@ -0,0 +1,48 @@ +using Blazored.FluentValidation; +using Elsa.Studio.Workflows.Designer.Options; +using Elsa.Studio.Workflows.Validators; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using MudBlazor; + +namespace Elsa.Studio.Workflows.DiagramDesigners.Flowcharts +{ + public partial class CaptureFlowchartDialog + { + private readonly CaptureOptions _captureModel = new(); + private EditContext _editContext = null!; + private CaptureOptionsValidator _validator = null!; + private FluentValidationValidator _fluentValidationValidator = null!; + private bool loading = false; + + [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; + [Parameter] public string FileName { get; set; } = null!; + + protected override void OnParametersSet() + { + _captureModel.FileName = FileName; + _editContext = new(_captureModel); + _validator = new(Localizer); + } + + private Task OnCancelClicked() + { + MudDialog.Cancel(); + return Task.CompletedTask; + } + + private async Task OnSubmitClicked() + { + if (!await _fluentValidationValidator.ValidateAsync()) + return; + + await OnValidSubmit(); + } + + private Task OnValidSubmit() + { + MudDialog.Close(_captureModel); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/CaptureFlowchartDialog.razor b/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/CaptureFlowchartDialog.razor new file mode 100644 index 000000000..10d40e64a --- /dev/null +++ b/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/CaptureFlowchartDialog.razor @@ -0,0 +1,28 @@ +@using Variant = MudBlazor.Variant +@inherits StudioComponentBase +@using Elsa.Studio.Workflows.Services +@inject ILocalizer Localizer + + + + + + + + PNG + JPEG + SVG + + + @if (_captureModel.Format != "SVG") + { + + } + + + + + @Localizer["Cancel"] + @Localizer["Ok"] + + \ No newline at end of file diff --git a/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDesignerWrapper.razor.cs b/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDesignerWrapper.razor.cs index 86d777038..1b668996c 100644 --- a/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDesignerWrapper.razor.cs +++ b/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDesignerWrapper.razor.cs @@ -1,10 +1,9 @@ -using System.Text.Json; -using System.Text.Json.Nodes; using Elsa.Api.Client.Extensions; using Elsa.Api.Client.Resources.ActivityDescriptors.Enums; using Elsa.Api.Client.Resources.ActivityDescriptors.Models; using Elsa.Api.Client.Shared.Models; using Elsa.Studio.Workflows.Designer.Components; +using Elsa.Studio.Workflows.Designer.Options; using Elsa.Studio.Workflows.Domain.Contracts; using Elsa.Studio.Workflows.Domain.Models; using Elsa.Studio.Workflows.Extensions; @@ -14,6 +13,8 @@ using Humanizer; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; +using System.Text.Json; +using System.Text.Json.Nodes; namespace Elsa.Studio.Workflows.DiagramDesigners.Flowcharts; @@ -137,7 +138,14 @@ public async Task UpdateActivityAsync(string id, JsonObject activity) /// Centers the content of the designer. /// public async Task CenterContentAsync() => await Designer.CenterContentAsync(); - + + /// + /// Exports the graphs content to a supplied format. + /// + /// The capture options + /// + public async Task ExportContentToFormatAsync(CaptureOptions captureOptions) => await Designer.ExportContentToFormatAsync(captureOptions); + /// /// Auto layouts the flowchart. /// @@ -207,7 +215,4 @@ private async Task OnCanvasSelected() if (ActivitySelected.HasDelegate) await ActivitySelected.InvokeAsync(Flowchart); } - - private async Task OnZoomToFitClick() => await Designer.ZoomToFitAsync(); - private async Task OnCenterContentClick() => await Designer.CenterContentAsync(); } \ No newline at end of file diff --git a/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDiagramDesigner.cs b/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDiagramDesigner.cs index 9965a032b..c2404ce43 100644 --- a/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDiagramDesigner.cs +++ b/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDiagramDesigner.cs @@ -1,53 +1,38 @@ -using System.Text.Json.Nodes; +using Elsa.Api.Client.Extensions; using Elsa.Studio.Localization; +using Elsa.Studio.Workflows.Designer.Options; using Elsa.Studio.Workflows.Domain.Models; -using Elsa.Studio.Workflows.Models; using Elsa.Studio.Workflows.UI.Contexts; using Elsa.Studio.Workflows.UI.Contracts; -using Elsa.Studio.Workflows.UI.Models; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using MudBlazor; +using System.Text.Json.Nodes; namespace Elsa.Studio.Workflows.DiagramDesigners.Flowcharts; /// /// A diagram designer that displays a flowchart. /// -public class FlowchartDiagramDesigner(ILocalizer localizer) : IDiagramDesignerToolboxProvider +public class FlowchartDiagramDesigner(ILocalizer localizer, IDialogService dialogService) : IDiagramDesignerToolboxProvider { private FlowchartDesignerWrapper? _designerWrapper; private readonly Guid _id = Guid.NewGuid(); /// - public async Task LoadRootActivityAsync(JsonObject activity, IDictionary? activityStatsMap) - { - await InvokeDesignerActionAsync(x => x.LoadFlowchartAsync(activity, activityStatsMap)); - } + public async Task LoadRootActivityAsync(JsonObject activity, IDictionary? activityStatsMap) => await InvokeDesignerActionAsync(x => x.LoadFlowchartAsync(activity, activityStatsMap)); /// - public async Task UpdateActivityAsync(string id, JsonObject activity) - { - await InvokeDesignerActionAsync(x => x.UpdateActivityAsync(id, activity)); - } - + public async Task UpdateActivityAsync(string id, JsonObject activity) => await InvokeDesignerActionAsync(x => x.UpdateActivityAsync(id, activity)); + /// - public async Task UpdateActivityStatsAsync(string id, ActivityStats stats) - { - await InvokeDesignerActionAsync(x => x.UpdateActivityStatsAsync(id, stats)); - } + public async Task UpdateActivityStatsAsync(string id, ActivityStats stats) => await InvokeDesignerActionAsync(x => x.UpdateActivityStatsAsync(id, stats)); /// - public async Task SelectActivityAsync(string id) - { - await InvokeDesignerActionAsync(x => x.SelectActivityAsync(id)); - } + public async Task SelectActivityAsync(string id) => await InvokeDesignerActionAsync(x => x.SelectActivityAsync(id)); /// - public async Task ReadRootActivityAsync() - { - return await _designerWrapper!.ReadRootActivityAsync(); - } + public async Task ReadRootActivityAsync() => await _designerWrapper!.ReadRootActivityAsync(); /// public RenderFragment DisplayDesigner(DisplayContext context) @@ -83,6 +68,8 @@ public IEnumerable GetToolboxItems(bool isReadonly) { yield return DisplayToolboxItem(localizer["Auto layout"], Icons.Material.Outlined.AutoAwesomeMosaic, localizer["Auto layout"], OnAutoLayoutClicked); } + + yield return DisplayToolboxItem(localizer["Capture"], @Icons.Material.Outlined.Fullscreen, localizer["Capture flowchart"], OnCaptureClicked); } private RenderFragment DisplayToolboxItem(string title, string icon, string description, Func onClick) @@ -113,4 +100,36 @@ private async Task InvokeDesignerActionAsync(Func _designerWrapper != null ? _designerWrapper.ZoomToFitAsync() : Task.CompletedTask; private Task OnCenterClicked() => _designerWrapper != null ? _designerWrapper!.CenterContentAsync() : Task.CompletedTask; private Task OnAutoLayoutClicked() => _designerWrapper != null ? _designerWrapper!.AutoLayoutAsync() : Task.CompletedTask; + private async Task OnCaptureClicked() + { + if (_designerWrapper is null) return; + + var flowchart = _designerWrapper.Flowchart; + var invalidChars = Path.GetInvalidFileNameChars(); + var name = flowchart?.GetName() ?? "Workflow"; + var version = flowchart?.GetVersion(); + var validFileName = string.Concat(name.Select(c => invalidChars.Contains(c) ? "_" : c.ToString())).Trim() + + (version is null ? string.Empty : $"_v{version}"); + + var parameters = new DialogParameters + { + { x => x.FileName, validFileName }, + }; + + var options = new DialogOptions + { + CloseOnEscapeKey = true, + Position = DialogPosition.Center, + CloseButton = true, + FullWidth = true, + MaxWidth = MaxWidth.Small + }; + + var dialogInstance = await dialogService.ShowAsync(localizer["Capture content"], parameters, options); + var dialogResult = await dialogInstance.Result; + if (dialogResult?.Canceled ?? true) return; + + var captureOptions = dialogResult?.Data as CaptureOptions ?? new(); + await _designerWrapper.ExportContentToFormatAsync(captureOptions); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDiagramDesignerProvider.cs b/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDiagramDesignerProvider.cs index 4c3c76f71..ccb30aa94 100644 --- a/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDiagramDesignerProvider.cs +++ b/src/modules/Elsa.Studio.Workflows/DiagramDesigners/Flowcharts/FlowchartDiagramDesignerProvider.cs @@ -1,20 +1,19 @@ -using System.Text.Json.Nodes; using Elsa.Api.Client.Extensions; -using Elsa.Api.Client.Resources.WorkflowDefinitions.Models; using Elsa.Studio.Localization; using Elsa.Studio.Workflows.UI.Contracts; using JetBrains.Annotations; +using MudBlazor; +using System.Text.Json.Nodes; namespace Elsa.Studio.Workflows.DiagramDesigners.Flowcharts; -/// -/// A diagram designer provider for the Flowchart designer. -/// -[UsedImplicitly] /// /// Provides flowchart diagram designer services. /// -public class FlowchartDiagramDesignerProvider(ILocalizer localizer) : IDiagramDesignerProvider +/// The localizer used to provide localized strings for the designer interface. +/// The dialog service used to display dialogs within the designer. +[UsedImplicitly] +public class FlowchartDiagramDesignerProvider(ILocalizer localizer, IDialogService dialogService) : IDiagramDesignerProvider { /// public double Priority => 0; @@ -23,5 +22,5 @@ public class FlowchartDiagramDesignerProvider(ILocalizer localizer) : IDiagramDe public bool GetSupportsActivity(JsonObject activity) => activity.GetTypeName() == "Elsa.Flowchart"; /// - public IDiagramDesigner GetEditor() => new FlowchartDiagramDesigner(localizer); + public IDiagramDesigner GetEditor() => new FlowchartDiagramDesigner(localizer, dialogService); } \ No newline at end of file diff --git a/src/modules/Elsa.Studio.Workflows/Shared/Components/DiagramDesignerWrapper.razor.cs b/src/modules/Elsa.Studio.Workflows/Shared/Components/DiagramDesignerWrapper.razor.cs index 2b268ce94..b97030a22 100644 --- a/src/modules/Elsa.Studio.Workflows/Shared/Components/DiagramDesignerWrapper.razor.cs +++ b/src/modules/Elsa.Studio.Workflows/Shared/Components/DiagramDesignerWrapper.razor.cs @@ -6,12 +6,9 @@ using Elsa.Studio.Workflows.Domain.Extensions; using Elsa.Studio.Workflows.Domain.Models; using Elsa.Studio.Workflows.Extensions; -using Elsa.Studio.Workflows.Models; using Elsa.Studio.Workflows.Shared.Args; using Elsa.Studio.Workflows.UI.Args; -using Elsa.Studio.Workflows.UI.Contexts; using Elsa.Studio.Workflows.UI.Contracts; -using Elsa.Studio.Workflows.UI.Models; using Humanizer; using Microsoft.AspNetCore.Components; using MudBlazor; @@ -21,108 +18,76 @@ namespace Elsa.Studio.Workflows.Shared.Components; /// A wrapper around the diagram designer that provides a breadcrumb and a toolbar. public partial class DiagramDesignerWrapper { + private string? _lastSelectedNodeId; private IDiagramDesigner? _diagramDesigner; private Stack _pathSegments = new(); private JsonObject? _currentContainerActivity; private List _breadcrumbItems = new(); - private IDictionary _activityStats = - new Dictionary(); + private IDictionary _activityStats = new Dictionary(); private ActivityGraph _activityGraph = null!; - private IDictionary _indexedActivityNodes = - new Dictionary(); + private IDictionary _indexedActivityNodes = new Dictionary(); + + [Inject] private IDiagramDesignerService DiagramDesignerService { get; set; } = null!; + [Inject] private IActivityDisplaySettingsRegistry ActivityDisplaySettingsRegistry { get; set; } = null!; + [Inject] private IActivityPortService ActivityPortService { get; set; } = null!; + [Inject] private IActivityRegistry ActivityRegistry { get; set; } = null!; + [Inject] private IIdentityGenerator IdentityGenerator { get; set; } = null!; + [Inject] private IActivityExecutionService ActivityExecutionService { get; set; } = null!; + [Inject] private IActivityVisitor ActivityVisitor { get; set; } = null!; + [Inject] private IWorkflowDefinitionService WorkflowDefinitionService { get; set; } = null!; + [Inject] private ISnackbar Snackbar { get; set; } = null!; - /// The workflow definition version ID. - [Parameter] /// /// Gets or sets the workflow definition version id. /// - public string WorkflowDefinitionVersionId { get; set; } = null!; + [Parameter] public string WorkflowDefinitionVersionId { get; set; } = null!; - /// The root activity to display. - [Parameter] /// - /// Gets or sets the activity. + /// Gets or sets the root activity. /// - public JsonObject Activity { get; set; } = null!; + [Parameter] public JsonObject Activity { get; set; } = null!; - /// Whether the designer is read-only. - [Parameter] /// /// Indicates whether is read only. /// - public bool IsReadOnly { get; set; } + [Parameter] public bool IsReadOnly { get; set; } - /// The workflow instance ID, if any. - [Parameter] /// /// Gets or sets the workflow instance id. /// - public string? WorkflowInstanceId { get; set; } + [Parameter] public string? WorkflowInstanceId { get; set; } - /// A custom toolbar to display. - [Parameter] /// /// Gets or sets the custom toolbar items. /// - public RenderFragment? CustomToolbarItems { get; set; } + [Parameter] public RenderFragment? CustomToolbarItems { get; set; } - /// Whether the designer is progressing. - [Parameter] /// /// Indicates whether is progressing. /// - public bool IsProgressing { get; set; } + [Parameter] public bool IsProgressing { get; set; } - /// An event raised when an activity is selected. - [Parameter] /// - /// Gets or sets the activity selected event callback. + /// Gets or sets the callback event when an activity is selected. /// - public EventCallback ActivitySelected { get; set; } + [Parameter] public EventCallback ActivitySelected { get; set; } - /// An event raised when an embedded port is selected. + /// + /// Gets or sets the callback event when an embedded port is selected. + /// [Parameter] public EventCallback GraphUpdated { get; set; } + /// + /// Gets or sets the callback event when the activity is updated. + /// [Parameter] public EventCallback ActivityUpdated { get; set; } - /// An event raised when the path changes. - [Parameter] /// - /// Gets or sets the path changed event callback. + /// Gets or sets the callback event when a path is changed. /// - public EventCallback PathChanged { get; set; } - - [Inject] - private IDiagramDesignerService DiagramDesignerService { get; set; } = null!; - - [Inject] - private IActivityDisplaySettingsRegistry ActivityDisplaySettingsRegistry { get; set; } = null!; - - [Inject] - private IActivityPortService ActivityPortService { get; set; } = null!; + [Parameter] public EventCallback PathChanged { get; set; } - [Inject] - private IActivityRegistry ActivityRegistry { get; set; } = null!; - - [Inject] - private IIdentityGenerator IdentityGenerator { get; set; } = null!; - - [Inject] - private IActivityExecutionService ActivityExecutionService { get; set; } = null!; - - [Inject] - private IActivityVisitor ActivityVisitor { get; set; } = null!; - - [Inject] - private IWorkflowDefinitionService WorkflowDefinitionService { get; set; } = null!; - - [Inject] - private ISnackbar Snackbar { get; set; } = null!; - - private ActivityPathSegment? CurrentPathSegment => - _pathSegments.TryPeek(out var segment) ? segment : null; - - private string? _lastSelectedNodeId; + private ActivityPathSegment? CurrentPathSegment => _pathSegments.TryPeek(out var segment) ? segment : null; /// Selects the activity with the specified ID. /// The ID of the activity ID select. diff --git a/src/modules/Elsa.Studio.Workflows/Validators/CaptureOptionsValidator.cs b/src/modules/Elsa.Studio.Workflows/Validators/CaptureOptionsValidator.cs new file mode 100644 index 000000000..b23ab1bcc --- /dev/null +++ b/src/modules/Elsa.Studio.Workflows/Validators/CaptureOptionsValidator.cs @@ -0,0 +1,17 @@ +using Elsa.Studio.Localization; +using Elsa.Studio.Workflows.Designer.Options; +using FluentValidation; + +namespace Elsa.Studio.Workflows.Validators; + +/// +/// A validator for instances. +/// +public class CaptureOptionsValidator : AbstractValidator +{ + public CaptureOptionsValidator(ILocalizer localizer) + { + RuleFor(x => x.FileName).NotEmpty().WithMessage(localizer["Please enter a filename for the export."]); + RuleFor(x => x.Padding).GreaterThanOrEqualTo(0); + } +} \ No newline at end of file