From 17b6f3ec42092ff871fe02bc64dbd0787a56cd34 Mon Sep 17 00:00:00 2001 From: Siarhei Huzarevich Date: Mon, 5 Aug 2024 17:28:16 +0200 Subject: [PATCH 1/2] update documentation engine --- .gitignore | 2 +- package.json | 4 +- .../connectable-side.component.scss | 1 - .../connection-behaviour.component.scss | 4 +- .../connection-markers.component.scss | 9 +- .../connection-type.component.scss | 6 +- .../custom-connection-type.component.html | 2 +- .../custom-connection-type.component.scss | 5 +- .../drag-to-connect.component.html} | 0 .../drag-to-connect.component.scss} | 4 +- .../drag-to-connect.component.ts} | 8 +- .../drag-to-reassign.component.html | 13 +++ .../drag-to-reassign.component.scss | 41 +++++++ .../drag-to-reassign.component.ts | 29 +++++ .../draggable-flow.component.scss | 1 - .../line-alignment-example.component.scss | 1 - .../simple-flow/simple-flow.component.scss | 1 - .../zoom-example/zoom-example.component.scss | 1 - .../get-element-rect-in-flow.execution.ts | 3 +- .../get-external-nodes-rect.execution.ts | 31 +++++ .../get-external-nodes-rect.request.ts | 3 + .../domain/get-external-nodes-rect/index.ts | 3 + .../get-nodes-rect.execution.ts | 5 +- projects/f-flow/src/domain/index.ts | 2 + .../rounded-rect/rounded-rect.ts | 29 ++++- projects/f-flow/src/domain/providers.ts | 3 + .../f-connection/common/f-connection-base.ts | 2 +- .../f-connection-drag-handle.component.ts | 13 ++- ...assign-connection-preparation.execution.ts | 5 +- .../external-item-preparation.execution.ts | 1 - .../src/f-draggable/f-draggable.directive.ts | 19 ++- .../f-flow/src/f-flow/f-flow.component.ts | 4 +- .../components/flow/flow.component.html | 52 +++++++++ .../components/flow/flow.component.scss | 72 ++++++++++++ .../components/flow/flow.component.ts | 98 ++++++++++++++++ .../components/node/node.component.html | 17 +++ .../components/node/node.component.scss | 79 +++++++++++++ .../components/node/node.component.ts | 19 +++ .../components/palette/palette.component.html | 6 + .../components/palette/palette.component.scss | 46 ++++++++ .../components/palette/palette.component.ts | 27 +++++ .../components/toolbar/toolbar.component.html | 12 ++ .../components/toolbar/toolbar.component.scss | 34 ++++++ .../components/toolbar/toolbar.component.ts | 36 ++++++ .../domain/configuration.ts | 32 +++++ .../add-new/create-connection.handler.ts | 36 ++++++ .../add-new/create-connection.request.ts | 8 ++ .../i-flow-connection-storage-model.ts | 6 + .../i-flow-connection-view-model.ts | 10 ++ .../map-to-connection-view-model.handler.ts | 55 +++++++++ .../reassign/reassign-connection.handler.ts | 36 ++++++ .../reassign/reassign-connection.request.ts | 9 ++ .../visual-programming/domain/e-node-type.ts | 16 +++ .../visual-programming/domain/flow.service.ts | 52 +++++++++ .../visual-programming/domain/flow.storage.ts | 109 ++++++++++++++++++ .../domain/i-flow-view-model.ts | 9 ++ .../add-new/add-new-node-to-flow.handler.ts | 21 ++++ .../add-new/add-new-node-to-flow.request.ts | 11 ++ .../domain/node/i-flow-node-storage-model.ts | 15 +++ .../domain/node/i-flow-node-view-model.ts | 6 + .../map/map-to-node-view-model.handler.ts | 21 ++++ .../node/move-nodes/move-node.handler.ts | 19 +++ .../node/move-nodes/move-node.request.ts | 10 ++ public/docs/en/environment.ts | 23 +++- public/docs/en/f-connection-component.md | 8 ++ .../en/f-connection-for-create-component.md | 8 +- public/docs/en/f-visual-programming-flow.md | 24 ++++ .../buttons-row/buttons-row.component.html | 2 +- tsconfig.json | 2 - 69 files changed, 1246 insertions(+), 55 deletions(-) rename projects/f-examples/{create-connection/create-connection.component.html => drag-to-connect/drag-to-connect.component.html} (100%) rename projects/f-examples/{create-connection/create-connection.component.scss => drag-to-connect/drag-to-connect.component.scss} (90%) rename projects/f-examples/{create-connection/create-connection.component.ts => drag-to-connect/drag-to-connect.component.ts} (78%) create mode 100644 projects/f-examples/drag-to-reassign/drag-to-reassign.component.html create mode 100644 projects/f-examples/drag-to-reassign/drag-to-reassign.component.scss create mode 100644 projects/f-examples/drag-to-reassign/drag-to-reassign.component.ts create mode 100644 projects/f-flow/src/domain/get-external-nodes-rect/get-external-nodes-rect.execution.ts create mode 100644 projects/f-flow/src/domain/get-external-nodes-rect/get-external-nodes-rect.request.ts create mode 100644 projects/f-flow/src/domain/get-external-nodes-rect/index.ts create mode 100644 projects/f-pro-examples/visual-programming/components/flow/flow.component.html create mode 100644 projects/f-pro-examples/visual-programming/components/flow/flow.component.scss create mode 100644 projects/f-pro-examples/visual-programming/components/flow/flow.component.ts create mode 100644 projects/f-pro-examples/visual-programming/components/node/node.component.html create mode 100644 projects/f-pro-examples/visual-programming/components/node/node.component.scss create mode 100644 projects/f-pro-examples/visual-programming/components/node/node.component.ts create mode 100644 projects/f-pro-examples/visual-programming/components/palette/palette.component.html create mode 100644 projects/f-pro-examples/visual-programming/components/palette/palette.component.scss create mode 100644 projects/f-pro-examples/visual-programming/components/palette/palette.component.ts create mode 100644 projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.html create mode 100644 projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.scss create mode 100644 projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/configuration.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/connection/add-new/create-connection.handler.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/connection/add-new/create-connection.request.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/connection/i-flow-connection-storage-model.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/connection/i-flow-connection-view-model.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/connection/map/map-to-connection-view-model.handler.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/connection/reassign/reassign-connection.handler.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/connection/reassign/reassign-connection.request.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/e-node-type.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/flow.service.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/flow.storage.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/i-flow-view-model.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/node/add-new/add-new-node-to-flow.handler.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/node/add-new/add-new-node-to-flow.request.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/node/i-flow-node-storage-model.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/node/i-flow-node-view-model.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/node/map/map-to-node-view-model.handler.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/node/move-nodes/move-node.handler.ts create mode 100644 projects/f-pro-examples/visual-programming/domain/node/move-nodes/move-node.request.ts create mode 100644 public/docs/en/f-visual-programming-flow.md diff --git a/.gitignore b/.gitignore index 188a772..229ba76 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,5 @@ node_modules *-debug.log .runtimeconfig.json -package.lock.json +package-lock.json diff --git a/package.json b/package.json index 04b712d..59ef7ab 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "f-flow-portal", + "name": "flow-documentation-portal", "version": "0.0.0", "scripts": { "ng": "ng", @@ -18,7 +18,7 @@ "@angular/platform-browser": "^18.1.0", "@angular/platform-browser-dynamic": "^18.1.0", "@angular/router": "^18.1.0", - "@foblex/f-docs": "^1.1.9", + "@foblex/f-docs": "^1.2.0", "@foblex/core": "^1.1.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", diff --git a/projects/f-examples/connectable-side/connectable-side.component.scss b/projects/f-examples/connectable-side/connectable-side.component.scss index e8aeb90..552152c 100644 --- a/projects/f-examples/connectable-side/connectable-side.component.scss +++ b/projects/f-examples/connectable-side/connectable-side.component.scss @@ -19,7 +19,6 @@ .f-node { width: 100px; - height: 100px; padding: 12px; color: #585858; cursor: move; diff --git a/projects/f-examples/connection-behaviour/connection-behaviour.component.scss b/projects/f-examples/connection-behaviour/connection-behaviour.component.scss index 2180de2..05c6ec8 100644 --- a/projects/f-examples/connection-behaviour/connection-behaviour.component.scss +++ b/projects/f-examples/connection-behaviour/connection-behaviour.component.scss @@ -17,8 +17,7 @@ } .f-connection-text { - fill: #BBBBBB; - font-size: 12px; + fill: #585858; } } } @@ -26,7 +25,6 @@ .f-node { opacity: 0.5; width: 100px; - height: 100px; padding: 12px; color: #585858; cursor: move; diff --git a/projects/f-examples/connection-markers/connection-markers.component.scss b/projects/f-examples/connection-markers/connection-markers.component.scss index 165a17c..bb89742 100644 --- a/projects/f-examples/connection-markers/connection-markers.component.scss +++ b/projects/f-examples/connection-markers/connection-markers.component.scss @@ -22,8 +22,7 @@ } .f-node { - min-width: 100px; - min-height: 100px; + width: 100px; padding: 12px; color: #585858; cursor: move; @@ -32,11 +31,11 @@ align-items: center; text-align: center; background: #FCFDFE; - border-radius: 8px; - box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2); + border-radius: 4px; + box-shadow: 0 0 1px 1px #E4E3E6; &:active { - box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 2px 2px #E4E3E6; } } diff --git a/projects/f-examples/connection-type/connection-type.component.scss b/projects/f-examples/connection-type/connection-type.component.scss index e8aeb90..8c3bd54 100644 --- a/projects/f-examples/connection-type/connection-type.component.scss +++ b/projects/f-examples/connection-type/connection-type.component.scss @@ -9,6 +9,11 @@ fill: none; } + .f-connection-text, .f-connection-center { + fill: #585858; + color: #585858; + } + .f-connection-path { fill: none; stroke: #585858; @@ -19,7 +24,6 @@ .f-node { width: 100px; - height: 100px; padding: 12px; color: #585858; cursor: move; diff --git a/projects/f-examples/custom-connection-type/custom-connection-type.component.html b/projects/f-examples/custom-connection-type/custom-connection-type.component.html index 830dadd..d0e77aa 100644 --- a/projects/f-examples/custom-connection-type/custom-connection-type.component.html +++ b/projects/f-examples/custom-connection-type/custom-connection-type.component.html @@ -1,6 +1,6 @@ - +
drag me
drag me
diff --git a/projects/f-examples/custom-connection-type/custom-connection-type.component.scss b/projects/f-examples/custom-connection-type/custom-connection-type.component.scss index f5bd05c..e23d6c3 100644 --- a/projects/f-examples/custom-connection-type/custom-connection-type.component.scss +++ b/projects/f-examples/custom-connection-type/custom-connection-type.component.scss @@ -10,6 +10,10 @@ fill: none; } + .f-connection-text { + fill: #585858; + } + .f-connection-path { fill: none; stroke: #585858; @@ -20,7 +24,6 @@ .f-node { width: 100px; - height: 100px; padding: 12px; color: #585858; cursor: move; diff --git a/projects/f-examples/create-connection/create-connection.component.html b/projects/f-examples/drag-to-connect/drag-to-connect.component.html similarity index 100% rename from projects/f-examples/create-connection/create-connection.component.html rename to projects/f-examples/drag-to-connect/drag-to-connect.component.html diff --git a/projects/f-examples/create-connection/create-connection.component.scss b/projects/f-examples/drag-to-connect/drag-to-connect.component.scss similarity index 90% rename from projects/f-examples/create-connection/create-connection.component.scss rename to projects/f-examples/drag-to-connect/drag-to-connect.component.scss index 8c638cc..51dd361 100644 --- a/projects/f-examples/create-connection/create-connection.component.scss +++ b/projects/f-examples/drag-to-connect/drag-to-connect.component.scss @@ -16,15 +16,13 @@ } .f-connection-text { - fill: #BBBBBB; - font-size: 12px; + fill: #585858; } } } .f-node { width: 100px; - height: 100px; padding: 12px; color: #585858; cursor: move; diff --git a/projects/f-examples/create-connection/create-connection.component.ts b/projects/f-examples/drag-to-connect/drag-to-connect.component.ts similarity index 78% rename from projects/f-examples/create-connection/create-connection.component.ts rename to projects/f-examples/drag-to-connect/drag-to-connect.component.ts index 8c3dac7..1ae3bf4 100644 --- a/projects/f-examples/create-connection/create-connection.component.ts +++ b/projects/f-examples/drag-to-connect/drag-to-connect.component.ts @@ -2,16 +2,16 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/ import { FCreateConnectionEvent, FFlowModule } from '@foblex/flow'; @Component({ - selector: 'create-connection', - styleUrls: [ './create-connection.component.scss' ], - templateUrl: './create-connection.component.html', + selector: 'drag-to-connect', + styleUrls: [ './drag-to-connect.component.scss' ], + templateUrl: './drag-to-connect.component.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ FFlowModule ] }) -export class CreateConnectionComponent { +export class DragToConnectComponent { public connections: { outputId: string, inputId: string }[] = []; diff --git a/projects/f-examples/drag-to-reassign/drag-to-reassign.component.html b/projects/f-examples/drag-to-reassign/drag-to-reassign.component.html new file mode 100644 index 0000000..4c7123e --- /dev/null +++ b/projects/f-examples/drag-to-reassign/drag-to-reassign.component.html @@ -0,0 +1,13 @@ + + + @for (connection of connections;track connection.inputId) { + + + } + +
Output
+
Input
+
Input
+
+
diff --git a/projects/f-examples/drag-to-reassign/drag-to-reassign.component.scss b/projects/f-examples/drag-to-reassign/drag-to-reassign.component.scss new file mode 100644 index 0000000..8ecae33 --- /dev/null +++ b/projects/f-examples/drag-to-reassign/drag-to-reassign.component.scss @@ -0,0 +1,41 @@ +:host ::ng-deep { + + .f-connection { + .f-connection-drag-handle { + fill: #585858; + } + + .f-connection-selection { + fill: none; + } + + .f-connection-path { + fill: none; + stroke: #585858; + stroke-width: 2; + } + + .f-connection-text { + fill: #585858; + } + } +} + +.f-node { + width: 100px; + padding: 12px; + color: #585858; + cursor: move; + display: inline-flex; + justify-content: center; + align-items: center; + text-align: center; + background: #FCFDFE; + border-radius: 4px; + box-shadow: 0 0 1px 1px #E4E3E6; + + &:active { + box-shadow: 0 0 2px 2px #E4E3E6; + } +} + diff --git a/projects/f-examples/drag-to-reassign/drag-to-reassign.component.ts b/projects/f-examples/drag-to-reassign/drag-to-reassign.component.ts new file mode 100644 index 0000000..4b96ca7 --- /dev/null +++ b/projects/f-examples/drag-to-reassign/drag-to-reassign.component.ts @@ -0,0 +1,29 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; +import { FFlowModule, FReassignConnectionEvent } from '@foblex/flow'; + +@Component({ + selector: 'drag-to-reassign', + styleUrls: [ './drag-to-reassign.component.scss' ], + templateUrl: './drag-to-reassign.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + FFlowModule + ] +}) +export class DragToReassignComponent { + + public connections: { outputId: string, inputId: string }[] = [ + { outputId: '1', inputId: '2' }, + ]; + + constructor( + private changeDetectorRef: ChangeDetectorRef + ) { + } + + public reassignConnection(event: FReassignConnectionEvent): void { + this.connections = [ { outputId: event.fOutputId, inputId: event.newFInputId } ]; + this.changeDetectorRef.detectChanges(); + } +} diff --git a/projects/f-examples/draggable-flow/draggable-flow.component.scss b/projects/f-examples/draggable-flow/draggable-flow.component.scss index e8aeb90..552152c 100644 --- a/projects/f-examples/draggable-flow/draggable-flow.component.scss +++ b/projects/f-examples/draggable-flow/draggable-flow.component.scss @@ -19,7 +19,6 @@ .f-node { width: 100px; - height: 100px; padding: 12px; color: #585858; cursor: move; diff --git a/projects/f-examples/line-alignment-example/line-alignment-example.component.scss b/projects/f-examples/line-alignment-example/line-alignment-example.component.scss index 4f39c31..1cb0124 100644 --- a/projects/f-examples/line-alignment-example/line-alignment-example.component.scss +++ b/projects/f-examples/line-alignment-example/line-alignment-example.component.scss @@ -25,7 +25,6 @@ .f-node { width: 100px; - height: 100px; padding: 12px; color: #585858; cursor: move; diff --git a/projects/f-examples/simple-flow/simple-flow.component.scss b/projects/f-examples/simple-flow/simple-flow.component.scss index aeb8b84..ca081e6 100644 --- a/projects/f-examples/simple-flow/simple-flow.component.scss +++ b/projects/f-examples/simple-flow/simple-flow.component.scss @@ -19,7 +19,6 @@ .f-node { width: 100px; - height: 100px; padding: 12px; color: #585858; display: inline-flex; diff --git a/projects/f-examples/zoom-example/zoom-example.component.scss b/projects/f-examples/zoom-example/zoom-example.component.scss index aeb8b84..ca081e6 100644 --- a/projects/f-examples/zoom-example/zoom-example.component.scss +++ b/projects/f-examples/zoom-example/zoom-example.component.scss @@ -19,7 +19,6 @@ .f-node { width: 100px; - height: 100px; padding: 12px; color: #585858; display: inline-flex; diff --git a/projects/f-flow/src/domain/get-element-rect-in-flow/get-element-rect-in-flow.execution.ts b/projects/f-flow/src/domain/get-element-rect-in-flow/get-element-rect-in-flow.execution.ts index 18ac778..8a4b7ef 100644 --- a/projects/f-flow/src/domain/get-element-rect-in-flow/get-element-rect-in-flow.execution.ts +++ b/projects/f-flow/src/domain/get-element-rect-in-flow/get-element-rect-in-flow.execution.ts @@ -4,7 +4,7 @@ import { Point, SizeExtensions } from '@foblex/core'; -import { Injectable } from '@angular/core'; +import { Injectable, Renderer2 } from '@angular/core'; import { GetElementRectInFlowRequest } from './get-element-rect-in-flow-request'; import { FExecutionRegister, IExecution } from '../../infrastructure'; import { FComponentsStore } from '../../f-storage'; @@ -23,6 +23,7 @@ export class GetElementRectInFlowExecution implements IExecution { + + constructor( + private fComponentsStore: FComponentsStore, + ) { + } + + public handle(request: GetExternalNodesRectRequest): IRect { + return RectExtensions.union(this.getNodesRects()); + } + + private getNodesRects(): IRect[] { + return this.getNodes().map((x) => { + const rect = RectExtensions.fromElement(x.hostElement); + return RectExtensions.initialize(x.position.x, x.position.y, rect.width, rect.height); + }) + } + + private getNodes(): FNodeBase[] { + return this.fComponentsStore.fNodes; + } +} diff --git a/projects/f-flow/src/domain/get-external-nodes-rect/get-external-nodes-rect.request.ts b/projects/f-flow/src/domain/get-external-nodes-rect/get-external-nodes-rect.request.ts new file mode 100644 index 0000000..710f14f --- /dev/null +++ b/projects/f-flow/src/domain/get-external-nodes-rect/get-external-nodes-rect.request.ts @@ -0,0 +1,3 @@ +export class GetExternalNodesRectRequest { + +} diff --git a/projects/f-flow/src/domain/get-external-nodes-rect/index.ts b/projects/f-flow/src/domain/get-external-nodes-rect/index.ts new file mode 100644 index 0000000..9f4a67b --- /dev/null +++ b/projects/f-flow/src/domain/get-external-nodes-rect/index.ts @@ -0,0 +1,3 @@ +export * from './get-external-nodes-rect.execution'; + +export * from './get-external-nodes-rect.request'; diff --git a/projects/f-flow/src/domain/get-nodes-rect/get-nodes-rect.execution.ts b/projects/f-flow/src/domain/get-nodes-rect/get-nodes-rect.execution.ts index 66fb99b..86c711c 100644 --- a/projects/f-flow/src/domain/get-nodes-rect/get-nodes-rect.execution.ts +++ b/projects/f-flow/src/domain/get-nodes-rect/get-nodes-rect.execution.ts @@ -19,10 +19,7 @@ export class GetNodesRectExecution implements IExecution { - const rect = RectExtensions.fromElement(x.hostElement); - return RectExtensions.initialize(x.position.x, x.position.y, rect.width, rect.height); - }) + return this.getNodes().map((x) => RectExtensions.fromElement(x.hostElement)); } private getNodes(): FNodeBase[] { diff --git a/projects/f-flow/src/domain/index.ts b/projects/f-flow/src/domain/index.ts index 02e15e7..5869093 100644 --- a/projects/f-flow/src/domain/index.ts +++ b/projects/f-flow/src/domain/index.ts @@ -8,6 +8,8 @@ export * from './get-connection-line'; export * from './get-element-rect-in-flow'; +export * from './get-external-nodes-rect'; + export * from './get-input-rect-in-flow'; export * from './get-nodes-rect'; diff --git a/projects/f-flow/src/domain/intersections/rounded-rect/rounded-rect.ts b/projects/f-flow/src/domain/intersections/rounded-rect/rounded-rect.ts index 881d96b..4054f09 100644 --- a/projects/f-flow/src/domain/intersections/rounded-rect/rounded-rect.ts +++ b/projects/f-flow/src/domain/intersections/rounded-rect/rounded-rect.ts @@ -39,13 +39,34 @@ export class RoundedRect implements IRoundedRect { } private static setRadiusFromElement(rect: RoundedRect, element: HTMLElement | SVGElement): RoundedRect { - rect.radius1 = parseFloat(element.style.borderTopLeftRadius) || 0; - rect.radius2 = parseFloat(element.style.borderTopRightRadius) || 0; - rect.radius3 = parseFloat(element.style.borderBottomRightRadius) || 0; - rect.radius4 = parseFloat(element.style.borderBottomLeftRadius) || 0; + const data = getComputedStyle(element); + rect.radius1 = this.convertToPixels(data.borderTopLeftRadius, element.clientWidth, element.clientHeight, data.fontSize) || 0; + rect.radius2 = this.convertToPixels(data.borderTopRightRadius, element.clientWidth, element.clientHeight, data.fontSize) || 0; + rect.radius3 = this.convertToPixels(data.borderBottomRightRadius, element.clientWidth, element.clientHeight, data.fontSize) || 0; + rect.radius4 = this.convertToPixels(data.borderBottomLeftRadius, element.clientWidth, element.clientHeight, data.fontSize) || 0; return rect; } + private static convertToPixels(value: string, clientWidth: number, clientHeight: number, fontSize: string): number { + if (value.endsWith('px')) { + return parseFloat(value); + } else if (value.endsWith('%')) { + const percentage = parseFloat(value) / 100; + return Math.max(clientWidth, clientHeight) * percentage; + } else if (value.endsWith('em')) { + return parseFloat(value) * parseFloat(fontSize); + } else if (value.endsWith('rem')) { + return parseFloat(value) * parseFloat(getComputedStyle(document.documentElement).fontSize); + } else if (value.endsWith('vh')) { + const vh = window.innerHeight / 100; + return parseFloat(value) * vh; + } else if (value.endsWith('vw')) { + const vw = window.innerWidth / 100; + return parseFloat(value) * vw; + } + return parseFloat(value) || 0; + }; + public addPoint(point: IPoint): RoundedRect { const copy = RoundedRect.fromRoundedRect(this); copy.x += point.x; diff --git a/projects/f-flow/src/domain/providers.ts b/projects/f-flow/src/domain/providers.ts index d07172c..add5b27 100644 --- a/projects/f-flow/src/domain/providers.ts +++ b/projects/f-flow/src/domain/providers.ts @@ -17,6 +17,7 @@ import { CreateConnectionMarkersExecution } from './create-connection-markers'; import { GetCanBeSelectedItemsExecution } from './get-can-be-selected-items'; import { IsConnectionUnderNodeExecution } from './is-connection-under-node'; import { SelectAndUpdateNodeLayerExecution } from './select-and-update-node-layer'; +import { GetExternalNodesRectExecution } from './get-external-nodes-rect'; export const COMMON_PROVIDERS = [ @@ -30,6 +31,8 @@ export const COMMON_PROVIDERS = [ GetElementRectInFlowExecution, + GetExternalNodesRectExecution, + GetNodesRectExecution, GetOutputRectInFlowExecution, diff --git a/projects/f-flow/src/f-connection/common/f-connection-base.ts b/projects/f-flow/src/f-connection/common/f-connection-base.ts index 89f9e2b..76973e3 100644 --- a/projects/f-flow/src/f-connection/common/f-connection-base.ts +++ b/projects/f-flow/src/f-connection/common/f-connection-base.ts @@ -132,7 +132,7 @@ export abstract class FConnectionBase extends MIXIN_BASE this.fPath.setPath(this.path); this.fSelection.setPath(this.path); this.fGradient.redraw(this.line); - this.fDragHandle.redraw(this.line.point2); + this.fDragHandle.redraw(this.line.point1, this.line.point2); this.fTextComponent.redraw(this.line); } } diff --git a/projects/f-flow/src/f-connection/common/f-drag-handle/f-connection-drag-handle.component.ts b/projects/f-flow/src/f-connection/common/f-drag-handle/f-connection-drag-handle.component.ts index e48af2c..b4c0748 100644 --- a/projects/f-flow/src/f-connection/common/f-drag-handle/f-connection-drag-handle.component.ts +++ b/projects/f-flow/src/f-connection/common/f-drag-handle/f-connection-drag-handle.component.ts @@ -23,13 +23,22 @@ export class FConnectionDragHandleComponent implements IHasHostElement { } constructor( - private elementReference: ElementRef + private elementReference: ElementRef ) { } - public redraw(point: IPoint): void { + public redraw(start: IPoint, end: IPoint): void { + const point = this.calculateCircleCenter(start, end, 8); this.hostElement.setAttribute('cx', point.x.toString()); this.hostElement.setAttribute('cy', point.y.toString()); } + + private calculateCircleCenter(start: IPoint, end: IPoint, radius: number): IPoint { + const direction = { x: end.x - start.x, y: end.y - start.y }; + const length = Math.sqrt(direction.x * direction.x + direction.y * direction.y); + const unitDirection = { x: direction.x / length, y: direction.y / length }; + const scaledDirection = { x: unitDirection.x * radius, y: unitDirection.y * radius }; + return { x: end.x - scaledDirection.x, y: end.y - scaledDirection.y }; + } } diff --git a/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection-preparation/reassign-connection-preparation.execution.ts b/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection-preparation/reassign-connection-preparation.execution.ts index 3015d6a..7185046 100644 --- a/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection-preparation/reassign-connection-preparation.execution.ts +++ b/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection-preparation/reassign-connection-preparation.execution.ts @@ -5,7 +5,7 @@ import { FComponentsStore } from '../../../../f-storage'; import { FDraggableDataContext } from '../../../f-draggable-data-context'; import { UpdateItemLayerRequest } from '../../../../domain'; import { FExecutionRegister, FFlowMediator, IExecution } from '../../../../infrastructure'; -import { F_CONNECTION_DRAG_HANDLE_CLASS, FConnectionBase } from '../../../../f-connection'; +import { F_CONNECTION_DRAG_HANDLE_CLASS, FConnectionBase, FConnectionComponent } from '../../../../f-connection'; import { ReassignConnectionDragHandler } from '../reassign-connection.drag-handler'; @Injectable() @@ -35,6 +35,9 @@ export class ReassignConnectionPreparationExecution implements IExecution( new UpdateItemLayerRequest( diff --git a/projects/f-flow/src/f-draggable/external-item/external-item-preparation/external-item-preparation.execution.ts b/projects/f-flow/src/f-draggable/external-item/external-item-preparation/external-item-preparation.execution.ts index d758cb5..edee548 100644 --- a/projects/f-flow/src/f-draggable/external-item/external-item-preparation/external-item-preparation.execution.ts +++ b/projects/f-flow/src/f-draggable/external-item/external-item-preparation/external-item-preparation.execution.ts @@ -23,7 +23,6 @@ export class ExternalItemPreparationExecution implements IExecution(new GetNodesRectRequest()); + return this.fMediator.send(new GetExternalNodesRectRequest()); } public getSelection(): FSelectionChangeEvent { diff --git a/projects/f-pro-examples/visual-programming/components/flow/flow.component.html b/projects/f-pro-examples/visual-programming/components/flow/flow.component.html new file mode 100644 index 0000000..c6db502 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/flow/flow.component.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + @for (connection of viewModel.connections;track connection) { + + + + + + + + + + + + + + + } + @for (node of viewModel.nodes;track node.id) { + + + } + + + + diff --git a/projects/f-pro-examples/visual-programming/components/flow/flow.component.scss b/projects/f-pro-examples/visual-programming/components/flow/flow.component.scss new file mode 100644 index 0000000..ca73f26 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/flow/flow.component.scss @@ -0,0 +1,72 @@ +:host { + display: flex; + width: 100%; + height: 100%; +} + + +::ng-deep .visual-programming-flow { + + .f-line-alignment { + .f-line { + background-color: var(--divider-color); + } + } + + .f-connection { + + .f-connection-drag-handle { + fill: transparent; + } + + .f-connection-selection { + stroke-width: 20px; + fill: none; + } + + &:hover { + .f-connection-selection { + stroke: var(--gray-soft); + } + } + + .f-connection-path { + stroke-width: 2px; + fill: none; + } + + &.f-connection-for-create { + .f-connection-path { + stroke: var(--primary-3); + stroke-width: 2px; + } + } + + .f-connection-text { + fill: var(--tertiary-text); + font-size: 12px; + } + + &.f-selected { + .f-connection-path { + stroke: var(--primary-3); + } + + .f-connection-text { + fill: var(--primary-3); + } + } + } + + .f-connection-for-create { + .f-connection-path { + stroke: #28374e; + stroke-width: 1px; + fill: none; + } + } + + .f-selection-area { + background-color: var(--primary-soft); + } +} diff --git a/projects/f-pro-examples/visual-programming/components/flow/flow.component.ts b/projects/f-pro-examples/visual-programming/components/flow/flow.component.ts new file mode 100644 index 0000000..e61947c --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/flow/flow.component.ts @@ -0,0 +1,98 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, OnInit, + ViewChild, +} from '@angular/core'; +import { + FCreateNodeEvent, EFMarkerType, + FCanvasComponent, FFlowModule, FZoomDirective, + FReassignConnectionEvent, FCreateConnectionEvent +} from '@foblex/flow'; +import { IPoint, Point } from '@foblex/core'; +import { IFlowNodeViewModel } from '../../domain/node/i-flow-node-view-model'; +import { IFlowConnectionViewModel } from '../../domain/connection/i-flow-connection-view-model'; +import { ENodeType } from '../../domain/e-node-type'; +import { ToolbarComponent } from '../toolbar/toolbar.component'; +import { PaletteComponent } from '../palette/palette.component'; +import { NodeComponent } from '../node/node.component'; +import { MapToNodeViewModelHandler } from '../../domain/node/map/map-to-node-view-model.handler'; +import { + MapToConnectionViewModelHandler +} from '../../domain/connection/map/map-to-connection-view-model.handler'; +import { FlowService } from '../../domain/flow.service'; +import { IFlowViewModel } from '../../domain/i-flow-view-model'; + +@Component({ + selector: 'visual-programming-flow', + templateUrl: './flow.component.html', + styleUrls: [ './flow.component.scss' ], + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + FlowService + ], + imports: [ + FFlowModule, + ToolbarComponent, + PaletteComponent, + NodeComponent + ] +}) +export class FlowComponent implements OnInit { + + protected viewModel: IFlowViewModel = { + nodes: [], + connections: [] + }; + + @ViewChild(FCanvasComponent, { static: true }) + public fCanvasComponent!: FCanvasComponent; + + @ViewChild(FZoomDirective, { static: true }) + public fZoomDirective!: FZoomDirective; + + protected readonly eMarkerType = EFMarkerType; + + constructor( + private apiService: FlowService, + private changeDetectorRef: ChangeDetectorRef + ) { + } + + public ngOnInit(): void { + this.getData(); + } + + public onInitialized(): void { + this.fCanvasComponent.fitToScreen(new Point(40, 40), false); + } + + private getData(): void { + this.viewModel = this.apiService.getFlow(); + this.changeDetectorRef.markForCheck(); + } + + public onNodeAdded(event: FCreateNodeEvent): void { + this.apiService.addNode(event.data as ENodeType, event.rect); + this.getData(); + } + + public onReassignConnection(event: FReassignConnectionEvent): void { + this.apiService.reassignConnection(event.fOutputId, event.oldFInputId, event.newFInputId); + this.getData(); + } + + public onConnectionAdded(event: FCreateConnectionEvent): void { + if (!event.fInputId) { + return; + } + this.apiService.addConnection(event.fOutputId, event.fInputId); + this.getData(); + } + + public onNodePositionChanged(point: IPoint, node: any): void { + node.position = point; + this.apiService.moveNode(node.id, point); + } +} diff --git a/projects/f-pro-examples/visual-programming/components/node/node.component.html b/projects/f-pro-examples/visual-programming/components/node/node.component.html new file mode 100644 index 0000000..4f773fb --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/node/node.component.html @@ -0,0 +1,17 @@ +@if (node) { +
+
+ @if (node.output) { +
+
+ } + + @if (node.input) { +
+ } +
+ {{ node.type[0] }} +
+
+} + diff --git a/projects/f-pro-examples/visual-programming/components/node/node.component.scss b/projects/f-pro-examples/visual-programming/components/node/node.component.scss new file mode 100644 index 0000000..9effd80 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/node/node.component.scss @@ -0,0 +1,79 @@ +:host { + display: block; + width: 34px; + height: 34px; + + .interactive-node-body { + display: flex; + width: 100%; + height: 100%; + border-radius: 50%; + position: relative; + background-color: var(--background-color); + border: 2px solid; + + .icon { + margin: auto; + line-height: 15px; + text-transform: uppercase; + font-size: 14px; + font-weight: 600; + color: inherit; + } + } + + .selected-background { + position: absolute; + width: 50px; + height: 50px; + left: -8px; + top: -8px; + display: none; + background-color: var(--gray-soft); + border-radius: 4px; + } + + .f-node-output, .f-node-input { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + border-radius: 17px; + } + + .f-node-outlet { + position: absolute; + right: -20px; + top: 50%; + transform: translateY(-50%); + width: 10px; + height: 10px; + background-color: var(--background-color); + border-radius: 50%; + border: 2px solid var(--primary-text); + display: none; + } + + &:hover { + .selected-background { + background-color: var(--gray-soft); + display: block; + } + } + + &.f-selected { + .selected-background { + display: block; + } + + .interactive-node-body { + border-color: var(--primary-3) !important; + } + + .f-node-outlet { + display: block; + } + } +} + diff --git a/projects/f-pro-examples/visual-programming/components/node/node.component.ts b/projects/f-pro-examples/visual-programming/components/node/node.component.ts new file mode 100644 index 0000000..b0c6ec5 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/node/node.component.ts @@ -0,0 +1,19 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { FFlowModule } from '@foblex/flow'; +import { IFlowNodeViewModel } from '../../domain/node/i-flow-node-view-model'; + +@Component({ + selector: 'visual-programming-node', + templateUrl: './node.component.html', + styleUrls: [ './node.component.scss' ], + standalone: true, + imports: [ + FFlowModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class NodeComponent { + + @Input() + public node: IFlowNodeViewModel | undefined; +} diff --git a/projects/f-pro-examples/visual-programming/components/palette/palette.component.html b/projects/f-pro-examples/visual-programming/components/palette/palette.component.html new file mode 100644 index 0000000..c400fe7 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/palette/palette.component.html @@ -0,0 +1,6 @@ +@for (item of palette;track item) { +
+
{{ item.type[0] }}
+
+} diff --git a/projects/f-pro-examples/visual-programming/components/palette/palette.component.scss b/projects/f-pro-examples/visual-programming/components/palette/palette.component.scss new file mode 100644 index 0000000..42d867c --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/palette/palette.component.scss @@ -0,0 +1,46 @@ +:host { + display: flex; + flex-direction: column; + justify-content: center; + width: 49px; + min-width: 49px; + height: 100%; + border-right: 1px solid var(--gutter-color); + overflow: hidden; + overflow-y: auto; + z-index: 1; + gap: 8px; + padding: 8px; +} + +::ng-deep .visual-programming-external-item { + position: relative; + display: flex; + width: 32px; + height: 32px; + overflow: hidden; + border-radius: 4px; + background-color: var(--background-color); + border: 2px solid; + cursor: move; + z-index: 50; + + .icon { + margin: auto; + line-height: 15px; + text-transform: uppercase; + font-size: 14px; + font-weight: 600; + color: inherit; + } +} + +.node-icon { + font-size: 14px; + mask-size: 100% 100%; + background-color: var(--secondary-text); + width: 21px; + height: 21px; +} + + diff --git a/projects/f-pro-examples/visual-programming/components/palette/palette.component.ts b/projects/f-pro-examples/visual-programming/components/palette/palette.component.ts new file mode 100644 index 0000000..b7fac42 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/palette/palette.component.ts @@ -0,0 +1,27 @@ +import { + ChangeDetectionStrategy, Component, +} from '@angular/core'; +import { FFlowModule } from '@foblex/flow'; +import { ENodeType } from '../../domain/e-node-type'; +import { NODE_CONFIGURATION } from '../../domain/configuration'; + +@Component({ + selector: 'visual-programming-palette', + templateUrl: './palette.component.html', + styleUrls: [ './palette.component.scss' ], + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + FFlowModule, + ] +}) +export class PaletteComponent { + + protected palette = Object.keys(NODE_CONFIGURATION).map((key) => { + return { + type: key, + color: NODE_CONFIGURATION[ key as ENodeType ].color, + text: NODE_CONFIGURATION[ key as ENodeType ].text, + } + }); +} diff --git a/projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.html b/projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.html new file mode 100644 index 0000000..33a3877 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.html @@ -0,0 +1,12 @@ + + + + diff --git a/projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.scss b/projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.scss new file mode 100644 index 0000000..db1ee32 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.scss @@ -0,0 +1,34 @@ +:host { + display: flex; + justify-content: flex-start; + align-items: center; + gap: 8px; + position: absolute; + right: 16px; + top: 16px; +} + +button { + display: inline-block; + text-align: center; + font-weight: 500; + white-space: nowrap; + border: none; + border-radius: 2px; + padding: 4px 8px; + font-size: 14px; + + color: var(--button-default-text); + background-color: var(--button-default-bg); + cursor: pointer; + + &:hover { + color: var(--button-default-hover-text); + background-color: var(--button-default-hover-bg); + } + + &:active { + color: var(--button-default-active-text); + background-color: var(--button-default-active-bg); + } +} diff --git a/projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.ts b/projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.ts new file mode 100644 index 0000000..b1e7cc0 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/components/toolbar/toolbar.component.ts @@ -0,0 +1,36 @@ +import { + ChangeDetectionStrategy, + Component, +} from '@angular/core'; +import { FlowComponent } from '../flow/flow.component'; + +@Component({ + selector: 'visual-programming-toolbar', + templateUrl: './toolbar.component.html', + styleUrls: [ './toolbar.component.scss' ], + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ToolbarComponent { + + constructor( + private flowComponent: FlowComponent + ) { + } + + public onZoomIn(): void { + this.flowComponent.fZoomDirective.zoomIn(); + } + + public onZoomOut(): void { + this.flowComponent.fZoomDirective.zoomOut(); + } + + public onFitToScreen(): void { + this.flowComponent.fCanvasComponent.fitToScreen(); + } + + public onOneToOne(): void { + this.flowComponent.fCanvasComponent.oneToOne(); + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/configuration.ts b/projects/f-pro-examples/visual-programming/domain/configuration.ts new file mode 100644 index 0000000..ec04b37 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/configuration.ts @@ -0,0 +1,32 @@ +import { ENodeType } from './e-node-type'; + +export const NODE_CONFIGURATION = { + [ ENodeType.Input ]: { + color: '#e0575b', + text: 'Input', + }, + [ ENodeType.Assign ]: { + color: '#9f6a00', + text: 'Assign', + }, + [ ENodeType.Switch ]: { + color: '#8e5cd9', + text: 'Switch', + }, + [ ENodeType.Cycle ]: { + color: '#8e5cd9', + text: 'Cycle', + }, + [ ENodeType.Error ]: { + color: '#ec8a82', + text: 'Error', + }, + [ ENodeType.Database ]: { + color: '#30a46c', + text: 'Database', + }, + [ ENodeType.Hash ]: { + color: '#8e5cd9', + text: 'Function', + }, +}; diff --git a/projects/f-pro-examples/visual-programming/domain/connection/add-new/create-connection.handler.ts b/projects/f-pro-examples/visual-programming/domain/connection/add-new/create-connection.handler.ts new file mode 100644 index 0000000..55ef052 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/connection/add-new/create-connection.handler.ts @@ -0,0 +1,36 @@ +import { IHandler } from '@foblex/core'; +import { CreateConnectionRequest } from './create-connection.request'; +import { IFlowStorage } from '../../flow.storage'; +import { IFlowConnectionStorageModel } from '../i-flow-connection-storage-model'; + +export class CreateConnectionHandler implements IHandler { + + constructor( + private flow: IFlowStorage + ) { + } + + public handle(request: CreateConnectionRequest): void { + const index = this.getConnectionIndex(request); + if (index > -1) { + throw new Error('Connection already exists'); + } + + this.flow.connections.push( + this.createConnection(request.outputId, request.inputId) + ); + } + + private getConnectionIndex(request: CreateConnectionRequest): number { + return this.flow.connections.findIndex((x) => { + return x.from === request.outputId && x.to === request.inputId; + }); + } + + private createConnection(outputId: string, inputId: string): IFlowConnectionStorageModel { + return { + from: outputId, + to: inputId, + }; + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/connection/add-new/create-connection.request.ts b/projects/f-pro-examples/visual-programming/domain/connection/add-new/create-connection.request.ts new file mode 100644 index 0000000..80ff869 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/connection/add-new/create-connection.request.ts @@ -0,0 +1,8 @@ +export class CreateConnectionRequest { + + constructor( + public readonly outputId: string, + public readonly inputId: string, + ) { + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/connection/i-flow-connection-storage-model.ts b/projects/f-pro-examples/visual-programming/domain/connection/i-flow-connection-storage-model.ts new file mode 100644 index 0000000..5c2c924 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/connection/i-flow-connection-storage-model.ts @@ -0,0 +1,6 @@ +export interface IFlowConnectionStorageModel { + + from: string; + + to: string; +} diff --git a/projects/f-pro-examples/visual-programming/domain/connection/i-flow-connection-view-model.ts b/projects/f-pro-examples/visual-programming/domain/connection/i-flow-connection-view-model.ts new file mode 100644 index 0000000..1ef27be --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/connection/i-flow-connection-view-model.ts @@ -0,0 +1,10 @@ +import { IFlowConnectionStorageModel } from './i-flow-connection-storage-model'; + +export interface IFlowConnectionViewModel extends IFlowConnectionStorageModel { + + color1: string; + + color2: string; + + text: string; +} diff --git a/projects/f-pro-examples/visual-programming/domain/connection/map/map-to-connection-view-model.handler.ts b/projects/f-pro-examples/visual-programming/domain/connection/map/map-to-connection-view-model.handler.ts new file mode 100644 index 0000000..5bafb45 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/connection/map/map-to-connection-view-model.handler.ts @@ -0,0 +1,55 @@ +import { IFlowStorage } from '../../flow.storage'; +import { IHandler } from '@foblex/core'; +import { IFlowConnectionViewModel } from '../i-flow-connection-view-model'; +import { IFlowConnectionStorageModel } from '../i-flow-connection-storage-model'; +import { IFlowNodeStorageModel } from '../../node/i-flow-node-storage-model'; +import { NODE_CONFIGURATION } from '../../configuration'; + +export class MapToConnectionViewModelHandler implements IHandler{ + + constructor( + private flow: IFlowStorage + ) { + } + + public handle(): IFlowConnectionViewModel[] { + return this.getConnections().map((x) => { + return this.mapConnection(x, this.getFromNode(x), this.getToNode(x)); + }); + } + + private getConnections(): IFlowConnectionStorageModel[] { + return this.flow.connections; + } + + private mapConnection( + connection: IFlowConnectionStorageModel, fromNode: IFlowNodeStorageModel, toNode: IFlowNodeStorageModel + ): IFlowConnectionViewModel { + return { + ...connection, + color1: NODE_CONFIGURATION[ fromNode.type ].color, + color2: NODE_CONFIGURATION[ toNode.type ].color, + text: NODE_CONFIGURATION[ fromNode.type ].text, + }; + } + + private getFromNode(connection: IFlowConnectionStorageModel): IFlowNodeStorageModel { + const result = this.getNodes().find((node) => node.output === connection.from); + if (!result) { + throw new Error('From node not found'); + } + return result; + } + + private getToNode(connection: IFlowConnectionStorageModel): IFlowNodeStorageModel { + const result = this.getNodes().find((node) => node.input === connection.to); + if (!result) { + throw new Error('To node not found'); + } + return result; + } + + private getNodes(): IFlowNodeStorageModel[] { + return this.flow.nodes; + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/connection/reassign/reassign-connection.handler.ts b/projects/f-pro-examples/visual-programming/domain/connection/reassign/reassign-connection.handler.ts new file mode 100644 index 0000000..7d571c0 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/connection/reassign/reassign-connection.handler.ts @@ -0,0 +1,36 @@ +import { IHandler } from '@foblex/core'; +import { ReassignConnectionRequest } from './reassign-connection.request'; +import { IFlowStorage } from '../../flow.storage'; +import { IFlowConnectionStorageModel } from '../i-flow-connection-storage-model'; + +export class ReassignConnectionHandler implements IHandler { + + constructor( + private flow: IFlowStorage + ) { + } + + public handle(request: ReassignConnectionRequest): void { + const index = this.getConnectionIndex(request); + if (index === -1) { + throw new Error('Connection not found'); + } + + this.flow.connections.splice(index, 1, + this.createConnection(request.outputId, request.newInputId) + ); + } + + private getConnectionIndex(request: ReassignConnectionRequest): number { + return this.flow.connections.findIndex((x) => { + return x.from === request.outputId && x.to === request.oldInputId; + }); + } + + private createConnection(outputId: string, inputId: string): IFlowConnectionStorageModel { + return { + from: outputId, + to: inputId, + }; + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/connection/reassign/reassign-connection.request.ts b/projects/f-pro-examples/visual-programming/domain/connection/reassign/reassign-connection.request.ts new file mode 100644 index 0000000..fa3875f --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/connection/reassign/reassign-connection.request.ts @@ -0,0 +1,9 @@ +export class ReassignConnectionRequest { + + constructor( + public readonly outputId: string, + public readonly oldInputId: string, + public readonly newInputId: string, + ) { + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/e-node-type.ts b/projects/f-pro-examples/visual-programming/domain/e-node-type.ts new file mode 100644 index 0000000..75fdb84 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/e-node-type.ts @@ -0,0 +1,16 @@ +export enum ENodeType { + + Input = 'input', + + Assign = 'settings', + + Cycle = 'cycle', + + Switch = 'switch', + + Error = 'error', + + Database = 'database', + + Hash = 'hash', +} diff --git a/projects/f-pro-examples/visual-programming/domain/flow.service.ts b/projects/f-pro-examples/visual-programming/domain/flow.service.ts new file mode 100644 index 0000000..00ec5e6 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/flow.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@angular/core'; +import { FLOW_STORAGE, IFlowStorage } from './flow.storage'; +import { MapToNodeViewModelHandler } from './node/map/map-to-node-view-model.handler'; +import { MapToConnectionViewModelHandler } from './connection/map/map-to-connection-view-model.handler'; +import { IPoint } from '@foblex/core'; +import { MoveNodeHandler } from './node/move-nodes/move-node.handler'; +import { MoveNodeRequest } from './node/move-nodes/move-node.request'; +import { AddNewNodeToFlowHandler } from './node/add-new/add-new-node-to-flow.handler'; +import { AddNewNodeToFlowRequest } from './node/add-new/add-new-node-to-flow.request'; +import { ENodeType } from './e-node-type'; +import { IFlowViewModel } from './i-flow-view-model'; +import { ReassignConnectionHandler } from './connection/reassign/reassign-connection.handler'; +import { ReassignConnectionRequest } from './connection/reassign/reassign-connection.request'; +import { CreateConnectionRequest } from './connection/add-new/create-connection.request'; +import { CreateConnectionHandler } from './connection/add-new/create-connection.handler'; + +@Injectable() +export class FlowService { + + private flow: IFlowStorage = FLOW_STORAGE; + + public getFlow(): IFlowViewModel { + return { + nodes: new MapToNodeViewModelHandler(this.flow).handle(), + connections: new MapToConnectionViewModelHandler(this.flow).handle(), + } + } + + public addNode(type: ENodeType, position: IPoint): void { + new AddNewNodeToFlowHandler(this.flow).handle( + new AddNewNodeToFlowRequest(type, position) + ); + } + + public moveNode(id: string, position: IPoint): void { + new MoveNodeHandler(this.flow).handle( + new MoveNodeRequest(id, position) + ); + } + + public addConnection(outputId: string, inputId: string): void { + new CreateConnectionHandler(this.flow).handle( + new CreateConnectionRequest(outputId, inputId) + ); + } + + public reassignConnection(outputId: string, oldInputId: string, newInputId: string): void { + new ReassignConnectionHandler(this.flow).handle( + new ReassignConnectionRequest(outputId, oldInputId, newInputId) + ); + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/flow.storage.ts b/projects/f-pro-examples/visual-programming/domain/flow.storage.ts new file mode 100644 index 0000000..1fc93ce --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/flow.storage.ts @@ -0,0 +1,109 @@ +import { ENodeType } from './e-node-type'; +import { IFlowNodeStorageModel } from './node/i-flow-node-storage-model'; +import { IFlowConnectionStorageModel } from './connection/i-flow-connection-storage-model'; + +export interface IFlowStorage { + + nodes: IFlowNodeStorageModel[]; + + connections: IFlowConnectionStorageModel[]; +} + +export const FLOW_STORAGE: IFlowStorage = { + + nodes: [ { + id: 'input1', + output: 'input_output', + type: ENodeType.Input, + position: { x: 300, y: 100 } + }, { + id: 'assign', + input: 'assign_input', + output: 'assign_output', + type: ENodeType.Assign, + position: { x: 300, y: 180 } + }, { + id: 'cycle1', + output: 'cycle1_output', + input: 'cycle1_input', + type: ENodeType.Cycle, + position: { x: 300, y: 260 } + }, { + id: 'switch1', + output: 'switch1_output', + input: 'switch1_input', + type: ENodeType.Switch, + position: { x: 540, y: 260 } + }, { + id: 'db_connector1', + output: 'db_connector1_output', + input: 'db_connector1_input', + type: ENodeType.Database, + position: { x: 580, y: 520 } + + }, { + id: 'db_connector2', + output: 'db_connector2_output', + input: 'db_connector2_input', + type: ENodeType.Database, + position: { x: 660, y: 520 } + }, { + id: 'db_connector3', + output: 'db_connector3_output', + input: 'db_connector3_input', + type: ENodeType.Database, + position: { x: 740, y: 520 } + }, { + id: 'function1', + output: 'function1_output', + input: 'function1_input', + type: ENodeType.Hash, + position: { x: 360, y: 400 } + }, { + id: 'function2', + output: 'function2_output', + input: 'function2_input', + type: ENodeType.Hash, + position: { x: 440, y: 400 } + }, { + id: 'function3', + output: 'function3_output', + input: 'function3_input', + type: ENodeType.Hash, + position: { x: 520, y: 400 } + }, { + id: 'exception1', + output: 'exception1_output', + input: 'exception1_input', + type: ENodeType.Error, + position: { x: 700, y: 260 } + }, { + id: 'cycle2', + output: 'cycle2_output', + input: 'cycle2_input', + type: ENodeType.Cycle, + position: { x: 300, y: 500 } + }, { + id: 'output', + input: 'output_input', + type: ENodeType.Hash, + position: { x: 300, y: 580 } + } ], + connections: [ + { from: 'input_output', to: 'assign_input' }, + { from: 'assign_output', to: 'cycle1_input' }, + { from: 'cycle1_output', to: 'switch1_input' }, + { from: 'switch1_output', to: 'exception1_input' }, + { from: 'cycle1_output', to: 'cycle2_input' }, + { from: 'cycle2_output', to: 'output_input' }, + { from: 'switch1_output', to: 'db_connector1_input' }, + { from: 'switch1_output', to: 'db_connector2_input' }, + { from: 'switch1_output', to: 'db_connector3_input' }, + { from: 'db_connector1_output', to: 'function1_input' }, + { from: 'db_connector2_output', to: 'function2_input' }, + { from: 'db_connector3_output', to: 'function3_input' }, + { from: 'function1_output', to: 'cycle1_input' }, + { from: 'function2_output', to: 'cycle1_input' }, + { from: 'function3_output', to: 'cycle1_input' }, + ] +}; diff --git a/projects/f-pro-examples/visual-programming/domain/i-flow-view-model.ts b/projects/f-pro-examples/visual-programming/domain/i-flow-view-model.ts new file mode 100644 index 0000000..0eb27f0 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/i-flow-view-model.ts @@ -0,0 +1,9 @@ +import { IFlowConnectionViewModel } from './connection/i-flow-connection-view-model'; +import { IFlowNodeViewModel } from './node/i-flow-node-view-model'; + +export interface IFlowViewModel { + + nodes: IFlowNodeViewModel[]; + + connections: IFlowConnectionViewModel[]; +} diff --git a/projects/f-pro-examples/visual-programming/domain/node/add-new/add-new-node-to-flow.handler.ts b/projects/f-pro-examples/visual-programming/domain/node/add-new/add-new-node-to-flow.handler.ts new file mode 100644 index 0000000..4b459dd --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/node/add-new/add-new-node-to-flow.handler.ts @@ -0,0 +1,21 @@ +import { GuidExtensions, IHandler } from '@foblex/core'; +import { AddNewNodeToFlowRequest } from './add-new-node-to-flow.request'; +import { IFlowStorage } from '../../flow.storage'; + +export class AddNewNodeToFlowHandler implements IHandler { + + constructor( + private flow: IFlowStorage + ) { + } + + public handle(request: AddNewNodeToFlowRequest): void { + this.flow.nodes.push({ + id: GuidExtensions.generate(), + input: GuidExtensions.generate(), + output: GuidExtensions.generate(), + type: request.type, + position: request.position, + }); + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/node/add-new/add-new-node-to-flow.request.ts b/projects/f-pro-examples/visual-programming/domain/node/add-new/add-new-node-to-flow.request.ts new file mode 100644 index 0000000..5d412d6 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/node/add-new/add-new-node-to-flow.request.ts @@ -0,0 +1,11 @@ +import { IPoint } from '@foblex/core'; +import { ENodeType } from '../../e-node-type'; + +export class AddNewNodeToFlowRequest { + + constructor( + public readonly type: ENodeType, + public readonly position: IPoint, + ) { + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/node/i-flow-node-storage-model.ts b/projects/f-pro-examples/visual-programming/domain/node/i-flow-node-storage-model.ts new file mode 100644 index 0000000..0e57b50 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/node/i-flow-node-storage-model.ts @@ -0,0 +1,15 @@ +import { ENodeType } from '../e-node-type'; +import { IPoint } from '@foblex/core'; + +export interface IFlowNodeStorageModel { + + id: string; + + input?: string; + + output?: string; + + type: ENodeType; + + position: IPoint; +} diff --git a/projects/f-pro-examples/visual-programming/domain/node/i-flow-node-view-model.ts b/projects/f-pro-examples/visual-programming/domain/node/i-flow-node-view-model.ts new file mode 100644 index 0000000..239624d --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/node/i-flow-node-view-model.ts @@ -0,0 +1,6 @@ +import { IFlowNodeStorageModel } from './i-flow-node-storage-model'; + +export interface IFlowNodeViewModel extends IFlowNodeStorageModel { + + color: string; +} diff --git a/projects/f-pro-examples/visual-programming/domain/node/map/map-to-node-view-model.handler.ts b/projects/f-pro-examples/visual-programming/domain/node/map/map-to-node-view-model.handler.ts new file mode 100644 index 0000000..6918b0b --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/node/map/map-to-node-view-model.handler.ts @@ -0,0 +1,21 @@ +import { IHandler } from '@foblex/core'; +import { IFlowNodeViewModel } from '../i-flow-node-view-model'; +import { IFlowStorage } from '../../flow.storage'; +import { NODE_CONFIGURATION } from '../../configuration'; + +export class MapToNodeViewModelHandler implements IHandler { + + constructor( + private flow: IFlowStorage + ) { + } + + public handle(): IFlowNodeViewModel[] { + return this.flow.nodes.map((node) => { + return { + ...node, + color: NODE_CONFIGURATION[ node.type ].color, + }; + }); + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/node/move-nodes/move-node.handler.ts b/projects/f-pro-examples/visual-programming/domain/node/move-nodes/move-node.handler.ts new file mode 100644 index 0000000..8e87fbb --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/node/move-nodes/move-node.handler.ts @@ -0,0 +1,19 @@ +import { IHandler } from '@foblex/core'; +import { MoveNodeRequest } from './move-node.request'; +import { IFlowStorage } from '../../flow.storage'; + +export class MoveNodeHandler implements IHandler { + + constructor( + private flow: IFlowStorage + ) { + } + + public handle(request: MoveNodeRequest): void { + const node = this.flow.nodes.find((x) => x.id === request.id); + if (!node) { + throw new Error(`Node with id ${ request.id } not found`); + } + node.position = request.position; + } +} diff --git a/projects/f-pro-examples/visual-programming/domain/node/move-nodes/move-node.request.ts b/projects/f-pro-examples/visual-programming/domain/node/move-nodes/move-node.request.ts new file mode 100644 index 0000000..146ab68 --- /dev/null +++ b/projects/f-pro-examples/visual-programming/domain/node/move-nodes/move-node.request.ts @@ -0,0 +1,10 @@ +import { IPoint } from '@foblex/core'; + +export class MoveNodeRequest { + + constructor( + public readonly id: string, + public readonly position: IPoint, + ) { + } +} diff --git a/public/docs/en/environment.ts b/public/docs/en/environment.ts index bdc5a68..88651f6 100644 --- a/public/docs/en/environment.ts +++ b/public/docs/en/environment.ts @@ -8,7 +8,7 @@ import { import { CustomConnectionTypeComponent } from '../../../projects/f-examples/custom-connection-type/custom-connection-type.component'; -import { CreateConnectionComponent } from '../../../projects/f-examples/create-connection/create-connection.component'; +import { DragToConnectComponent } from '../../../projects/f-examples/drag-to-connect/drag-to-connect.component'; import { ConnectableSideComponent } from '../../../projects/f-examples/connectable-side/connectable-side.component'; import { ConnectionFromOutletComponent @@ -23,6 +23,8 @@ import { import { LineAlignmentExampleComponent } from '../../../projects/f-examples/line-alignment-example/line-alignment-example.component'; +import { DragToReassignComponent } from '../../../projects/f-examples/drag-to-reassign/drag-to-reassign.component'; +import { FlowComponent } from '../../../projects/f-pro-examples/visual-programming/components/flow/flow.component'; export const ENGLISH_ENVIRONMENT: IDocsEnvironment = createEnvironment(); @@ -41,7 +43,20 @@ function createEnvironment(): IDocsEnvironment { nodeGroup(), connectorGroup(), connectionGroup(), - extendsGroup() + extendsGroup(), + { + text: 'Pro Examples', + items: [{ + text: 'Visual Programming Flow', + link: 'f-visual-programming-flow', + }, { + text: 'Call Center Flow', + link: 'https://github.com/Foblex/f-flow-example', + }, { + text: 'Scheme Editor', + link: 'https://github.com/Foblex/f-scheme-editor', + }] + } ], footerNavigation: { editLink: { @@ -57,13 +72,15 @@ function createEnvironment(): IDocsEnvironment { { tag: 'connection-type', component: ConnectionTypeComponent }, { tag: 'connection-behaviour', component: ConnectionBehaviourComponent }, { tag: 'custom-connection-type', component: CustomConnectionTypeComponent }, - { tag: 'create-connection', component: CreateConnectionComponent }, + { tag: 'drag-to-connect', component: DragToConnectComponent }, + { tag: 'drag-to-reassign', component: DragToReassignComponent }, { tag: 'connectable-side', component: ConnectableSideComponent }, { tag: 'connection-from-outlet', component: ConnectionFromOutletComponent }, { tag: 'connection-markers', component: ConnectionMarkersComponent }, { tag: 'zoom-example', component: ZoomExampleComponent }, { tag: 'background-example', component: BackgroundExampleComponent }, { tag: 'line-alignment-example', component: LineAlignmentExampleComponent }, + { tag: 'visual-programming-flow', component: FlowComponent }, ], socialLinks: [ { icon: 'github', link: 'https://github.com/Foblex/f-flow' }, diff --git a/public/docs/en/f-connection-component.md b/public/docs/en/f-connection-component.md index 829888a..3ba5e37 100644 --- a/public/docs/en/f-connection-component.md +++ b/public/docs/en/f-connection-component.md @@ -74,3 +74,11 @@ Examples of providing custom connection types. The connection type can be set us [component.ts] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/custom-connection-type/custom-connection-type.component.ts [component.scss] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/custom-connection-type/custom-connection-type.component.scss ::: + +#### Reassign Connection +Each connection can be reassigned to another [fNodeInput](f-node-input-directive). The `fReassignDisabled` property can be used to disable this feature. Each connection has a `DragHandle` at the end, drag it to reassign the connection to another [fNodeInput](f-node-input-directive). +::: ng-component +[component.html] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/drag-to-reassign/drag-to-reassign.component.html +[component.ts] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/drag-to-reassign/drag-to-reassign.component.ts +[component.scss] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/drag-to-reassign/drag-to-reassign.component.scss +::: diff --git a/public/docs/en/f-connection-for-create-component.md b/public/docs/en/f-connection-for-create-component.md index cfb178f..971b4b0 100644 --- a/public/docs/en/f-connection-for-create-component.md +++ b/public/docs/en/f-connection-for-create-component.md @@ -24,8 +24,8 @@ Add the `f-connection-for-create` component to the [f-canvas](f-canvas-component The following example shows how to create a connection between two nodes. -::: ng-component -[component.html] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/create-connection/create-connection.component.html -[component.ts] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/create-connection/create-connection.component.ts -[component.scss] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/create-connection/create-connection.component.scss +::: ng-component +[component.html] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/drag-to-connect/drag-to-connect.component.html +[component.ts] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/drag-to-connect/drag-to-connect.component.ts +[component.scss] <<< https://raw.githubusercontent.com/Foblex/f-flow/main/projects/f-examples/drag-to-connect/drag-to-connect.component.scss ::: diff --git a/public/docs/en/f-visual-programming-flow.md b/public/docs/en/f-visual-programming-flow.md new file mode 100644 index 0000000..a3b0b04 --- /dev/null +++ b/public/docs/en/f-visual-programming-flow.md @@ -0,0 +1,24 @@ +# Visual Programming + +## Example + +This example demonstrates how to use the Foblex Flow to create a visual programming interface. + +::: ng-component [height]="600" +::: + +## Possibilities + +- Add nodes from the palette to the canvas using the [fExternalItem](f-external-item-directive) directive. +- Connect nodes using [f-connection-for-create](f-connection-for-create-component) component. +- Reassign connections. +- Move nodes. +- Move canvas. +- Zoom with the `mouse wheel`, `double click` and `buttons` using [fZoom](f-zoom-directive) directive. +- Set the background using the [f-background](f-background-component) component. +- Line alignment using [f-line-alignment](f-line-alignment-component) component. +- Select multiple items using the [f-selection](f-selection-component) component with the `mouse` and by holding down the `Shift key`. + +## Source Code + +The source code for this example can be found in our [GitHub repository](https://github.com/Foblex/f-flow/tree/main/projects/f-pro-examples). diff --git a/src/app/home/hero/buttons-row/buttons-row.component.html b/src/app/home/hero/buttons-row/buttons-row.component.html index 138a5b5..0c4d4bd 100644 --- a/src/app/home/hero/buttons-row/buttons-row.component.html +++ b/src/app/home/hero/buttons-row/buttons-row.component.html @@ -1,3 +1,3 @@ Get Started -Documentation +Examples Github diff --git a/tsconfig.json b/tsconfig.json index eae95a0..a7df90b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,3 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "compileOnSave": false, "compilerOptions": { From b4d485485a9f2c15a180457e8a22ae9d05889bc3 Mon Sep 17 00:00:00 2001 From: Siarhei Huzarevich Date: Mon, 5 Aug 2024 17:37:17 +0200 Subject: [PATCH 2/2] remove unused renderer --- .../get-element-rect-in-flow.execution.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/f-flow/src/domain/get-element-rect-in-flow/get-element-rect-in-flow.execution.ts b/projects/f-flow/src/domain/get-element-rect-in-flow/get-element-rect-in-flow.execution.ts index 8a4b7ef..18ac778 100644 --- a/projects/f-flow/src/domain/get-element-rect-in-flow/get-element-rect-in-flow.execution.ts +++ b/projects/f-flow/src/domain/get-element-rect-in-flow/get-element-rect-in-flow.execution.ts @@ -4,7 +4,7 @@ import { Point, SizeExtensions } from '@foblex/core'; -import { Injectable, Renderer2 } from '@angular/core'; +import { Injectable } from '@angular/core'; import { GetElementRectInFlowRequest } from './get-element-rect-in-flow-request'; import { FExecutionRegister, IExecution } from '../../infrastructure'; import { FComponentsStore } from '../../f-storage'; @@ -23,7 +23,6 @@ export class GetElementRectInFlowExecution implements IExecution