diff --git a/CHANGELOG.md b/CHANGELOG.md
index 991aa334..a1088888 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+# 0.30.0
+
+This version introduces a new step component: `launchPad`.
+
+The `launchPad` step component allows you to place multiple steps along a horizontal axis. Its design suggests that any of the contained steps can be executed independently or simultaneously. You can use it as a container for parallel execution or as a trigger hub-waiting for one or more embedded trigger steps to activate the workflow.
+
+The main goal of this addition is to enable the creation of workflows with multiple triggers in the standard version of the designer.
+
+To see how it looks, please check out [this example](https://nocode-js.github.io/sequential-workflow-designer/examples/triggers.html).
+
# 0.29.2
Added a new theme: `soft`.
diff --git a/README.md b/README.md
index 4abb9e96..58d358f9 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,7 @@ Features:
## 👀 Examples
* [⏩ Live Testing](https://nocode-js.github.io/sequential-workflow-designer/examples/live-testing.html)
+* [💥 Triggers](https://nocode-js.github.io/sequential-workflow-designer/examples/triggers.html)
* [❎ Fullscreen](https://nocode-js.github.io/sequential-workflow-designer/examples/fullscreen.html)
* [🌅 Image Filter](https://nocode-js.github.io/sequential-workflow-designer/examples/image-filter.html)
* [🔴 Particles](https://nocode-js.github.io/sequential-workflow-designer/examples/particles.html)
@@ -104,10 +105,10 @@ Add the below code to your head section in HTML document.
```html
...
-
-
-
-
+
+
+
+
```
Call the designer by:
diff --git a/angular/designer/package.json b/angular/designer/package.json
index cea6b807..1ba1a371 100644
--- a/angular/designer/package.json
+++ b/angular/designer/package.json
@@ -1,7 +1,7 @@
{
"name": "sequential-workflow-designer-angular",
"description": "Angular wrapper for Sequential Workflow Designer component.",
- "version": "0.29.2",
+ "version": "0.30.0",
"author": {
"name": "NoCode JS",
"url": "https://nocode-js.com/"
@@ -15,7 +15,7 @@
"peerDependencies": {
"@angular/common": "12 - 19",
"@angular/core": "12 - 19",
- "sequential-workflow-designer": "^0.29.2"
+ "sequential-workflow-designer": "^0.30.0"
},
"dependencies": {
"tslib": "^2.3.0"
diff --git a/angular/designer/src/designer.component.ts b/angular/designer/src/designer.component.ts
index 43cbd3f9..64dc9a6e 100644
--- a/angular/designer/src/designer.component.ts
+++ b/angular/designer/src/designer.component.ts
@@ -29,6 +29,7 @@ import {
ToolboxConfiguration,
UidGenerator,
ValidatorConfiguration,
+ PlaceholderConfiguration,
I18n,
PreferenceStorage
} from 'sequential-workflow-designer';
@@ -69,6 +70,8 @@ export class DesignerComponent implements AfterViewInit, OnChanges, OnDestroy {
public stepsConfiguration?: StepsConfiguration;
@Input('validatorConfiguration')
public validatorConfiguration?: ValidatorConfiguration;
+ @Input('placeholderConfiguration')
+ public placeholderConfiguration?: PlaceholderConfiguration;
@Input('toolboxConfiguration')
public toolboxConfiguration?: AngularToolboxConfiguration | false;
@Input('controlBar')
@@ -214,6 +217,7 @@ export class DesignerComponent implements AfterViewInit, OnChanges, OnDestroy {
},
steps: this.stepsConfiguration,
validator: this.validatorConfiguration,
+ placeholder: this.placeholderConfiguration,
toolbox: this.toolboxConfiguration
? {
isCollapsed: this.isToolboxCollapsed,
diff --git a/demos/angular-app/package.json b/demos/angular-app/package.json
index cfa8da8b..f967d4ac 100644
--- a/demos/angular-app/package.json
+++ b/demos/angular-app/package.json
@@ -26,8 +26,8 @@
"@angular/platform-browser-dynamic": "^17.3.9",
"@angular/router": "^17.3.9",
"rxjs": "~7.8.0",
- "sequential-workflow-designer": "^0.29.2",
- "sequential-workflow-designer-angular": "^0.29.2",
+ "sequential-workflow-designer": "^0.30.0",
+ "sequential-workflow-designer-angular": "^0.30.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.6"
},
diff --git a/demos/angular-app/yarn.lock b/demos/angular-app/yarn.lock
index 6dbf3908..da36d43f 100644
--- a/demos/angular-app/yarn.lock
+++ b/demos/angular-app/yarn.lock
@@ -6744,17 +6744,17 @@ send@0.18.0:
range-parser "~1.2.1"
statuses "2.0.1"
-sequential-workflow-designer-angular@^0.29.2:
- version "0.29.2"
- resolved "https://registry.yarnpkg.com/sequential-workflow-designer-angular/-/sequential-workflow-designer-angular-0.29.2.tgz#04a12899ea6daf50e12bae286bd2d792519f55c2"
- integrity sha512-IiOi46UrE31EUe4DL9ZT1MwswDIsmpq/qIgC5GBEeI5kKENXL09e4KbePhJQXurnpSJCzcX7cUCnRuoFsMsCdg==
+sequential-workflow-designer-angular@^0.30.0:
+ version "0.30.0"
+ resolved "https://registry.yarnpkg.com/sequential-workflow-designer-angular/-/sequential-workflow-designer-angular-0.30.0.tgz#94cf034281c6e3409a0fef921d132b67e3562fcd"
+ integrity sha512-7eRPzyZkEF65nhBMmNS4O3u9S0tuWBo07QxJsNjAijQMpkDE3BgMmNDssvjeUwFdKPR1oQdH5H1LzsBAIyXN3Q==
dependencies:
tslib "^2.3.0"
-sequential-workflow-designer@^0.29.2:
- version "0.29.2"
- resolved "https://registry.yarnpkg.com/sequential-workflow-designer/-/sequential-workflow-designer-0.29.2.tgz#bdb610325396baab5f5e91f95bb846159cb0cb0b"
- integrity sha512-yPxMLLZUV529HQIAVSEWHEjOLDWn+abEZ+E7VIq3vIAs9ew3fG7U/StUz+mA8+zr7iTSp9wlGUXdZi8ZRtQ1Qw==
+sequential-workflow-designer@^0.30.0:
+ version "0.30.0"
+ resolved "https://registry.yarnpkg.com/sequential-workflow-designer/-/sequential-workflow-designer-0.30.0.tgz#98a11796ab3323030048965f8849d1b93a79e8c4"
+ integrity sha512-SwjlRMhO6auFpB9DiYm+1Lb1KHbLL7h66GgOEs60Ej4n0Y5yDTN9ifDLGOiMvUSWqTe3xkfEEsVQ/WLuW9Lz8Q==
dependencies:
sequential-workflow-model "^0.2.0"
diff --git a/demos/react-app/package.json b/demos/react-app/package.json
index 85d1954a..a4735d00 100644
--- a/demos/react-app/package.json
+++ b/demos/react-app/package.json
@@ -6,8 +6,8 @@
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "sequential-workflow-designer": "^0.29.2",
- "sequential-workflow-designer-react": "^0.29.2"
+ "sequential-workflow-designer": "^0.30.0",
+ "sequential-workflow-designer-react": "^0.30.0"
},
"devDependencies": {
"@types/jest": "^29.2.5",
diff --git a/demos/svelte-app/package.json b/demos/svelte-app/package.json
index 8880f86a..b16d4208 100644
--- a/demos/svelte-app/package.json
+++ b/demos/svelte-app/package.json
@@ -16,8 +16,8 @@
"eslint": "eslint ./src --ext .ts"
},
"dependencies": {
- "sequential-workflow-designer": "^0.29.2",
- "sequential-workflow-designer-svelte": "^0.29.2"
+ "sequential-workflow-designer": "^0.30.0",
+ "sequential-workflow-designer-svelte": "^0.30.0"
},
"devDependencies": {
"@sveltejs/adapter-static": "^2.0.3",
diff --git a/designer/package.json b/designer/package.json
index eeac9c14..fba08d55 100644
--- a/designer/package.json
+++ b/designer/package.json
@@ -1,7 +1,7 @@
{
"name": "sequential-workflow-designer",
"description": "Customizable no-code component for building flow-based programming applications.",
- "version": "0.29.2",
+ "version": "0.30.0",
"type": "module",
"main": "./lib/esm/index.js",
"types": "./lib/index.d.ts",
diff --git a/designer/sass/designer-dark.scss b/designer/sass/designer-dark.scss
index 5fffb7c2..b0ae97e3 100644
--- a/designer/sass/designer-dark.scss
+++ b/designer/sass/designer-dark.scss
@@ -65,4 +65,10 @@
$outputFillColor: #707070
);
@include sqd-theme-switch-step-component('dark', $inputStrokeColor: #707070, $inputFillColor: #c6c6c6);
+@include sqd-theme-launch-pad-step-component(
+ 'dark',
+ $emptyInputStrokeColor: #707070,
+ $emptyInputFillColor: #c6c6c6,
+ $emptyOutputFillColor: #707070
+);
@include sqd-theme-container-step-component('dark', $inputStrokeColor: #707070, $inputFillColor: #c6c6c6);
diff --git a/designer/sass/designer-light.scss b/designer/sass/designer-light.scss
index 1a2afb5d..40dd8367 100644
--- a/designer/sass/designer-light.scss
+++ b/designer/sass/designer-light.scss
@@ -17,4 +17,5 @@
@include sqd-theme-task-step-component('light');
@include sqd-theme-switch-step-component('light');
+@include sqd-theme-launch-pad-step-component('light');
@include sqd-theme-container-step-component('light');
diff --git a/designer/sass/designer-soft.scss b/designer/sass/designer-soft.scss
index 0dfd24d2..de28668a 100644
--- a/designer/sass/designer-soft.scss
+++ b/designer/sass/designer-soft.scss
@@ -44,4 +44,5 @@ $joinColor: #2a2a2a;
$labelSecondaryFillColor: #e9e9e9,
$inputStrokeColor: $joinColor
);
+@include sqd-theme-launch-pad-step-component('soft', $emptyInputStrokeColor: $joinColor, $emptyOutputFillColor: $joinColor);
@include sqd-theme-container-step-component('soft', $labelFillColor: #3747dd, $inputStrokeColor: $joinColor);
diff --git a/designer/sass/designer-theme.scss b/designer/sass/designer-theme.scss
index 0bf9eaa5..a18ff93e 100644
--- a/designer/sass/designer-theme.scss
+++ b/designer/sass/designer-theme.scss
@@ -354,6 +354,32 @@
}
}
+@mixin sqd-theme-launch-pad-step-component(
+ $theme,
+ $stepType: '',
+ $emptyInputStrokeWidth: 2,
+ $emptyInputStrokeColor: #000,
+ $emptyInputFillColor: #fff,
+ $emptyOutputFillColor: #000,
+ $emptyOutputStrokeWidth: 0,
+ $emptyOutputStrokeColor: #000
+) {
+ .sqd-theme-#{$theme} .sqd-step-launch-pad#{if($stepType != '', '.sqd-type-' + $stepType, '')} {
+ & > g > {
+ @include _sqd-input(
+ $inputFillColor: $emptyInputFillColor,
+ $inputStrokeWidth: $emptyInputStrokeWidth,
+ $inputStrokeColor: $emptyInputStrokeColor
+ );
+ @include _sqd-output(
+ $outputFillColor: $emptyOutputFillColor,
+ $outputStrokeWidth: $emptyOutputStrokeWidth,
+ $outputStrokeColor: $emptyOutputStrokeColor
+ );
+ }
+ }
+}
+
@mixin sqd-theme-container-step-component(
$theme,
$stepType: '',
diff --git a/designer/src/api/designer-api.ts b/designer/src/api/designer-api.ts
index 85327ee7..a88bad93 100644
--- a/designer/src/api/designer-api.ts
+++ b/designer/src/api/designer-api.ts
@@ -14,15 +14,15 @@ export class DesignerApi {
const workspace = new WorkspaceApi(context.state, context.definitionWalker, context.workspaceController);
const viewportController = context.services.viewportController.create(workspace);
const toolboxDataProvider = new ToolboxDataProvider(
- context.componentContext.iconProvider,
context.i18n,
+ context.componentContext.iconProvider,
context.configuration.toolbox
);
return new DesignerApi(
context.configuration.shadowRoot,
ControlBarApi.create(context.state, context.historyController, context.stateModifier),
- new ToolboxApi(context.state, context, context.behaviorController, toolboxDataProvider, context.configuration.uidGenerator),
+ new ToolboxApi(context.state, context, context.behaviorController, toolboxDataProvider, context.uidGenerator),
new EditorApi(context.state, context.definitionWalker, context.stateModifier),
workspace,
new ViewportApi(context.state, context.workspaceController, viewportController),
diff --git a/designer/src/api/toolbox-api.ts b/designer/src/api/toolbox-api.ts
index c2bda4cd..e3cb149e 100644
--- a/designer/src/api/toolbox-api.ts
+++ b/designer/src/api/toolbox-api.ts
@@ -1,6 +1,6 @@
import { Step } from '../definition';
import { BehaviorController } from '../behaviors/behavior-controller';
-import { ObjectCloner, SimpleEventListener, Uid, Vector } from '../core';
+import { ObjectCloner, SimpleEventListener, Vector } from '../core';
import { StepDefinition, UidGenerator } from '../designer-configuration';
import { DesignerState } from '../designer-state';
import { DragStepBehavior } from '../behaviors/drag-step-behavior';
@@ -13,7 +13,7 @@ export class ToolboxApi {
private readonly designerContext: DesignerContext,
private readonly behaviorController: BehaviorController,
private readonly toolboxDataProvider: ToolboxDataProvider,
- private readonly uidGenerator: UidGenerator | undefined
+ private readonly uidGenerator: UidGenerator
) {}
public isCollapsed(): boolean {
@@ -52,7 +52,7 @@ export class ToolboxApi {
private activateStep(step: StepDefinition): Step {
const newStep = ObjectCloner.deepClone(step) as Step;
- newStep.id = this.uidGenerator ? this.uidGenerator() : Uid.next();
+ newStep.id = this.uidGenerator();
return newStep;
}
}
diff --git a/designer/src/behaviors/drag-step-behavior.ts b/designer/src/behaviors/drag-step-behavior.ts
index 3fdf82fc..41a22d1e 100644
--- a/designer/src/behaviors/drag-step-behavior.ts
+++ b/designer/src/behaviors/drag-step-behavior.ts
@@ -9,7 +9,7 @@ import { DesignerState } from '../designer-state';
import { StateModifier } from '../modifier/state-modifier';
import { WorkspaceController } from '../workspace/workspace-controller';
import { StepComponent } from '../workspace/step-component';
-import { PlaceholderController } from '../designer-extension';
+import { PlaceholderController } from '../workspace/placeholder/placeholder-controller';
export class DragStepBehavior implements Behavior {
public static create(designerContext: DesignerContext, step: Step, draggedStepComponent?: StepComponent): DragStepBehavior {
diff --git a/designer/src/behaviors/placeholder-finder.ts b/designer/src/behaviors/placeholder-finder.ts
index ac93e43d..a3be3d58 100644
--- a/designer/src/behaviors/placeholder-finder.ts
+++ b/designer/src/behaviors/placeholder-finder.ts
@@ -14,6 +14,7 @@ export class PlaceholderFinder {
placeholder: Placeholder;
lt: Vector; // left top
br: Vector; // bottom right
+ diagSq: number; // left top diagonal squared
}[];
private constructor(
@@ -27,15 +28,17 @@ export class PlaceholderFinder {
this.cache = this.placeholders.map(placeholder => {
const rect = placeholder.getClientRect();
+ const lt = new Vector(rect.x, rect.y).add(scroll);
+ const br = new Vector(rect.x + rect.width, rect.y + rect.height).add(scroll);
return {
placeholder,
- lt: new Vector(rect.x, rect.y).add(scroll),
- br: new Vector(rect.x + rect.width, rect.y + rect.height).add(scroll)
+ lt,
+ br,
+ diagSq: lt.x * lt.x + lt.y * lt.y
};
});
- this.cache.sort((a, b) => a.lt.y - b.lt.y);
+ this.cache.sort((a, b) => a.diagSq - b.diagSq);
}
-
const vR = vLt.x + vWidth;
const vB = vLt.y + vHeight;
return this.cache.find(p => {
diff --git a/designer/src/behaviors/select-step-behavior.ts b/designer/src/behaviors/select-step-behavior.ts
index a022915e..9a3bb675 100644
--- a/designer/src/behaviors/select-step-behavior.ts
+++ b/designer/src/behaviors/select-step-behavior.ts
@@ -46,7 +46,10 @@ export class SelectStepBehavior implements Behavior {
return;
}
- this.stateModifier.trySelectStep(this.pressedStepComponent.step, this.pressedStepComponent.parentSequence);
+ if (!this.stateModifier.trySelectStep(this.pressedStepComponent.step, this.pressedStepComponent.parentSequence)) {
+ // If we cannot select the step, we clear the selection.
+ this.state.setSelectedStepId(null);
+ }
return new SelectStepBehaviorEndToken(this.pressedStepComponent.step.id, Date.now());
}
}
diff --git a/designer/src/component-context.ts b/designer/src/component-context.ts
index 67b82692..1b42ef83 100644
--- a/designer/src/component-context.ts
+++ b/designer/src/component-context.ts
@@ -2,20 +2,20 @@ import { DefinitionWalker } from 'sequential-workflow-model';
import { DefinitionValidator } from './core/definition-validator';
import { IconProvider } from './core/icon-provider';
import { DesignerConfiguration, I18n, PreferenceStorage } from './designer-configuration';
-import { PlaceholderController } from './designer-extension';
import { DesignerState } from './designer-state';
import { Services } from './services';
import { StepComponentFactory } from './workspace/step-component-factory';
import { StepExtensionResolver } from './workspace/step-extension-resolver';
+import { PlaceholderController } from './workspace/placeholder/placeholder-controller';
export class ComponentContext {
public static create(
configuration: DesignerConfiguration,
state: DesignerState,
stepExtensionResolver: StepExtensionResolver,
+ placeholderController: PlaceholderController,
definitionWalker: DefinitionWalker,
preferenceStorage: PreferenceStorage,
- placeholderController: PlaceholderController,
i18n: I18n,
services: Services
): ComponentContext {
diff --git a/designer/src/designer-configuration.ts b/designer/src/designer-configuration.ts
index e56bb3dc..4b851e7c 100644
--- a/designer/src/designer-configuration.ts
+++ b/designer/src/designer-configuration.ts
@@ -28,6 +28,11 @@ export interface DesignerConfiguration string | null;
+export interface PlaceholderConfiguration {
+ canCreate?: (sequence: Sequence, index: number) => boolean;
+ canShow?: (sequence: Sequence, index: number, draggingStepComponentType: ComponentType, draggingStepType: string) => boolean;
+}
+
export interface ValidatorConfiguration {
step?: StepValidator;
root?: RootValidator;
diff --git a/designer/src/designer-context.ts b/designer/src/designer-context.ts
index b1aa97f4..c076fdfb 100644
--- a/designer/src/designer-context.ts
+++ b/designer/src/designer-context.ts
@@ -4,7 +4,7 @@ import { ObjectCloner } from './core/object-cloner';
import { CustomActionController } from './custom-action-controller';
import { Definition, DefinitionWalker } from './definition';
import { StateModifier } from './modifier/state-modifier';
-import { DesignerConfiguration, I18n } from './designer-configuration';
+import { DesignerConfiguration, I18n, UidGenerator } from './designer-configuration';
import { DesignerState } from './designer-state';
import { HistoryController } from './history-controller';
import { LayoutController } from './layout-controller';
@@ -12,7 +12,8 @@ import { Services } from './services';
import { StepExtensionResolver } from './workspace/step-extension-resolver';
import { WorkspaceController, WorkspaceControllerWrapper } from './workspace/workspace-controller';
import { MemoryPreferenceStorage } from './core/memory-preference-storage';
-import { PlaceholderController } from './designer-extension';
+import { PlaceholderController } from './workspace/placeholder/placeholder-controller';
+import { Uid } from './core';
export class DesignerContext {
public static create(
@@ -32,12 +33,13 @@ export class DesignerContext {
const theme = configuration.theme || 'light';
const state = new DesignerState(definition, isReadonly, isToolboxCollapsed, isEditorCollapsed);
const workspaceController = new WorkspaceControllerWrapper();
- const placeholderController = services.placeholderController.create();
const behaviorController = BehaviorController.create(configuration.shadowRoot);
const stepExtensionResolver = StepExtensionResolver.create(services);
+ const placeholderController = PlaceholderController.create(configuration.placeholder);
const definitionWalker = configuration.definitionWalker ?? new DefinitionWalker();
const i18n: I18n = configuration.i18n ?? ((_, defaultValue) => defaultValue);
- const stateModifier = StateModifier.create(definitionWalker, state, configuration);
+ const uidGenerator = configuration.uidGenerator ?? Uid.next;
+ const stateModifier = StateModifier.create(definitionWalker, uidGenerator, state, configuration.steps);
const customActionController = new CustomActionController(configuration, state, stateModifier);
let historyController: HistoryController | undefined = undefined;
@@ -50,9 +52,9 @@ export class DesignerContext {
configuration,
state,
stepExtensionResolver,
+ placeholderController,
definitionWalker,
preferenceStorage,
- placeholderController,
i18n,
services
);
@@ -65,6 +67,7 @@ export class DesignerContext {
componentContext,
definitionWalker,
i18n,
+ uidGenerator,
stateModifier,
layoutController,
workspaceController,
@@ -83,6 +86,7 @@ export class DesignerContext {
public readonly componentContext: ComponentContext,
public readonly definitionWalker: DefinitionWalker,
public readonly i18n: I18n,
+ public readonly uidGenerator: UidGenerator,
public readonly stateModifier: StateModifier,
public readonly layoutController: LayoutController,
public readonly workspaceController: WorkspaceControllerWrapper,
diff --git a/designer/src/designer-extension.ts b/designer/src/designer-extension.ts
index cd58b660..36fab73f 100644
--- a/designer/src/designer-extension.ts
+++ b/designer/src/designer-extension.ts
@@ -15,6 +15,7 @@ import {
Placeholder,
PlaceholderDirection,
SequenceComponent,
+ StepComponent,
StepComponentView
} from './workspace';
@@ -28,7 +29,6 @@ export interface DesignerExtension {
draggedComponent?: DraggedComponentExtension;
wheelController?: WheelControllerExtension;
viewportController?: ViewportControllerExtension;
- placeholderController?: PlaceholderControllerExtension;
placeholder?: PlaceholderExtension;
regionComponentView?: RegionComponentViewExtension;
grid?: GridExtension;
@@ -51,7 +51,15 @@ export interface StepComponentViewContext {
i18n: I18n;
getStepName(): string;
getStepIconUrl(): string | null;
+ createStepComponent(parentElement: SVGElement, parentSequence: Sequence, step: Step, position: number): StepComponent;
createSequenceComponent(parentElement: SVGElement, sequence: Sequence): SequenceComponent;
+ getPlaceholderGapSize(orientation: PlaceholderGapOrientation): Vector;
+ createPlaceholderForGap(
+ parentElement: SVGElement,
+ sequence: Sequence,
+ index: number,
+ orientation: PlaceholderGapOrientation
+ ): Placeholder;
createPlaceholderForArea(
parentElement: SVGElement,
size: Vector,
@@ -204,22 +212,16 @@ export interface ContextMenuItem {
readonly callback?: () => void;
}
-// PlaceholderControllerExtension
-
-export interface PlaceholderControllerExtension {
- create(): PlaceholderController;
-}
+// PlaceholderExtension
-export interface PlaceholderController {
- canCreate(sequence: Sequence, index: number): boolean;
- canShow?: (sequence: Sequence, index: number, draggingStepComponentType: ComponentType, draggingStepType: string) => boolean;
+export enum PlaceholderGapOrientation {
+ along = 0, // Goes along with the flow
+ perpendicular = 1 // Goes perpendicular to the flow
}
-// PlaceholderExtension
-
export interface PlaceholderExtension {
- gapSize: Vector;
- createForGap(parentElement: SVGElement, sequence: Sequence, index: number): Placeholder;
+ getGapSize(orientation: PlaceholderGapOrientation): Vector;
+ createForGap(parentElement: SVGElement, sequence: Sequence, index: number, orientation: PlaceholderGapOrientation): Placeholder;
createForArea(parentElement: SVGElement, size: Vector, direction: PlaceholderDirection, sequence: Sequence, index: number): Placeholder;
}
diff --git a/designer/src/extensions/steps-designer-extension.ts b/designer/src/extensions/steps-designer-extension.ts
index bda4ef79..c43f5c31 100644
--- a/designer/src/extensions/steps-designer-extension.ts
+++ b/designer/src/extensions/steps-designer-extension.ts
@@ -6,11 +6,14 @@ import { SwitchStepExtensionConfiguration } from '../workspace/switch-step/switc
import { SwitchStepExtension } from '../workspace/switch-step/switch-step-extension';
import { TaskStepExtensionConfiguration } from '../workspace/task-step/task-step-extension-configuration';
import { TaskStepExtension } from '../workspace/task-step/task-step-extension';
+import { LaunchPadStepExtensionConfiguration } from '../workspace/launch-pad-step/launch-pad-step-extension-configuration';
+import { LaunchPadStepExtension } from '../workspace/launch-pad-step/launch-pad-step-extension';
export interface StepsDesignerExtensionConfiguration {
container?: ContainerStepExtensionConfiguration;
switch?: SwitchStepExtensionConfiguration;
task?: TaskStepExtensionConfiguration;
+ launchPad?: LaunchPadStepExtensionConfiguration;
}
export class StepsDesignerExtension implements DesignerExtension {
@@ -25,6 +28,9 @@ export class StepsDesignerExtension implements DesignerExtension {
if (configuration.task) {
steps.push(TaskStepExtension.create(configuration.task));
}
+ if (configuration.launchPad) {
+ steps.push(LaunchPadStepExtension.create(configuration.launchPad));
+ }
return new StepsDesignerExtension(steps);
}
diff --git a/designer/src/modifier/state-modifier.ts b/designer/src/modifier/state-modifier.ts
index 4db16abb..97b82cad 100644
--- a/designer/src/modifier/state-modifier.ts
+++ b/designer/src/modifier/state-modifier.ts
@@ -1,25 +1,30 @@
-import { Uid } from '../core';
import { SequenceModifier } from './sequence-modifier';
import { StepDuplicator } from '../core/step-duplicator';
import { Definition, DefinitionWalker, Sequence, Step } from '../definition';
-import { DefinitionChangeType, DesignerConfiguration } from '../designer-configuration';
+import { DefinitionChangeType, StepsConfiguration, UidGenerator } from '../designer-configuration';
import { DesignerState } from '../designer-state';
import { StateModifierDependency } from './state-modifier-dependency';
import { FolderPathDefinitionModifierDependency } from './folder-path-definition-modifier-dependency';
import { SelectedStepIdDefinitionModifierDependency } from './selected-step-id-definition-modifier-dependency';
export class StateModifier {
- public static create(definitionWalker: DefinitionWalker, state: DesignerState, configuration: DesignerConfiguration): StateModifier {
+ public static create(
+ definitionWalker: DefinitionWalker,
+ uidGenerator: UidGenerator,
+ state: DesignerState,
+ configuration: StepsConfiguration
+ ): StateModifier {
const dependencies: StateModifierDependency[] = [];
dependencies.push(new SelectedStepIdDefinitionModifierDependency(state, definitionWalker));
dependencies.push(new FolderPathDefinitionModifierDependency(state, definitionWalker));
- return new StateModifier(definitionWalker, state, configuration, dependencies);
+ return new StateModifier(definitionWalker, uidGenerator, state, configuration, dependencies);
}
public constructor(
private readonly definitionWalker: DefinitionWalker,
+ private readonly uidGenerator: UidGenerator,
private readonly state: DesignerState,
- private readonly configuration: DesignerConfiguration,
+ private readonly configuration: StepsConfiguration,
private readonly dependencies: StateModifierDependency[]
) {}
@@ -28,17 +33,19 @@ export class StateModifier {
}
public isSelectable(step: Step, parentSequence: Sequence): boolean {
- return this.configuration.steps.isSelectable ? this.configuration.steps.isSelectable(step, parentSequence) : true;
+ return this.configuration.isSelectable ? this.configuration.isSelectable(step, parentSequence) : true;
}
- public trySelectStep(step: Step, parentSequence: Sequence) {
+ public trySelectStep(step: Step, parentSequence: Sequence): boolean {
if (this.isSelectable(step, parentSequence)) {
this.state.setSelectedStepId(step.id);
+ return true;
}
+ return false;
}
public trySelectStepById(stepId: string) {
- if (this.configuration.steps.isSelectable) {
+ if (this.configuration.isSelectable) {
const result = this.definitionWalker.getParentSequence(this.state.definition, stepId);
this.trySelectStep(result.step, result.parentSequence);
} else {
@@ -47,9 +54,9 @@ export class StateModifier {
}
public isDeletable(stepId: string): boolean {
- if (this.configuration.steps.isDeletable) {
+ if (this.configuration.isDeletable) {
const result = this.definitionWalker.getParentSequence(this.state.definition, stepId);
- return this.configuration.steps.isDeletable(result.step, result.parentSequence);
+ return this.configuration.isDeletable(result.step, result.parentSequence);
}
return true;
}
@@ -57,8 +64,8 @@ export class StateModifier {
public tryDelete(stepId: string): boolean {
const result = this.definitionWalker.getParentSequence(this.state.definition, stepId);
- const canDeleteStep = this.configuration.steps.canDeleteStep
- ? this.configuration.steps.canDeleteStep(result.step, result.parentSequence)
+ const canDeleteStep = this.configuration.canDeleteStep
+ ? this.configuration.canDeleteStep(result.step, result.parentSequence)
: true;
if (!canDeleteStep) {
return false;
@@ -72,9 +79,7 @@ export class StateModifier {
}
public tryInsert(step: Step, targetSequence: Sequence, targetIndex: number): boolean {
- const canInsertStep = this.configuration.steps.canInsertStep
- ? this.configuration.steps.canInsertStep(step, targetSequence, targetIndex)
- : true;
+ const canInsertStep = this.configuration.canInsertStep ? this.configuration.canInsertStep(step, targetSequence, targetIndex) : true;
if (!canInsertStep) {
return false;
}
@@ -82,14 +87,14 @@ export class StateModifier {
SequenceModifier.insertStep(step, targetSequence, targetIndex);
this.state.notifyDefinitionChanged(DefinitionChangeType.stepInserted, step.id);
- if (!this.configuration.steps.isAutoSelectDisabled) {
+ if (!this.configuration.isAutoSelectDisabled) {
this.trySelectStepById(step.id);
}
return true;
}
public isDraggable(step: Step, parentSequence: Sequence): boolean {
- return this.configuration.steps.isDraggable ? this.configuration.steps.isDraggable(step, parentSequence) : true;
+ return this.configuration.isDraggable ? this.configuration.isDraggable(step, parentSequence) : true;
}
public tryMove(sourceSequence: Sequence, step: Step, targetSequence: Sequence, targetIndex: number): boolean {
@@ -98,8 +103,8 @@ export class StateModifier {
return false;
}
- const canMoveStep = this.configuration.steps.canMoveStep
- ? this.configuration.steps.canMoveStep(sourceSequence, step, targetSequence, targetIndex)
+ const canMoveStep = this.configuration.canMoveStep
+ ? this.configuration.canMoveStep(sourceSequence, step, targetSequence, targetIndex)
: true;
if (!canMoveStep) {
return false;
@@ -108,19 +113,18 @@ export class StateModifier {
apply();
this.state.notifyDefinitionChanged(DefinitionChangeType.stepMoved, step.id);
- if (!this.configuration.steps.isAutoSelectDisabled) {
+ if (!this.configuration.isAutoSelectDisabled) {
this.trySelectStep(step, targetSequence);
}
return true;
}
public isDuplicable(step: Step, parentSequence: Sequence): boolean {
- return this.configuration.steps.isDuplicable ? this.configuration.steps.isDuplicable(step, parentSequence) : false;
+ return this.configuration.isDuplicable ? this.configuration.isDuplicable(step, parentSequence) : false;
}
public tryDuplicate(step: Step, parentSequence: Sequence): boolean {
- const uidGenerator = this.configuration.uidGenerator ? this.configuration.uidGenerator : Uid.next;
- const duplicator = new StepDuplicator(uidGenerator, this.definitionWalker);
+ const duplicator = new StepDuplicator(this.uidGenerator, this.definitionWalker);
const index = parentSequence.indexOf(step);
const newStep = duplicator.duplicate(step);
diff --git a/designer/src/services.ts b/designer/src/services.ts
index 7389a8ec..cfa2ef7a 100644
--- a/designer/src/services.ts
+++ b/designer/src/services.ts
@@ -7,7 +7,6 @@ import { SmartEditorExtension } from './smart-editor/smart-editor-extension';
import { ToolboxExtension } from './toolbox/toolbox-extension';
import { ValidationErrorBadgeExtension } from './workspace/badges/validation-error/validation-error-badge-extension';
import { ContainerStepExtension } from './workspace/container-step/container-step-extension';
-import { DefaultPlaceholderControllerExtension } from './workspace/placeholder/default-placeholder-controller-extension';
import { RectPlaceholderExtension } from './workspace/placeholder/rect-placeholder-extension';
import { StartStopRootComponentExtension } from './workspace/start-stop-root/start-stop-root-component-extension';
import { SwitchStepExtension } from './workspace/switch-step/switch-step-extension';
@@ -21,6 +20,7 @@ import { LineGridExtension } from './workspace/grid/line-grid-extension';
import { DefaultRegionComponentViewExtension } from './workspace/region/default-region-component-view-extension';
import { DefaultClickBehaviorWrapperExtension } from './behaviors/default-click-behavior-wrapper-extension';
import { DefaultStepBadgesDecoratorExtension } from './workspace/badges/default-step-badges-decorator-extension';
+import { LaunchPadStepExtension } from './workspace/launch-pad-step/launch-pad-step-extension';
export type Services = Required;
@@ -59,9 +59,6 @@ function merge(services: Partial, extensions: DesignerExtension[]) {
if (ext.wheelController) {
services.wheelController = ext.wheelController;
}
- if (ext.placeholderController) {
- services.placeholderController = ext.placeholderController;
- }
if (ext.placeholder) {
services.placeholder = ext.placeholder;
}
@@ -96,6 +93,7 @@ function setDefaults(services: Partial, configuration: DesignerConfigu
services.steps.push(ContainerStepExtension.create());
services.steps.push(SwitchStepExtension.create());
services.steps.push(TaskStepExtension.create());
+ services.steps.push(LaunchPadStepExtension.create());
if (!services.stepComponentViewWrapper) {
services.stepComponentViewWrapper = new DefaultStepComponentViewWrapperExtension();
@@ -133,9 +131,6 @@ function setDefaults(services: Partial, configuration: DesignerConfigu
if (!services.wheelController) {
services.wheelController = new ClassicWheelControllerExtension();
}
- if (!services.placeholderController) {
- services.placeholderController = new DefaultPlaceholderControllerExtension();
- }
if (!services.placeholder) {
services.placeholder = RectPlaceholderExtension.create();
}
diff --git a/designer/src/smart-editor/editor.spec.ts b/designer/src/smart-editor/editor.spec.ts
index caef9f8a..cff85193 100644
--- a/designer/src/smart-editor/editor.spec.ts
+++ b/designer/src/smart-editor/editor.spec.ts
@@ -4,6 +4,7 @@ import { DesignerState } from '../designer-state';
import { createDefinitionStub, createDesignerConfigurationStub, createStepStub } from '../test-tools/stubs';
import { StateModifier } from '../modifier/state-modifier';
import { Editor } from './editor';
+import { Uid } from '../core';
describe('Editor', () => {
const step = createStepStub();
@@ -27,7 +28,7 @@ describe('Editor', () => {
state = new DesignerState(definition, false, false, false);
const walker = new DefinitionWalker();
- const modifier = StateModifier.create(walker, state, configuration);
+ const modifier = StateModifier.create(walker, Uid.next, state, configuration.steps);
api = new EditorApi(state, walker, modifier);
stepEditorProvider = jasmine.createSpy().and.returnValue(document.createElement('div'));
diff --git a/designer/src/toolbox/toolbox-data-provider.spec.ts b/designer/src/toolbox/toolbox-data-provider.spec.ts
index 69898f4b..5b5e9302 100644
--- a/designer/src/toolbox/toolbox-data-provider.spec.ts
+++ b/designer/src/toolbox/toolbox-data-provider.spec.ts
@@ -29,13 +29,13 @@ describe('ToolboxDataProvider', () => {
describe('getAllGroups()', () => {
it('return empty array if configuration is false', () => {
- const dataProvider = new ToolboxDataProvider(iconProvider, i18n, false);
+ const dataProvider = new ToolboxDataProvider(i18n, iconProvider, false);
expect(dataProvider.getAllGroups()).toEqual([]);
});
it('returns groups with correct labels and descriptions', () => {
- const dataProvider = new ToolboxDataProvider(iconProvider, i18n, {
+ const dataProvider = new ToolboxDataProvider(i18n, iconProvider, {
groups
});
@@ -53,7 +53,7 @@ describe('ToolboxDataProvider', () => {
});
it('returns groups with correct labels and descriptions when custom providers are provided', () => {
- const dataProvider = new ToolboxDataProvider(iconProvider, i18n, {
+ const dataProvider = new ToolboxDataProvider(i18n, iconProvider, {
groups,
labelProvider: reverseStepName,
descriptionProvider: step => `Description of ${step.name}`
@@ -75,7 +75,7 @@ describe('ToolboxDataProvider', () => {
describe('applyFilter()', () => {
it('filters by label', () => {
- const dataProvider = new ToolboxDataProvider(iconProvider, i18n, {
+ const dataProvider = new ToolboxDataProvider(i18n, iconProvider, {
groups
});
@@ -98,7 +98,7 @@ describe('ToolboxDataProvider', () => {
});
it('filters by custom label', () => {
- const dataProvider = new ToolboxDataProvider(iconProvider, i18n, {
+ const dataProvider = new ToolboxDataProvider(i18n, iconProvider, {
groups,
labelProvider: reverseStepName
});
diff --git a/designer/src/toolbox/toolbox-data-provider.ts b/designer/src/toolbox/toolbox-data-provider.ts
index e1ea254f..f22e1ed2 100644
--- a/designer/src/toolbox/toolbox-data-provider.ts
+++ b/designer/src/toolbox/toolbox-data-provider.ts
@@ -4,8 +4,8 @@ import { I18n, StepDefinition, ToolboxConfiguration } from '../designer-configur
export class ToolboxDataProvider {
public constructor(
- private readonly iconProvider: IconProvider,
private readonly i18n: I18n,
+ private readonly iconProvider: IconProvider,
private readonly configuration: ToolboxConfiguration | false
) {}
diff --git a/designer/src/workspace/common-views/join-view.ts b/designer/src/workspace/common-views/join-view.ts
index 0b02eb0d..828767f5 100644
--- a/designer/src/workspace/common-views/join-view.ts
+++ b/designer/src/workspace/common-views/join-view.ts
@@ -24,7 +24,7 @@ export class JoinView {
switch (targets.length) {
case 1:
if (start.x === targets[0].x) {
- JoinView.createStraightJoin(parent, start, firstTarget.y * dy);
+ JoinView.createStraightJoin(parent, start, h * 2 * dy);
} else {
appendCurvedJoins(parent, start, targets, h, dy);
}
diff --git a/designer/src/workspace/component.ts b/designer/src/workspace/component.ts
index c311b26b..2a1c0199 100644
--- a/designer/src/workspace/component.ts
+++ b/designer/src/workspace/component.ts
@@ -25,7 +25,7 @@ export interface ComponentView {
}
export interface StepComponentView extends ComponentView {
- sequenceComponents: SequenceComponent[] | null;
+ components: Component[] | null;
placeholders: Placeholder[] | null;
hasOutput: boolean;
@@ -120,7 +120,7 @@ export interface Placeholder {
}
export enum PlaceholderDirection {
- none = 0,
+ gap = 0,
in = 1,
out = 2
}
diff --git a/designer/src/workspace/container-step/container-step-component-view.ts b/designer/src/workspace/container-step/container-step-component-view.ts
index 62d43726..553be8b1 100644
--- a/designer/src/workspace/container-step/container-step-component-view.ts
+++ b/designer/src/workspace/container-step/container-step-component-view.ts
@@ -47,7 +47,7 @@ export const createContainerStepComponentViewFactory =
height,
joinX,
placeholders: null,
- sequenceComponents: [sequenceComponent],
+ components: [sequenceComponent],
hasOutput: sequenceComponent.hasOutput,
getClientPosition(): Vector {
diff --git a/designer/src/workspace/index.ts b/designer/src/workspace/index.ts
index 04b82d6a..1c9cde94 100644
--- a/designer/src/workspace/index.ts
+++ b/designer/src/workspace/index.ts
@@ -4,6 +4,7 @@ export * from './sequence';
export * from './start-stop-root';
export * from './container-step';
export * from './grid';
+export * from './launch-pad-step';
export * from './switch-step';
export * from './task-step';
export * from './viewport';
diff --git a/designer/src/workspace/launch-pad-step/index.ts b/designer/src/workspace/launch-pad-step/index.ts
new file mode 100644
index 00000000..88e77054
--- /dev/null
+++ b/designer/src/workspace/launch-pad-step/index.ts
@@ -0,0 +1,3 @@
+export * from './launch-pad-step-component-view';
+export * from './launch-pad-step-component-view-configuration';
+export * from './launch-pad-step-extension-configuration';
diff --git a/designer/src/workspace/launch-pad-step/launch-pad-step-component-view-configuration.ts b/designer/src/workspace/launch-pad-step/launch-pad-step-component-view-configuration.ts
new file mode 100644
index 00000000..0275f7e6
--- /dev/null
+++ b/designer/src/workspace/launch-pad-step/launch-pad-step-component-view-configuration.ts
@@ -0,0 +1,10 @@
+export interface LaunchPadStepComponentViewConfiguration {
+ isRegionEnabled: boolean;
+ paddingY: number;
+ connectionHeight: number;
+ emptyPaddingX: number;
+ emptyPaddingY: number;
+ emptyInputSize: number;
+ emptyOutputSize: number;
+ emptyIconSize: number;
+}
diff --git a/designer/src/workspace/launch-pad-step/launch-pad-step-component-view.spec.ts b/designer/src/workspace/launch-pad-step/launch-pad-step-component-view.spec.ts
new file mode 100644
index 00000000..842a6f03
--- /dev/null
+++ b/designer/src/workspace/launch-pad-step/launch-pad-step-component-view.spec.ts
@@ -0,0 +1,45 @@
+import { Dom } from '../../core/dom';
+import { SequentialStep } from '../../definition';
+import { StepContext } from '../../designer-extension';
+import { createComponentContextStub } from '../../test-tools/stubs';
+import { StepComponentViewContextFactory } from '../step-component-view-context-factory';
+import { createLaunchPadStepComponentViewFactory } from './launch-pad-step-component-view';
+
+describe('LaunchPadStepComponentView', () => {
+ it('create() creates view', () => {
+ const parent = Dom.svg('svg');
+ const step: SequentialStep = {
+ id: '0x',
+ componentType: 'launchPad',
+ name: 'x',
+ properties: {},
+ type: 'launchPad',
+ sequence: []
+ };
+ const stepContext: StepContext = {
+ depth: 0,
+ position: 0,
+ isInputConnected: true,
+ isOutputConnected: false,
+ step,
+ parentSequence: [step],
+ isPreview: false
+ };
+ const componentContext = createComponentContextStub();
+ const viewContext = StepComponentViewContextFactory.create(stepContext, componentContext);
+
+ const factory = createLaunchPadStepComponentViewFactory(false, {
+ isRegionEnabled: true,
+ paddingY: 10,
+ connectionHeight: 20,
+ emptyPaddingX: 20,
+ emptyPaddingY: 20,
+ emptyInputSize: 14,
+ emptyOutputSize: 10,
+ emptyIconSize: 24
+ });
+ factory(parent, stepContext, viewContext);
+
+ expect(parent.children.length).not.toEqual(0);
+ });
+});
diff --git a/designer/src/workspace/launch-pad-step/launch-pad-step-component-view.ts b/designer/src/workspace/launch-pad-step/launch-pad-step-component-view.ts
new file mode 100644
index 00000000..b76103f1
--- /dev/null
+++ b/designer/src/workspace/launch-pad-step/launch-pad-step-component-view.ts
@@ -0,0 +1,192 @@
+import { SequentialStep } from 'sequential-workflow-model';
+import {
+ PlaceholderGapOrientation,
+ RegionView,
+ RegionViewFactory,
+ StepComponentViewContext,
+ StepComponentViewFactory,
+ StepContext
+} from '../../designer-extension';
+import { ComponentDom, InputView, JoinView, OutputView } from '../common-views';
+import { LaunchPadStepComponentViewConfiguration } from './launch-pad-step-component-view-configuration';
+import { ClickCommand, ClickDetails, Placeholder, StepComponentView } from '../component';
+import { Dom, getAbsolutePosition, Vector } from '../../core';
+import { StepComponent } from '../step-component';
+
+const COMPONENT_CLASS_NAME = 'launch-pad';
+
+function createView(
+ parentElement: SVGElement,
+ stepContext: StepContext,
+ viewContext: StepComponentViewContext,
+ regionViewFactory: RegionViewFactory | null,
+ isInterruptedIfEmpty: boolean,
+ cfg: LaunchPadStepComponentViewConfiguration
+): StepComponentView {
+ const step = stepContext.step;
+ const sequence = stepContext.step.sequence;
+ const g = ComponentDom.stepG(COMPONENT_CLASS_NAME, step.type, step.id);
+ parentElement.appendChild(g);
+
+ const components: StepComponent[] = [];
+ let width: number;
+ let height: number;
+ let joinX: number;
+
+ const placeholdersX: number[] = [];
+ let placeholderOrientation: PlaceholderGapOrientation;
+ let placeholderSize: Vector;
+ let hasOutput: boolean;
+
+ let inputView: InputView | null = null;
+ let outputView: OutputView | null = null;
+
+ if (sequence.length > 0) {
+ let maxComponentHeight = 0;
+ for (let i = 0; i < sequence.length; i++) {
+ const component = viewContext.createStepComponent(g, sequence, sequence[i], i);
+ components.push(component);
+ maxComponentHeight = Math.max(maxComponentHeight, component.view.height);
+ }
+
+ const joinsX: number[] = [];
+ const positionsX: number[] = [];
+ const spacesY: number[] = [];
+
+ placeholderOrientation = PlaceholderGapOrientation.perpendicular;
+ placeholderSize = viewContext.getPlaceholderGapSize(placeholderOrientation);
+ placeholdersX.push(0);
+ let positionX = placeholderSize.x;
+
+ for (let i = 0; i < components.length; i++) {
+ if (i > 0) {
+ placeholdersX.push(positionX);
+ positionX += placeholderSize.x;
+ }
+ const component = components[i];
+ const componentY = (maxComponentHeight - component.view.height) / 2 + cfg.connectionHeight + cfg.paddingY;
+ Dom.translate(component.view.g, positionX, componentY);
+
+ joinsX.push(positionX + component.view.joinX);
+ positionX += component.view.width;
+ positionsX.push(positionX);
+ spacesY.push(Math.max(0, (maxComponentHeight - component.view.height) / 2));
+ }
+
+ placeholdersX.push(positionX);
+ positionX += placeholderSize.x;
+
+ width = positionX;
+ height = maxComponentHeight + 2 * cfg.connectionHeight + 2 * cfg.paddingY;
+
+ const contentJoinX =
+ components.length % 2 === 0
+ ? positionsX[Math.max(0, Math.floor(components.length / 2) - 1)] + placeholderSize.x / 2
+ : joinsX[Math.floor(components.length / 2)];
+
+ if (stepContext.isInputConnected) {
+ const joinsTopY = joinsX.map(x => new Vector(x, cfg.connectionHeight));
+ JoinView.createJoins(g, new Vector(contentJoinX, 0), joinsTopY);
+ for (let i = 0; i < joinsX.length; i++) {
+ JoinView.createStraightJoin(g, joinsTopY[i], cfg.paddingY + spacesY[i]);
+ }
+ }
+
+ const joinsBottomY = joinsX.map(x => new Vector(x, cfg.connectionHeight + 2 * cfg.paddingY + maxComponentHeight));
+ JoinView.createJoins(g, new Vector(contentJoinX, height), joinsBottomY);
+ for (let i = 0; i < joinsX.length; i++) {
+ JoinView.createStraightJoin(g, joinsBottomY[i], -(cfg.paddingY + spacesY[i]));
+ }
+
+ hasOutput = true;
+ joinX = contentJoinX;
+ } else {
+ placeholderOrientation = PlaceholderGapOrientation.along;
+ placeholderSize = viewContext.getPlaceholderGapSize(placeholderOrientation);
+
+ placeholdersX.push(cfg.emptyPaddingX);
+
+ width = placeholderSize.x + cfg.emptyPaddingX * 2;
+ height = placeholderSize.y + cfg.emptyPaddingY * 2;
+ hasOutput = !isInterruptedIfEmpty;
+
+ if (stepContext.isInputConnected) {
+ inputView = InputView.createRoundInput(g, width / 2, 0, cfg.emptyInputSize);
+ }
+
+ if (stepContext.isOutputConnected && hasOutput) {
+ outputView = OutputView.create(g, width / 2, height, cfg.emptyOutputSize);
+ }
+
+ if (cfg.emptyIconSize > 0) {
+ const iconUrl = viewContext.getStepIconUrl();
+ if (iconUrl) {
+ const icon = Dom.svg('image', {
+ href: iconUrl,
+ x: (width - cfg.emptyIconSize) / 2,
+ y: (height - cfg.emptyIconSize) / 2,
+ width: cfg.emptyIconSize,
+ height: cfg.emptyIconSize
+ });
+ g.appendChild(icon);
+ }
+ }
+
+ joinX = width / 2;
+ }
+
+ let regionView: RegionView | null = null;
+ if (regionViewFactory) {
+ regionView = regionViewFactory(g, [width], height);
+ }
+
+ const placeholders: Placeholder[] = [];
+ const placeholderY = (height - placeholderSize.y) / 2;
+ for (let i = 0; i < placeholdersX.length; i++) {
+ const placeholder = viewContext.createPlaceholderForGap(g, sequence, i, placeholderOrientation);
+ placeholders.push(placeholder);
+ Dom.translate(placeholder.view.g, placeholdersX[i], placeholderY);
+ }
+
+ return {
+ g,
+ width,
+ height,
+ joinX,
+ components,
+ placeholders,
+ hasOutput,
+
+ getClientPosition(): Vector {
+ return getAbsolutePosition(g);
+ },
+ resolveClick(click: ClickDetails): true | ClickCommand | null {
+ if (regionView) {
+ const result = regionView.resolveClick(click);
+ return result === true || (result === null && g.contains(click.element)) ? true : result;
+ }
+ return null;
+ },
+ setIsDragging(isDragging: boolean) {
+ inputView?.setIsHidden(isDragging);
+ outputView?.setIsHidden(isDragging);
+ },
+ setIsDisabled(isDisabled: boolean) {
+ Dom.toggleClass(g, isDisabled, 'sqd-disabled');
+ },
+ setIsSelected(isSelected: boolean) {
+ regionView?.setIsSelected(isSelected);
+ }
+ };
+}
+
+export const createLaunchPadStepComponentViewFactory =
+ (isInterruptedIfEmpty: boolean, cfg: LaunchPadStepComponentViewConfiguration): StepComponentViewFactory =>
+ (parentElement: SVGElement, stepContext: StepContext, viewContext: StepComponentViewContext): StepComponentView => {
+ if (cfg.isRegionEnabled) {
+ return viewContext.createRegionComponentView(parentElement, COMPONENT_CLASS_NAME, (g, regionViewBuilder) => {
+ return createView(g, stepContext, viewContext, regionViewBuilder, isInterruptedIfEmpty, cfg);
+ });
+ }
+ return createView(parentElement, stepContext, viewContext, null, isInterruptedIfEmpty, cfg);
+ };
diff --git a/designer/src/workspace/launch-pad-step/launch-pad-step-extension-configuration.ts b/designer/src/workspace/launch-pad-step/launch-pad-step-extension-configuration.ts
new file mode 100644
index 00000000..614c634c
--- /dev/null
+++ b/designer/src/workspace/launch-pad-step/launch-pad-step-extension-configuration.ts
@@ -0,0 +1,5 @@
+import { LaunchPadStepComponentViewConfiguration } from './launch-pad-step-component-view-configuration';
+
+export interface LaunchPadStepExtensionConfiguration {
+ view?: LaunchPadStepComponentViewConfiguration;
+}
diff --git a/designer/src/workspace/launch-pad-step/launch-pad-step-extension.ts b/designer/src/workspace/launch-pad-step/launch-pad-step-extension.ts
new file mode 100644
index 00000000..9c3938c4
--- /dev/null
+++ b/designer/src/workspace/launch-pad-step/launch-pad-step-extension.ts
@@ -0,0 +1,31 @@
+import { SequentialStep } from 'sequential-workflow-model';
+import { StepExtension } from '../../designer-extension';
+import { LaunchPadStepExtensionConfiguration } from './launch-pad-step-extension-configuration';
+import { createLaunchPadStepComponentViewFactory } from './launch-pad-step-component-view';
+import { LaunchPadStepComponentViewConfiguration } from './launch-pad-step-component-view-configuration';
+
+const defaultViewConfiguration: LaunchPadStepComponentViewConfiguration = {
+ isRegionEnabled: true,
+ paddingY: 10,
+ connectionHeight: 20,
+ emptyPaddingX: 20,
+ emptyPaddingY: 20,
+ emptyInputSize: 14,
+ emptyOutputSize: 10,
+ emptyIconSize: 24
+};
+
+export class LaunchPadStepExtension implements StepExtension {
+ public static create(configuration?: LaunchPadStepExtensionConfiguration): LaunchPadStepExtension {
+ return new LaunchPadStepExtension(configuration);
+ }
+
+ public readonly componentType = 'launchPad';
+
+ private constructor(private readonly configuration: LaunchPadStepExtensionConfiguration | undefined) {}
+
+ public readonly createComponentView = createLaunchPadStepComponentViewFactory(
+ false,
+ this.configuration?.view ?? defaultViewConfiguration
+ );
+}
diff --git a/designer/src/workspace/placeholder/default-placeholder-controller-extension.ts b/designer/src/workspace/placeholder/default-placeholder-controller-extension.ts
deleted file mode 100644
index 60683253..00000000
--- a/designer/src/workspace/placeholder/default-placeholder-controller-extension.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { PlaceholderController, PlaceholderControllerExtension } from '../../designer-extension';
-
-export class DefaultPlaceholderControllerExtension implements PlaceholderControllerExtension {
- public create(): PlaceholderController {
- return {
- canCreate: () => true
- };
- }
-}
diff --git a/designer/src/workspace/placeholder/index.ts b/designer/src/workspace/placeholder/index.ts
index 7e772d83..071001ea 100644
--- a/designer/src/workspace/placeholder/index.ts
+++ b/designer/src/workspace/placeholder/index.ts
@@ -1,3 +1,4 @@
+export * from './placeholder-controller';
export * from './rect-placeholder';
export * from './rect-placeholder-view';
export * from './rect-placeholder-configuration';
diff --git a/designer/src/workspace/placeholder/placeholder-controller.ts b/designer/src/workspace/placeholder/placeholder-controller.ts
new file mode 100644
index 00000000..8de0e8db
--- /dev/null
+++ b/designer/src/workspace/placeholder/placeholder-controller.ts
@@ -0,0 +1,15 @@
+import { Sequence } from 'sequential-workflow-model';
+import { PlaceholderConfiguration } from '../../designer-configuration';
+
+export class PlaceholderController {
+ public static create(configuration: PlaceholderConfiguration | undefined): PlaceholderController {
+ return new PlaceholderController(configuration);
+ }
+
+ private constructor(private readonly configuration: PlaceholderConfiguration | undefined) {}
+
+ public readonly canCreate: (sequence: Sequence, index: number) => boolean = this.configuration?.canCreate ?? (() => true);
+
+ public readonly canShow: (sequence: Sequence, index: number, draggingStepComponentType: string, draggingStepType: string) => boolean =
+ this.configuration?.canShow ?? (() => true);
+}
diff --git a/designer/src/workspace/placeholder/rect-placeholder-extension.ts b/designer/src/workspace/placeholder/rect-placeholder-extension.ts
index 8c92a035..b6775b8c 100644
--- a/designer/src/workspace/placeholder/rect-placeholder-extension.ts
+++ b/designer/src/workspace/placeholder/rect-placeholder-extension.ts
@@ -1,6 +1,6 @@
import { Sequence } from '../../definition';
import { Vector } from '../../core';
-import { PlaceholderExtension } from '../../designer-extension';
+import { PlaceholderExtension, PlaceholderGapOrientation } from '../../designer-extension';
import { PlaceholderDirection, Placeholder } from '../component';
import { RectPlaceholder } from './rect-placeholder';
import { RectPlaceholderConfiguration } from './rect-placeholder-configuration';
@@ -17,12 +17,26 @@ export class RectPlaceholderExtension implements PlaceholderExtension {
return new RectPlaceholderExtension(configuration ?? defaultConfiguration);
}
- public readonly gapSize = new Vector(this.configuration.gapWidth, this.configuration.gapHeight);
+ private readonly alongGapSize = new Vector(defaultConfiguration.gapWidth, defaultConfiguration.gapHeight);
+ private readonly perpendicularGapSize = new Vector(defaultConfiguration.gapHeight, defaultConfiguration.gapWidth);
private constructor(private readonly configuration: RectPlaceholderConfiguration) {}
- public createForGap(parent: SVGElement, parentSequence: Sequence, index: number): Placeholder {
- return RectPlaceholder.create(parent, this.gapSize, PlaceholderDirection.none, parentSequence, index, this.configuration);
+ public getGapSize(orientation: PlaceholderGapOrientation): Vector {
+ return orientation === PlaceholderGapOrientation.perpendicular ? this.perpendicularGapSize : this.alongGapSize;
+ }
+
+ public createForGap(parent: SVGElement, parentSequence: Sequence, index: number, orientation: PlaceholderGapOrientation): Placeholder {
+ const gapSize = this.getGapSize(orientation);
+ return RectPlaceholder.create(
+ parent,
+ gapSize,
+ PlaceholderDirection.gap,
+ parentSequence,
+ index,
+ this.configuration.radius,
+ this.configuration.iconSize
+ );
}
public createForArea(
@@ -32,6 +46,14 @@ export class RectPlaceholderExtension implements PlaceholderExtension {
parentSequence: Sequence,
index: number
): Placeholder {
- return RectPlaceholder.create(parent, size, direction, parentSequence, index, this.configuration);
+ return RectPlaceholder.create(
+ parent,
+ size,
+ direction,
+ parentSequence,
+ index,
+ this.configuration.radius,
+ this.configuration.iconSize
+ );
}
}
diff --git a/designer/src/workspace/placeholder/rect-placeholder.ts b/designer/src/workspace/placeholder/rect-placeholder.ts
index e7325b8b..b34c1602 100644
--- a/designer/src/workspace/placeholder/rect-placeholder.ts
+++ b/designer/src/workspace/placeholder/rect-placeholder.ts
@@ -1,7 +1,6 @@
import { Vector } from '../../core';
import { Sequence } from '../../definition';
import { Placeholder, PlaceholderDirection } from '../component';
-import { RectPlaceholderConfiguration } from './rect-placeholder-configuration';
import { RectPlaceholderView } from './rect-placeholder-view';
export class RectPlaceholder implements Placeholder {
@@ -11,9 +10,10 @@ export class RectPlaceholder implements Placeholder {
direction: PlaceholderDirection,
sequence: Sequence,
index: number,
- configuration: RectPlaceholderConfiguration
+ radius: number,
+ iconSize: number
): RectPlaceholder {
- const view = RectPlaceholderView.create(parent, size.x, size.y, configuration.radius, configuration.iconSize, direction);
+ const view = RectPlaceholderView.create(parent, size.x, size.y, radius, iconSize, direction);
return new RectPlaceholder(view, sequence, index);
}
diff --git a/designer/src/workspace/sequence/default-sequence-component-view.ts b/designer/src/workspace/sequence/default-sequence-component-view.ts
index c7437ad8..87666c87 100644
--- a/designer/src/workspace/sequence/default-sequence-component-view.ts
+++ b/designer/src/workspace/sequence/default-sequence-component-view.ts
@@ -3,7 +3,7 @@ import { Vector } from '../../core/vector';
import { JoinView } from '../common-views/join-view';
import { ComponentView, Placeholder } from '../component';
import { ComponentContext } from '../../component-context';
-import { SequenceContext, StepContext } from '../../designer-extension';
+import { PlaceholderGapOrientation, SequenceContext, StepContext } from '../../designer-extension';
import { StepComponent } from '../step-component';
export class DefaultSequenceComponentView implements ComponentView {
@@ -12,8 +12,9 @@ export class DefaultSequenceComponentView implements ComponentView {
sequenceContext: SequenceContext,
componentContext: ComponentContext
): DefaultSequenceComponentView {
- const phWidth = componentContext.services.placeholder.gapSize.x;
- const phHeight = componentContext.services.placeholder.gapSize.y;
+ const phSize = componentContext.services.placeholder.getGapSize(PlaceholderGapOrientation.along);
+ const phWidth = phSize.x;
+ const phHeight = phSize.y;
const { sequence } = sequenceContext;
const g = Dom.svg('g');
@@ -57,7 +58,7 @@ export class DefaultSequenceComponentView implements ComponentView {
}
if (!sequenceContext.isPreview && componentContext.placeholderController.canCreate(sequence, i)) {
- const ph = componentContext.services.placeholder.createForGap(g, sequence, i);
+ const ph = componentContext.services.placeholder.createForGap(g, sequence, i, PlaceholderGapOrientation.along);
Dom.translate(ph.view.g, joinX - phWidth / 2, offsetY - phHeight);
placeholders.push(ph);
}
@@ -72,7 +73,7 @@ export class DefaultSequenceComponentView implements ComponentView {
const newIndex = components.length;
if (!sequenceContext.isPreview && componentContext.placeholderController.canCreate(sequence, newIndex)) {
- const ph = componentContext.services.placeholder.createForGap(g, sequence, newIndex);
+ const ph = componentContext.services.placeholder.createForGap(g, sequence, newIndex, PlaceholderGapOrientation.along);
Dom.translate(ph.view.g, joinX - phWidth / 2, offsetY - phHeight);
placeholders.push(ph);
}
diff --git a/designer/src/workspace/sequence/default-sequence-component.ts b/designer/src/workspace/sequence/default-sequence-component.ts
index 48f762b3..3c3476dc 100644
--- a/designer/src/workspace/sequence/default-sequence-component.ts
+++ b/designer/src/workspace/sequence/default-sequence-component.ts
@@ -33,22 +33,20 @@ export class DefaultSequenceComponent implements SequenceComponent {
public findById(stepId: string): StepComponent | null {
for (const component of this.view.components) {
- const sc = component.findById(stepId);
- if (sc) {
- return sc;
+ const result = component.findById(stepId);
+ if (result) {
+ return result;
}
}
return null;
}
public resolvePlaceholders(skipComponent: StepComponent | undefined, result: FoundPlaceholders) {
+ this.view.components.forEach(component => component.resolvePlaceholders(skipComponent, result));
this.view.placeholders.forEach(placeholder => result.placeholders.push(placeholder));
- this.view.components.forEach(c => c.resolvePlaceholders(skipComponent, result));
}
public updateBadges(result: BadgesResult) {
- for (const component of this.view.components) {
- component.updateBadges(result);
- }
+ this.view.components.forEach(component => component.updateBadges(result));
}
}
diff --git a/designer/src/workspace/start-stop-root/start-stop-root-component-extension-configuration.ts b/designer/src/workspace/start-stop-root/start-stop-root-component-extension-configuration.ts
index 5aa2e66f..f0907deb 100644
--- a/designer/src/workspace/start-stop-root/start-stop-root-component-extension-configuration.ts
+++ b/designer/src/workspace/start-stop-root/start-stop-root-component-extension-configuration.ts
@@ -1,5 +1,5 @@
import { StartStopRootComponentViewConfiguration } from './start-stop-root-component-view-configuration';
export interface StartStopRootComponentExtensionConfiguration {
- view: StartStopRootComponentViewConfiguration;
+ view?: Partial;
}
diff --git a/designer/src/workspace/start-stop-root/start-stop-root-component-extension.ts b/designer/src/workspace/start-stop-root/start-stop-root-component-extension.ts
index 17dd2fd4..2c186f0c 100644
--- a/designer/src/workspace/start-stop-root/start-stop-root-component-extension.ts
+++ b/designer/src/workspace/start-stop-root/start-stop-root-component-extension.ts
@@ -5,24 +5,25 @@ import { RootComponentExtension, SequencePlaceIndicator } from '../../designer-e
import { StartStopRootComponent } from './start-stop-root-component';
import { StartStopRootComponentExtensionConfiguration } from './start-stop-root-component-extension-configuration';
import { Component } from '../component';
+import { StartStopRootComponentViewConfiguration } from './start-stop-root-component-view-configuration';
-const defaultConfiguration: StartStopRootComponentExtensionConfiguration = {
- view: {
- size: 30,
- defaultIconSize: 22,
- folderIconSize: 18,
- startIconD: Icons.play,
- stopIconD: Icons.stop,
- folderIconD: Icons.folder
- }
+const defaultViewConfiguration: StartStopRootComponentViewConfiguration = {
+ size: 30,
+ defaultIconSize: 22,
+ folderIconSize: 18,
+ start: {
+ iconD: Icons.play
+ },
+ stopIconD: Icons.stop,
+ folderIconD: Icons.folder
};
export class StartStopRootComponentExtension implements RootComponentExtension {
public static create(configuration?: StartStopRootComponentExtensionConfiguration) {
- return new StartStopRootComponentExtension(configuration ?? defaultConfiguration);
+ return new StartStopRootComponentExtension(configuration);
}
- private constructor(private readonly configuration: StartStopRootComponentExtensionConfiguration) {}
+ private constructor(private readonly configuration: StartStopRootComponentExtensionConfiguration | undefined) {}
public create(
parentElement: SVGElement,
@@ -30,6 +31,7 @@ export class StartStopRootComponentExtension implements RootComponentExtension {
parentPlaceIndicator: SequencePlaceIndicator | null,
context: ComponentContext
): Component {
- return StartStopRootComponent.create(parentElement, sequence, parentPlaceIndicator, context, this.configuration.view);
+ const view = this.configuration?.view ? { ...defaultViewConfiguration, ...this.configuration.view } : defaultViewConfiguration;
+ return StartStopRootComponent.create(parentElement, sequence, parentPlaceIndicator, context, view);
}
}
diff --git a/designer/src/workspace/start-stop-root/start-stop-root-component-view-configuration.ts b/designer/src/workspace/start-stop-root/start-stop-root-component-view-configuration.ts
index ee1c4b88..39889be5 100644
--- a/designer/src/workspace/start-stop-root/start-stop-root-component-view-configuration.ts
+++ b/designer/src/workspace/start-stop-root/start-stop-root-component-view-configuration.ts
@@ -3,6 +3,8 @@ export interface StartStopRootComponentViewConfiguration {
defaultIconSize: number;
folderIconSize: number;
folderIconD: string;
- startIconD: string;
+ start: {
+ iconD: string;
+ } | null;
stopIconD: string;
}
diff --git a/designer/src/workspace/start-stop-root/start-stop-root-component-view.spec.ts b/designer/src/workspace/start-stop-root/start-stop-root-component-view.spec.ts
index d89e3fd0..89506bce 100644
--- a/designer/src/workspace/start-stop-root/start-stop-root-component-view.spec.ts
+++ b/designer/src/workspace/start-stop-root/start-stop-root-component-view.spec.ts
@@ -12,7 +12,9 @@ describe('StartStopRootComponentView', () => {
defaultIconSize: 22,
folderIconSize: 22,
folderIconD: Icons.folder,
- startIconD: Icons.play,
+ start: {
+ iconD: Icons.play
+ },
stopIconD: Icons.stop
});
expect(parent.children.length).not.toEqual(0);
diff --git a/designer/src/workspace/start-stop-root/start-stop-root-component-view.ts b/designer/src/workspace/start-stop-root/start-stop-root-component-view.ts
index 84742f4d..654e8646 100644
--- a/designer/src/workspace/start-stop-root/start-stop-root-component-view.ts
+++ b/designer/src/workspace/start-stop-root/start-stop-root-component-view.ts
@@ -26,7 +26,7 @@ export class StartStopRootComponentView implements ComponentView {
{
sequence,
depth: 0,
- isInputConnected: true,
+ isInputConnected: Boolean(cfg.start),
isOutputConnected: true,
isPreview: false
},
@@ -36,11 +36,13 @@ export class StartStopRootComponentView implements ComponentView {
const x = view.joinX - cfg.size / 2;
const endY = cfg.size + view.height;
-
const iconSize = parentPlaceIndicator ? cfg.folderIconSize : cfg.defaultIconSize;
- const startCircle = createCircle('start', parentPlaceIndicator ? cfg.folderIconD : cfg.startIconD, cfg.size, iconSize);
- Dom.translate(startCircle, x, 0);
- g.appendChild(startCircle);
+
+ if (cfg.start) {
+ const startCircle = createCircle('start', parentPlaceIndicator ? cfg.folderIconD : cfg.start.iconD, cfg.size, iconSize);
+ Dom.translate(startCircle, x, 0);
+ g.appendChild(startCircle);
+ }
Dom.translate(view.g, 0, cfg.size);
diff --git a/designer/src/workspace/start-stop-root/start-stop-root-component.spec.ts b/designer/src/workspace/start-stop-root/start-stop-root-component.spec.ts
index d2b861a2..61b0e5c6 100644
--- a/designer/src/workspace/start-stop-root/start-stop-root-component.spec.ts
+++ b/designer/src/workspace/start-stop-root/start-stop-root-component.spec.ts
@@ -12,7 +12,9 @@ describe('StartStopRootComponent', () => {
defaultIconSize: 22,
folderIconSize: 22,
folderIconD: Icons.folder,
- startIconD: Icons.play,
+ start: {
+ iconD: Icons.play
+ },
stopIconD: Icons.stop
});
diff --git a/designer/src/workspace/step-component-view-context-factory.ts b/designer/src/workspace/step-component-view-context-factory.ts
index 24b92a87..c6b858e8 100644
--- a/designer/src/workspace/step-component-view-context-factory.ts
+++ b/designer/src/workspace/step-component-view-context-factory.ts
@@ -10,6 +10,21 @@ export class StepComponentViewContextFactory {
i18n: componentContext.i18n,
getStepIconUrl: () => componentContext.iconProvider.getIconUrl(stepContext.step),
getStepName: () => componentContext.i18n(`step.${stepContext.step.type}.name`, stepContext.step.name),
+ createStepComponent: (parentElement: SVGElement, parentSequence: Sequence, step: Step, position: number) => {
+ return componentContext.stepComponentFactory.create(
+ parentElement,
+ {
+ parentSequence,
+ step,
+ depth: stepContext.depth + 1,
+ position,
+ isInputConnected: stepContext.isInputConnected,
+ isOutputConnected: stepContext.isOutputConnected,
+ isPreview: stepContext.isPreview
+ },
+ componentContext
+ );
+ },
createSequenceComponent: (parentElement: SVGElement, sequence: Sequence) => {
const sequenceContext: SequenceContext = {
sequence,
@@ -33,6 +48,8 @@ export class StepComponentViewContextFactory {
contentFactory
);
},
+ getPlaceholderGapSize: orientation => componentContext.services.placeholder.getGapSize(orientation),
+ createPlaceholderForGap: componentContext.services.placeholder.createForGap.bind(componentContext.services.placeholder),
createPlaceholderForArea: componentContext.services.placeholder.createForArea.bind(componentContext.services.placeholder),
getPreference: (key: string) => componentContext.preferenceStorage.getItem(preferenceKeyPrefix + key),
setPreference: (key: string, value: string) => componentContext.preferenceStorage.setItem(preferenceKeyPrefix + key, value)
diff --git a/designer/src/workspace/step-component.ts b/designer/src/workspace/step-component.ts
index 0d223a91..306f4596 100644
--- a/designer/src/workspace/step-component.ts
+++ b/designer/src/workspace/step-component.ts
@@ -22,8 +22,8 @@ export class StepComponent implements Component {
if (this.step.id === stepId) {
return this;
}
- if (this.view.sequenceComponents) {
- for (const component of this.view.sequenceComponents) {
+ if (this.view.components) {
+ for (const component of this.view.components) {
const result = component.findById(stepId);
if (result) {
return result;
@@ -34,14 +34,22 @@ export class StepComponent implements Component {
}
public resolveClick(click: ClickDetails): ClickCommand | null {
- if (this.view.sequenceComponents) {
- for (const component of this.view.sequenceComponents) {
+ if (this.view.components) {
+ for (const component of this.view.components) {
const result = component.resolveClick(click);
if (result) {
return result;
}
}
}
+ if (this.view.placeholders) {
+ for (const placeholder of this.view.placeholders) {
+ const result = placeholder.resolveClick(click);
+ if (result) {
+ return result;
+ }
+ }
+ }
const badgeResult = this.badges.resolveClick(click);
if (badgeResult) {
return badgeResult;
@@ -60,8 +68,8 @@ export class StepComponent implements Component {
public resolvePlaceholders(skipComponent: StepComponent | undefined, result: FoundPlaceholders) {
if (skipComponent !== this) {
- if (this.view.sequenceComponents) {
- this.view.sequenceComponents.forEach(component => component.resolvePlaceholders(skipComponent, result));
+ if (this.view.components) {
+ this.view.components.forEach(component => component.resolvePlaceholders(skipComponent, result));
}
if (this.view.placeholders) {
this.view.placeholders.forEach(ph => result.placeholders.push(ph));
@@ -83,8 +91,8 @@ export class StepComponent implements Component {
}
public updateBadges(result: BadgesResult) {
- if (this.view.sequenceComponents) {
- this.view.sequenceComponents.forEach(component => component.updateBadges(result));
+ if (this.view.components) {
+ this.view.components.forEach(component => component.updateBadges(result));
}
this.badges.update(result);
}
diff --git a/designer/src/workspace/switch-step/switch-step-component-view.ts b/designer/src/workspace/switch-step/switch-step-component-view.ts
index 83a200e9..45bbe24c 100644
--- a/designer/src/workspace/switch-step/switch-step-component-view.ts
+++ b/designer/src/workspace/switch-step/switch-step-component-view.ts
@@ -32,7 +32,7 @@ function createView(
height,
joinX,
placeholders: null,
- sequenceComponents,
+ components: sequenceComponents,
hasOutput: sequenceComponents ? sequenceComponents.some(c => c.hasOutput) : true,
getClientPosition(): Vector {
diff --git a/designer/src/workspace/task-step/task-step-component-view.ts b/designer/src/workspace/task-step/task-step-component-view.ts
index 44fc0760..614ed209 100644
--- a/designer/src/workspace/task-step/task-step-component-view.ts
+++ b/designer/src/workspace/task-step/task-step-component-view.ts
@@ -60,7 +60,7 @@ export const createTaskStepComponentViewFactory =
});
g.appendChild(icon);
- const isInputViewHidden = stepContext.depth === 0 && stepContext.position === 0 && !stepContext.isInputConnected;
+ const isInputViewHidden = !stepContext.isInputConnected; // TODO: handle inside the folder
const isOutputViewHidden = isInterrupted;
const inputView = isInputViewHidden ? null : InputView.createRoundInput(g, boxWidth / 2, 0, cfg.inputSize);
@@ -71,7 +71,7 @@ export const createTaskStepComponentViewFactory =
width: boxWidth,
height: boxHeight,
joinX: boxWidth / 2,
- sequenceComponents: null,
+ components: null,
placeholders: null,
hasOutput: !!outputView,
diff --git a/examples/assets/icon-trigger.svg b/examples/assets/icon-trigger.svg
new file mode 100644
index 00000000..11922200
--- /dev/null
+++ b/examples/assets/icon-trigger.svg
@@ -0,0 +1 @@
+
diff --git a/examples/assets/lib.js b/examples/assets/lib.js
index 94c33462..5c46b1ab 100644
--- a/examples/assets/lib.js
+++ b/examples/assets/lib.js
@@ -13,7 +13,7 @@ function embedStylesheet(url) {
document.write(` `);
}
-const baseUrl = isTestEnv() ? '../designer' : '//cdn.jsdelivr.net/npm/sequential-workflow-designer@0.29.2';
+const baseUrl = isTestEnv() ? '../designer' : '//cdn.jsdelivr.net/npm/sequential-workflow-designer@0.30.0';
embedScript(`${baseUrl}/dist/index.umd.js`);
embedStylesheet(`${baseUrl}/css/designer.css`);
diff --git a/examples/assets/triggers.js b/examples/assets/triggers.js
new file mode 100644
index 00000000..d1297067
--- /dev/null
+++ b/examples/assets/triggers.js
@@ -0,0 +1,151 @@
+/* global document, sequentialWorkflowDesigner */
+
+function createTriggerStep(id, name, properties) {
+ return {
+ id,
+ componentType: 'task',
+ type: 'trigger',
+ name,
+ properties: properties || {}
+ };
+}
+
+function createTaskStep(id, type, name, properties) {
+ return {
+ id,
+ componentType: 'task',
+ type,
+ name,
+ properties: properties || {}
+ };
+}
+
+function createLoopStep(id, steps) {
+ return {
+ id,
+ componentType: 'container',
+ type: 'loop',
+ name: 'Loop',
+ properties: {},
+ sequence: steps
+ };
+}
+
+const configuration = {
+ theme: 'soft',
+ undoStackSize: 20,
+
+ toolbox: {
+ groups: [
+ {
+ name: 'Triggers',
+ steps: [
+ createTriggerStep(null, 'On email received'),
+ createTriggerStep(null, 'On file created'),
+ createTriggerStep(null, 'On file modified')
+ ]
+ },
+ {
+ name: 'Tasks',
+ steps: [createTaskStep(null, 'save', 'Save file'), createTaskStep(null, 'text', 'Send email'), createLoopStep(null, [])]
+ }
+ ]
+ },
+
+ controlBar: true,
+
+ steps: {
+ iconUrlProvider: (_, type) => {
+ if (type === 'launchPad') {
+ return './assets/icon-trigger.svg';
+ }
+ return `./assets/icon-${type}.svg`;
+ },
+ isDuplicable(step) {
+ return step.componentType !== 'launchPad';
+ },
+ isDeletable(step) {
+ return step.componentType !== 'launchPad';
+ },
+ isDraggable(step) {
+ return step.componentType !== 'launchPad';
+ },
+ isSelectable(step) {
+ return step.componentType !== 'launchPad';
+ }
+ },
+
+ placeholder: {
+ canCreate(sequence, index) {
+ const definition = designer.getDefinition();
+ const isRoot = sequence === definition.sequence;
+ if (isRoot) {
+ return index !== 0;
+ }
+ return true;
+ },
+ canShow(sequence, _0, _1, draggingStepType) {
+ const definition = designer.getDefinition();
+ const isTriggerStep = draggingStepType === 'trigger';
+ const isLaunchPadSequence = sequence === definition.sequence[0].sequence;
+ return (isLaunchPadSequence && isTriggerStep) || (!isLaunchPadSequence && !isTriggerStep);
+ }
+ },
+
+ validator: {
+ step: step => {
+ return !step.properties['isInvalid'];
+ }
+ },
+
+ editors: {
+ rootEditorProvider: () => {
+ const root = document.createElement('div');
+ const h3 = document.createElement('h3');
+ h3.innerText = 'Workflows Activated by Triggers';
+ const p0 = document.createElement('p');
+ p0.innerText =
+ 'This example demonstrates how to build a sequential workflow designer with support for triggers and tasks. A workflow can be initiated by any one of multiple triggers, after which a defined sequence of tasks is executed.';
+ const p1 = document.createElement('p');
+ p1.innerText =
+ 'Please note that only trigger steps can be added in the launch section, while any task steps can be added in the section below.';
+
+ root.appendChild(h3);
+ root.appendChild(p0);
+ root.appendChild(p1);
+ return root;
+ },
+ stepEditorProvider: step => {
+ const root = document.createElement('div');
+ root.innerText = `Selected step type: ${step.type}`;
+ return root;
+ }
+ },
+
+ extensions: [
+ sequentialWorkflowDesigner.StartStopRootComponentDesignerExtension.create({
+ view: {
+ start: null
+ }
+ })
+ ]
+};
+
+const definition = {
+ properties: {},
+ sequence: [
+ {
+ id: 'launchPad',
+ name: 'Launch Pad',
+ componentType: 'launchPad',
+ type: 'launchPad',
+ properties: {},
+ sequence: [createTriggerStep('0x1', 'On email received'), createTriggerStep('0x2', 'On file created')]
+ },
+ createTaskStep('0x3', 'save', 'Save file'),
+ createLoopStep('0x4', [createTaskStep('0x6', 'text', 'Send SMS')])
+ ]
+};
+
+const placeholder = document.getElementById('designer');
+const designer = sequentialWorkflowDesigner.Designer.create(placeholder, definition, configuration);
diff --git a/examples/triggers.html b/examples/triggers.html
new file mode 100644
index 00000000..a10b351c
--- /dev/null
+++ b/examples/triggers.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+ 💥 Triggers Example - Sequential Workflow Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
💥 Triggers Example
+
+
+
+
+
+
+
+
+
+
diff --git a/react/package.json b/react/package.json
index 70f44b43..e9106b05 100644
--- a/react/package.json
+++ b/react/package.json
@@ -1,7 +1,7 @@
{
"name": "sequential-workflow-designer-react",
"description": "React wrapper for Sequential Workflow Designer component.",
- "version": "0.29.2",
+ "version": "0.30.0",
"type": "module",
"main": "./lib/esm/index.js",
"types": "./lib/index.d.ts",
@@ -47,7 +47,7 @@
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0",
- "sequential-workflow-designer": "^0.29.2"
+ "sequential-workflow-designer": "^0.30.0"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^16.0.1",
@@ -63,7 +63,7 @@
"prettier": "^3.2.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "sequential-workflow-designer": "^0.29.2",
+ "sequential-workflow-designer": "^0.30.0",
"rollup": "^4.40.0",
"rollup-plugin-dts": "^6.2.1",
"rollup-plugin-typescript2": "^0.36.0",
diff --git a/react/src/SequentialWorkflowDesigner.tsx b/react/src/SequentialWorkflowDesigner.tsx
index 7656ddb3..d33aff23 100644
--- a/react/src/SequentialWorkflowDesigner.tsx
+++ b/react/src/SequentialWorkflowDesigner.tsx
@@ -18,7 +18,8 @@ import {
StepEditorProvider,
KeyboardConfiguration,
I18n,
- PreferenceStorage
+ PreferenceStorage,
+ PlaceholderConfiguration
} from 'sequential-workflow-designer';
import { RootEditorWrapperContext } from './RootEditorWrapper';
import { StepEditorWrapperContext } from './StepEditorWrapper';
@@ -46,6 +47,7 @@ export interface SequentialWorkflowDesignerProps
undoStackSize?: number;
stepsConfiguration: StepsConfiguration;
validatorConfiguration?: ValidatorConfiguration;
+ placeholderConfiguration?: PlaceholderConfiguration;
toolboxConfiguration: false | ReactToolboxConfiguration;
isToolboxCollapsed?: boolean;
onIsToolboxCollapsedChanged?: (isCollapsed: boolean) => void;
@@ -63,7 +65,7 @@ export interface SequentialWorkflowDesignerProps
}
export function SequentialWorkflowDesigner(props: SequentialWorkflowDesignerProps) {
- const [placeholder, setPlaceholder] = useState(null);
+ const [root, setRoot] = useState(null);
const onDefinitionChangeRef = useRef(props.onDefinitionChange);
const onSelectedStepIdChangedRef = useRef(props.onSelectedStepIdChanged);
@@ -84,6 +86,7 @@ export function SequentialWorkflowDesigner(props
const undoStackSize = props.undoStackSize;
const steps = props.stepsConfiguration;
const validator = props.validatorConfiguration;
+ const placeholder = props.placeholderConfiguration;
const toolbox = props.toolboxConfiguration;
const isEditorCollapsed = props.isEditorCollapsed;
const isToolboxCollapsed = props.isToolboxCollapsed;
@@ -181,7 +184,7 @@ export function SequentialWorkflowDesigner(props
}, [props.customActionHandler]);
useEffect(() => {
- if (!placeholder) {
+ if (!root) {
return;
}
@@ -215,7 +218,7 @@ export function SequentialWorkflowDesigner(props
tryDestroy();
}
- const designer = Designer.create(placeholder, definition.value, {
+ const designer = Designer.create(root, definition.value, {
theme,
undoStackSize,
toolbox: toolbox
@@ -226,6 +229,7 @@ export function SequentialWorkflowDesigner(props
: false,
steps,
validator,
+ placeholder,
controlBar,
contextMenu,
keyboard,
@@ -272,7 +276,7 @@ export function SequentialWorkflowDesigner(props
designerRef.current = designer;
}, [
- placeholder,
+ root,
definition,
selectedStepId,
isReadonly,
@@ -287,6 +291,7 @@ export function SequentialWorkflowDesigner(props
controlBar,
steps,
validator,
+ placeholder,
extensions,
i18n
]);
@@ -295,5 +300,5 @@ export function SequentialWorkflowDesigner(props
return tryDestroy;
}, []);
- return
;
+ return
;
}
diff --git a/svelte/package.json b/svelte/package.json
index f406acc6..d233fb13 100644
--- a/svelte/package.json
+++ b/svelte/package.json
@@ -1,7 +1,7 @@
{
"name": "sequential-workflow-designer-svelte",
"description": "Svelte wrapper for Sequential Workflow Designer component.",
- "version": "0.29.2",
+ "version": "0.30.0",
"license": "MIT",
"scripts": {
"prepare": "cp ../LICENSE LICENSE",
@@ -28,10 +28,10 @@
],
"peerDependencies": {
"svelte": "^4.0.0",
- "sequential-workflow-designer": "^0.29.2"
+ "sequential-workflow-designer": "^0.30.0"
},
"devDependencies": {
- "sequential-workflow-designer": "^0.29.2",
+ "sequential-workflow-designer": "^0.30.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.20.4",
"@sveltejs/package": "^2.0.0",
diff --git a/svelte/src/lib/SequentialWorkflowDesigner.svelte b/svelte/src/lib/SequentialWorkflowDesigner.svelte
index 09cf5ae1..55dacfd4 100644
--- a/svelte/src/lib/SequentialWorkflowDesigner.svelte
+++ b/svelte/src/lib/SequentialWorkflowDesigner.svelte
@@ -11,6 +11,7 @@
type RootEditorContext,
type UndoStack,
type ValidatorConfiguration,
+ type PlaceholderConfiguration,
type UidGenerator,
type DesignerExtension,
type EditorsConfiguration,
@@ -50,6 +51,7 @@
export let undoStackSize: number | undefined = undefined;
export let undoStack: UndoStack | undefined = undefined;
export let validator: ValidatorConfiguration | undefined = undefined;
+ export let placeholder: PlaceholderConfiguration | undefined = undefined;
export let definitionWalker: DefinitionWalker | undefined = undefined;
export let extensions: DesignerExtension[] | undefined = undefined;
export let i18n: I18n | undefined = undefined;
@@ -79,7 +81,7 @@
let isFirstChange = true;
let designer: Designer | null = null;
- let placeholder: HTMLElement;
+ let root: HTMLElement;
function init() {
const editors: EditorsConfiguration | false =
@@ -123,7 +125,7 @@
}
: false;
- const d = Designer.create(placeholder, definition, {
+ const d = Designer.create(root, definition, {
steps,
controlBar,
toolbox: _toolbox,
@@ -135,6 +137,7 @@
undoStackSize,
undoStack,
validator,
+ placeholder,
definitionWalker,
extensions,
isReadonly,
@@ -205,4 +208,4 @@
});
-
+