From 1b2d3190ef8bc09e40c1c46d6595688edd113dde Mon Sep 17 00:00:00 2001 From: Bart Tadych Date: Thu, 16 May 2024 22:12:28 +0200 Subject: [PATCH] 0.21.0. (#143) --- CHANGELOG.md | 4 + README.md | 9 +- angular/designer/package.json | 4 +- angular/designer/src/designer.component.ts | 6 +- demos/angular-app/package.json | 6 +- demos/angular-app/yarn.lock | 16 +- demos/react-app/package.json | 6 +- demos/svelte-app/package.json | 6 +- designer/package.json | 4 +- designer/src/component-context.ts | 29 +- .../core/memory-preference-storage.spec.ts | 13 + .../src/core/memory-preference-storage.ts | 13 + designer/src/designer-configuration.ts | 10 + designer/src/designer-context.ts | 7 +- designer/src/designer-extension.ts | 48 +++- designer/src/services.ts | 7 + designer/src/workspace/badges/badges.ts | 2 +- .../validation-error-badge-extension.ts | 11 +- .../validation-error-badge.ts | 8 +- .../validation-error/validator-factory.ts | 32 +++ designer/src/workspace/common-views/index.ts | 1 - designer/src/workspace/component.ts | 4 +- .../container-step-component-view.ts | 95 +++--- designer/src/workspace/index.ts | 1 + .../placeholder/rect-placeholder-extension.ts | 2 +- ...default-region-component-view-extension.ts | 23 ++ .../default-region-view.spec.ts} | 6 +- .../default-region-view.ts} | 16 +- designer/src/workspace/region/index.ts | 2 + .../step-component-view-context-factory.ts | 21 +- designer/src/workspace/step-component.ts | 2 +- .../switch-step/switch-step-component-view.ts | 271 +++++++++--------- .../task-step/task-step-component-view.ts | 8 +- examples/assets/lib.js | 2 +- react/package.json | 8 +- react/src/SequentialWorkflowDesigner.tsx | 7 +- svelte/package.json | 8 +- .../src/lib/SequentialWorkflowDesigner.svelte | 5 +- 38 files changed, 462 insertions(+), 261 deletions(-) create mode 100644 designer/src/core/memory-preference-storage.spec.ts create mode 100644 designer/src/core/memory-preference-storage.ts create mode 100644 designer/src/workspace/badges/validation-error/validator-factory.ts create mode 100644 designer/src/workspace/region/default-region-component-view-extension.ts rename designer/src/workspace/{common-views/region-view.spec.ts => region/default-region-view.spec.ts} (52%) rename designer/src/workspace/{common-views/region-view.ts => region/default-region-view.ts} (78%) create mode 100644 designer/src/workspace/region/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 00d7de3..db22ba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.21.0 + +This version introduces several changes related with the collapsible regions in the pro version. + ## 0.20.0 This version introduces the localization feature. Now you can localize the designer to any language you want. diff --git a/README.md b/README.md index 9412989..7e8a255 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Pro: * [🍬 Custom Theme Flat](https://nocode-js.com/examples/sequential-workflow-designer-pro/webpack-pro-app/public/custom-theme-flat.html) * [🌹 Custom Step Types](https://nocode-js.com/examples/sequential-workflow-designer-pro/webpack-pro-app/public/custom-step-types.html) * [📺 Popup Editor](https://nocode-js.com/examples/sequential-workflow-designer-pro/webpack-pro-app/public/popup-editor.html) +* [🔽 Collapsible Region](https://nocode-js.com/examples/sequential-workflow-designer-pro/webpack-pro-app/public/collapsible-region.html) * [💼 Copy Paste](https://nocode-js.com/examples/sequential-workflow-designer-pro/webpack-pro-app/public/copy-paste.html) * [👈 Goto](https://nocode-js.com/examples/sequential-workflow-designer-pro/webpack-pro-app/public/goto.html) * [📁 Folders](https://nocode-js.com/examples/sequential-workflow-designer-pro/webpack-pro-app/public/folders.html) @@ -101,10 +102,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 23a2160..cd4f20e 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.20.0", + "version": "0.21.0", "author": { "name": "NoCode JS", "url": "https://nocode-js.com/" @@ -15,7 +15,7 @@ "peerDependencies": { "@angular/common": "12 - 16", "@angular/core": "12 - 16", - "sequential-workflow-designer": "^0.20.0" + "sequential-workflow-designer": "^0.21.0" }, "dependencies": { "tslib": "^2.3.0" diff --git a/angular/designer/src/designer.component.ts b/angular/designer/src/designer.component.ts index 4e51683..43cbd3f 100644 --- a/angular/designer/src/designer.component.ts +++ b/angular/designer/src/designer.component.ts @@ -29,7 +29,8 @@ import { ToolboxConfiguration, UidGenerator, ValidatorConfiguration, - I18n + I18n, + PreferenceStorage } from 'sequential-workflow-designer'; export interface RootEditorWrapper { @@ -76,6 +77,8 @@ export class DesignerComponent implements AfterViewInit, OnChanges, OnDestroy { public contextMenu?: boolean; @Input('keyboard') public keyboard?: boolean | KeyboardConfiguration; + @Input('preferenceStorage') + public preferenceStorage?: PreferenceStorage; @Input('extensions') public extensions?: DesignerExtension[]; @Input('i18n') @@ -220,6 +223,7 @@ export class DesignerComponent implements AfterViewInit, OnChanges, OnDestroy { controlBar: this.controlBar, contextMenu: this.contextMenu, keyboard: this.keyboard, + preferenceStorage: this.preferenceStorage, extensions: this.extensions, isReadonly: this.isReadonly, i18n: this.i18n, diff --git a/demos/angular-app/package.json b/demos/angular-app/package.json index ad40fd1..34e90e2 100644 --- a/demos/angular-app/package.json +++ b/demos/angular-app/package.json @@ -26,8 +26,8 @@ "@angular/platform-browser-dynamic": "^15.2.9", "@angular/router": "^15.2.9", "rxjs": "~7.8.0", - "sequential-workflow-designer": "^0.20.0", - "sequential-workflow-designer-angular": "^0.20.0", + "sequential-workflow-designer": "^0.21.0", + "sequential-workflow-designer-angular": "^0.21.0", "tslib": "^2.3.0", "zone.js": "~0.13.0" }, @@ -45,4 +45,4 @@ "prettier": "^3.2.5", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/demos/angular-app/yarn.lock b/demos/angular-app/yarn.lock index 148e67a..8260b1c 100644 --- a/demos/angular-app/yarn.lock +++ b/demos/angular-app/yarn.lock @@ -5956,17 +5956,17 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" -sequential-workflow-designer-angular@^0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/sequential-workflow-designer-angular/-/sequential-workflow-designer-angular-0.20.0.tgz#abe658c0fe0911acc04506c32030fe5e1a0c6db2" - integrity sha512-dAV0U+Iw+xJUJfH5lva4Y0hNd3jcs8FfUDVQgohr8TTSYpnQRQlCDUN8oiI1zmZd59GInX3ckWR7Vfl0hE764Q== +sequential-workflow-designer-angular@^0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/sequential-workflow-designer-angular/-/sequential-workflow-designer-angular-0.21.0.tgz#c18481347b4011dc2ce9c8f422027702382a33dc" + integrity sha512-MajrGNFskcG2V1OpiiL/OigP1MD1CJtdD8Nl+X8KClqeZ+kw+aqNRJqIBjC7zPdcoGsHyKGGORUyPzlwTWoLwg== dependencies: tslib "^2.3.0" -sequential-workflow-designer@^0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/sequential-workflow-designer/-/sequential-workflow-designer-0.20.0.tgz#fb6a1fc15969e62e060251e57c9977c0b22cecd3" - integrity sha512-nndGqJuOR7KAmY5OasPBYt842KWeDC05DgJ3J/M+tQNxddqCHWlafy6rwHfO1cPB3Ui1m0b0sOrNNJmv1r6dWA== +sequential-workflow-designer@^0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/sequential-workflow-designer/-/sequential-workflow-designer-0.21.0.tgz#34a461163634b140b12a46943cf9c1eb9443c7c7" + integrity sha512-P+LTR0umWwiZEWtwvaZ4ZhWAAwrF4vYJhY6ge32yrltJzsViiM951+wo9l3945i5Vv7GoLvgovSX4nhZDHDlSQ== dependencies: sequential-workflow-model "^0.2.0" diff --git a/demos/react-app/package.json b/demos/react-app/package.json index 314c968..36bb817 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.20.0", - "sequential-workflow-designer-react": "^0.20.0" + "sequential-workflow-designer": "^0.21.0", + "sequential-workflow-designer-react": "^0.21.0" }, "devDependencies": { "@types/jest": "^29.2.5", @@ -48,4 +48,4 @@ "last 1 safari version" ] } -} +} \ No newline at end of file diff --git a/demos/svelte-app/package.json b/demos/svelte-app/package.json index c96b6ff..0b55c48 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.20.0", - "sequential-workflow-designer-svelte": "^0.20.0" + "sequential-workflow-designer": "^0.21.0", + "sequential-workflow-designer-svelte": "^0.21.0" }, "devDependencies": { "@sveltejs/adapter-static": "^2.0.3", @@ -33,4 +33,4 @@ "@typescript-eslint/parser": "^5.47.0", "eslint": "^8.30.0" } -} +} \ No newline at end of file diff --git a/designer/package.json b/designer/package.json index 85e36d6..7ac118e 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.20.0", + "version": "0.21.0", "type": "module", "main": "./lib/esm/index.js", "types": "./lib/index.d.ts", @@ -104,4 +104,4 @@ "lowcode", "flow" ] -} +} \ No newline at end of file diff --git a/designer/src/component-context.ts b/designer/src/component-context.ts index 14b015b..6c404cf 100644 --- a/designer/src/component-context.ts +++ b/designer/src/component-context.ts @@ -1,6 +1,7 @@ +import { DefinitionWalker } from 'sequential-workflow-model'; import { DefinitionValidator } from './core/definition-validator'; import { IconProvider } from './core/icon-provider'; -import { I18n, StepsConfiguration, ValidatorConfiguration } from './designer-configuration'; +import { DesignerConfiguration, I18n, PreferenceStorage } from './designer-configuration'; import { PlaceholderController } from './designer-extension'; import { DesignerState } from './designer-state'; import { Services } from './services'; @@ -9,18 +10,28 @@ import { StepExtensionResolver } from './workspace/step-extension-resolver'; export class ComponentContext { public static create( - stepsConfiguration: StepsConfiguration, - validatorConfiguration: ValidatorConfiguration | undefined, + configuration: DesignerConfiguration, state: DesignerState, stepExtensionResolver: StepExtensionResolver, + definitionWalker: DefinitionWalker, + preferenceStorage: PreferenceStorage, i18n: I18n, services: Services ): ComponentContext { - const validator = new DefinitionValidator(validatorConfiguration, state); - const iconProvider = new IconProvider(stepsConfiguration); + const validator = new DefinitionValidator(configuration.validator, state); + const iconProvider = new IconProvider(configuration.steps); const placeholderController = services.placeholderController.create(); const stepComponentFactory = new StepComponentFactory(stepExtensionResolver); - return new ComponentContext(validator, iconProvider, placeholderController, stepComponentFactory, i18n, services); + return new ComponentContext( + validator, + iconProvider, + placeholderController, + stepComponentFactory, + definitionWalker, + services, + preferenceStorage, + i18n + ); } private constructor( @@ -28,7 +39,9 @@ export class ComponentContext { public readonly iconProvider: IconProvider, public readonly placeholderController: PlaceholderController, public readonly stepComponentFactory: StepComponentFactory, - public readonly i18n: I18n, - public readonly services: Services + public readonly definitionWalker: DefinitionWalker, + public readonly services: Services, + public readonly preferenceStorage: PreferenceStorage, + public readonly i18n: I18n ) {} } diff --git a/designer/src/core/memory-preference-storage.spec.ts b/designer/src/core/memory-preference-storage.spec.ts new file mode 100644 index 0000000..029010f --- /dev/null +++ b/designer/src/core/memory-preference-storage.spec.ts @@ -0,0 +1,13 @@ +import { MemoryPreferenceStorage } from './memory-preference-storage'; + +describe('MemoryPreferenceStorage', () => { + it('remember a value', () => { + const storage = new MemoryPreferenceStorage(); + + expect(storage.getItem('key0')).toBeNull(); + + storage.setItem('key0', 'value'); + + expect(storage.getItem('key0')).toBe('value'); + }); +}); diff --git a/designer/src/core/memory-preference-storage.ts b/designer/src/core/memory-preference-storage.ts new file mode 100644 index 0000000..8f85fbd --- /dev/null +++ b/designer/src/core/memory-preference-storage.ts @@ -0,0 +1,13 @@ +import { PreferenceStorage } from '../designer-configuration'; + +export class MemoryPreferenceStorage implements PreferenceStorage { + private readonly map: Record = {}; + + public setItem(key: string, value: string) { + this.map[key] = value; + } + + public getItem(key: string): string | null { + return this.map[key] ?? null; + } +} diff --git a/designer/src/designer-configuration.ts b/designer/src/designer-configuration.ts index d1c047a..76b3eb9 100644 --- a/designer/src/designer-configuration.ts +++ b/designer/src/designer-configuration.ts @@ -73,6 +73,11 @@ export interface DesignerConfiguration boolean; canInsertStep?: (step: Step, targetSequence: Sequence, targetIndex: number) => boolean; diff --git a/designer/src/designer-context.ts b/designer/src/designer-context.ts index 4c46361..60667e6 100644 --- a/designer/src/designer-context.ts +++ b/designer/src/designer-context.ts @@ -11,6 +11,7 @@ import { LayoutController } from './layout-controller'; 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'; export class DesignerContext { public static create( @@ -42,11 +43,13 @@ export class DesignerContext { historyController = HistoryController.create(configuration.undoStack, state, stateModifier, configuration); } + const preferenceStorage = configuration.preferenceStorage ?? new MemoryPreferenceStorage(); const componentContext = ComponentContext.create( - configuration.steps, - configuration.validator, + configuration, state, stepExtensionResolver, + definitionWalker, + preferenceStorage, i18n, services ); diff --git a/designer/src/designer-extension.ts b/designer/src/designer-extension.ts index 039dd2e..4d81de0 100644 --- a/designer/src/designer-extension.ts +++ b/designer/src/designer-extension.ts @@ -5,7 +5,16 @@ import { Vector } from './core'; import { CustomActionController } from './custom-action-controller'; import { ComponentType, Sequence, Step } from './definition'; import { I18n } from './designer-configuration'; -import { Badge, Component, Placeholder, PlaceholderDirection, SequenceComponent, StepComponentView } from './workspace'; +import { + Badge, + ClickCommand, + ClickDetails, + Component, + Placeholder, + PlaceholderDirection, + SequenceComponent, + StepComponentView +} from './workspace'; export interface DesignerExtension { steps?: StepExtension[]; @@ -17,6 +26,7 @@ export interface DesignerExtension { viewportController?: ViewportControllerExtension; placeholderController?: PlaceholderControllerExtension; placeholder?: PlaceholderExtension; + regionComponentView?: RegionComponentViewExtension; grid?: GridExtension; rootComponent?: RootComponentExtension; sequenceComponent?: SequenceComponentExtension; @@ -34,6 +44,7 @@ export interface StepExtension { export type StepComponentViewFactory = StepExtension['createComponentView']; export interface StepComponentViewContext { + i18n: I18n; getStepName(): string; getStepIconUrl(): string | null; createSequenceComponent(parentElement: SVGElement, sequence: Sequence): SequenceComponent; @@ -44,7 +55,13 @@ export interface StepComponentViewContext { sequence: Sequence, index: number ): Placeholder; - i18n: I18n; + createRegionComponentView( + parentElement: SVGElement, + componentClassName: string, + contentFactory: RegionComponentViewContentFactory + ): StepComponentView; + getPreference(key: string): string | null; + setPreference(key: string, value: string): void; } export interface StepContext { @@ -71,7 +88,7 @@ export interface StepComponentViewWrapperExtension { export interface BadgeExtension { id: string; - createForStep(parentElement: SVGElement, stepContext: StepContext, componentContext: ComponentContext): Badge; + createForStep(parentElement: SVGElement, view: StepComponentView, stepContext: StepContext, componentContext: ComponentContext): Badge; createForRoot?: (parentElement: SVGElement, componentContext: ComponentContext) => Badge; createStartValue(): unknown; } @@ -202,3 +219,28 @@ export interface DaemonExtension { export interface Daemon { destroy(): void; } + +// RegionComponentViewExtension + +export interface RegionView { + getClientPosition(): Vector; + /** + * @returns `true` if the click is inside the region, `null` if it's outside. The view may return a command to be executed. + */ + resolveClick(click: ClickDetails): true | ClickCommand | null; + setIsSelected(isSelected: boolean): void; +} + +export type RegionViewFactory = (parent: SVGElement, widths: number[], height: number) => RegionView; + +export type RegionComponentViewContentFactory = (g: SVGGElement, regionViewFactory: RegionViewFactory) => StepComponentView; + +export interface RegionComponentViewExtension { + create( + parentElement: SVGElement, + componentClassName: string, + stepContext: StepContext, + viewContext: StepComponentViewContext, + contentFactory: RegionComponentViewContentFactory + ): StepComponentView; +} diff --git a/designer/src/services.ts b/designer/src/services.ts index f4ea200..0700d3b 100644 --- a/designer/src/services.ts +++ b/designer/src/services.ts @@ -18,6 +18,7 @@ import { findValidationBadgeIndex } from './workspace/badges/find-validation-bad import { DefaultSequenceComponentExtension } from './workspace/sequence/default-sequence-component-extension'; import { DefaultStepComponentViewWrapperExtension } from './workspace/default-step-component-view-wrapper-extension'; import { LineGridExtension } from './workspace/grid/line-grid-extension'; +import { DefaultRegionComponentViewExtension } from './workspace/region/default-region-component-view-extension'; export type Services = Required; @@ -56,6 +57,9 @@ function merge(services: Partial, extensions: DesignerExtension[]) { if (ext.placeholder) { services.placeholder = ext.placeholder; } + if (ext.regionComponentView) { + services.regionComponentView = ext.regionComponentView; + } if (ext.viewportController) { services.viewportController = ext.viewportController; } @@ -121,6 +125,9 @@ function setDefaults(services: Partial, configuration: DesignerConfigu if (!services.placeholder) { services.placeholder = RectPlaceholderExtension.create(); } + if (!services.regionComponentView) { + services.regionComponentView = new DefaultRegionComponentViewExtension(); + } if (!services.viewportController) { services.viewportController = new DefaultViewportControllerExtension(); } diff --git a/designer/src/workspace/badges/badges.ts b/designer/src/workspace/badges/badges.ts index 0194006..f18b092 100644 --- a/designer/src/workspace/badges/badges.ts +++ b/designer/src/workspace/badges/badges.ts @@ -8,7 +8,7 @@ const BADGE_GAP = 4; export class Badges { public static createForStep(stepContext: StepContext, view: StepComponentView, componentContext: ComponentContext): Badges { const g = createG(view.g); - const badges = componentContext.services.badges.map(ext => ext.createForStep(g, stepContext, componentContext)); + const badges = componentContext.services.badges.map(ext => ext.createForStep(g, view, stepContext, componentContext)); const position = new Vector(view.width, 0); return new Badges(g, position, badges); } diff --git a/designer/src/workspace/badges/validation-error/validation-error-badge-extension.ts b/designer/src/workspace/badges/validation-error/validation-error-badge-extension.ts index e5aebc0..2be1a43 100644 --- a/designer/src/workspace/badges/validation-error/validation-error-badge-extension.ts +++ b/designer/src/workspace/badges/validation-error/validation-error-badge-extension.ts @@ -1,7 +1,7 @@ import { Step } from '../../../definition'; import { ComponentContext } from '../../../component-context'; import { BadgeExtension, StepContext } from '../../../designer-extension'; -import { Badge } from '../../component'; +import { Badge, StepComponentView } from '../../component'; import { ValidationErrorBadge } from './validation-error-badge'; import { ValidationErrorBadgeExtensionConfiguration } from './validation-error-badge-extension-configuration'; @@ -21,8 +21,13 @@ export class ValidationErrorBadgeExtension implements BadgeExtension { private constructor(private readonly configuration: ValidationErrorBadgeExtensionConfiguration) {} - public createForStep(parentElement: SVGElement, stepContext: StepContext, componentContext: ComponentContext): Badge { - return ValidationErrorBadge.createForStep(parentElement, stepContext, componentContext, this.configuration.view); + public createForStep( + parentElement: SVGElement, + view: StepComponentView, + stepContext: StepContext, + componentContext: ComponentContext + ): Badge { + return ValidationErrorBadge.createForStep(parentElement, view, stepContext, componentContext, this.configuration.view); } public createForRoot(parentElement: SVGElement, componentContext: ComponentContext): Badge { diff --git a/designer/src/workspace/badges/validation-error/validation-error-badge.ts b/designer/src/workspace/badges/validation-error/validation-error-badge.ts index 05508e4..ab29806 100644 --- a/designer/src/workspace/badges/validation-error/validation-error-badge.ts +++ b/designer/src/workspace/badges/validation-error/validation-error-badge.ts @@ -1,17 +1,19 @@ import { ComponentContext } from '../../../component-context'; import { StepContext } from '../../../designer-extension'; -import { Badge } from '../../component'; +import { Badge, StepComponentView } from '../../component'; import { ValidationErrorBadgeView } from './validation-error-badge-view'; import { ValidationErrorBadgeViewConfiguration } from './validation-error-badge-view-configuration'; +import { ValidatorFactory } from './validator-factory'; export class ValidationErrorBadge implements Badge { public static createForStep( parentElement: SVGElement, + view: StepComponentView, stepContext: StepContext, componentContext: ComponentContext, configuration: ValidationErrorBadgeViewConfiguration ): ValidationErrorBadge { - const validator = () => componentContext.validator.validateStep(stepContext.step, stepContext.parentSequence); + const validator = ValidatorFactory.createForStep(stepContext, view, componentContext); return new ValidationErrorBadge(parentElement, validator, configuration); } @@ -20,7 +22,7 @@ export class ValidationErrorBadge implements Badge { componentContext: ComponentContext, configuration: ValidationErrorBadgeViewConfiguration ) { - const validator = () => componentContext.validator.validateRoot(); + const validator = ValidatorFactory.createForRoot(componentContext); return new ValidationErrorBadge(parentElement, validator, configuration); } diff --git a/designer/src/workspace/badges/validation-error/validator-factory.ts b/designer/src/workspace/badges/validation-error/validator-factory.ts new file mode 100644 index 0000000..ced8525 --- /dev/null +++ b/designer/src/workspace/badges/validation-error/validator-factory.ts @@ -0,0 +1,32 @@ +import { ComponentContext } from '../../../component-context'; +import { StepContext } from '../../../designer-extension'; +import { StepComponentView } from '../../component'; + +export class ValidatorFactory { + public static createForStep(stepContext: StepContext, view: StepComponentView, componentContext: ComponentContext): () => boolean { + return () => { + if (!componentContext.validator.validateStep(stepContext.step, stepContext.parentSequence)) { + return false; + } + if (view.haveCollapsedChildren) { + let allChildrenValid = true; + componentContext.definitionWalker.forEachChildren(stepContext.step, (step, _, parentSequence) => { + if (!componentContext.validator.validateStep(step, parentSequence)) { + allChildrenValid = false; + return false; + } + }); + if (!allChildrenValid) { + return false; + } + } + return true; + }; + } + + public static createForRoot(componentContext: ComponentContext): () => boolean { + return () => { + return componentContext.validator.validateRoot(); + }; + } +} diff --git a/designer/src/workspace/common-views/index.ts b/designer/src/workspace/common-views/index.ts index 4e30476..1f7cfba 100644 --- a/designer/src/workspace/common-views/index.ts +++ b/designer/src/workspace/common-views/index.ts @@ -3,4 +3,3 @@ export * from './input-view'; export * from './join-view'; export * from './label-view'; export * from './output-view'; -export * from './region-view'; diff --git a/designer/src/workspace/component.ts b/designer/src/workspace/component.ts index ce0fdd3..c5eb25a 100644 --- a/designer/src/workspace/component.ts +++ b/designer/src/workspace/component.ts @@ -24,7 +24,9 @@ export interface StepComponentView extends ComponentView { sequenceComponents: SequenceComponent[] | null; placeholders: Placeholder[] | null; - hasOutput(): boolean; + hasOutput: boolean; + haveCollapsedChildren?: boolean; + /** * @param click Details about the click. * @returns `true` if selected a step, a click command if clicked a specific action, `null` if not clicked at this view. 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 293f870..c0e5c65 100644 --- a/designer/src/workspace/container-step/container-step-component-view.ts +++ b/designer/src/workspace/container-step/container-step-component-view.ts @@ -1,69 +1,68 @@ import { Dom } from '../../core/dom'; import { Vector } from '../../core/vector'; import { SequentialStep } from '../../definition'; -import { ClickDetails, StepComponentView } from '../component'; +import { ClickDetails, StepComponentView, ClickCommand } from '../component'; import { InputView } from '../common-views/input-view'; import { JoinView } from '../common-views/join-view'; import { LabelView } from '../common-views/label-view'; -import { RegionView } from '../common-views/region-view'; import { StepComponentViewContext, StepComponentViewFactory, StepContext } from '../../designer-extension'; import { ContainerStepComponentViewConfiguration } from './container-step-component-view-configuration'; -import { ComponentDom } from '../common-views/component-dom'; + +const COMPONENT_CLASS_NAME = 'container'; export const createContainerStepComponentViewFactory = (cfg: ContainerStepComponentViewConfiguration): StepComponentViewFactory => (parentElement: SVGElement, stepContext: StepContext, viewContext: StepComponentViewContext): StepComponentView => { - const { step } = stepContext; - const g = ComponentDom.stepG('container', step.type, step.id); - parentElement.appendChild(g); - - const name = viewContext.getStepName(); - const labelView = LabelView.create(g, cfg.paddingTop, cfg.label, name, 'primary'); - const sequenceComponent = viewContext.createSequenceComponent(g, step.sequence); + return viewContext.createRegionComponentView(parentElement, COMPONENT_CLASS_NAME, (g, regionViewBuilder) => { + const step = stepContext.step; + const name = viewContext.getStepName(); + const labelView = LabelView.create(g, cfg.paddingTop, cfg.label, name, 'primary'); + const sequenceComponent = viewContext.createSequenceComponent(g, step.sequence); - const halfOfWidestElement = labelView.width / 2; - const offsetLeft = Math.max(halfOfWidestElement - sequenceComponent.view.joinX, 0) + cfg.paddingX; - const offsetRight = Math.max(halfOfWidestElement - (sequenceComponent.view.width - sequenceComponent.view.joinX), 0) + cfg.paddingX; + const halfOfWidestElement = labelView.width / 2; + const offsetLeft = Math.max(halfOfWidestElement - sequenceComponent.view.joinX, 0) + cfg.paddingX; + const offsetRight = + Math.max(halfOfWidestElement - (sequenceComponent.view.width - sequenceComponent.view.joinX), 0) + cfg.paddingX; - const width = offsetLeft + sequenceComponent.view.width + offsetRight; - const height = cfg.paddingTop + cfg.label.height + sequenceComponent.view.height; - const joinX = sequenceComponent.view.joinX + offsetLeft; + const width = offsetLeft + sequenceComponent.view.width + offsetRight; + const height = cfg.paddingTop + cfg.label.height + sequenceComponent.view.height; + const joinX = sequenceComponent.view.joinX + offsetLeft; - Dom.translate(labelView.g, joinX, 0); - Dom.translate(sequenceComponent.view.g, offsetLeft, cfg.paddingTop + cfg.label.height); + Dom.translate(labelView.g, joinX, 0); + Dom.translate(sequenceComponent.view.g, offsetLeft, cfg.paddingTop + cfg.label.height); - const iconUrl = viewContext.getStepIconUrl(); - const inputView = InputView.createRectInput(g, joinX, 0, cfg.inputSize, cfg.inputIconSize, iconUrl); + const iconUrl = viewContext.getStepIconUrl(); + const inputView = InputView.createRectInput(g, joinX, 0, cfg.inputSize, cfg.inputIconSize, iconUrl); - JoinView.createStraightJoin(g, new Vector(joinX, 0), cfg.paddingTop); + JoinView.createStraightJoin(g, new Vector(joinX, 0), cfg.paddingTop); - const regionView = RegionView.create(g, [width], height); + const regionView = regionViewBuilder(g, [width], height); - return { - g, - width, - height, - joinX, - placeholders: null, - sequenceComponents: [sequenceComponent], + return { + g, + width, + height, + joinX, + placeholders: null, + sequenceComponents: [sequenceComponent], + hasOutput: sequenceComponent.hasOutput, - getClientPosition(): Vector { - return regionView.getClientPosition(); - }, - resolveClick(click: ClickDetails): true | null { - return regionView.resolveClick(click) || g.contains(click.element) ? true : null; - }, - setIsDragging(isDragging: boolean) { - inputView.setIsHidden(isDragging); - }, - setIsSelected(isSelected: boolean) { - regionView.setIsSelected(isSelected); - }, - setIsDisabled(isDisabled: boolean) { - Dom.toggleClass(g, isDisabled, 'sqd-disabled'); - }, - hasOutput(): boolean { - return sequenceComponent.hasOutput; - } - }; + getClientPosition(): Vector { + return regionView.getClientPosition(); + }, + resolveClick(click: ClickDetails): true | ClickCommand | null { + const result = regionView.resolveClick(click); + return result === true || (result === null && g.contains(click.element)) ? true : result; + }, + setIsDragging(isDragging: boolean) { + inputView.setIsHidden(isDragging); + }, + setIsSelected(isSelected: boolean) { + regionView.setIsSelected(isSelected); + }, + setIsDisabled(isDisabled: boolean) { + Dom.toggleClass(g, isDisabled, 'sqd-disabled'); + } + }; + }); }; diff --git a/designer/src/workspace/index.ts b/designer/src/workspace/index.ts index 0307fc7..d862ba9 100644 --- a/designer/src/workspace/index.ts +++ b/designer/src/workspace/index.ts @@ -10,3 +10,4 @@ export * from './component'; export * from './step-component'; export * from './step-extension-resolver'; export * from './placeholder'; +export * from './region'; diff --git a/designer/src/workspace/placeholder/rect-placeholder-extension.ts b/designer/src/workspace/placeholder/rect-placeholder-extension.ts index 68be55b..8c92a03 100644 --- a/designer/src/workspace/placeholder/rect-placeholder-extension.ts +++ b/designer/src/workspace/placeholder/rect-placeholder-extension.ts @@ -6,7 +6,7 @@ import { RectPlaceholder } from './rect-placeholder'; import { RectPlaceholderConfiguration } from './rect-placeholder-configuration'; const defaultConfiguration: RectPlaceholderConfiguration = { - gapWidth: 100, + gapWidth: 88, gapHeight: 24, radius: 6, iconSize: 16 diff --git a/designer/src/workspace/region/default-region-component-view-extension.ts b/designer/src/workspace/region/default-region-component-view-extension.ts new file mode 100644 index 0000000..ab0973c --- /dev/null +++ b/designer/src/workspace/region/default-region-component-view-extension.ts @@ -0,0 +1,23 @@ +import { + RegionComponentViewExtension, + RegionComponentViewContentFactory, + StepComponentViewContext, + StepContext +} from '../../designer-extension'; +import { ComponentDom } from '../common-views'; +import { StepComponentView } from '../component'; +import { DefaultRegionView } from './default-region-view'; + +export class DefaultRegionComponentViewExtension implements RegionComponentViewExtension { + public create( + parentElement: SVGElement, + componentClassName: string, + stepContext: StepContext, + _: StepComponentViewContext, + contentFactory: RegionComponentViewContentFactory + ): StepComponentView { + const g = ComponentDom.stepG(componentClassName, stepContext.step.type, stepContext.step.id); + parentElement.appendChild(g); + return contentFactory(g, DefaultRegionView.create); + } +} diff --git a/designer/src/workspace/common-views/region-view.spec.ts b/designer/src/workspace/region/default-region-view.spec.ts similarity index 52% rename from designer/src/workspace/common-views/region-view.spec.ts rename to designer/src/workspace/region/default-region-view.spec.ts index d21307b..0e29d4b 100644 --- a/designer/src/workspace/common-views/region-view.spec.ts +++ b/designer/src/workspace/region/default-region-view.spec.ts @@ -1,10 +1,10 @@ import { Dom } from '../../core/dom'; -import { RegionView } from './region-view'; +import { DefaultRegionView } from './default-region-view'; -describe('RegionView', () => { +describe('DefaultRegionView', () => { it('create() creates view', () => { const parent = Dom.svg('svg'); - RegionView.create(parent, [70, 80, 90], 100); + DefaultRegionView.create(parent, [70, 80, 90], 100); expect(parent.children.length).not.toEqual(0); }); }); diff --git a/designer/src/workspace/common-views/region-view.ts b/designer/src/workspace/region/default-region-view.ts similarity index 78% rename from designer/src/workspace/common-views/region-view.ts rename to designer/src/workspace/region/default-region-view.ts index c0d0e76..90eafef 100644 --- a/designer/src/workspace/common-views/region-view.ts +++ b/designer/src/workspace/region/default-region-view.ts @@ -1,12 +1,12 @@ import { Dom } from '../../core/dom'; import { getAbsolutePosition } from '../../core/get-absolute-position'; import { Vector } from '../../core/vector'; +import { RegionView } from '../../designer-extension'; import { ClickDetails } from '../component'; -export class RegionView { - public static create(parent: SVGElement, widths: number[], height: number): RegionView { +export class DefaultRegionView implements RegionView { + public static create(parent: SVGElement, widths: number[], height: number): DefaultRegionView { const totalWidth = widths.reduce((result, width) => result + width, 0); - const lines: SVGLineElement[] = [ drawLine(parent, 0, 0, totalWidth, 0), drawLine(parent, 0, 0, 0, height), @@ -19,8 +19,7 @@ export class RegionView { lines.push(drawLine(parent, offsetX, 0, offsetX, height)); offsetX += widths[i]; } - - return new RegionView(lines, totalWidth, height); + return new DefaultRegionView(lines, totalWidth, height); } public constructor( @@ -33,10 +32,13 @@ export class RegionView { return getAbsolutePosition(this.lines[0]); } - public resolveClick(click: ClickDetails): boolean { + public resolveClick(click: ClickDetails): true | null { const regionPosition = this.getClientPosition(); const d = click.position.subtract(regionPosition); - return d.x >= 0 && d.y >= 0 && d.x < this.width * click.scale && d.y < this.height * click.scale; + if (d.x >= 0 && d.y >= 0 && d.x < this.width * click.scale && d.y < this.height * click.scale) { + return true; + } + return null; } public setIsSelected(isSelected: boolean) { diff --git a/designer/src/workspace/region/index.ts b/designer/src/workspace/region/index.ts new file mode 100644 index 0000000..5b65093 --- /dev/null +++ b/designer/src/workspace/region/index.ts @@ -0,0 +1,2 @@ +export * from './default-region-view'; +export * from './default-region-component-view-extension'; diff --git a/designer/src/workspace/step-component-view-context-factory.ts b/designer/src/workspace/step-component-view-context-factory.ts index dcca526..0802460 100644 --- a/designer/src/workspace/step-component-view-context-factory.ts +++ b/designer/src/workspace/step-component-view-context-factory.ts @@ -1,10 +1,13 @@ import { Sequence, Step } from '../definition'; import { ComponentContext } from '../component-context'; -import { SequenceContext, StepComponentViewContext, StepContext } from '../designer-extension'; +import { SequenceContext, StepComponentViewContext, StepContext, RegionComponentViewContentFactory } from '../designer-extension'; export class StepComponentViewContextFactory { public static create(stepContext: StepContext, componentContext: ComponentContext): StepComponentViewContext { + const preferenceKeyPrefix = stepContext.step.id + ':'; + return { + i18n: componentContext.i18n, getStepIconUrl: () => componentContext.iconProvider.getIconUrl(stepContext.step), getStepName: () => componentContext.i18n(`step.${stepContext.step.type}.name`, stepContext.step.name), createSequenceComponent: (parentElement: SVGElement, sequence: Sequence) => { @@ -16,8 +19,22 @@ export class StepComponentViewContextFactory { }; return componentContext.services.sequenceComponent.create(parentElement, sequenceContext, componentContext); }, + createRegionComponentView( + parentElement: SVGElement, + componentClassName: string, + contentFactory: RegionComponentViewContentFactory + ) { + return componentContext.services.regionComponentView.create( + parentElement, + componentClassName, + stepContext, + this, + contentFactory + ); + }, createPlaceholderForArea: componentContext.services.placeholder.createForArea.bind(componentContext.services.placeholder), - i18n: componentContext.i18n + 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 88bfcc8..65b7f00 100644 --- a/designer/src/workspace/step-component.ts +++ b/designer/src/workspace/step-component.ts @@ -7,7 +7,7 @@ import { BadgesResult, ClickDetails, ClickCommand, Component, Placeholder, StepC export class StepComponent implements Component { public static create(view: StepComponentView, stepContext: StepContext, componentContext: ComponentContext) { const badges = Badges.createForStep(stepContext, view, componentContext); - return new StepComponent(view, stepContext.step, stepContext.parentSequence, view.hasOutput(), badges); + return new StepComponent(view, stepContext.step, stepContext.parentSequence, view.hasOutput, badges); } private isDisabled = false; 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 6fa888b..182710d 100644 --- a/designer/src/workspace/switch-step/switch-step-component-view.ts +++ b/designer/src/workspace/switch-step/switch-step-component-view.ts @@ -3,160 +3,159 @@ import { Vector } from '../../core/vector'; import { BranchedStep } from '../../definition'; import { JoinView } from '../common-views/join-view'; import { LabelView } from '../common-views/label-view'; -import { RegionView } from '../common-views//region-view'; import { InputView } from '../common-views/input-view'; -import { ClickDetails, StepComponentView } from '../component'; +import { ClickDetails, StepComponentView, ClickCommand } from '../component'; import { StepComponentViewContext, StepComponentViewFactory, StepContext } from '../../designer-extension'; import { SwitchStepComponentViewConfiguration } from './switch-step-component-view-configuration'; -import { ComponentDom } from '../common-views/component-dom'; + +const COMPONENT_CLASS_NAME = 'switch'; export const createSwitchStepComponentViewFactory = (cfg: SwitchStepComponentViewConfiguration): StepComponentViewFactory => (parent: SVGElement, stepContext: StepContext, viewContext: StepComponentViewContext): StepComponentView => { - const { step } = stepContext; - const g = ComponentDom.stepG('switch', step.type, step.id); - parent.appendChild(g); + return viewContext.createRegionComponentView(parent, COMPONENT_CLASS_NAME, (g, regionViewBuilder) => { + const step = stepContext.step; + + const branchNames = Object.keys(step.branches); + const branchComponents = branchNames.map(branchName => { + return viewContext.createSequenceComponent(g, step.branches[branchName]); + }); + + const branchLabelViews = branchNames.map(branchName => { + const labelY = cfg.paddingTop + cfg.nameLabel.height + cfg.connectionHeight; + const translatedBranchName = viewContext.i18n(`stepComponent.${step.type}.branchName`, branchName); + return LabelView.create(g, labelY, cfg.branchNameLabel, translatedBranchName, 'secondary'); + }); + + const name = viewContext.getStepName(); + const nameLabelView = LabelView.create(g, cfg.paddingTop, cfg.nameLabel, name, 'primary'); + + let prevOffsetX = 0; + const branchSizes = branchComponents.map((component, i) => { + const halfOfWidestBranchElement = Math.max(branchLabelViews[i].width, cfg.minContainerWidth) / 2; + + const branchOffsetLeft = Math.max(halfOfWidestBranchElement - component.view.joinX, 0) + cfg.paddingX; + const branchOffsetRight = + Math.max(halfOfWidestBranchElement - (component.view.width - component.view.joinX), 0) + cfg.paddingX; + + const width: number = component.view.width + branchOffsetLeft + branchOffsetRight; + const joinX = component.view.joinX + branchOffsetLeft; + + const offsetX = prevOffsetX; + prevOffsetX += width; + return { width, branchOffsetLeft, offsetX, joinX }; + }); + + const centerBranchIndex = Math.floor(branchNames.length / 2); + const centerBranchSize = branchSizes[centerBranchIndex]; + let joinX = centerBranchSize.offsetX; + if (branchNames.length % 2 !== 0) { + joinX += centerBranchSize.joinX; + } - const branchNames = Object.keys(step.branches); - const branchComponents = branchNames.map(branchName => { - return viewContext.createSequenceComponent(g, step.branches[branchName]); - }); + const totalBranchesWidth = branchSizes.reduce((result, s) => result + s.width, 0); + const maxBranchesHeight = Math.max(...branchComponents.map(s => s.view.height)); - const branchLabelViews = branchNames.map(branchName => { - const labelY = cfg.paddingTop + cfg.nameLabel.height + cfg.connectionHeight; - const translatedBranchName = viewContext.i18n(`stepComponent.${step.type}.branchName`, branchName); - return LabelView.create(g, labelY, cfg.branchNameLabel, translatedBranchName, 'secondary'); - }); + const halfOfWidestSwitchElement = nameLabelView.width / 2 + cfg.paddingX; + const switchOffsetLeft = Math.max(halfOfWidestSwitchElement - joinX, 0); + const switchOffsetRight = Math.max(halfOfWidestSwitchElement - (totalBranchesWidth - joinX), 0); - const name = viewContext.getStepName(); - const nameLabelView = LabelView.create(g, cfg.paddingTop, cfg.nameLabel, name, 'primary'); + const viewWidth = switchOffsetLeft + totalBranchesWidth + switchOffsetRight; + const viewHeight = + maxBranchesHeight + cfg.paddingTop + cfg.nameLabel.height + cfg.branchNameLabel.height + cfg.connectionHeight * 2; - let prevOffsetX = 0; - const branchSizes = branchComponents.map((component, i) => { - const halfOfWidestBranchElement = Math.max(branchLabelViews[i].width, cfg.minContainerWidth) / 2; + const shiftedJoinX = switchOffsetLeft + joinX; + Dom.translate(nameLabelView.g, shiftedJoinX, 0); - const branchOffsetLeft = Math.max(halfOfWidestBranchElement - component.view.joinX, 0) + cfg.paddingX; - const branchOffsetRight = Math.max(halfOfWidestBranchElement - (component.view.width - component.view.joinX), 0) + cfg.paddingX; + const branchOffsetTop = cfg.paddingTop + cfg.nameLabel.height + cfg.branchNameLabel.height + cfg.connectionHeight; - const width: number = component.view.width + branchOffsetLeft + branchOffsetRight; - const joinX = component.view.joinX + branchOffsetLeft; + branchComponents.forEach((component, i) => { + const branchSize = branchSizes[i]; + const branchOffsetLeft = switchOffsetLeft + branchSize.offsetX + branchSize.branchOffsetLeft; - const offsetX = prevOffsetX; - prevOffsetX += width; - return { width, branchOffsetLeft, offsetX, joinX }; - }); + Dom.translate(branchLabelViews[i].g, switchOffsetLeft + branchSize.offsetX + branchSize.joinX, 0); + Dom.translate(component.view.g, branchOffsetLeft, branchOffsetTop); - const centerBranchIndex = Math.floor(branchNames.length / 2); - const centerBranchSize = branchSizes[centerBranchIndex]; - let joinX = centerBranchSize.offsetX; - if (branchNames.length % 2 !== 0) { - joinX += centerBranchSize.joinX; - } - - const totalBranchesWidth = branchSizes.reduce((result, s) => result + s.width, 0); - const maxBranchesHeight = Math.max(...branchComponents.map(s => s.view.height)); - - const halfOfWidestSwitchElement = nameLabelView.width / 2 + cfg.paddingX; - const switchOffsetLeft = Math.max(halfOfWidestSwitchElement - joinX, 0); - const switchOffsetRight = Math.max(halfOfWidestSwitchElement - (totalBranchesWidth - joinX), 0); - - const viewWidth = switchOffsetLeft + totalBranchesWidth + switchOffsetRight; - const viewHeight = - maxBranchesHeight + cfg.paddingTop + cfg.nameLabel.height + cfg.branchNameLabel.height + cfg.connectionHeight * 2; - - const shiftedJoinX = switchOffsetLeft + joinX; - Dom.translate(nameLabelView.g, shiftedJoinX, 0); - - const branchOffsetTop = cfg.paddingTop + cfg.nameLabel.height + cfg.branchNameLabel.height + cfg.connectionHeight; - - branchComponents.forEach((component, i) => { - const branchSize = branchSizes[i]; - const branchOffsetLeft = switchOffsetLeft + branchSize.offsetX + branchSize.branchOffsetLeft; - - Dom.translate(branchLabelViews[i].g, switchOffsetLeft + branchSize.offsetX + branchSize.joinX, 0); - Dom.translate(component.view.g, branchOffsetLeft, branchOffsetTop); - - if (component.hasOutput && stepContext.isOutputConnected) { - const endOffsetTopOfComponent = - cfg.paddingTop + cfg.nameLabel.height + cfg.branchNameLabel.height + cfg.connectionHeight + component.view.height; - const missingHeight = viewHeight - endOffsetTopOfComponent - cfg.connectionHeight; - if (missingHeight > 0) { - JoinView.createStraightJoin( - g, - new Vector(switchOffsetLeft + branchSize.offsetX + branchSize.joinX, endOffsetTopOfComponent), - missingHeight - ); + if (component.hasOutput && stepContext.isOutputConnected) { + const endOffsetTopOfComponent = + cfg.paddingTop + cfg.nameLabel.height + cfg.branchNameLabel.height + cfg.connectionHeight + component.view.height; + const missingHeight = viewHeight - endOffsetTopOfComponent - cfg.connectionHeight; + if (missingHeight > 0) { + JoinView.createStraightJoin( + g, + new Vector(switchOffsetLeft + branchSize.offsetX + branchSize.joinX, endOffsetTopOfComponent), + missingHeight + ); + } } + }); + + let inputView: InputView | null = null; + if (cfg.inputSize > 0) { + const iconUrl = viewContext.getStepIconUrl(); + inputView = InputView.createRectInput(g, shiftedJoinX, 0, cfg.inputSize, cfg.inputIconSize, iconUrl); } - }); - let inputView: InputView | null = null; - if (cfg.inputSize > 0) { - const iconUrl = viewContext.getStepIconUrl(); - inputView = InputView.createRectInput(g, shiftedJoinX, 0, cfg.inputSize, cfg.inputIconSize, iconUrl); - } - - JoinView.createStraightJoin(g, new Vector(shiftedJoinX, 0), cfg.paddingTop); - - JoinView.createJoins( - g, - new Vector(shiftedJoinX, cfg.paddingTop + cfg.nameLabel.height), - branchSizes.map( - o => new Vector(switchOffsetLeft + o.offsetX + o.joinX, cfg.paddingTop + cfg.nameLabel.height + cfg.connectionHeight) - ) - ); - - if (stepContext.isOutputConnected) { - const ongoingSequenceIndexes = branchComponents - .map((component, index) => (component.hasOutput ? index : null)) - .filter(index => index !== null) as number[]; - const ongoingJoinTargets = ongoingSequenceIndexes.map( - (i: number) => - new Vector( - switchOffsetLeft + branchSizes[i].offsetX + branchSizes[i].joinX, - cfg.paddingTop + cfg.connectionHeight + cfg.nameLabel.height + cfg.branchNameLabel.height + maxBranchesHeight - ) + JoinView.createStraightJoin(g, new Vector(shiftedJoinX, 0), cfg.paddingTop); + + JoinView.createJoins( + g, + new Vector(shiftedJoinX, cfg.paddingTop + cfg.nameLabel.height), + branchSizes.map( + o => new Vector(switchOffsetLeft + o.offsetX + o.joinX, cfg.paddingTop + cfg.nameLabel.height + cfg.connectionHeight) + ) ); - if (ongoingJoinTargets.length > 0) { - JoinView.createJoins(g, new Vector(shiftedJoinX, viewHeight), ongoingJoinTargets); - } - } - - const regions = branchSizes.map(s => s.width); - regions[0] += switchOffsetLeft; - regions[regions.length - 1] += switchOffsetRight; - const regionView = RegionView.create(g, regions, viewHeight); - - return { - g, - width: viewWidth, - height: viewHeight, - joinX: shiftedJoinX, - placeholders: null, - sequenceComponents: branchComponents, - - getClientPosition(): Vector { - return regionView.getClientPosition(); - }, - - resolveClick(click: ClickDetails): true | null { - return regionView.resolveClick(click) || g.contains(click.element) ? true : null; - }, - - setIsDragging(isDragging: boolean) { - inputView?.setIsHidden(isDragging); - }, - - setIsSelected(isSelected: boolean) { - regionView.setIsSelected(isSelected); - }, - - setIsDisabled(isDisabled: boolean) { - Dom.toggleClass(g, isDisabled, 'sqd-disabled'); - }, - - hasOutput(): boolean { - return branchComponents.some(c => c.hasOutput); + + if (stepContext.isOutputConnected) { + const ongoingSequenceIndexes = branchComponents + .map((component, index) => (component.hasOutput ? index : null)) + .filter(index => index !== null) as number[]; + const ongoingJoinTargets = ongoingSequenceIndexes.map( + (i: number) => + new Vector( + switchOffsetLeft + branchSizes[i].offsetX + branchSizes[i].joinX, + cfg.paddingTop + cfg.connectionHeight + cfg.nameLabel.height + cfg.branchNameLabel.height + maxBranchesHeight + ) + ); + if (ongoingJoinTargets.length > 0) { + JoinView.createJoins(g, new Vector(shiftedJoinX, viewHeight), ongoingJoinTargets); + } } - }; + + const regions = branchSizes.map(s => s.width); + regions[0] += switchOffsetLeft; + regions[regions.length - 1] += switchOffsetRight; + const regionView = regionViewBuilder(g, regions, viewHeight); + + return { + g, + width: viewWidth, + height: viewHeight, + joinX: shiftedJoinX, + placeholders: null, + sequenceComponents: branchComponents, + hasOutput: branchComponents.some(c => c.hasOutput), + + getClientPosition(): Vector { + return regionView.getClientPosition(); + }, + + resolveClick(click: ClickDetails): true | ClickCommand | null { + const result = regionView.resolveClick(click); + return result === true || (result === null && g.contains(click.element)) ? true : result; + }, + + setIsDragging(isDragging: boolean) { + inputView?.setIsHidden(isDragging); + }, + + setIsSelected(isSelected: boolean) { + regionView.setIsSelected(isSelected); + }, + + setIsDisabled(isDisabled: boolean) { + Dom.toggleClass(g, isDisabled, 'sqd-disabled'); + } + }; + }); }; 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 69aa8af..44fc076 100644 --- a/designer/src/workspace/task-step/task-step-component-view.ts +++ b/designer/src/workspace/task-step/task-step-component-view.ts @@ -9,11 +9,13 @@ import { OutputView } from '../common-views/output-view'; import { ClickDetails, StepComponentView } from '../component'; import { TaskStepComponentViewConfiguration } from './task-step-component-view-configuration'; +const COMPONENT_CLASS_NAME = 'task'; + export const createTaskStepComponentViewFactory = (isInterrupted: boolean, cfg: TaskStepComponentViewConfiguration): StepComponentViewFactory => (parentElement: SVGElement, stepContext: StepContext, viewContext: StepComponentViewContext): StepComponentView => { const { step } = stepContext; - const g = ComponentDom.stepG('task', step.type, step.id); + const g = ComponentDom.stepG(COMPONENT_CLASS_NAME, step.type, step.id); parentElement.appendChild(g); const boxHeight = cfg.paddingY * 2 + cfg.iconSize; @@ -71,10 +73,8 @@ export const createTaskStepComponentViewFactory = joinX: boxWidth / 2, sequenceComponents: null, placeholders: null, + hasOutput: !!outputView, - hasOutput(): boolean { - return !!outputView; - }, getClientPosition(): Vector { return getAbsolutePosition(rect); }, diff --git a/examples/assets/lib.js b/examples/assets/lib.js index 1804209..218320a 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.20.0'; +const baseUrl = isTestEnv() ? '../designer' : '//cdn.jsdelivr.net/npm/sequential-workflow-designer@0.21.0'; embedScript(`${baseUrl}/dist/index.umd.js`); embedStylesheet(`${baseUrl}/css/designer.css`); diff --git a/react/package.json b/react/package.json index dd9b30e..a38d636 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.20.0", + "version": "0.21.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.20.0" + "sequential-workflow-designer": "^0.21.0" }, "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.1", @@ -63,7 +63,7 @@ "prettier": "^3.2.5", "react": "^18.2.0", "react-dom": "^18.2.0", - "sequential-workflow-designer": "^0.20.0", + "sequential-workflow-designer": "^0.21.0", "rollup": "^3.18.0", "rollup-plugin-dts": "^5.2.0", "rollup-plugin-typescript2": "^0.34.1", @@ -80,4 +80,4 @@ "react", "reactjs" ] -} +} \ No newline at end of file diff --git a/react/src/SequentialWorkflowDesigner.tsx b/react/src/SequentialWorkflowDesigner.tsx index b6f2ab8..7656ddb 100644 --- a/react/src/SequentialWorkflowDesigner.tsx +++ b/react/src/SequentialWorkflowDesigner.tsx @@ -17,7 +17,8 @@ import { RootEditorProvider, StepEditorProvider, KeyboardConfiguration, - I18n + I18n, + PreferenceStorage } from 'sequential-workflow-designer'; import { RootEditorWrapperContext } from './RootEditorWrapper'; import { StepEditorWrapperContext } from './StepEditorWrapper'; @@ -54,6 +55,7 @@ export interface SequentialWorkflowDesignerProps controlBar: boolean; contextMenu?: boolean; keyboard?: boolean | KeyboardConfiguration; + preferenceStorage?: PreferenceStorage; controller?: SequentialWorkflowDesignerController; customActionHandler?: CustomActionHandler; extensions?: DesignerExtension[]; @@ -88,6 +90,7 @@ export function SequentialWorkflowDesigner(props const controlBar = props.controlBar; const contextMenu = props.contextMenu; const keyboard = props.keyboard; + const preferenceStorage = props.preferenceStorage; const extensions = props.extensions; const i18n = props.i18n; @@ -226,6 +229,7 @@ export function SequentialWorkflowDesigner(props controlBar, contextMenu, keyboard, + preferenceStorage, editors: rootEditorRef.current && stepEditorRef.current ? { @@ -279,6 +283,7 @@ export function SequentialWorkflowDesigner(props isEditorCollapsed, contextMenu, keyboard, + preferenceStorage, controlBar, steps, validator, diff --git a/svelte/package.json b/svelte/package.json index c1d60c8..af8e84f 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.20.0", + "version": "0.21.0", "license": "MIT", "scripts": { "prepare": "cp ../LICENSE LICENSE", @@ -28,10 +28,10 @@ ], "peerDependencies": { "svelte": "^4.0.0", - "sequential-workflow-designer": "^0.20.0" + "sequential-workflow-designer": "^0.21.0" }, "devDependencies": { - "sequential-workflow-designer": "^0.20.0", + "sequential-workflow-designer": "^0.21.0", "@sveltejs/adapter-static": "^2.0.3", "@sveltejs/kit": "^1.20.4", "@sveltejs/package": "^2.0.0", @@ -59,4 +59,4 @@ "svelte", "sveltejs" ] -} +} \ No newline at end of file diff --git a/svelte/src/lib/SequentialWorkflowDesigner.svelte b/svelte/src/lib/SequentialWorkflowDesigner.svelte index da144bb..09cf5ae 100644 --- a/svelte/src/lib/SequentialWorkflowDesigner.svelte +++ b/svelte/src/lib/SequentialWorkflowDesigner.svelte @@ -18,7 +18,8 @@ type StepEditorProvider, type RootEditorProvider, type KeyboardConfiguration, - type I18n + type I18n, + type PreferenceStorage } from 'sequential-workflow-designer'; const dispatch = createEventDispatcher<{ @@ -45,6 +46,7 @@ export let theme = 'light'; export let contextMenu = true; export let keyboard: boolean | KeyboardConfiguration | undefined = undefined; + export let preferenceStorage: PreferenceStorage | undefined = undefined; export let undoStackSize: number | undefined = undefined; export let undoStack: UndoStack | undefined = undefined; export let validator: ValidatorConfiguration | undefined = undefined; @@ -129,6 +131,7 @@ theme, contextMenu, keyboard, + preferenceStorage, undoStackSize, undoStack, validator,