diff --git a/projects/f-examples/extensions/grid-system-example/grid-system-example.component.html b/projects/f-examples/extensions/grid-system-example/grid-system-example.component.html index f56e139b..2398808e 100644 --- a/projects/f-examples/extensions/grid-system-example/grid-system-example.component.html +++ b/projects/f-examples/extensions/grid-system-example/grid-system-example.component.html @@ -1,4 +1,8 @@ - + @@ -8,3 +12,8 @@
I'm a node
+
+ Adjust cell size while dragging + +
diff --git a/projects/f-examples/extensions/grid-system-example/grid-system-example.component.scss b/projects/f-examples/extensions/grid-system-example/grid-system-example.component.scss index e31db810..46f6a894 100644 --- a/projects/f-examples/extensions/grid-system-example/grid-system-example.component.scss +++ b/projects/f-examples/extensions/grid-system-example/grid-system-example.component.scss @@ -8,3 +8,6 @@ @include flow-common.node; } +.toolbar { + @include flow-common.toolbar; +} diff --git a/projects/f-examples/extensions/grid-system-example/grid-system-example.component.ts b/projects/f-examples/extensions/grid-system-example/grid-system-example.component.ts index 7f55022d..e5a1ebc0 100644 --- a/projects/f-examples/extensions/grid-system-example/grid-system-example.component.ts +++ b/projects/f-examples/extensions/grid-system-example/grid-system-example.component.ts @@ -1,5 +1,6 @@ import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core'; import { FCanvasComponent, FFlowModule } from '@foblex/flow'; +import { FCheckboxComponent } from '@foblex/m-render'; @Component({ selector: 'grid-system-example', @@ -9,14 +10,21 @@ import { FCanvasComponent, FFlowModule } from '@foblex/flow'; standalone: true, imports: [ FFlowModule, + FCheckboxComponent, ] }) export class GridSystemExampleComponent { + protected adjustCellSizeWhileDragging: boolean = false; + @ViewChild(FCanvasComponent, { static: true }) - public fCanvas!: FCanvasComponent; + protected fCanvas!: FCanvasComponent; - public onLoaded(): void { + protected onLoaded(): void { this.fCanvas.resetScaleAndCenter(false); } + + protected onAdjustCellSizeWhileDraggingChange(event: boolean): void { + this.adjustCellSizeWhileDragging = event; + } } diff --git a/projects/f-examples/nodes/resize-handle/resize-handle.component.html b/projects/f-examples/nodes/resize-handle/resize-handle.component.html index 1d950bc9..aaf38591 100644 --- a/projects/f-examples/nodes/resize-handle/resize-handle.component.html +++ b/projects/f-examples/nodes/resize-handle/resize-handle.component.html @@ -37,10 +37,14 @@
+
+
-
+
+
+
Node with all ResizeHandles
diff --git a/projects/f-examples/nodes/resize-handle/resize-handle.component.scss b/projects/f-examples/nodes/resize-handle/resize-handle.component.scss index 184fab8e..8e1d2cb8 100644 --- a/projects/f-examples/nodes/resize-handle/resize-handle.component.scss +++ b/projects/f-examples/nodes/resize-handle/resize-handle.component.scss @@ -23,22 +23,34 @@ background-color: var(--node-background-color); border: 1px solid var(--node-border-color); + &.f-resize-handle-left { + top: calc(50% - 6px); + left: -6px; + cursor: col-resize; + } + &.f-resize-handle-left-top { top: -6px; left: -6px; cursor: nwse-resize; } + &.f-resize-handle-top { + top: -6px; + left: calc(50% - 6px); + cursor: row-resize; + } + &.f-resize-handle-right-top { top: -6px; right: -6px; cursor: nesw-resize; } - &.f-resize-handle-left-bottom { - bottom: -6px; - left: -6px; - cursor: nesw-resize; + &.f-resize-handle-right { + top: calc(50% - 6px); + right: -6px; + cursor: col-resize; } &.f-resize-handle-right-bottom { @@ -46,4 +58,16 @@ right: -6px; cursor: nwse-resize; } + + &.f-resize-handle-bottom { + bottom: -6px; + left: calc(50% - 6px); + cursor: row-resize; + } + + &.f-resize-handle-left-bottom { + bottom: -6px; + left: -6px; + cursor: nesw-resize; + } } diff --git a/projects/f-flow/package.json b/projects/f-flow/package.json index 8535b221..5d1e45b0 100644 --- a/projects/f-flow/package.json +++ b/projects/f-flow/package.json @@ -1,6 +1,6 @@ { "name": "@foblex/flow", - "version": "17.1.0", + "version": "17.1.1", "description": "An Angular library designed to simplify the creation and manipulation of dynamic flow. Provides components for flows, nodes, and connections, automating node manipulation and inter-node connections.", "main": "index.js", "types": "index.d.ts", diff --git a/projects/f-flow/src/domain/f-connection/redraw-connections/redraw-connections.execution.ts b/projects/f-flow/src/domain/f-connection/redraw-connections/redraw-connections.execution.ts index 12a7ed0a..dab68b0d 100644 --- a/projects/f-flow/src/domain/f-connection/redraw-connections/redraw-connections.execution.ts +++ b/projects/f-flow/src/domain/f-connection/redraw-connections/redraw-connections.execution.ts @@ -55,7 +55,7 @@ export class RedrawConnectionsExecution implements IExecution { + return this._getCanBeConnectedInputs(payload.fOutputOrOutlet).map((x) => { return this._fMediator.execute(new GetConnectorAndRectRequest(x)); }); } - private _getCanBeConnectedInputs(fOutput: FNodeOutputDirective | FNodeOutletDirective): FConnectorBase[] { + private _getCanBeConnectedInputs(fOutputOrOutlet: FNodeOutputBase | FNodeOutletBase): FConnectorBase[] { let fInputs: FConnectorBase[] = []; - if (fOutput.canBeConnectedInputs?.length) { - fInputs = this._fInputs.filter((x) => fOutput.canBeConnectedInputs.includes(x.fId)); + if (fOutputOrOutlet.canBeConnectedInputs?.length) { + fInputs = this._fInputs.filter((x) => fOutputOrOutlet.canBeConnectedInputs.includes(x.fId)); } else { fInputs = this._fInputs.filter((x) => x.canBeConnected); - if(!fOutput.isSelfConnectable) { - fInputs = this._filterSelfConnectable(fInputs, fOutput); + if(!fOutputOrOutlet.isSelfConnectable) { + fInputs = this._filterSelfConnectable(fInputs, fOutputOrOutlet); } } return fInputs; } - private _filterSelfConnectable(fInputs: FConnectorBase[], fOutput: FConnectorBase): FConnectorBase[] { - return fInputs.filter((x) => fOutput.fNodeId !== x.fNodeId); + private _filterSelfConnectable(fInputs: FConnectorBase[], fOutputOrOutlet: FConnectorBase): FConnectorBase[] { + return fInputs.filter((x) => fOutputOrOutlet.fNodeId !== x.fNodeId); } } diff --git a/projects/f-flow/src/domain/f-connectors/get-all-can-be-connected-inputs-and-rects/get-all-can-be-connected-inputs-and-rects.request.ts b/projects/f-flow/src/domain/f-connectors/get-all-can-be-connected-inputs-and-rects/get-all-can-be-connected-inputs-and-rects.request.ts index 8b9ba17c..b97e83e7 100644 --- a/projects/f-flow/src/domain/f-connectors/get-all-can-be-connected-inputs-and-rects/get-all-can-be-connected-inputs-and-rects.request.ts +++ b/projects/f-flow/src/domain/f-connectors/get-all-can-be-connected-inputs-and-rects/get-all-can-be-connected-inputs-and-rects.request.ts @@ -1,9 +1,9 @@ -import { FNodeOutletDirective, FNodeOutputDirective } from '../../../f-connectors'; +import { FNodeOutletBase, FNodeOutputBase } from '../../../f-connectors'; export class GetAllCanBeConnectedInputsAndRectsRequest { constructor( - public fOutput: FNodeOutputDirective | FNodeOutletDirective, + public fOutputOrOutlet: FNodeOutputBase | FNodeOutletBase, ) { } } diff --git a/projects/f-flow/src/domain/f-draggable/prepare-drag-sequence/prepare-drag-sequence.execution.ts b/projects/f-flow/src/domain/f-draggable/prepare-drag-sequence/prepare-drag-sequence.execution.ts index d8ad2942..ec795edc 100644 --- a/projects/f-flow/src/domain/f-draggable/prepare-drag-sequence/prepare-drag-sequence.execution.ts +++ b/projects/f-flow/src/domain/f-draggable/prepare-drag-sequence/prepare-drag-sequence.execution.ts @@ -15,7 +15,7 @@ export class PrepareDragSequenceExecution implements IExecution(new StartDragSequenceRequest()); + this._fMediator.execute(new StartDragSequenceRequest()); } private _callPrepareDragSequence(): void { diff --git a/projects/f-flow/src/domain/f-draggable/start-drag-sequence/start-drag-sequence.execution.ts b/projects/f-flow/src/domain/f-draggable/start-drag-sequence/start-drag-sequence.execution.ts index 37726eeb..136bb452 100644 --- a/projects/f-flow/src/domain/f-draggable/start-drag-sequence/start-drag-sequence.execution.ts +++ b/projects/f-flow/src/domain/f-draggable/start-drag-sequence/start-drag-sequence.execution.ts @@ -11,7 +11,6 @@ import { F_CSS_CLASS } from '../../css-cls'; export class StartDragSequenceExecution implements IExecution { private _fMediator = inject(FMediator); - private _fComponentsStore = inject(FComponentsStore); private get _hostElement(): HTMLElement { diff --git a/projects/f-flow/src/domain/f-event-trigger.ts b/projects/f-flow/src/domain/f-event-trigger.ts new file mode 100644 index 00000000..9d99e9f5 --- /dev/null +++ b/projects/f-flow/src/domain/f-event-trigger.ts @@ -0,0 +1,11 @@ +export type FEventTrigger = (event: TriggerEvent) => boolean; + +export type TriggerEvent = MouseEvent | TouchEvent | WheelEvent; + +export function isValidEventTrigger(event: TriggerEvent, fTrigger: FEventTrigger): boolean { + return fTrigger(event); +} + +export function defaultEventTrigger(event: TriggerEvent): boolean { + return true; +} diff --git a/projects/f-flow/src/domain/f-node/update-node-when-state-or-size-changed/update-node-when-state-or-size-changed.execution.ts b/projects/f-flow/src/domain/f-node/update-node-when-state-or-size-changed/update-node-when-state-or-size-changed.execution.ts index b18978ff..16b09b82 100644 --- a/projects/f-flow/src/domain/f-node/update-node-when-state-or-size-changed/update-node-when-state-or-size-changed.execution.ts +++ b/projects/f-flow/src/domain/f-node/update-node-when-state-or-size-changed/update-node-when-state-or-size-changed.execution.ts @@ -22,7 +22,7 @@ export class UpdateNodeWhenStateOrSizeChangedExecution stateChanges ).pipe(notifyOnStart(), debounceTime(10)).listen(request.destroyRef, () => { this._calculateConnectorsConnectableSide(connectors, hostElement); - this._fMediator.send(new NotifyDataChangedRequest()); + this._fMediator.execute(new NotifyDataChangedRequest()); }); } diff --git a/projects/f-flow/src/domain/f-selection/clear-selection/clear-selection.execution.spec.ts b/projects/f-flow/src/domain/f-selection/clear-selection/clear-selection.execution.spec.ts index 798e3957..44fc1210 100644 --- a/projects/f-flow/src/domain/f-selection/clear-selection/clear-selection.execution.spec.ts +++ b/projects/f-flow/src/domain/f-selection/clear-selection/clear-selection.execution.spec.ts @@ -3,15 +3,15 @@ import { FMediator } from '@foblex/mediator'; import { ClearSelectionRequest } from './clear-selection.request'; import { ClearSelectionExecution } from './clear-selection.execution'; import { setupTestModule } from '../../test-setup'; -import { ICanChangeSelection } from '../../../mixins'; +import { ISelectable } from '../../../mixins'; import { FDraggableDataContext } from '../../../f-draggable'; -export const MOCK_SELECTABLE_ITEM: ICanChangeSelection = { +export const MOCK_SELECTABLE_ITEM: ISelectable = { fId: '1', fSelectionDisabled: false, hostElement: document.createElement('svg'), - select: jasmine.createSpy('select'), - deselect: jasmine.createSpy('deselect'), + markAsSelected: jasmine.createSpy('markAsSelected'), + unmarkAsSelected: jasmine.createSpy('unmarkAsSelected'), isSelected: jasmine.createSpy('isSelected').and.returnValue(true) }; @@ -32,7 +32,7 @@ describe('ClearSelectionExecution', () => { expect(fDraggableDataContext.selectedItems.length).toBe(2); expect(fDraggableDataContext.isSelectedChanged).toBe(false); - fMediator.send(new ClearSelectionRequest()); + fMediator.execute(new ClearSelectionRequest()); expect(fDraggableDataContext.selectedItems.length).toBe(0); expect(fDraggableDataContext.isSelectedChanged).toBe(true); }); diff --git a/projects/f-flow/src/domain/f-selection/clear-selection/clear-selection.execution.ts b/projects/f-flow/src/domain/f-selection/clear-selection/clear-selection.execution.ts index 079cd90d..214cf3b8 100644 --- a/projects/f-flow/src/domain/f-selection/clear-selection/clear-selection.execution.ts +++ b/projects/f-flow/src/domain/f-selection/clear-selection/clear-selection.execution.ts @@ -1,5 +1,5 @@ import { ClearSelectionRequest } from './clear-selection.request'; -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { FExecutionRegister, IExecution } from '@foblex/mediator'; import { FDraggableDataContext } from '../../../f-draggable'; @@ -7,14 +7,11 @@ import { FDraggableDataContext } from '../../../f-draggable'; @FExecutionRegister(ClearSelectionRequest) export class ClearSelectionExecution implements IExecution { - constructor( - private fDraggableDataContext: FDraggableDataContext, - ) { - } + private _fDraggableDataContext = inject(FDraggableDataContext); public handle(request: ClearSelectionRequest): void { - this.fDraggableDataContext.selectedItems.forEach((x) => x.deselect()); - this.fDraggableDataContext.selectedItems = []; - this.fDraggableDataContext.isSelectedChanged = true; + this._fDraggableDataContext.selectedItems.forEach((x) => x.unmarkAsSelected()); + this._fDraggableDataContext.selectedItems = []; + this._fDraggableDataContext.isSelectedChanged = true; } } diff --git a/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/get-can-be-selected-items.execution.ts b/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/get-can-be-selected-items.execution.ts index 9bef8704..eb40edb8 100644 --- a/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/get-can-be-selected-items.execution.ts +++ b/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/get-can-be-selected-items.execution.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core'; import { IRect, ITransformModel, RectExtensions } from '@foblex/2d'; -import { ICanBeSelected } from './i-can-be-selected'; +import { ICanBeSelectedElementAndRect } from './i-can-be-selected-element-and-rect'; import { GetCanBeSelectedItemsRequest } from './get-can-be-selected-items-request'; import { FNodeBase } from '../../../f-node'; import { FConnectionBase } from '../../../f-connection'; @@ -11,10 +11,11 @@ import { GetNormalizedElementRectRequest } from '../../get-normalized-element-re @Injectable() @FExecutionRegister(GetCanBeSelectedItemsRequest) -export class GetCanBeSelectedItemsExecution implements IExecution { +export class GetCanBeSelectedItemsExecution implements IExecution { private _fMediator = inject(FMediator); private _fComponentsStore = inject(FComponentsStore); + private _fDraggableDataContext = inject(FDraggableDataContext); private get fNodes(): FNodeBase[] { return this._fComponentsStore.fNodes; @@ -28,22 +29,17 @@ export class GetCanBeSelectedItemsExecution implements IExecution { - return !this.fDraggableDataContext.selectedItems.includes(x.element); + return !this._fDraggableDataContext.selectedItems.includes(x.element); }); } - private getNodesWithRects(): ICanBeSelected[] { + private getNodesWithRects(): ICanBeSelectedElementAndRect[] { return this.fNodes.filter((x) => !x.fSelectionDisabled).map((x) => { return { element: x, - rect: RectExtensions.mult( + fRect: RectExtensions.mult( this._fMediator.send(new GetNormalizedElementRectRequest(x.hostElement, false)), this.transform.scale ) @@ -51,11 +47,11 @@ export class GetCanBeSelectedItemsExecution implements IExecution !x.fSelectionDisabled).map((x) => { return { element: x, - rect: RectExtensions.mult( + fRect: RectExtensions.mult( this._fMediator.send(new GetNormalizedElementRectRequest(x.boundingElement, false)), this.transform.scale ) diff --git a/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/i-can-be-selected-element-and-rect.ts b/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/i-can-be-selected-element-and-rect.ts new file mode 100644 index 00000000..2e99399b --- /dev/null +++ b/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/i-can-be-selected-element-and-rect.ts @@ -0,0 +1,9 @@ +import { IRect } from '@foblex/2d'; +import { ISelectable } from '../../../mixins'; + +export interface ICanBeSelectedElementAndRect { + + element: ISelectable; + + fRect: IRect; +} diff --git a/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/i-can-be-selected.ts b/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/i-can-be-selected.ts deleted file mode 100644 index 54263dfa..00000000 --- a/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/i-can-be-selected.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IRect } from '@foblex/2d'; -import { ICanChangeSelection } from '../../../mixins'; - -export interface ICanBeSelected { - - element: ICanChangeSelection; - - rect: IRect; -} diff --git a/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/index.ts b/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/index.ts index 89ded6e1..563ccd3e 100644 --- a/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/index.ts +++ b/projects/f-flow/src/domain/f-selection/get-can-be-selected-items/index.ts @@ -2,4 +2,4 @@ export * from './get-can-be-selected-items.execution'; export * from './get-can-be-selected-items-request'; -export * from './i-can-be-selected'; +export * from './i-can-be-selected-element-and-rect'; diff --git a/projects/f-flow/src/domain/f-selection/select-all/select-all.execution.spec.ts b/projects/f-flow/src/domain/f-selection/select-all/select-all.execution.spec.ts index 23a9784c..200c8677 100644 --- a/projects/f-flow/src/domain/f-selection/select-all/select-all.execution.spec.ts +++ b/projects/f-flow/src/domain/f-selection/select-all/select-all.execution.spec.ts @@ -18,25 +18,25 @@ describe('SelectAllExecution', () => { fMediator = TestBed.inject(FMediator) as jasmine.SpyObj; }); - it('should deselect all items and clear selectedItems array', () => { + it('should unmarkAsSelected all items and clear selectedItems array', () => { const mockSelectedItems = [ - { deselect: jasmine.createSpy('deselect') }, - { deselect: jasmine.createSpy('deselect') } + { unmarkAsSelected: jasmine.createSpy('unmarkAsSelected') }, + { unmarkAsSelected: jasmine.createSpy('unmarkAsSelected') } ]; fDraggableDataContext.selectedItems = mockSelectedItems as any; fMediator.send(new SelectAllRequest()); mockSelectedItems.forEach(item => { - expect(item.deselect).toHaveBeenCalled(); + expect(item.unmarkAsSelected).toHaveBeenCalled(); }); expect(fDraggableDataContext.selectedItems.length).toBe(0); expect(fDraggableDataContext.isSelectedChanged).toBe(true); }); it('should select all nodes and connections', () => { - const mockNode = { fId: 'node1', select: jasmine.createSpy('select') }; - const mockConnection = { fId: 'conn1', select: jasmine.createSpy('select') }; + const mockNode = { fId: 'node1', markAsSelected: jasmine.createSpy('markAsSelected') }; + const mockConnection = { fId: 'conn1', markAsSelected: jasmine.createSpy('markAsSelected') }; fComponentsStore.fNodes = [mockNode] as any; fComponentsStore.fConnections = [mockConnection] as any; @@ -44,8 +44,8 @@ describe('SelectAllExecution', () => { fMediator.send(new SelectAllRequest()); - expect(mockNode.select).toHaveBeenCalled(); - expect(mockConnection.select).toHaveBeenCalled(); + expect(mockNode.markAsSelected).toHaveBeenCalled(); + expect(mockConnection.markAsSelected).toHaveBeenCalled(); expect(fDraggableDataContext.selectedItems.length).toEqual(2); expect(fDraggableDataContext.isSelectedChanged).toBe(true); }); diff --git a/projects/f-flow/src/domain/f-selection/select-all/select-all.execution.ts b/projects/f-flow/src/domain/f-selection/select-all/select-all.execution.ts index 5755ab73..915a66d0 100644 --- a/projects/f-flow/src/domain/f-selection/select-all/select-all.execution.ts +++ b/projects/f-flow/src/domain/f-selection/select-all/select-all.execution.ts @@ -13,15 +13,15 @@ export class SelectAllExecution implements IExecution { public handle(request: SelectAllRequest): void { this._fDraggableDataContext.selectedItems.forEach((x) => { - x.deselect(); + x.unmarkAsSelected(); }); this._fDraggableDataContext.selectedItems = []; this._fComponentsStore.fNodes.forEach((x) => { - x.select(); + x.markAsSelected(); this._fDraggableDataContext.selectedItems.push(x); }); this._fComponentsStore.fConnections.forEach((x) => { - x.select(); + x.markAsSelected(); this._fDraggableDataContext.selectedItems.push(x); }); this._fDraggableDataContext.isSelectedChanged = true; diff --git a/projects/f-flow/src/domain/f-selection/select-and-update-node-layer/select-and-update-node-layer.execution.ts b/projects/f-flow/src/domain/f-selection/select-and-update-node-layer/select-and-update-node-layer.execution.ts index 035bddce..f20d2000 100644 --- a/projects/f-flow/src/domain/f-selection/select-and-update-node-layer/select-and-update-node-layer.execution.ts +++ b/projects/f-flow/src/domain/f-selection/select-and-update-node-layer/select-and-update-node-layer.execution.ts @@ -26,7 +26,7 @@ export class SelectAndUpdateNodeLayerExecution implements IHandler { it('should deselect all items and clear selectedItems array', () => { const mockSelectedItems = [ - { deselect: jasmine.createSpy('deselect') }, - { deselect: jasmine.createSpy('deselect') } + { unmarkAsSelected: jasmine.createSpy('unmarkAsSelected') }, + { unmarkAsSelected: jasmine.createSpy('unmarkAsSelected') } ]; fDraggableDataContext.selectedItems = mockSelectedItems as any; fMediator.send(new SelectRequest([], [])); mockSelectedItems.forEach(item => { - expect(item.deselect).toHaveBeenCalled(); + expect(item.unmarkAsSelected).toHaveBeenCalled(); }); expect(fDraggableDataContext.selectedItems.length).toBe(0); expect(fDraggableDataContext.isSelectedChanged).toBe(true); }); it('should select nodes and connections based on request', () => { - const mockNode = { fId: 'node1', select: jasmine.createSpy('select') }; - const mockConnection = { fId: 'conn1', select: jasmine.createSpy('select') }; + const mockNode = { fId: 'node1', markAsSelected: jasmine.createSpy('markAsSelected') }; + const mockConnection = { fId: 'conn1', markAsSelected: jasmine.createSpy('markAsSelected') }; fComponentsStore.fNodes = [mockNode] as any; fComponentsStore.fConnections = [mockConnection] as any; @@ -44,8 +44,8 @@ describe('SelectExecution', () => { fMediator.send(new SelectRequest(['node1'], ['conn1'])); - expect(mockNode.select).toHaveBeenCalled(); - expect(mockConnection.select).toHaveBeenCalled(); + expect(mockNode.markAsSelected).toHaveBeenCalled(); + expect(mockConnection.markAsSelected).toHaveBeenCalled(); expect(fDraggableDataContext.selectedItems.length).toEqual(2); expect(fDraggableDataContext.isSelectedChanged).toBe(true); }); diff --git a/projects/f-flow/src/domain/f-selection/select/select.execution.ts b/projects/f-flow/src/domain/f-selection/select/select.execution.ts index f8cda5cb..39522c85 100644 --- a/projects/f-flow/src/domain/f-selection/select/select.execution.ts +++ b/projects/f-flow/src/domain/f-selection/select/select.execution.ts @@ -13,14 +13,14 @@ export class SelectExecution implements IExecution { public handle(request: SelectRequest): void { this._fDraggableDataContext.selectedItems.forEach((x) => { - x.deselect(); + x.unmarkAsSelected(); }); this._fDraggableDataContext.selectedItems = []; request.nodes.forEach((key) => { const node = this._fComponentsStore.fNodes.find((x) => x.fId === key); if(node) { - node.select(); + node.markAsSelected(); this._fDraggableDataContext.selectedItems.push(node); } }); @@ -28,7 +28,7 @@ export class SelectExecution implements IExecution { request.connections.forEach((key) => { const connection = this._fComponentsStore.fConnections.find((x) => x.fId === key); if(connection) { - connection.select(); + connection.markAsSelected(); this._fDraggableDataContext.selectedItems.push(connection); } }); diff --git a/projects/f-flow/src/domain/f-zoom/set-zoom/set-zoom.execution.ts b/projects/f-flow/src/domain/f-zoom/set-zoom/set-zoom.execution.ts index 71ea9f08..7a5dc38c 100644 --- a/projects/f-flow/src/domain/f-zoom/set-zoom/set-zoom.execution.ts +++ b/projects/f-flow/src/domain/f-zoom/set-zoom/set-zoom.execution.ts @@ -13,7 +13,6 @@ import { IsDragStartedRequest } from '../../f-draggable'; export class SetZoomExecution implements IExecution { private _fMediator = inject(FMediator); - private _fComponentsStore = inject(FComponentsStore); private get _fHost(): HTMLElement { diff --git a/projects/f-flow/src/domain/index.ts b/projects/f-flow/src/domain/index.ts index 83a43c52..58bdb3c1 100644 --- a/projects/f-flow/src/domain/index.ts +++ b/projects/f-flow/src/domain/index.ts @@ -34,6 +34,8 @@ export * from './create-dom-element'; export * from './css-cls'; +export * from './f-event-trigger'; + export * from './i-f-action-trigger'; export * from './i-map'; diff --git a/projects/f-flow/src/domain/sort-item-layers/sort-items-by-parent/sort-items-by-parent.execution.ts b/projects/f-flow/src/domain/sort-item-layers/sort-items-by-parent/sort-items-by-parent.execution.ts index 04e10e87..60f33aab 100644 --- a/projects/f-flow/src/domain/sort-item-layers/sort-items-by-parent/sort-items-by-parent.execution.ts +++ b/projects/f-flow/src/domain/sort-item-layers/sort-items-by-parent/sort-items-by-parent.execution.ts @@ -12,7 +12,7 @@ export class SortItemsByParentExecution implements IExecution { - this.moveChildrenItems(this.getSortedChildrenItems(parent), parent); + this._getItemsOfContainer().forEach((fItem: FNodeBase) => { + this.moveChildrenItems(this._getSortedChildrenItems(fItem), fItem); }); } - private getItems(): FNodeBase[] { - return this.fComponentsStore.fNodes.filter((x) => this.fItemsContainer.contains(x.hostElement)); + private _getItemsOfContainer(): FNodeBase[] { + return this.fComponentsStore.fNodes + .filter((x) => this.fItemsContainer.contains(x.hostElement)); } - private getSortedChildrenItems( - parent: FNodeBase, + private _getSortedChildrenItems( + fItem: FNodeBase, ): HTMLElement[] { - const allElements = this.fItemsFromContainer; - const parentIndex = allElements.indexOf(parent.hostElement); - return this.getChildrenGroups(parent.fId) - .filter((child: HTMLElement) => allElements.indexOf(child) < parentIndex) - .sort((a, b) => allElements.indexOf(a) - allElements.indexOf(b)); + const indexInContainer = this._fItemElements.indexOf(fItem.hostElement); + return this._getChildrenItems(fItem.fId) + .filter((child: HTMLElement) => this._fItemElements.indexOf(child) < indexInContainer) + .sort((a, b) => this._fItemElements.indexOf(a) - this._fItemElements.indexOf(b)); } - private getChildrenGroups(fId: string): HTMLElement[] { + private _getChildrenItems(fId: string): HTMLElement[] { return this.fMediator.send(new GetDeepChildrenNodesAndGroupsRequest(fId)) .filter((x) => this.fItemsContainer.contains(x.hostElement)).map((x) => x.hostElement); } diff --git a/projects/f-flow/src/domain/update-item-and-children-layers/update-item-and-children-layers.request.ts b/projects/f-flow/src/domain/update-item-and-children-layers/update-item-and-children-layers.request.ts index 5c4014a5..125232a7 100644 --- a/projects/f-flow/src/domain/update-item-and-children-layers/update-item-and-children-layers.request.ts +++ b/projects/f-flow/src/domain/update-item-and-children-layers/update-item-and-children-layers.request.ts @@ -1,9 +1,9 @@ -import { ICanChangeSelection } from '../../mixins'; +import { ISelectable } from '../../mixins'; export class UpdateItemAndChildrenLayersRequest { constructor( - public item: ICanChangeSelection, + public item: ISelectable, public itemContainer: HTMLElement, ) { } 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 0556433a..61b9ebdc 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 @@ -14,7 +14,7 @@ import { EFConnectableSide } from '../../f-connectors'; import { FConnectionFactory } from '../f-connection-builder'; import { IHasHostElement } from '../../i-has-host-element'; import { - ICanChangeSelection, ICanChangeVisibility, + ISelectable, ICanChangeVisibility, mixinChangeSelection, mixinChangeVisibility } from '../../mixins'; @@ -29,7 +29,7 @@ const MIXIN_BASE = mixinChangeSelection( @Directive() export abstract class FConnectionBase extends MIXIN_BASE - implements IHasHostElement, ICanChangeSelection, + implements IHasHostElement, ISelectable, ICanChangeVisibility, IHasConnectionColor, IHasConnectionFromTo, IHasConnectionText { @@ -122,11 +122,11 @@ export abstract class FConnectionBase extends MIXIN_BASE return `position: absolute; pointerEvents: all; transform: translate(-50%, -50%); left: ${ position.x }px; top: ${ position.y }px`; } - public override selectChild(): void { + public override markChildrenAsSelected(): void { this.fPath.select(); } - public override deselectChild(): void { + public override unmarkChildrenAsSelected(): void { this.fPath.deselect(); } diff --git a/projects/f-flow/src/f-connectors/f-node-outlet/f-node-outlet-base.ts b/projects/f-flow/src/f-connectors/f-node-outlet/f-node-outlet-base.ts index 187151e0..067d35b9 100644 --- a/projects/f-flow/src/f-connectors/f-node-outlet/f-node-outlet-base.ts +++ b/projects/f-flow/src/f-connectors/f-node-outlet/f-node-outlet-base.ts @@ -14,6 +14,8 @@ export abstract class FNodeOutletBase extends FConnectorBase { return !this.disabled && this.outputs.some((output) => output.canBeConnected); } + public abstract canBeConnectedInputs: string[]; + public setOutputs(outputs: FConnectorBase[]): void { this.outputs = outputs; } diff --git a/projects/f-flow/src/f-connectors/f-node-outlet/f-node-outlet.directive.ts b/projects/f-flow/src/f-connectors/f-node-outlet/f-node-outlet.directive.ts index 86283eb1..72a530eb 100644 --- a/projects/f-flow/src/f-connectors/f-node-outlet/f-node-outlet.directive.ts +++ b/projects/f-flow/src/f-connectors/f-node-outlet/f-node-outlet.directive.ts @@ -39,7 +39,7 @@ export class FNodeOutletDirective extends FNodeOutletBase implements OnInit, OnD public override isConnectionFromOutlet: boolean = false; @Input({ alias: 'fCanBeConnectedInputs' }) - public canBeConnectedInputs: string[] = []; + public override canBeConnectedInputs: string[] = []; public override get fNodeId(): string { return this._fNode.fId; diff --git a/projects/f-flow/src/f-connectors/f-node-output/f-node-output-base.ts b/projects/f-flow/src/f-connectors/f-node-output/f-node-output-base.ts index cac05a24..fc26e1ff 100644 --- a/projects/f-flow/src/f-connectors/f-node-output/f-node-output-base.ts +++ b/projects/f-flow/src/f-connectors/f-node-output/f-node-output-base.ts @@ -11,4 +11,6 @@ export abstract class FNodeOutputBase extends FConnectorBase { public override get canBeConnected(): boolean { return !this.disabled && (this.multiple ? true : !this.isConnected); } + + public abstract canBeConnectedInputs: string[]; } diff --git a/projects/f-flow/src/f-connectors/f-node-output/f-node-output.directive.ts b/projects/f-flow/src/f-connectors/f-node-output/f-node-output.directive.ts index 3fc494cf..f9761773 100644 --- a/projects/f-flow/src/f-connectors/f-node-output/f-node-output.directive.ts +++ b/projects/f-flow/src/f-connectors/f-node-output/f-node-output.directive.ts @@ -55,7 +55,7 @@ export class FNodeOutputDirective extends FNodeOutputBase implements OnInit, OnC public override isSelfConnectable: boolean = true; @Input({ alias: 'fCanBeConnectedInputs' }) - public canBeConnectedInputs: string[] = []; + public override canBeConnectedInputs: string[] = []; public override get fNodeId(): string { return this._fNode.fId; diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler-preparation/create-connection-drag-handler-preparation.execution.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler-preparation/create-connection-drag-handler-preparation.execution.ts new file mode 100644 index 00000000..637526d4 --- /dev/null +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler-preparation/create-connection-drag-handler-preparation.execution.ts @@ -0,0 +1,39 @@ +import { ITransformModel, Point } from '@foblex/2d'; +import { IHandler } from '@foblex/mediator'; +import { inject, Injectable } from '@angular/core'; +import { CreateConnectionDragHandlerPreparationRequest } from './create-connection-drag-handler-preparation.request'; +import { FComponentsStore } from '../../../../../f-storage'; +import { FExecutionRegister, FMediator } from '@foblex/mediator'; +import { FDraggableDataContext } from '../../../../f-draggable-data-context'; +import { CreateConnectionDragHandler } from '../../create-connection.drag-handler'; + +@Injectable() +@FExecutionRegister(CreateConnectionDragHandlerPreparationRequest) +export class CreateConnectionDragHandlerPreparationExecution + implements IHandler { + + private _fComponentsStore = inject(FComponentsStore); + private _fDraggableDataContext = inject(FDraggableDataContext); + + private get _fHost(): HTMLElement { + return this._fComponentsStore.fFlow!.hostElement; + } + + private get _transform(): ITransformModel { + return this._fComponentsStore.fCanvas!.transform; + } + + public handle(request: CreateConnectionDragHandlerPreparationRequest): void { + this._fDraggableDataContext.onPointerDownScale = this._transform.scale; + const positionRelativeToFlowComponent = Point.fromPoint(request.onPointerDownPosition) + .elementTransform(this._fHost).div(this._transform.scale); + this._fDraggableDataContext.onPointerDownPosition = positionRelativeToFlowComponent; + + const positionRelativeToCanvasComponent = Point.fromPoint(positionRelativeToFlowComponent).mult(this._transform.scale) + .sub(this._transform.position).sub(this._transform.scaledPosition).div(this._transform.scale); + + this._fDraggableDataContext.draggableItems = [ + new CreateConnectionDragHandler(request.fOutputOrOutlet, positionRelativeToCanvasComponent) + ]; + } +} diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler-preparation/create-connection-drag-handler-preparation.request.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler-preparation/create-connection-drag-handler-preparation.request.ts new file mode 100644 index 00000000..10acc227 --- /dev/null +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler-preparation/create-connection-drag-handler-preparation.request.ts @@ -0,0 +1,11 @@ +import { IPoint } from '@foblex/2d'; +import { FNodeOutletBase, FNodeOutputBase } from '../../../../../f-connectors'; + +export class CreateConnectionDragHandlerPreparationRequest { + + constructor( + public onPointerDownPosition: IPoint, + public fOutputOrOutlet: FNodeOutputBase | FNodeOutletBase + ) { + } +} diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler-preparation/index.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler-preparation/index.ts new file mode 100644 index 00000000..7149b6fd --- /dev/null +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler-preparation/index.ts @@ -0,0 +1,3 @@ +export * from './create-connection-drag-handler-preparation.execution'; + +export * from './create-connection-drag-handler-preparation.request'; diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler/create-connection-drag-handler.execution.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler/create-connection-drag-handler.execution.ts deleted file mode 100644 index 9c59d945..00000000 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler/create-connection-drag-handler.execution.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ITransformModel, Point } from '@foblex/2d'; -import { IHandler } from '@foblex/mediator'; -import { Injectable } from '@angular/core'; -import { CreateConnectionDragHandlerRequest } from './create-connection-drag-handler.request'; -import { FComponentsStore } from '../../../../../f-storage'; -import { FExecutionRegister, FMediator } from '@foblex/mediator'; -import { FDraggableDataContext } from '../../../../f-draggable-data-context'; -import { CreateConnectionDragHandler } from '../../create-connection.drag-handler'; - -@Injectable() -@FExecutionRegister(CreateConnectionDragHandlerRequest) -export class CreateConnectionDragHandlerExecution - implements IHandler { - - private get flowHost(): HTMLElement { - return this.fComponentsStore.flowHost; - } - - private get transform(): ITransformModel { - return this.fComponentsStore.fCanvas!.transform; - } - - constructor( - private fComponentsStore: FComponentsStore, - private fDraggableDataContext: FDraggableDataContext, - private fMediator: FMediator, - ) { - } - - public handle(request: CreateConnectionDragHandlerRequest): void { - this.fDraggableDataContext.onPointerDownScale = this.transform.scale; - const positionRelativeToFlowComponent = Point.fromPoint(request.onPointerDownPosition) - .elementTransform(this.flowHost).div(this.transform.scale); - this.fDraggableDataContext.onPointerDownPosition = positionRelativeToFlowComponent; - - const positionRelativeToCanvasComponent = Point.fromPoint(positionRelativeToFlowComponent).mult(this.transform.scale) - .sub(this.transform.position).sub(this.transform.scaledPosition).div(this.transform.scale); - - this.fDraggableDataContext.draggableItems = [ - new CreateConnectionDragHandler(this.fMediator, this.fComponentsStore, request.fOutput, positionRelativeToCanvasComponent) - ]; - } -} diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler/create-connection-drag-handler.request.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler/create-connection-drag-handler.request.ts deleted file mode 100644 index 1be7de89..00000000 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler/create-connection-drag-handler.request.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IPoint } from '@foblex/2d'; -import { FNodeOutletDirective, FNodeOutputDirective } from '../../../../../f-connectors'; - -export class CreateConnectionDragHandlerRequest { - - constructor( - public onPointerDownPosition: IPoint, - public fOutput: FNodeOutputDirective | FNodeOutletDirective - ) { - } -} diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler/index.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler/index.ts deleted file mode 100644 index c04dca4b..00000000 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-drag-handler/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './create-connection-drag-handler.execution'; - -export * from './create-connection-drag-handler.request'; diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-outlet-preparation/create-connection-from-outlet-preparation.execution.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-outlet-preparation/create-connection-from-outlet-preparation.execution.ts index ff7bbd4c..3e7e9be3 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-outlet-preparation/create-connection-from-outlet-preparation.execution.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-outlet-preparation/create-connection-from-outlet-preparation.execution.ts @@ -1,63 +1,63 @@ import { IHandler } from '@foblex/mediator'; import { IPoint } from '@foblex/2d'; -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { CreateConnectionFromOutletPreparationRequest } from './create-connection-from-outlet-preparation.request'; import { FComponentsStore } from '../../../../../f-storage'; import { FExecutionRegister, FMediator } from '@foblex/mediator'; import { FConnectorBase, FNodeOutletBase, - FNodeOutletDirective, FNodeOutputBase, - FNodeOutputDirective } from '../../../../../f-connectors'; import { GetCanBeConnectedOutputByOutletRequest } from '../../get-can-be-connected-output-by-outlet'; -import { RequiredOutput } from '../../../../../errors'; -import { CreateConnectionDragHandlerRequest } from '../create-connection-drag-handler'; +import { CreateConnectionDragHandlerPreparationRequest } from '../create-connection-drag-handler-preparation'; +import { FNodeBase } from '../../../../../f-node'; @Injectable() @FExecutionRegister(CreateConnectionFromOutletPreparationRequest) export class CreateConnectionFromOutletPreparationExecution implements IHandler { - constructor( - private fComponentsStore: FComponentsStore, - private fMediator: FMediator, - ) { - } + private _fMediator = inject(FMediator); + private _fComponentsStore = inject(FComponentsStore); public handle(request: CreateConnectionFromOutletPreparationRequest): void { - const { event } = request; - const node = this.fComponentsStore - .fNodes.find(n => n.isContains(event.targetElement))!; - - const outlet = this.fComponentsStore.fOutlets.find((x) => { - return x.hostElement.contains(event.targetElement); - }); - if (!outlet) { - throw new Error('Outlet not found'); + + const fOutlet = this._getNodeOutlet(request.fNode); + const fOutputs = this._getNodeOutputs(request.fNode); + + fOutlet.setOutputs(fOutputs); + + if (!fOutlet.canBeConnected) { + return; + } + + if (fOutlet.isConnectionFromOutlet) { + this._createDragHandler(request.event.getPosition(), fOutlet); + } else { + this._createDragHandler(request.event.getPosition(), this._getConnectableOutput(fOutlet)); } - const nodeOutputs = this.fComponentsStore.fOutputs.filter((x) => node.isContains(x.hostElement)); - (outlet as FNodeOutletBase).setOutputs(nodeOutputs); - - if (outlet.canBeConnected) { - - if ((outlet as FNodeOutletBase).isConnectionFromOutlet) { - - this.createDragHandler(event.getPosition(), outlet as FNodeOutletDirective); - } else { - const output = this.fMediator.send( - new GetCanBeConnectedOutputByOutletRequest(outlet as FNodeOutletDirective) - ); - if (!output) { - throw RequiredOutput(); - } - this.createDragHandler(event.getPosition(), output as FNodeOutputDirective); - } + } + + private _getNodeOutlet(fNode: FNodeBase): FNodeOutletBase { + const result = this._fComponentsStore.fOutlets + .find((x) => fNode.isContains(x.hostElement)); + if (!result) { + throw new Error('Outlet not found'); } + return result as FNodeOutletBase; + } + + private _getNodeOutputs(fNode: FNodeBase): FConnectorBase[] { + return this._fComponentsStore.fOutputs + .filter((x) => fNode.isContains(x.hostElement)); + } + + private _createDragHandler(position: IPoint, fOutputOrOutlet: FNodeOutputBase | FNodeOutletBase): void { + this._fMediator.execute(new CreateConnectionDragHandlerPreparationRequest(position, fOutputOrOutlet)); } - private createDragHandler(position: IPoint, fOutput: FNodeOutputDirective | FNodeOutletDirective): void { - this.fMediator.send(new CreateConnectionDragHandlerRequest(position, fOutput)); + private _getConnectableOutput(fOutlet: FNodeOutletBase): FNodeOutputBase { + return this._fMediator.execute(new GetCanBeConnectedOutputByOutletRequest(fOutlet)); } } diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-outlet-preparation/create-connection-from-outlet-preparation.request.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-outlet-preparation/create-connection-from-outlet-preparation.request.ts index 459d042b..e87f57bd 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-outlet-preparation/create-connection-from-outlet-preparation.request.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-outlet-preparation/create-connection-from-outlet-preparation.request.ts @@ -1,9 +1,11 @@ import { IPointerEvent } from '@foblex/drag-toolkit'; +import { FNodeBase } from '../../../../../f-node'; export class CreateConnectionFromOutletPreparationRequest { constructor( - public event: IPointerEvent + public event: IPointerEvent, + public fNode: FNodeBase ) { } } diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.execution.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.execution.ts index 32e22d2d..41861fd0 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.execution.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.execution.ts @@ -1,38 +1,52 @@ import { IHandler } from '@foblex/mediator'; -import { IPoint } from '@foblex/2d'; -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { CreateConnectionFromOutputPreparationRequest } from './create-connection-from-output-preparation.request'; -import { FConnectorBase, FNodeOutputDirective } from '../../../../../f-connectors'; -import { CreateConnectionDragHandlerRequest } from '../create-connection-drag-handler'; +import { FConnectorBase, FNodeOutputBase, isNodeOutput } from '../../../../../f-connectors'; +import { CreateConnectionDragHandlerPreparationRequest } from '../create-connection-drag-handler-preparation'; import { FComponentsStore } from '../../../../../f-storage'; import { FExecutionRegister, FMediator } from '@foblex/mediator'; +import { FNodeBase } from '../../../../../f-node'; @Injectable() @FExecutionRegister(CreateConnectionFromOutputPreparationRequest) export class CreateConnectionFromOutputPreparationExecution implements IHandler { - constructor( - private fComponentsStore: FComponentsStore, - private fMediator: FMediator, - ) { - } + private _fMediator = inject(FMediator); + private _fComponentsStore = inject(FComponentsStore); public handle(request: CreateConnectionFromOutputPreparationRequest): void { - const { event } = request; - - const fOutput = this.fComponentsStore.fOutputs.find((x) => { - return x.hostElement.contains(event.targetElement); - }); - if (!fOutput) { - throw new Error('Output not found'); + if(!this._isValid(request)) { + return; } + const fOutput = this._getOutput(request.event.targetElement); + if (fOutput.canBeConnected) { - this.createDragHandler(event.getPosition(), fOutput as FNodeOutputDirective); + this._fMediator.execute( + new CreateConnectionDragHandlerPreparationRequest(request.event.getPosition(), fOutput) + ); } } - private createDragHandler(position: IPoint, fOutput: FNodeOutputDirective): void { - this.fMediator.send(new CreateConnectionDragHandlerRequest(position, fOutput)); + private _isValid(request: CreateConnectionFromOutputPreparationRequest): boolean { + return this._isNodeOutput(request.event.targetElement, request.fNode); + } + + private _isNodeOutput(element: HTMLElement, fNode: FNodeBase): boolean { + return isNodeOutput(element) && !this._getNodeOutlets(fNode).length; + } + + private _getNodeOutlets(node: FNodeBase): FConnectorBase[] { + return this._fComponentsStore.fOutlets + .filter((x) => node.isContains(x.hostElement)); + } + + private _getOutput(element: Element): FNodeOutputBase { + const result = this._fComponentsStore.fOutputs + .find((x) => x.hostElement.contains(element)); + if (!result) { + throw new Error('Output not found'); + } + return result as FNodeOutputBase; } } diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.request.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.request.ts index 360b19fb..2722b3e1 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.request.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.request.ts @@ -1,9 +1,11 @@ import { IPointerEvent } from '@foblex/drag-toolkit'; +import { FNodeBase } from '../../../../../f-node'; export class CreateConnectionFromOutputPreparationRequest { constructor( - public event: IPointerEvent + public event: IPointerEvent, + public fNode: FNodeBase ) { } } diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.validator.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.validator.ts deleted file mode 100644 index 552cf118..00000000 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/create-connection-from-output-preparation.validator.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Injectable } from '@angular/core'; -import { CreateConnectionFromOutputPreparationRequest } from './create-connection-from-output-preparation.request'; -import { IPointerEvent } from '@foblex/drag-toolkit'; -import { FValidatorRegister, IValidator } from '@foblex/mediator'; -import { FComponentsStore } from '../../../../../f-storage'; -import { FNodeBase } from '../../../../../f-node'; -import { FConnectorBase, isNodeOutput } from '../../../../../f-connectors'; - -@Injectable() -@FValidatorRegister(CreateConnectionFromOutputPreparationRequest) -export class CreateConnectionFromOutputPreparationValidator implements IValidator { - - constructor( - private fComponentsStore: FComponentsStore - ) { - } - - public handle(request: CreateConnectionFromOutputPreparationRequest): boolean { - return this.isNodeOutput(request.event.targetElement, this.getNode(request.event)); - } - - private getNode(event: IPointerEvent): FNodeBase { - return this.fComponentsStore - .fNodes.find(n => n.isContains(event.targetElement))!; - } - - private isNodeOutput(targetElement: HTMLElement, node: FNodeBase): boolean { - return isNodeOutput(targetElement) && !this.getOutlets(node).length; - } - - private getOutlets(node: FNodeBase): FConnectorBase[] { - return this.fComponentsStore.fOutlets.filter((x) => node.isContains(x.hostElement)); - } -} diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/index.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/index.ts index 390412d7..e634004d 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/index.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-from-output-preparation/index.ts @@ -1,5 +1,3 @@ export * from './create-connection-from-output-preparation.execution'; export * from './create-connection-from-output-preparation.request'; - -export * from './create-connection-from-output-preparation.validator'; diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-preparation.execution.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-preparation.execution.ts index aaf20a57..e6781e1a 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-preparation.execution.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-preparation.execution.ts @@ -2,7 +2,7 @@ import { IHandler } from '@foblex/mediator'; import { IPointerEvent } from '@foblex/drag-toolkit'; import { inject, Injectable } from '@angular/core'; import { FComponentsStore } from '../../../../f-storage'; -import { FConnectorBase, isNodeOutlet, isNodeOutput } from '../../../../f-connectors'; +import { isNodeOutlet, isNodeOutput } from '../../../../f-connectors'; import { FNodeBase } from '../../../../f-node'; import { CreateConnectionPreparationRequest } from './create-connection-preparation.request'; import { FExecutionRegister, FMediator } from '@foblex/mediator'; @@ -19,15 +19,21 @@ export class CreateConnectionPreparationExecution private _fComponentsStore = inject(FComponentsStore); private _fDraggableDataContext = inject(FDraggableDataContext); + private _fNode: FNodeBase | undefined; + public handle(request: CreateConnectionPreparationRequest): void { if (!this._isValid(request)) { return; } if (isNodeOutlet(request.event.targetElement)) { - this._fMediator.send(new CreateConnectionFromOutletPreparationRequest(request.event)); - } else if (this._isNodeOutput(request.event.targetElement, this._getNode(request.event)!)) { - this._fMediator.send(new CreateConnectionFromOutputPreparationRequest(request.event)); + this._fMediator.execute( + new CreateConnectionFromOutletPreparationRequest(request.event, this._fNode!) + ); + } else if (isNodeOutput(request.event.targetElement)) { + this._fMediator.execute( + new CreateConnectionFromOutputPreparationRequest(request.event, this._fNode!) + ); } } @@ -36,19 +42,12 @@ export class CreateConnectionPreparationExecution } private _getNode(event: IPointerEvent): FNodeBase | undefined { - return this._fComponentsStore + this._fNode = this._fComponentsStore .fNodes.find(n => n.isContains(event.targetElement)); + return this._fNode; } private _isValidConditions(): boolean { - return !this._fDraggableDataContext.draggableItems.length && !!this._fComponentsStore.fTempConnection; - } - - private _isNodeOutput(targetElement: HTMLElement, node: FNodeBase): boolean { - return isNodeOutput(targetElement) && !this._getOutlets(node).length; - } - - private _getOutlets(node: FNodeBase): FConnectorBase[] { - return this._fComponentsStore.fOutlets.filter((x) => node.isContains(x.hostElement)); + return this._fDraggableDataContext.isEmpty() && !!this._fComponentsStore.fTempConnection; } } diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-preparation.request.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-preparation.request.ts index c2f7de3c..5b112c94 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-preparation.request.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/create-connection-preparation.request.ts @@ -1,9 +1,11 @@ import { IPointerEvent } from '@foblex/drag-toolkit'; +import { FEventTrigger } from '../../../../domain'; export class CreateConnectionPreparationRequest { constructor( - public event: IPointerEvent + public event: IPointerEvent, + public fTrigger: FEventTrigger ) { } } diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/index.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/index.ts index e10d8ef8..43c34de4 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/index.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection-preparation/index.ts @@ -1,4 +1,4 @@ -export * from './create-connection-drag-handler'; +export * from './create-connection-drag-handler-preparation'; export * from './create-connection-from-outlet-preparation'; diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection.drag-handler.ts b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection.drag-handler.ts index ea7a5782..8dc34277 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/create-connection.drag-handler.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/create-connection.drag-handler.ts @@ -8,17 +8,20 @@ import { } from '../../../domain'; import { FConnectionBase, FSnapConnectionComponent } from '../../../f-connection'; import { - EFConnectableSide, - FNodeOutletDirective, - FNodeOutputDirective + EFConnectableSide, FNodeOutletBase, + FNodeOutputBase, } from '../../../f-connectors'; import { FMediator } from '@foblex/mediator'; import { RoundedRect, ILine, IPoint, PointExtensions, RectExtensions, IRoundedRect } from '@foblex/2d'; import { FComponentsStore } from '../../../f-storage'; import { ICreateReassignConnectionDragData } from '../i-create-reassign-connection-drag-data'; +import { fInject } from '../../f-injector'; export class CreateConnectionDragHandler implements IDraggableItem { + private _fMediator = fInject(FMediator); + private _fComponentsStore = fInject(FComponentsStore); + private readonly _toConnectorRect = new RoundedRect(); private get _fConnection(): FConnectionBase { @@ -34,9 +37,7 @@ export class CreateConnectionDragHandler implements IDraggableItem(new GetConnectorAndRectRequest(this._fOutput)); + this._fOutputWithRect = this._fMediator.execute(new GetConnectorAndRectRequest(this._fOutputOrOutlet)); this._fConnection.show(); this.onPointerMove(PointExtensions.initialize()); @@ -57,7 +58,7 @@ export class CreateConnectionDragHandler implements IDraggableItem( - new GetAllCanBeConnectedInputsAndRectsRequest(this._fOutput) + new GetAllCanBeConnectedInputsAndRectsRequest(this._fOutputOrOutlet) ); this._fMediator.execute( @@ -69,12 +70,12 @@ export class CreateConnectionDragHandler implements IDraggableItem { - private get fNodes(): FNodeBase[] { - return this.fComponentsStore.fNodes; - } + private _fComponentStore = inject(FComponentsStore); - private get fOutputs(): FConnectorBase[] { - return this.fComponentsStore.fOutputs; + private get _fNodes(): FNodeBase[] { + return this._fComponentStore.fNodes; } - constructor( - private fComponentsStore: FComponentsStore, - ) { + private get _fOutputs(): FConnectorBase[] { + return this._fComponentStore.fOutputs; } + private _fNode: FNodeBase | undefined; + public handle(request: GetCanBeConnectedOutputByOutletRequest): FConnectorBase | undefined { - const outputs = this.getConnectableOutputs(this.getNode(request.outlet)); - if (!outputs.length) { - throw RequiredOutput(); + if(!this._isValid(request)) { + return; + } + + const fOutputs = this._getConnectableOutputs(); + if(!fOutputs.length) { + throw new Error('The fNode must contain at least one fOutput if there is an fOutlet') } - return outputs.length > 0 ? outputs[ 0 ] : undefined; + return fOutputs[0]; + } + + private _isValid(request: GetCanBeConnectedOutputByOutletRequest): boolean { + return !!this._getNode(request.fOutlet); } - private getConnectableOutputs(node: FNodeBase): FConnectorBase[] { - return this.fOutputs.filter((x) => { - return node.isContains(x.hostElement) && x.canBeConnected; - }); + private _getNode(fOutlet: FNodeOutletBase): FNodeBase { + this._fNode = this._fNodes.find((x) => x.isContains(fOutlet.hostElement))!; + return this._fNode; } - private getNode(outlet: FNodeOutletBase): FNodeBase { - return this.fNodes.find((x) => x.isContains(outlet.hostElement))!; + private _getConnectableOutputs(): FConnectorBase[] { + return this._fOutputs + .filter((x) => this._fNode!.isContains(x.hostElement) && x.canBeConnected); } } diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/get-can-be-connected-output-by-outlet.request.ts b/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/get-can-be-connected-output-by-outlet.request.ts index c9c5f028..54d298fa 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/get-can-be-connected-output-by-outlet.request.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/get-can-be-connected-output-by-outlet.request.ts @@ -3,7 +3,7 @@ import { FNodeOutletBase } from '../../../../f-connectors'; export class GetCanBeConnectedOutputByOutletRequest { constructor( - public outlet: FNodeOutletBase + public fOutlet: FNodeOutletBase ) { } } diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/get-can-be-connected-output-by-outlet.validator.ts b/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/get-can-be-connected-output-by-outlet.validator.ts deleted file mode 100644 index 2be93b58..00000000 --- a/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/get-can-be-connected-output-by-outlet.validator.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable } from '@angular/core'; -import { GetCanBeConnectedOutputByOutletRequest } from './get-can-be-connected-output-by-outlet.request'; -import { FValidatorRegister, IValidator } from '@foblex/mediator'; -import { FNodeBase } from '../../../../f-node'; -import { FComponentsStore } from '../../../../f-storage'; - -@Injectable() -@FValidatorRegister(GetCanBeConnectedOutputByOutletRequest) -export class GetCanBeConnectedOutputByOutletValidator implements IValidator { - - private get fNodes(): FNodeBase[] { - return this.fComponentsStore.fNodes; - } - - constructor( - private fComponentsStore: FComponentsStore, - ) { - } - - public handle(request: GetCanBeConnectedOutputByOutletRequest): boolean { - return !!this.fNodes.find((x) => x.isContains(request.outlet.hostElement)); - } -} diff --git a/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/index.ts b/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/index.ts index ab7f7ce9..ad9b671a 100644 --- a/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/index.ts +++ b/projects/f-flow/src/f-draggable/connections/create-connection/get-can-be-connected-output-by-outlet/index.ts @@ -1,5 +1,3 @@ export * from './get-can-be-connected-output-by-outlet.execution'; export * from './get-can-be-connected-output-by-outlet.request'; - -export * from './get-can-be-connected-output-by-outlet.validator'; diff --git a/projects/f-flow/src/f-draggable/connections/providers.ts b/projects/f-flow/src/f-draggable/connections/providers.ts index 56c71092..783fe1c5 100644 --- a/projects/f-flow/src/f-draggable/connections/providers.ts +++ b/projects/f-flow/src/f-draggable/connections/providers.ts @@ -1,12 +1,10 @@ import { - CreateConnectionDragHandlerExecution, + CreateConnectionDragHandlerPreparationExecution, CreateConnectionFinalizeExecution, CreateConnectionFromOutletPreparationExecution, CreateConnectionFromOutputPreparationExecution, - CreateConnectionFromOutputPreparationValidator, CreateConnectionPreparationExecution, GetCanBeConnectedOutputByOutletExecution, - GetCanBeConnectedOutputByOutletValidator } from './create-connection'; import { ReassignConnectionFinalizeExecution, @@ -17,18 +15,14 @@ export const CONNECTIONS_PROVIDERS = [ CreateConnectionFinalizeExecution, - CreateConnectionDragHandlerExecution, + CreateConnectionDragHandlerPreparationExecution, CreateConnectionFromOutletPreparationExecution, GetCanBeConnectedOutputByOutletExecution, - GetCanBeConnectedOutputByOutletValidator, - CreateConnectionFromOutputPreparationExecution, - CreateConnectionFromOutputPreparationValidator, - CreateConnectionPreparationExecution, ReassignConnectionFinalizeExecution, 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 c341877b..cf0ab566 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 @@ -39,7 +39,7 @@ export class ReassignConnectionPreparationExecution implements IExecution this._updateConnectionLayer()); diff --git a/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection-preparation/reassign-connection-preparation.request.ts b/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection-preparation/reassign-connection-preparation.request.ts index 06832161..4baace76 100644 --- a/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection-preparation/reassign-connection-preparation.request.ts +++ b/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection-preparation/reassign-connection-preparation.request.ts @@ -1,9 +1,11 @@ import { IPointerEvent } from '@foblex/drag-toolkit'; +import { FEventTrigger } from '../../../../domain'; export class ReassignConnectionPreparationRequest { constructor( - public event: IPointerEvent + public event: IPointerEvent, + public fTrigger: FEventTrigger ) { } } diff --git a/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection.drag-handler.ts b/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection.drag-handler.ts index b5de329f..7f7bf9db 100644 --- a/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection.drag-handler.ts +++ b/projects/f-flow/src/f-draggable/connections/reassign-connection/reassign-connection.drag-handler.ts @@ -7,13 +7,17 @@ import { } from '../../../domain'; import { FConnectionBase, FSnapConnectionComponent } from '../../../f-connection'; import { EFConnectableSide, FConnectorBase, FNodeOutputDirective } from '../../../f-connectors'; -import { FMediator } from '@foblex/mediator'; import { ILine, IPoint, RectExtensions, RoundedRect } from '@foblex/2d'; -import { FComponentsStore } from '../../../f-storage'; import { ICreateReassignConnectionDragData } from '../i-create-reassign-connection-drag-data'; +import { fInject } from '../../f-injector'; +import { FMediator } from '@foblex/mediator'; +import { FComponentsStore } from '../../../f-storage'; export class ReassignConnectionDragHandler implements IDraggableItem { + private _fMediator = fInject(FMediator); + private _fComponentsStore = fInject(FComponentsStore); + private readonly _toConnectorRect = new RoundedRect(); private get _fSnapConnection(): FSnapConnectionComponent | undefined { @@ -42,8 +46,6 @@ export class ReassignConnectionDragHandler implements IDraggableItem { + return (this._fPlatform.getOS() === EOperationSystem.MAC_OS) ? event.metaKey : event.ctrlKey; + }; + + @Input() + public fReassignConnectionTrigger: FEventTrigger = defaultEventTrigger; + + @Input() + public fCreateConnectionTrigger: FEventTrigger = defaultEventTrigger; + @Output() public override fSelectionChange = new EventEmitter(); @@ -92,6 +106,9 @@ export class FDraggableDirective extends FDraggableBase implements OnInit, After @Input() public override hCellSize = 1; + @Input() + public override fCellSizeWhileDragging: boolean = false; + @ContentChildren(F_DRAG_AND_DROP_PLUGIN, { descendants: true }) private plugins!: QueryList; @@ -103,7 +120,7 @@ export class FDraggableDirective extends FDraggableBase implements OnInit, After } public ngOnInit(): void { - this._fMediator.send(new AddDndToStoreRequest(this)); + this._fMediator.execute(new AddDndToStoreRequest(this)); } public ngAfterViewInit(): void { @@ -111,37 +128,40 @@ export class FDraggableDirective extends FDraggableBase implements OnInit, After } public override onPointerDown(event: IPointerEvent): boolean { + FInjector.set(this._injector); + this._fMediator.execute(new InitializeDragSequenceRequest()); this.plugins.forEach((p) => p.onPointerDown?.(event)); - this._fMediator.execute(new SingleSelectRequest(event)); + this._fMediator.execute(new SingleSelectRequest(event, this.fMultiSelectTrigger)); - this._fMediator.execute(new ReassignConnectionPreparationRequest(event)); + this._fMediator.execute(new ReassignConnectionPreparationRequest(event, this.fReassignConnectionTrigger)); - this._fMediator.execute(new CreateConnectionPreparationRequest(event)); + this._fMediator.execute(new CreateConnectionPreparationRequest(event, this.fCreateConnectionTrigger)); - const isMouseLeftButton = event.isMouseLeftButton(); - if (!isMouseLeftButton) { + const isMouseLeftOrTouch = event.isMouseLeftButton(); + if (!isMouseLeftOrTouch) { this.finalizeDragSequence(); } - return isMouseLeftButton; + return isMouseLeftOrTouch; } protected override prepareDragSequence(event: IPointerEvent) { + this.plugins.forEach((p) => p.prepareDragSequence?.(event)); - this._fMediator.send(new NodeResizePreparationRequest(event)); + this._fMediator.execute(new NodeResizePreparationRequest(event)); - this._fMediator.send(new NodeMovePreparationRequest(event)); + this._fMediator.execute(new NodeMovePreparationRequest(event)); this._fMediator.send(new NodeDragToParentPreparationRequest(event)); - this._fMediator.send(new CanvasMovePreparationRequest(event)); + this._fMediator.execute(new CanvasMovePreparationRequest(event)); this._fMediator.send(new ExternalItemPreparationRequest(event)); - this._fMediator.send(new PrepareDragSequenceRequest()); + this._fMediator.execute(new PrepareDragSequenceRequest()); } protected override onSelect(event: Event): void { @@ -167,20 +187,26 @@ export class FDraggableDirective extends FDraggableBase implements OnInit, After this._fMediator.execute(new NodeDragToParentFinalizeRequest(event)); - this._fMediator.send(new CanvasMoveFinalizeRequest(event)); + this._fMediator.execute(new CanvasMoveFinalizeRequest(event)); this._fMediator.execute(new ExternalItemFinalizeRequest(event)); this._fMediator.execute(new EndDragSequenceRequest()); + + FInjector.clear(); } protected override finalizeDragSequence(): void { this._fMediator.execute(new EmitSelectionChangeEventRequest()); + + FInjector.clear(); } public ngOnDestroy(): void { - this._fMediator.send(new RemoveDndFromStoreRequest()); + this._fMediator.execute(new RemoveDndFromStoreRequest()); super.unsubscribe(); + + FInjector.clear(); } } diff --git a/projects/f-flow/src/f-draggable/f-injector.ts b/projects/f-flow/src/f-draggable/f-injector.ts new file mode 100644 index 00000000..b5535c50 --- /dev/null +++ b/projects/f-flow/src/f-draggable/f-injector.ts @@ -0,0 +1,25 @@ +import {Injector, Type} from "@angular/core"; + +export class FInjector { + + private static _injector: Injector | undefined; + + public static set(injector: Injector): void { + this._injector = injector; + } + + public static clear(): void { + this._injector = undefined; + } + + public static get(): Injector { + if (!this._injector) { + throw new Error('No injector available'); + } + return this._injector; + } +} + +export function fInject(token: Type): T { + return FInjector.get().get(token); +} diff --git a/projects/f-flow/src/f-draggable/index.ts b/projects/f-flow/src/f-draggable/index.ts index 9bfdc084..99d690aa 100644 --- a/projects/f-flow/src/f-draggable/index.ts +++ b/projects/f-flow/src/f-draggable/index.ts @@ -18,6 +18,8 @@ export * from './f-draggable-base'; export * from './f-draggable-data-context'; +export * from './f-injector'; + export * from './f-selection-change-event'; export * from './i-f-drag-and-drop-plugin'; diff --git a/projects/f-flow/src/f-draggable/node-resize/apply-child-resize-restrictions/apply-child-resize-restrictions.execution.ts b/projects/f-flow/src/f-draggable/node-resize/apply-child-resize-restrictions/apply-child-resize-restrictions.execution.ts index ee5c701b..179c7bdf 100644 --- a/projects/f-flow/src/f-draggable/node-resize/apply-child-resize-restrictions/apply-child-resize-restrictions.execution.ts +++ b/projects/f-flow/src/f-draggable/node-resize/apply-child-resize-restrictions/apply-child-resize-restrictions.execution.ts @@ -3,7 +3,7 @@ import { ApplyChildResizeRestrictionsRequest } from './apply-child-resize-restri import { IRect } from '@foblex/2d'; import { FExecutionRegister, IExecution } from '@foblex/mediator'; -const OFFSET = 0; +const CHILD_RESIZE_OFFSET = 0; @Injectable() @FExecutionRegister(ApplyChildResizeRestrictionsRequest) @@ -11,39 +11,43 @@ export class ApplyChildResizeRestrictionsExecution implements IExecution { public handle(request: ApplyChildResizeRestrictionsRequest): void { - this.applyRestrictions(request.rect, request.restrictionsRect); + this._apply(request.rect, request.restrictionsRect); } - private applyRestrictions(rect: IRect, restrictionsRect: IRect): void { - this.left(rect, restrictionsRect); - this.top(rect, restrictionsRect); - this.right(rect, restrictionsRect); - this.bottom(rect, restrictionsRect); + private _apply(rect: IRect, restrictionsRect: IRect): void { + this._restrictLeft(rect, restrictionsRect); + this._restrictTop(rect, restrictionsRect); + this._restrictRight(rect, restrictionsRect); + this._restrictBottom(rect, restrictionsRect); } - private left(rect: IRect, restrictionsRect: IRect): void { - if (rect.x > restrictionsRect.x - OFFSET) { - rect.width += rect.x - restrictionsRect.x - OFFSET; - rect.x = restrictionsRect.x - OFFSET; + private _restrictLeft(rect: IRect, restrictions: IRect): void { + const delta = rect.x - (restrictions.x - CHILD_RESIZE_OFFSET); + if (delta > 0) { + rect.x -= delta; + rect.width += delta; } } - private top(rect: IRect, restrictionsRect: IRect): void { - if (rect.y > restrictionsRect.y - OFFSET) { - rect.height += rect.y - restrictionsRect.y - OFFSET; - rect.y = restrictionsRect.y - OFFSET; + private _restrictTop(rect: IRect, restrictions: IRect): void { + const delta = rect.y - (restrictions.y - CHILD_RESIZE_OFFSET); + if (delta > 0) { + rect.y -= delta; + rect.height += delta; } } - private right(rect: IRect, restrictionsRect: IRect): void { - if (rect.x + rect.width <= restrictionsRect.x + restrictionsRect.width + OFFSET) { - rect.width = restrictionsRect.x + restrictionsRect.width - rect.x + OFFSET; + private _restrictRight(rect: IRect, restrictions: IRect): void { + const maxRight = restrictions.x + restrictions.width + CHILD_RESIZE_OFFSET; + if (rect.x + rect.width <= maxRight) { + rect.width = maxRight - rect.x; } } - private bottom(rect: IRect, restrictionsRect: IRect): void { - if (rect.y + rect.height <= restrictionsRect.y + restrictionsRect.height + OFFSET) { - rect.height = restrictionsRect.y + restrictionsRect.height - rect.y + OFFSET; + private _restrictBottom(rect: IRect, restrictions: IRect): void { + const maxBottom = restrictions.y + restrictions.height + CHILD_RESIZE_OFFSET; + if (rect.y + rect.height <= maxBottom) { + rect.height = maxBottom - rect.y; } } } diff --git a/projects/f-flow/src/f-draggable/node-resize/get-node-resize-restrictions/get-node-resize-restrictions.execution.ts b/projects/f-flow/src/f-draggable/node-resize/get-node-resize-restrictions/get-node-resize-restrictions.execution.ts index 61acd2fd..59c5b809 100644 --- a/projects/f-flow/src/f-draggable/node-resize/get-node-resize-restrictions/get-node-resize-restrictions.execution.ts +++ b/projects/f-flow/src/f-draggable/node-resize/get-node-resize-restrictions/get-node-resize-restrictions.execution.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { GetNodeResizeRestrictionsRequest } from './get-node-resize-restrictions.request'; import { IRect, SizeExtensions } from '@foblex/2d'; import { INodeResizeRestrictions } from './i-node-resize-restrictions'; @@ -14,24 +14,27 @@ import { GetNodePaddingRequest } from '../../../domain'; export class GetNodeResizeRestrictionsExecution implements IExecution { - constructor( - private fMediator: FMediator - ) { - } + private _fMediator = inject(FMediator); public handle(request: GetNodeResizeRestrictionsRequest): INodeResizeRestrictions { - const fNodePaddings = this.getNodePaddings(request.fNode, request.rect); - const childRect = this.fMediator.send(new GetNormalizedChildrenNodesRectRequest(request.fNode, fNodePaddings)); - const parentRect = this.fMediator.send(new GetNormalizedParentNodeRectRequest(request.fNode)); + const paddings = this._calculateNodePaddings(request.fNode, request.rect); return { - parentRect, - childRect, - minSize: SizeExtensions.initialize(fNodePaddings[0] + fNodePaddings[2], fNodePaddings[1] + fNodePaddings[3]) + parentBounds: this._getNormalizedParentBounds(request.fNode), + childrenBounds: this._getNormalizedChildrenBounds(request.fNode, paddings), + minimumSize: SizeExtensions.initialize(paddings[0] + paddings[2], paddings[1] + paddings[3]) } } - private getNodePaddings(node: FNodeBase, rect: IRect): [ number, number, number, number ] { - return this.fMediator.send<[ number, number, number, number ]>(new GetNodePaddingRequest(node, rect)); + private _calculateNodePaddings(node: FNodeBase, rect: IRect): [ number, number, number, number ] { + return this._fMediator.execute<[ number, number, number, number ]>(new GetNodePaddingRequest(node, rect)); + } + + private _getNormalizedParentBounds(fNode: FNodeBase): IRect { + return this._fMediator.execute(new GetNormalizedParentNodeRectRequest(fNode)); + } + + private _getNormalizedChildrenBounds(fNode: FNodeBase, fNodePaddings: [ number, number, number, number ]): IRect | null { + return this._fMediator.execute(new GetNormalizedChildrenNodesRectRequest(fNode, fNodePaddings)); } } diff --git a/projects/f-flow/src/f-draggable/node-resize/get-node-resize-restrictions/i-node-resize-restrictions.ts b/projects/f-flow/src/f-draggable/node-resize/get-node-resize-restrictions/i-node-resize-restrictions.ts index 0b8266c9..0e7869d7 100644 --- a/projects/f-flow/src/f-draggable/node-resize/get-node-resize-restrictions/i-node-resize-restrictions.ts +++ b/projects/f-flow/src/f-draggable/node-resize/get-node-resize-restrictions/i-node-resize-restrictions.ts @@ -2,9 +2,9 @@ import { IRect, ISize } from '@foblex/2d'; export interface INodeResizeRestrictions { - parentRect: IRect; + parentBounds: IRect; - childRect: IRect | null; + childrenBounds: IRect | null; - minSize: ISize; + minimumSize: ISize; } diff --git a/projects/f-flow/src/f-draggable/node-resize/node-resize-finalize/node-resize-finalize.execution.ts b/projects/f-flow/src/f-draggable/node-resize/node-resize-finalize/node-resize-finalize.execution.ts index 0ead7479..4299732b 100644 --- a/projects/f-flow/src/f-draggable/node-resize/node-resize-finalize/node-resize-finalize.execution.ts +++ b/projects/f-flow/src/f-draggable/node-resize/node-resize-finalize/node-resize-finalize.execution.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { NodeResizeFinalizeRequest } from './node-resize-finalize.request'; import { FExecutionRegister, IExecution } from '@foblex/mediator'; import { FDraggableDataContext } from '../../f-draggable-data-context'; @@ -8,22 +8,19 @@ import { NodeResizeDragHandler } from '../node-resize.drag-handler'; @FExecutionRegister(NodeResizeFinalizeRequest) export class NodeResizeFinalizeExecution implements IExecution { - constructor( - private fDraggableDataContext: FDraggableDataContext, - ) { - } + private _fDraggableDataContext = inject(FDraggableDataContext); public handle(request: NodeResizeFinalizeRequest): void { if (!this._isValid()) { return; } - this.fDraggableDataContext.draggableItems.forEach((x) => { + this._fDraggableDataContext.draggableItems.forEach((x) => { x.onPointerUp?.(); }); } private _isValid(): boolean { - return this.fDraggableDataContext.draggableItems.some((x) => + return this._fDraggableDataContext.draggableItems.some((x) => x instanceof NodeResizeDragHandler ); } diff --git a/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/index.ts b/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/index.ts index efaed1b9..217c8c10 100644 --- a/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/index.ts +++ b/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/index.ts @@ -1,7 +1,3 @@ -export * from './providers'; - export * from './node-resize-preparation.execution'; export * from './node-resize-preparation.request'; - -export * from './node-resize-preparation.validator'; diff --git a/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/node-resize-preparation.execution.ts b/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/node-resize-preparation.execution.ts index 2c1ebc87..1738360b 100644 --- a/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/node-resize-preparation.execution.ts +++ b/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/node-resize-preparation.execution.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { NodeResizePreparationRequest } from './node-resize-preparation.request'; import { ITransformModel, Point } from '@foblex/2d'; import { FExecutionRegister, FMediator, IExecution } from '@foblex/mediator'; @@ -7,56 +7,74 @@ import { FDraggableDataContext } from '../../f-draggable-data-context'; import { SelectAndUpdateNodeLayerRequest, } from '../../../domain'; -import { IDraggableItem } from '../../i-draggable-item'; import { EFResizeHandleType, FNodeBase } from '../../../f-node'; import { NodeResizeDragHandler } from '../node-resize.drag-handler'; -import { getDataAttrValueFromClosestElementWithClass } from '@foblex/utils'; +import { getDataAttrValueFromClosestElementWithClass, isClosestElementHasClass } from '@foblex/utils'; @Injectable() @FExecutionRegister(NodeResizePreparationRequest) export class NodeResizePreparationExecution implements IExecution { - private get transform(): ITransformModel { - return this.fComponentsStore.fCanvas!.transform; - } + private _fMediator = inject(FMediator); + private _fComponentsStore = inject(FComponentsStore); + private _fDraggableDataContext = inject(FDraggableDataContext); - private get flowHost(): HTMLElement { - return this.fComponentsStore.fFlow!.hostElement; + private get _transform(): ITransformModel { + return this._fComponentsStore.fCanvas!.transform; } - constructor( - private fComponentsStore: FComponentsStore, - private fDraggableDataContext: FDraggableDataContext, - private fMediator: FMediator - ) { + private get _fHost(): HTMLElement { + return this._fComponentsStore.fFlow!.hostElement; } + private _fNode: FNodeBase | undefined; + public handle(request: NodeResizePreparationRequest): void { - this.selectAndUpdateNodeLayer(request.event.targetElement); + if(!this._isValid(request)) { + return; + } + + this._selectAndUpdateNodeLayer(); - const handleType = getDataAttrValueFromClosestElementWithClass(request.event.targetElement, 'fResizeHandleType', '.f-resize-handle'); - const itemsToDrag: IDraggableItem[] = [ + this._fDraggableDataContext.onPointerDownScale = this._transform.scale; + this._fDraggableDataContext.onPointerDownPosition = Point.fromPoint(request.event.getPosition()) + .elementTransform(this._fHost).div(this._transform.scale); + + this._fDraggableDataContext.draggableItems = [ new NodeResizeDragHandler( - this.fMediator, - this.getNode(request.event.targetElement), - EFResizeHandleType[ handleType as keyof typeof EFResizeHandleType ] + this._fNode!, + EFResizeHandleType[ this._getHandleType(request.event.targetElement) ] ) ]; + } + + private _isValid(request: NodeResizePreparationRequest): boolean { + return this._fDraggableDataContext.isEmpty() + && this._isDragHandleElement(request.event.targetElement) + && this._isNodeCanBeDragged(this._getNode(request.event.targetElement)); + } + + private _isDragHandleElement(element: HTMLElement): boolean { + return isClosestElementHasClass(element, '.f-resize-handle'); + } + + private _isNodeCanBeDragged(fNode?: FNodeBase): boolean { + return !!fNode && !fNode.fDraggingDisabled; + } - this.fDraggableDataContext.onPointerDownScale = this.transform.scale; - this.fDraggableDataContext.onPointerDownPosition = Point.fromPoint(request.event.getPosition()) - .elementTransform(this.flowHost).div(this.transform.scale); - this.fDraggableDataContext.draggableItems = itemsToDrag; + private _getNode(element: HTMLElement): FNodeBase | undefined { + this._fNode = this._fComponentsStore + .fNodes.find(x => x.isContains(element)); + return this._fNode; } - private selectAndUpdateNodeLayer(targetElement: HTMLElement) { - this.fMediator.send( - new SelectAndUpdateNodeLayerRequest(this.getNode(targetElement)) + private _selectAndUpdateNodeLayer() { + this._fMediator.execute( + new SelectAndUpdateNodeLayerRequest(this._fNode!) ); } - private getNode(targetElement: HTMLElement): FNodeBase { - return this.fComponentsStore - .fNodes.find(n => n.isContains(targetElement))!; + private _getHandleType(element: HTMLElement): keyof typeof EFResizeHandleType { + return getDataAttrValueFromClosestElementWithClass(element, 'fResizeHandleType', '.f-resize-handle'); } } diff --git a/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/node-resize-preparation.validator.ts b/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/node-resize-preparation.validator.ts deleted file mode 100644 index 5440407f..00000000 --- a/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/node-resize-preparation.validator.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Injectable } from '@angular/core'; -import { NodeResizePreparationRequest } from './node-resize-preparation.request'; -import { FValidatorRegister, IValidator } from '@foblex/mediator'; -import { FComponentsStore } from '../../../f-storage'; -import { FDraggableDataContext } from '../../f-draggable-data-context'; -import { FNodeBase } from '../../../f-node'; -import { isClosestElementHasClass } from '@foblex/utils'; - -@Injectable() -@FValidatorRegister(NodeResizePreparationRequest) -export class NodeResizePreparationValidator implements IValidator { - - constructor( - private fComponentsStore: FComponentsStore, - private fDraggableDataContext: FDraggableDataContext - ) { - } - - public handle(request: NodeResizePreparationRequest): boolean { - return this.isDragHandlesEmpty() - && this.isDragHandleElement(request.event.targetElement) - && this.isNodeCanBeDragged(this.getNode(request.event.targetElement)); - } - - private isDragHandlesEmpty(): boolean { - return !this.fDraggableDataContext.draggableItems.length; - } - - private getNode(targetElement: HTMLElement): FNodeBase | undefined { - return this.fComponentsStore - .fNodes.find(n => n.isContains(targetElement)); - } - - private isNodeCanBeDragged(node: FNodeBase | undefined): boolean { - return !!node && !node.fDraggingDisabled; - } - - private isDragHandleElement(targetElement: HTMLElement): boolean { - return isClosestElementHasClass(targetElement, '.f-resize-handle'); - } -} diff --git a/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/providers.ts b/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/providers.ts deleted file mode 100644 index 8a745a4c..00000000 --- a/projects/f-flow/src/f-draggable/node-resize/node-resize-preparation/providers.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NodeResizePreparationExecution } from './node-resize-preparation.execution'; -import { NodeResizePreparationValidator } from './node-resize-preparation.validator'; - -export const NODE_RESIZE_PREPARATION_PROVIDERS = [ - - NodeResizePreparationExecution, - - NodeResizePreparationValidator, -]; diff --git a/projects/f-flow/src/f-draggable/node-resize/node-resize.drag-handler.ts b/projects/f-flow/src/f-draggable/node-resize/node-resize.drag-handler.ts index 1ed10a18..b017f3b5 100644 --- a/projects/f-flow/src/f-draggable/node-resize/node-resize.drag-handler.ts +++ b/projects/f-flow/src/f-draggable/node-resize/node-resize.drag-handler.ts @@ -8,74 +8,90 @@ import { CalculateChangedSizeRequest } from './calculate-changed-size'; import { CalculateChangedPositionRequest } from './calculate-changed-position'; import { ApplyParentResizeRestrictionsRequest } from './apply-parent-resize-restrictions'; import { GetNormalizedElementRectRequest } from '../../domain'; +import { fInject } from '../f-injector'; export class NodeResizeDragHandler implements IDraggableItem { - private originalRect!: IRect; + private _fMediator = fInject(FMediator); - private restrictions!: INodeResizeRestrictions; - - private childRestrictions: (rect: IRect, restrictionsRect: IRect) => void = () => { - }; + private _originalRect!: IRect; + private _resizeRestrictions!: INodeResizeRestrictions; constructor( - private fMediator: FMediator, - public fNode: FNodeBase, - public fResizeHandleType: EFResizeHandleType, + private _fNode: FNodeBase, + private _fResizeHandleType: EFResizeHandleType, ) { } public prepareDragSequence(): void { - this.originalRect = this.fMediator.send(new GetNormalizedElementRectRequest(this.fNode.hostElement, false)); + this._originalRect = this._getOriginalNodeRect(); + this._resizeRestrictions = this._getNodeResizeRestrictions(); + } - this.restrictions = this.fMediator.send(new GetNodeResizeRestrictionsRequest(this.fNode, this.originalRect)); - if (this.restrictions.childRect) { - this.childRestrictions = (rect: IRect, restrictionsRect: IRect) => { - this.applyChildRestrictions(rect, restrictionsRect); - }; - } + private _getOriginalNodeRect(): IRect { + return this._fMediator.execute(new GetNormalizedElementRectRequest(this._fNode.hostElement, false)); } - public onPointerMove(difference: IPoint): void { - const changedRect = this.changePosition(difference, this.changeSize(difference, this.restrictions.minSize)); + private _getNodeResizeRestrictions(): INodeResizeRestrictions { + return this._fMediator.execute( + new GetNodeResizeRestrictionsRequest(this._fNode, this._originalRect) + ); + } - this.childRestrictions(changedRect, this.restrictions.childRect!); - this.applyParentRestrictions(changedRect, this.restrictions.parentRect); + public onPointerMove(difference: IPoint): void { + this._applyResizeChanges(this._calculateChangedRect(difference)); + } - this.fNode.updatePosition(changedRect); - this.fNode.updateSize(changedRect); - this.fNode.redraw(); + private _calculateChangedRect(difference: IPoint): IRect { + return this._calculatePosition(difference, this._calculateSize(difference, this._resizeRestrictions.minimumSize)); } - private changeSize(difference: IPoint, minSize: ISize): IRect { - return this.fMediator.send( - new CalculateChangedSizeRequest(this.originalRect, difference, this.fResizeHandleType) + private _calculateSize(difference: IPoint, minimumSize: ISize): IRect { + return this._fMediator.send( + new CalculateChangedSizeRequest(this._originalRect, difference, this._fResizeHandleType) ); } - private changePosition(difference: IPoint, changedRect: IRect): IRect { - return this.fMediator.send( - new CalculateChangedPositionRequest(this.originalRect, changedRect, difference, this.fResizeHandleType) + private _calculatePosition(difference: IPoint, changedSize: IRect): IRect { + return this._fMediator.send( + new CalculateChangedPositionRequest(this._originalRect, changedSize, difference, this._fResizeHandleType) ); } - private applyChildRestrictions(rect: IRect, restrictionsRect: IRect): void { - this.fMediator.send( - new ApplyChildResizeRestrictionsRequest(rect, restrictionsRect) + private _applyResizeChanges(changedRect: IRect): void { + if(this._resizeRestrictions.childrenBounds) { + this._applyChildRestrictions(changedRect, this._resizeRestrictions.childrenBounds); + } + + this._applyParentRestrictions(changedRect, this._resizeRestrictions.parentBounds); + this._updateNodeRendering(changedRect); + } + + private _updateNodeRendering(changedRect: IRect): void { + this._fNode.updatePosition(changedRect); + this._fNode.updateSize(changedRect); + this._fNode.redraw(); + } + + private _applyChildRestrictions(changedRect: IRect, restrictions: IRect): void { + this._fMediator.execute( + new ApplyChildResizeRestrictionsRequest(changedRect, restrictions) ); } - private applyParentRestrictions(rect: IRect, restrictionsRect: IRect): void { - this.fMediator.send( - new ApplyParentResizeRestrictionsRequest(rect, restrictionsRect) + private _applyParentRestrictions(changedRect: IRect, restrictions: IRect): void { + this._fMediator.execute( + new ApplyParentResizeRestrictionsRequest(changedRect, restrictions) ); } public onPointerUp(): void { - this.fNode.sizeChange.emit( - RectExtensions.initialize( - this.fNode.position.x, this.fNode.position.y, this.fNode.size?.width, this.fNode.size?.height - ) + this._fNode.sizeChange.emit(this._getNewRect()); + } + + private _getNewRect(): IRect { + return RectExtensions.initialize( + this._fNode.position.x, this._fNode.position.y, this._fNode.size?.width, this._fNode.size?.height ); } } diff --git a/projects/f-flow/src/f-draggable/node-resize/providers.ts b/projects/f-flow/src/f-draggable/node-resize/providers.ts index b3da024e..a18f3891 100644 --- a/projects/f-flow/src/f-draggable/node-resize/providers.ts +++ b/projects/f-flow/src/f-draggable/node-resize/providers.ts @@ -1,5 +1,5 @@ import { NODE_RESIZE_FINALIZE_PROVIDERS } from './node-resize-finalize'; -import { NODE_RESIZE_PREPARATION_PROVIDERS } from './node-resize-preparation'; +import { NodeResizePreparationExecution } from './node-resize-preparation'; import { GetNodeResizeRestrictionsExecution } from './get-node-resize-restrictions'; import { ApplyChildResizeRestrictionsExecution } from './apply-child-resize-restrictions'; import { ApplyParentResizeRestrictionsExecution } from './apply-parent-resize-restrictions'; @@ -23,5 +23,5 @@ export const NODE_RESIZE_PROVIDERS = [ ...NODE_RESIZE_FINALIZE_PROVIDERS, - ...NODE_RESIZE_PREPARATION_PROVIDERS, + NodeResizePreparationExecution, ]; diff --git a/projects/f-flow/src/f-draggable/node-resize/resize-direction.ts b/projects/f-flow/src/f-draggable/node-resize/resize-direction.ts index 7f01ed63..aea46afd 100644 --- a/projects/f-flow/src/f-draggable/node-resize/resize-direction.ts +++ b/projects/f-flow/src/f-draggable/node-resize/resize-direction.ts @@ -2,11 +2,19 @@ import { EFResizeHandleType } from '../../f-node'; export const RESIZE_DIRECTIONS = { + [ EFResizeHandleType.LEFT ]: { x: -1, y: 0 }, + [ EFResizeHandleType.LEFT_TOP ]: { x: -1, y: -1 }, + [ EFResizeHandleType.TOP ]: { x: 0, y: -1 }, + [ EFResizeHandleType.RIGHT_TOP ]: { x: 1, y: -1 }, + [ EFResizeHandleType.RIGHT ]: { x: 1, y: 0 }, + [ EFResizeHandleType.RIGHT_BOTTOM ]: { x: 1, y: 1 }, + [ EFResizeHandleType.BOTTOM ]: { x: 0, y: 1 }, + [ EFResizeHandleType.LEFT_BOTTOM ]: { x: -1, y: 1 }, }; diff --git a/projects/f-flow/src/f-draggable/node/base-connection.drag-handler.ts b/projects/f-flow/src/f-draggable/node/base-connection.drag-handler.ts new file mode 100644 index 00000000..0811a5ca --- /dev/null +++ b/projects/f-flow/src/f-draggable/node/base-connection.drag-handler.ts @@ -0,0 +1,80 @@ +import { ILine, IPoint, PointExtensions, RoundedRect } from '@foblex/2d'; +import { FMediator } from '@foblex/mediator'; +import { CalculateConnectionLineByBehaviorRequest, GetConnectorAndRectRequest, IConnectorAndRect } from '../../domain'; +import { FConnectorBase } from '../../f-connectors'; +import { FComponentsStore } from '../../f-storage'; +import { fInject } from '../f-injector'; +import { FConnectionBase } from '../../f-connection'; + +export class BaseConnectionDragHandler { + + private _fMediator = fInject(FMediator); + private _fComponentStore = fInject(FComponentsStore); + + private _fOutputWithRect!: IConnectorAndRect; + private _fInputWithRect!: IConnectorAndRect; + + private get _fOutput(): FConnectorBase { + const result = this._fComponentStore.fOutputs.find((x) => x.fId === this.fConnection.fOutputId)!; + if (!result) { + throw new Error(this._connectorNotFoundPrefix(`fOutput with id ${ this.fConnection.fOutputId } not found`)); + } + return result; + } + + private get _fInput(): FConnectorBase { + const result = this._fComponentStore.fInputs.find((x) => x.fId === this.fConnection.fInputId)!; + if (!result) { + throw new Error(this._connectorNotFoundPrefix(`fInput with id ${ this.fConnection.fInputId } not found`)); + } + return result; + } + + private _sourceDifference = PointExtensions.initialize(); + private _targetDifference = PointExtensions.initialize(); + + constructor( + public fConnection: FConnectionBase, + ) { + this._initialize(); + } + + private _initialize(): void { + this._fOutputWithRect = this._fMediator.execute(new GetConnectorAndRectRequest(this._fOutput)); + this._fInputWithRect = this._fMediator.execute(new GetConnectorAndRectRequest(this._fInput)); + } + + public setSourceDifference(difference: IPoint): void { + this._sourceDifference = difference; + } + + public setTargetDifference(difference: IPoint): void { + this._targetDifference = difference; + } + + protected redraw(): void { + this._redrawConnection(this._recalculateConnection()); + } + + private _recalculateConnection(): ILine { + return this._fMediator.execute(new CalculateConnectionLineByBehaviorRequest( + RoundedRect.fromRoundedRect(this._fOutputWithRect.fRect).addPoint(this._sourceDifference), + RoundedRect.fromRoundedRect(this._fInputWithRect.fRect).addPoint(this._targetDifference), + this.fConnection.fBehavior, + this._fOutputWithRect.fConnector.fConnectableSide, + this._fInputWithRect.fConnector.fConnectableSide + )); + } + + private _redrawConnection(line: ILine): void { + this.fConnection.setLine( + line.point1, this._fOutputWithRect.fConnector.fConnectableSide, + line.point2, this._fInputWithRect.fConnector.fConnectableSide + ); + this.fConnection.redraw(); + } + + private _connectorNotFoundPrefix(message: string): string { + return `ConnectionDragHandler Error: Connection From (fOutput)${ this.fConnection.fOutputId } To (fInput)${ this.fConnection.fInputId }. ${ message }. Please ensure that all f-connections are associated with existing connectors`; + } +} diff --git a/projects/f-flow/src/f-draggable/node/connection-base-drag-handler.ts b/projects/f-flow/src/f-draggable/node/connection-base-drag-handler.ts deleted file mode 100644 index 79a91a76..00000000 --- a/projects/f-flow/src/f-draggable/node/connection-base-drag-handler.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { IDraggableItem } from '../i-draggable-item'; -import { FConnectionBase } from '../../f-connection'; -import { FConnectorBase } from '../../f-connectors'; -import { FMediator } from '@foblex/mediator'; -import { Directive } from '@angular/core'; -import { ILine, IMinMaxPoint, IPoint } from '@foblex/2d'; -import { FComponentsStore } from '../../f-storage'; -import { GetConnectorAndRectRequest, IConnectorAndRect } from '../../domain'; - -@Directive() -export abstract class ConnectionBaseDragHandler implements IDraggableItem { - - protected fOutputWithRect!: IConnectorAndRect; - protected fInputWithRect!: IConnectorAndRect; - - protected constructor( - protected fMediator: FMediator, - protected fComponentsStore: FComponentsStore, - public connection: FConnectionBase, - ) { - } - - public prepareDragSequence(): void { - this.fOutputWithRect = this.fMediator.send(new GetConnectorAndRectRequest(this.getOutput())); - this.fInputWithRect = this.fMediator.send(new GetConnectorAndRectRequest(this.getInput())); - } - - private getOutput(): FConnectorBase { - const result = this.fComponentsStore.fOutputs.find((x) => x.fId === this.connection.fOutputId)!; - if (!result) { - throw new Error(this.connectorNotFoundPrefix(`fOutput with id ${ this.connection.fOutputId } not found`)); - } - return result; - } - - private getInput(): FConnectorBase { - const result = this.fComponentsStore.fInputs.find((x) => x.fId === this.connection.fInputId)!; - if (!result) { - throw new Error(this.connectorNotFoundPrefix(`fInput with id ${ this.connection.fInputId } not found`)); - } - return result; - } - - private connectorNotFoundPrefix(message: string): string { - return `ConnectionDragHandler Error: Connection From (fOutput)${ this.connection.fOutputId } To (fInput)${ this.connection.fInputId }. ${ message }. Please ensure that all f-connections are associated with existing connectors`; - } - - public abstract onPointerMove(difference: IPoint): void; - - protected getRestrictedDifference(difference: IPoint, restrictions: IMinMaxPoint): IPoint { - return { - x: Math.min(Math.max(difference.x, restrictions.min.x), restrictions.max.x), - y: Math.min(Math.max(difference.y, restrictions.min.y), restrictions.max.y) - } - } - - protected redrawConnection(line: ILine): void { - this.connection.setLine(line.point1, this.fOutputWithRect.fConnector.fConnectableSide, line.point2, this.fInputWithRect.fConnector.fConnectableSide); - this.connection.redraw(); - } -} diff --git a/projects/f-flow/src/f-draggable/node/connection-source.drag-handler.ts b/projects/f-flow/src/f-draggable/node/connection-source.drag-handler.ts deleted file mode 100644 index 9ae89d3a..00000000 --- a/projects/f-flow/src/f-draggable/node/connection-source.drag-handler.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ILine, IMinMaxPoint, IPoint, RoundedRect } from '@foblex/2d'; -import { - CalculateConnectionLineByBehaviorRequest -} from '../../domain'; -import { FConnectionBase } from '../../f-connection'; -import { FMediator } from '@foblex/mediator'; -import { ConnectionBaseDragHandler } from './connection-base-drag-handler'; -import { FComponentsStore } from '../../f-storage'; - -export class ConnectionSourceDragHandler extends ConnectionBaseDragHandler { - - constructor( - fMediator: FMediator, - fComponentsStore: FComponentsStore, - connection: FConnectionBase, - public restrictions: IMinMaxPoint, - ) { - super(fMediator, fComponentsStore, connection); - } - - public override onPointerMove(difference: IPoint): void { - this.redrawConnection(this.getNewLineValue(difference)); - } - - private getNewLineValue(difference: IPoint): ILine { - return this.fMediator.execute(new CalculateConnectionLineByBehaviorRequest( - RoundedRect.fromRoundedRect(this.fOutputWithRect.fRect).addPoint(this.getRestrictedDifference({ ...difference }, this.restrictions)), - this.fInputWithRect.fRect, - this.connection.fBehavior, - this.fOutputWithRect.fConnector.fConnectableSide, - this.fInputWithRect.fConnector.fConnectableSide - )); - } -} diff --git a/projects/f-flow/src/f-draggable/node/connection-target.drag-handler.ts b/projects/f-flow/src/f-draggable/node/connection-target.drag-handler.ts deleted file mode 100644 index 3a9b633b..00000000 --- a/projects/f-flow/src/f-draggable/node/connection-target.drag-handler.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ILine, IMinMaxPoint, IPoint, RoundedRect } from '@foblex/2d'; -import { - CalculateConnectionLineByBehaviorRequest, -} from '../../domain'; -import { FConnectionBase } from '../../f-connection'; -import { FMediator } from '@foblex/mediator'; -import { ConnectionBaseDragHandler } from './connection-base-drag-handler'; -import { FComponentsStore } from '../../f-storage'; - -export class ConnectionTargetDragHandler extends ConnectionBaseDragHandler { - - constructor( - fMediator: FMediator, - fComponentsStore: FComponentsStore, - connection: FConnectionBase, - public restrictions: IMinMaxPoint, - ) { - super(fMediator, fComponentsStore, connection); - } - - public override onPointerMove(difference: IPoint): void { - this.redrawConnection(this.getNewLineValue(difference)); - } - - private getNewLineValue(difference: IPoint): ILine { - return this.fMediator.execute(new CalculateConnectionLineByBehaviorRequest( - this.fOutputWithRect.fRect, - RoundedRect.fromRoundedRect(this.fInputWithRect.fRect).addPoint(this.getRestrictedDifference({ ...difference }, this.restrictions)), - this.connection.fBehavior, - this.fOutputWithRect.fConnector.fConnectableSide, - this.fInputWithRect.fConnector.fConnectableSide - )); - } -} diff --git a/projects/f-flow/src/f-draggable/node/connection.drag-handler.ts b/projects/f-flow/src/f-draggable/node/connection.drag-handler.ts deleted file mode 100644 index f1534245..00000000 --- a/projects/f-flow/src/f-draggable/node/connection.drag-handler.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ILine, IMinMaxPoint, IPoint, RoundedRect } from '@foblex/2d'; -import { FConnectionBase } from '../../f-connection'; -import { FMediator } from '@foblex/mediator'; -import { - CalculateConnectionLineByBehaviorRequest, -} from '../../domain'; -import { ConnectionBaseDragHandler } from './connection-base-drag-handler'; -import { FComponentsStore } from '../../f-storage'; - -export class ConnectionDragHandler extends ConnectionBaseDragHandler { - - private sourceRestrictions!: IMinMaxPoint; - private targetRestrictions!: IMinMaxPoint; - - constructor( - fMediator: FMediator, - fComponentsStore: FComponentsStore, - connection: FConnectionBase, - ) { - super(fMediator, fComponentsStore, connection); - } - - public setOutputRestrictions(restrictions: IMinMaxPoint) { - this.sourceRestrictions = restrictions; - } - - public setInputRestrictions(restrictions: IMinMaxPoint) { - this.targetRestrictions = restrictions; - } - - public override onPointerMove(difference: IPoint): void { - this.redrawConnection(this.getNewLineValue(difference)); - } - - private getNewLineValue(difference: IPoint): ILine { - return this.fMediator.execute(new CalculateConnectionLineByBehaviorRequest( - RoundedRect.fromRoundedRect(this.fOutputWithRect.fRect).addPoint(this.getRestrictedDifference({ ...difference }, this.sourceRestrictions)), - RoundedRect.fromRoundedRect(this.fInputWithRect.fRect).addPoint(this.getRestrictedDifference({ ...difference }, this.targetRestrictions)), - this.connection.fBehavior, - this.fOutputWithRect.fConnector.fConnectableSide, - this.fInputWithRect.fConnector.fConnectableSide - )); - } -} diff --git a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/create-move-nodes-drag-model-from-selection.execution.ts b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/create-move-nodes-drag-model-from-selection.execution.ts index 33c61148..67ae8e9a 100644 --- a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/create-move-nodes-drag-model-from-selection.execution.ts +++ b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/create-move-nodes-drag-model-from-selection.execution.ts @@ -13,12 +13,12 @@ import { PutOutputConnectionHandlersToArrayRequest } from './domain/put-output-c import { PutInputConnectionHandlersToArrayRequest } from './domain/put-input-connection-handlers-to-array'; -import { NodeResizeByChildDragHandler } from '../node-resize-by-child.drag-handler'; import { IsArrayHasParentNodeRequest } from '../../domain'; import { GetDeepChildrenNodesAndGroupsRequest, GetParentNodesRequest } from '../../../domain'; import { flatMap } from '@foblex/utils'; import { CalculateCommonNodeMoveRestrictionsRequest } from './domain/calculate-common-node-move-restrictions'; import { IMinMaxPoint } from '@foblex/2d'; +import { BaseConnectionDragHandler } from '../base-connection.drag-handler'; @Injectable() @FExecutionRegister(CreateMoveNodesDragModelFromSelectionRequest) @@ -34,7 +34,7 @@ export class CreateMoveNodesDragModelFromSelectionExecution this._getDraggedNodes(request.nodeWithDisabledSelection) ); return this.getDragHandlersWithConnections( - this.getDragHandlersFromNodes(fItemsToDrag), + this._mapToNodeDragHandlers(fItemsToDrag), this.getAllOutputIds(fItemsToDrag), this.getAllInputIds(fItemsToDrag) ); @@ -116,26 +116,21 @@ export class CreateMoveNodesDragModelFromSelectionExecution return this._fComponentsStore.fInputs.filter((x) => node.isContains(x.hostElement)); } - private getDragHandlersFromNodes(items: INodeWithDistanceRestrictions[]): IDraggableItem[] { - let result: IDraggableItem[] = []; - - items.forEach((node) => { - result.push( - new NodeDragHandler(this._fComponentsStore, node.fDraggedNode, { min: node.min, max: node.max }), - ...(node.fParentNodes || []).map(() => new NodeResizeByChildDragHandler(this._fDraggableDataContext)) - ); - }); - return result; + private _mapToNodeDragHandlers(items: INodeWithDistanceRestrictions[]): NodeDragHandler[] { + return items.map((x) => new NodeDragHandler( + x.fDraggedNode, { min: x.min, max: x.max }) + ); } private getDragHandlersWithConnections( - handlers: IDraggableItem[], outputIds: string[], inputIds: string[] + handlers: NodeDragHandler[], outputIds: string[], inputIds: string[] ): IDraggableItem[] { - let result: IDraggableItem[] = handlers; - handlers.filter((x) => x instanceof NodeDragHandler).forEach((x) => { - this._fMediator.execute(new PutOutputConnectionHandlersToArrayRequest(x, inputIds, result)); - this._fMediator.execute(new PutInputConnectionHandlersToArrayRequest(x, outputIds, result)); + const fConnectionHandlers: BaseConnectionDragHandler[] = []; + handlers.forEach((fNodeHandler) => { + this._fMediator.execute(new PutOutputConnectionHandlersToArrayRequest(fNodeHandler, inputIds, fConnectionHandlers)); + this._fMediator.execute(new PutInputConnectionHandlersToArrayRequest(fNodeHandler, outputIds, fConnectionHandlers)); }); - return result; + + return handlers; } } diff --git a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/calculate-common-node-move-restrictions/calculate-common-node-move-restrictions.execution.ts b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/calculate-common-node-move-restrictions/calculate-common-node-move-restrictions.execution.ts index f68a62dd..a0470eae 100644 --- a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/calculate-common-node-move-restrictions/calculate-common-node-move-restrictions.execution.ts +++ b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/calculate-common-node-move-restrictions/calculate-common-node-move-restrictions.execution.ts @@ -1,22 +1,35 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { CalculateCommonNodeMoveRestrictionsRequest } from './calculate-common-node-move-restrictions.request'; import { IMinMaxPoint, IPoint } from '@foblex/2d'; import { FExecutionRegister, IExecution } from '@foblex/mediator'; import { INodeWithDistanceRestrictions } from '../../i-node-with-distance-restrictions'; +import { FComponentsStore } from '../../../../../f-storage'; @Injectable() @FExecutionRegister(CalculateCommonNodeMoveRestrictionsRequest) export class CalculateCommonNodeMoveRestrictionsExecution implements IExecution { + private _fComponentsStore = inject(FComponentsStore); + + private get _vCellSize(): number { + return this._fComponentsStore.fDraggable!.vCellSize; + } + + private get _hCellSize(): number { + return this._fComponentsStore.fDraggable!.hCellSize; + } + public handle(request: CalculateCommonNodeMoveRestrictionsRequest): IMinMaxPoint { return this._calculateCommonRestrictions(request.restrictions); } private _calculateCommonRestrictions(restrictions: INodeWithDistanceRestrictions[]): IMinMaxPoint { - return restrictions.reduce((result: IMinMaxPoint, x) => - this._clampRestrictions(result, x), restrictions[0] - ); + this._snapLimitToGrid(restrictions[0]); + return restrictions.reduce((result: IMinMaxPoint, x) => { + this._snapLimitToGrid(x); + return this._clampRestrictions(result, x); + }, restrictions[0]); } private _clampRestrictions(common: IMinMaxPoint, x: INodeWithDistanceRestrictions): IMinMaxPoint { @@ -38,4 +51,25 @@ export class CalculateCommonNodeMoveRestrictionsExecution y: Math.min(common.y, node.y), } } + + private _snapLimitToGrid(restriction: INodeWithDistanceRestrictions): void { + const { min, max, fDraggedNode } = restriction; + const position = fDraggedNode.position; + restriction.min = { + x: this._snapLimitToGridMinimum(position.x + min.x, this._hCellSize) - position.x, + y: this._snapLimitToGridMinimum(position.y + min.y, this._vCellSize) - position.y, + }; + restriction.max = { + x: this._snapLimitToGridMaximum(position.x + max.x, this._hCellSize) - position.x, + y: this._snapLimitToGridMaximum(position.y + max.y, this._vCellSize) - position.y, + }; + } + + private _snapLimitToGridMinimum(value: number, cellSize: number): number { + return Math.ceil(value / cellSize) * cellSize; + } + + private _snapLimitToGridMaximum(value: number, cellSize: number): number { + return Math.floor(value / cellSize) * cellSize; + } } diff --git a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/calculate-node-move-restrictions/calculate-node-move-restrictions.execution.ts b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/calculate-node-move-restrictions/calculate-node-move-restrictions.execution.ts index 185720d1..ffc90513 100644 --- a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/calculate-node-move-restrictions/calculate-node-move-restrictions.execution.ts +++ b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/calculate-node-move-restrictions/calculate-node-move-restrictions.execution.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core'; import { CalculateNodeMoveRestrictionsRequest } from './calculate-node-move-restrictions.request'; -import { IMinMaxPoint, IRect, PointExtensions } from '@foblex/2d'; +import { IMinMaxPoint, IPoint, IRect, PointExtensions } from '@foblex/2d'; import { FExecutionRegister, FMediator, IExecution } from '@foblex/mediator'; import { GetNormalizedParentNodeRectRequest } from '../../../../domain'; import { FNodeBase } from '../../../../../f-node'; @@ -18,24 +18,35 @@ export class CalculateNodeMoveRestrictionsExecution return { ...DEFAULT_RESTRICTIONS }; } - const fParentNodeRect = this._getParentNodeRect(request.fNode); - const fCurrentNodeRect = this._getNodeRect(request.fNode); + return this._calculateDifference( + this._fParentRect(request.fNode), this._fNodeRect(request.fNode) + ); + } + private _fNodeRect(fNode: FNodeBase): IRect { + return this._fMediator.execute(new GetNormalizedElementRectRequest(fNode.hostElement, false)); + } + + private _fParentRect(fNode: FNodeBase): IRect { + return this._fMediator.execute(new GetNormalizedParentNodeRectRequest(fNode)); + } + + private _calculateDifference(fParentRect: IRect, fRect: IRect): IMinMaxPoint { return { - min: PointExtensions.initialize(fParentNodeRect.x - fCurrentNodeRect.x, fParentNodeRect.y - fCurrentNodeRect.y), - max: PointExtensions.initialize( - (fParentNodeRect.x + fParentNodeRect.width) - (fCurrentNodeRect.x + fCurrentNodeRect.width), - (fParentNodeRect.y + fParentNodeRect.height) - (fCurrentNodeRect.y + fCurrentNodeRect.height), - ) + min: this._calculateMinimumDifference(fParentRect, fRect), + max: this._calculateMaximumDifference(fParentRect, fRect) }; } - private _getNodeRect(fNode: FNodeBase): IRect { - return this._fMediator.execute(new GetNormalizedElementRectRequest(fNode.hostElement, false)); + private _calculateMinimumDifference(fParentRect: IRect, fRect: IRect): IPoint { + return PointExtensions.initialize(fParentRect.x - fRect.x, fParentRect.y - fRect.y); } - private _getParentNodeRect(fNode: FNodeBase): IRect { - return this._fMediator.execute(new GetNormalizedParentNodeRectRequest(fNode)); + private _calculateMaximumDifference(fParentRect: IRect, fRect: IRect): IPoint { + return PointExtensions.initialize( + (fParentRect.x + fParentRect.width) - (fRect.x + fRect.width), + (fParentRect.y + fParentRect.height) - (fRect.y + fRect.height), + ); } } diff --git a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-input-connection-handlers-to-array/put-input-connection-handlers-to-array.execution.ts b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-input-connection-handlers-to-array/put-input-connection-handlers-to-array.execution.ts index c733369d..02db4b76 100644 --- a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-input-connection-handlers-to-array/put-input-connection-handlers-to-array.execution.ts +++ b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-input-connection-handlers-to-array/put-input-connection-handlers-to-array.execution.ts @@ -1,21 +1,18 @@ import { inject, Injectable } from '@angular/core'; import { PutInputConnectionHandlersToArrayRequest } from './put-input-connection-handlers-to-array.request'; import { FComponentsStore } from '../../../../../f-storage'; -import { FExecutionRegister, FMediator, IExecution } from '@foblex/mediator'; +import { FExecutionRegister, IExecution } from '@foblex/mediator'; import { FNodeBase } from '../../../../../f-node'; -import { NodeDragHandler } from '../../../node.drag-handler'; -import { IDraggableItem } from '../../../../i-draggable-item'; -import { ConnectionDragHandler } from '../../../connection.drag-handler'; -import { FConnectorBase } from '../../../../../f-connectors'; import { FConnectionBase } from '../../../../../f-connection'; -import { ConnectionTargetDragHandler } from '../../../connection-target.drag-handler'; +import { BaseConnectionDragHandler } from '../../../base-connection.drag-handler'; +import { SourceTargetConnectionDragHandler } from '../../../source-target-connection.drag-handler'; +import { TargetConnectionDragHandler } from '../../../target-connection.drag-handler'; @Injectable() @FExecutionRegister(PutInputConnectionHandlersToArrayRequest) export class PutInputConnectionHandlersToArrayExecution implements IExecution { - private _fMediator = inject(FMediator); private _fComponentsStore = inject(FComponentsStore); private get _fConnections(): FConnectionBase[] { @@ -23,53 +20,43 @@ export class PutInputConnectionHandlersToArrayExecution } public handle(request: PutInputConnectionHandlersToArrayRequest): void { - this.getInputConnections(request.nodeDragHandler.fNode).forEach((connection) => { - const index = this.getExistingConnectionHandlerIndex(request.result, connection); - if (index !== -1) { - this.updateExistingConnectionHandler(request.result, index, request.nodeDragHandler); - } else { - request.result.push(this.createNewConnectionHandler(request.nodeDragHandler, request.outputIds, connection)); - } + this._getInputConnections(request.fDragHandler.fNode).forEach((fConnection) => { + this._createAndSetConnectionToNodeHandler(fConnection, request); }); } - public getInputConnections(node: FNodeBase): FConnectionBase[] { - const inputIds = new Set(this.getInputsForNode(node).map((x) => x.fId)); - return this._fConnections.filter((x) => inputIds.has(x.fInputId)); + private _getInputConnections(fNode: FNodeBase): FConnectionBase[] { + const ids = new Set(this._getNodeInputIds(fNode)); + return this._fConnections.filter((x) => ids.has(x.fInputId)); } - private getInputsForNode(node: FNodeBase): FConnectorBase[] { - return this._fComponentsStore.fInputs.filter((x) => node.isContains(x.hostElement)); + private _getNodeInputIds(fNode: FNodeBase): string[] { + return this._fComponentsStore.fInputs + .filter((x) => fNode.isContains(x.hostElement)) + .map((x) => x.fId); } - private getExistingConnectionHandlerIndex(result: IDraggableItem[], connection: FConnectionBase): number { - return result.findIndex( - (x) => x instanceof ConnectionDragHandler && x.connection.fId === connection.fId - ); - } - - private createNewConnectionHandler(nodeDragHandler: NodeDragHandler, outputIds: string[], connection: FConnectionBase): IDraggableItem { - let result: IDraggableItem | undefined; - if (outputIds.includes(connection.fOutputId)) { - result = this.getNewConnectionHandler(connection, nodeDragHandler); - } else { - result = this.getNewSourceConnectionHandler(connection, nodeDragHandler); + private _createAndSetConnectionToNodeHandler(fConnection: FConnectionBase, request: PutInputConnectionHandlersToArrayRequest): void { + let fHandler = this._getExistingConnectionHandler(request.result, fConnection); + if (!fHandler) { + fHandler = this._createConnectionHandler(request.outputIds, fConnection); + request.result.push(fHandler); } - return result; + request.fDragHandler.fTargetHandlers.push(fHandler); } - private getNewConnectionHandler(connection: FConnectionBase, nodeDragHandler: NodeDragHandler): ConnectionDragHandler { - const handler = new ConnectionDragHandler(this._fMediator, this._fComponentsStore, connection); - handler.setInputRestrictions(nodeDragHandler.restrictions); - return handler; + private _getExistingConnectionHandler(fHandlers: BaseConnectionDragHandler[], fConnection: FConnectionBase): BaseConnectionDragHandler | undefined { + return fHandlers.find((x) => x.fConnection.fId === fConnection.fId); } - private updateExistingConnectionHandler(result: IDraggableItem[], index: number, nodeDragHandler: NodeDragHandler): void { - (result[ index ] as ConnectionDragHandler).setInputRestrictions(nodeDragHandler.restrictions); - } - - private getNewSourceConnectionHandler(connection: FConnectionBase, nodeDragHandler: NodeDragHandler): ConnectionTargetDragHandler { - return new ConnectionTargetDragHandler(this._fMediator, this._fComponentsStore, connection, nodeDragHandler.restrictions); + private _createConnectionHandler(outputIds: string[], fConnection: FConnectionBase): BaseConnectionDragHandler { + let result: BaseConnectionDragHandler | undefined; + if (outputIds.includes(fConnection.fOutputId)) { + result = new SourceTargetConnectionDragHandler(fConnection); + } else { + result = new TargetConnectionDragHandler(fConnection); + } + return result; } } diff --git a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-input-connection-handlers-to-array/put-input-connection-handlers-to-array.request.ts b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-input-connection-handlers-to-array/put-input-connection-handlers-to-array.request.ts index 24d247ab..f1711357 100644 --- a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-input-connection-handlers-to-array/put-input-connection-handlers-to-array.request.ts +++ b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-input-connection-handlers-to-array/put-input-connection-handlers-to-array.request.ts @@ -1,12 +1,12 @@ import { NodeDragHandler } from '../../../node.drag-handler'; -import { IDraggableItem } from '../../../../i-draggable-item'; +import { BaseConnectionDragHandler } from '../../../base-connection.drag-handler'; export class PutInputConnectionHandlersToArrayRequest { constructor( - public nodeDragHandler: NodeDragHandler, + public fDragHandler: NodeDragHandler, public outputIds: string[], - public result: IDraggableItem[] + public result: BaseConnectionDragHandler[] ) { } } diff --git a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-output-connection-handlers-to-array/put-output-connection-handlers-to-array.execution.ts b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-output-connection-handlers-to-array/put-output-connection-handlers-to-array.execution.ts index 2c7deff4..61221731 100644 --- a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-output-connection-handlers-to-array/put-output-connection-handlers-to-array.execution.ts +++ b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-output-connection-handlers-to-array/put-output-connection-handlers-to-array.execution.ts @@ -1,78 +1,62 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { PutOutputConnectionHandlersToArrayRequest } from './put-output-connection-handlers-to-array.request'; import { FComponentsStore } from '../../../../../f-storage'; -import { FExecutionRegister, FMediator, IExecution } from '@foblex/mediator'; +import { FExecutionRegister, IExecution } from '@foblex/mediator'; import { FNodeBase } from '../../../../../f-node'; -import { NodeDragHandler } from '../../../node.drag-handler'; -import { IDraggableItem } from '../../../../i-draggable-item'; -import { ConnectionDragHandler } from '../../../connection.drag-handler'; -import { ConnectionSourceDragHandler } from '../../../connection-source.drag-handler'; -import { FConnectorBase } from '../../../../../f-connectors'; import { FConnectionBase } from '../../../../../f-connection'; +import { SourceTargetConnectionDragHandler } from '../../../source-target-connection.drag-handler'; +import { BaseConnectionDragHandler } from '../../../base-connection.drag-handler'; +import { SourceConnectionDragHandler } from '../../../source-connection.drag-handler'; @Injectable() @FExecutionRegister(PutOutputConnectionHandlersToArrayRequest) export class PutOutputConnectionHandlersToArrayExecution implements IExecution { - private get fConnections(): FConnectionBase[] { - return this.fComponentsStore.fConnections; - } + private _fComponentsStore = inject(FComponentsStore); - constructor( - private fComponentsStore: FComponentsStore, - private fMediator: FMediator - ) { + private get _fConnections(): FConnectionBase[] { + return this._fComponentsStore.fConnections; } public handle(request: PutOutputConnectionHandlersToArrayRequest): void { - this.getOutputConnections(request.nodeDragHandler.fNode).forEach((connection) => { - const index = this.getExistingConnectionHandlerIndex(request.result, connection); - if (index !== -1) { - this.updateExistingConnectionHandler(request.result, index, request.nodeDragHandler); - } else { - request.result.push(this.createNewConnectionHandler(request.nodeDragHandler, request.inputIds, connection)); - } + this._getOutputConnections(request.fDragHandler.fNode).forEach((fConnection) => { + this._createAndSetConnectionToNodeHandler(fConnection, request); }); } - public getOutputConnections(node: FNodeBase): FConnectionBase[] { - const outputsIds = new Set(this.getOutputsForNode(node).map((x) => x.fId)); - return this.fConnections.filter((x) => outputsIds.has(x.fOutputId)); - } - - private getOutputsForNode(node: FNodeBase): FConnectorBase[] { - return this.fComponentsStore.fOutputs.filter((x) => node.isContains(x.hostElement)); + public _getOutputConnections(node: FNodeBase): FConnectionBase[] { + const ids = new Set(this._getNodeOutputIds(node)); + return this._fConnections.filter((x) => ids.has(x.fOutputId)); } - private getExistingConnectionHandlerIndex(result: IDraggableItem[], connection: FConnectionBase): number { - return result.findIndex( - (x) => x instanceof ConnectionDragHandler && x.connection.fId === connection.fId - ); + private _getNodeOutputIds(node: FNodeBase): string[] { + return this._fComponentsStore.fOutputs + .filter((x) => node.isContains(x.hostElement)) + .map((x) => x.fId); } - private createNewConnectionHandler(nodeDragHandler: NodeDragHandler, inputIds: string[], connection: FConnectionBase): IDraggableItem { - let result: IDraggableItem; - if (inputIds.includes(connection.fInputId)) { - result = this.getNewConnectionHandler(connection, nodeDragHandler); - } else { - result = this.getNewSourceConnectionHandler(connection, nodeDragHandler); + private _createAndSetConnectionToNodeHandler(fConnection: FConnectionBase, request: PutOutputConnectionHandlersToArrayRequest): void { + let fHandler = this._getExistingConnectionHandler(request.result, fConnection); + if (!fHandler) { + fHandler = this._createConnectionHandler(request.inputIds, fConnection); + request.result.push(fHandler); } - return result; + request.fDragHandler.fSourceHandlers.push(fHandler); } - private getNewConnectionHandler(connection: FConnectionBase, nodeDragHandler: NodeDragHandler): ConnectionDragHandler { - const handler = new ConnectionDragHandler(this.fMediator, this.fComponentsStore, connection); - handler.setOutputRestrictions(nodeDragHandler.restrictions); - return handler; + private _getExistingConnectionHandler(fHandlers: BaseConnectionDragHandler[], fConnection: FConnectionBase): BaseConnectionDragHandler | undefined { + return fHandlers.find((x) => x.fConnection.fId === fConnection.fId); } - private updateExistingConnectionHandler(result: IDraggableItem[], index: number, nodeDragHandler: NodeDragHandler): void { - (result[ index ] as ConnectionDragHandler).setOutputRestrictions(nodeDragHandler.restrictions); - } - - private getNewSourceConnectionHandler(connection: FConnectionBase, nodeDragHandler: NodeDragHandler): ConnectionSourceDragHandler { - return new ConnectionSourceDragHandler(this.fMediator, this.fComponentsStore, connection, nodeDragHandler.restrictions); + private _createConnectionHandler(inputIds: string[], fConnection: FConnectionBase): BaseConnectionDragHandler { + let result: BaseConnectionDragHandler; + if (inputIds.includes(fConnection.fInputId)) { + result = new SourceTargetConnectionDragHandler(fConnection); + } else { + result = new SourceConnectionDragHandler(fConnection) + } + return result; } } diff --git a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-output-connection-handlers-to-array/put-output-connection-handlers-to-array.request.ts b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-output-connection-handlers-to-array/put-output-connection-handlers-to-array.request.ts index 1b1f8cce..3e51d946 100644 --- a/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-output-connection-handlers-to-array/put-output-connection-handlers-to-array.request.ts +++ b/projects/f-flow/src/f-draggable/node/create-move-nodes-drag-model-from-selection/domain/put-output-connection-handlers-to-array/put-output-connection-handlers-to-array.request.ts @@ -1,12 +1,12 @@ import { NodeDragHandler } from '../../../node.drag-handler'; -import { IDraggableItem } from '../../../../i-draggable-item'; +import { BaseConnectionDragHandler } from '../../../base-connection.drag-handler'; export class PutOutputConnectionHandlersToArrayRequest { constructor( - public nodeDragHandler: NodeDragHandler, + public fDragHandler: NodeDragHandler, public inputIds: string[], - public result: IDraggableItem[] + public result: BaseConnectionDragHandler[] ) { } } diff --git a/projects/f-flow/src/f-draggable/node/index.ts b/projects/f-flow/src/f-draggable/node/index.ts index 5fd087f5..be88ee1c 100644 --- a/projects/f-flow/src/f-draggable/node/index.ts +++ b/projects/f-flow/src/f-draggable/node/index.ts @@ -10,14 +10,6 @@ export * from './node-move-preparation'; export * from './node-move-finalize'; -export * from './connection.drag-handler'; - -export * from './connection-base-drag-handler'; - -export * from './connection-source.drag-handler'; - -export * from './connection-target.drag-handler'; - export * from './f-drop-to-group.event'; export * from './line-alignment.drag-handler'; @@ -28,4 +20,14 @@ export * from './node-drag-to-parent.drag-handler'; export * from './node-resize-by-child.drag-handler'; +export * from './point-bounds-limiter'; + export * from './providers'; + +export * from './source-connection.drag-handler'; + +export * from './base-connection.drag-handler'; + +export * from './source-target-connection.drag-handler'; + +export * from './target-connection.drag-handler'; diff --git a/projects/f-flow/src/f-draggable/node/line-alignment-preparation/line-alignment-preparation.execution.ts b/projects/f-flow/src/f-draggable/node/line-alignment-preparation/line-alignment-preparation.execution.ts index ccf3c645..77bd1db0 100644 --- a/projects/f-flow/src/f-draggable/node/line-alignment-preparation/line-alignment-preparation.execution.ts +++ b/projects/f-flow/src/f-draggable/node/line-alignment-preparation/line-alignment-preparation.execution.ts @@ -36,7 +36,6 @@ export class LineAlignmentPreparationExecution implements IExecution x instanceof NodeDragHandler)!; - const differenceWithCellSize = firstNodeOrGroup.getDifferenceWithCellSize(difference); - - this._finalizeMove(differenceWithCellSize); + this._finalizeMove(firstNodeOrGroup.calculateRestrictedDifference(difference)); this._applyConnectionUnderDroppedNode(); } diff --git a/projects/f-flow/src/f-draggable/node/node.drag-handler.ts b/projects/f-flow/src/f-draggable/node/node.drag-handler.ts index 765efcbc..f4836285 100644 --- a/projects/f-flow/src/f-draggable/node/node.drag-handler.ts +++ b/projects/f-flow/src/f-draggable/node/node.drag-handler.ts @@ -1,35 +1,40 @@ -import { IMinMaxPoint, IPoint, Point, PointExtensions } from '@foblex/2d'; +import { IMinMaxPoint, IPoint, PointExtensions } from '@foblex/2d'; import { IDraggableItem } from '../i-draggable-item'; import { FNodeBase } from '../../f-node'; import { FComponentsStore } from '../../f-storage'; +import { fInject } from '../f-injector'; +import { PointBoundsLimiter } from './point-bounds-limiter'; +import { BaseConnectionDragHandler } from './base-connection.drag-handler'; export class NodeDragHandler implements IDraggableItem { + private _fComponentStore = fInject(FComponentsStore); private readonly _onPointerDownPosition = PointExtensions.initialize(); + private readonly _fBoundsLimiter: PointBoundsLimiter; + constructor( - private _fComponentsStore: FComponentsStore, public fNode: FNodeBase, public restrictions: IMinMaxPoint, + public fSourceHandlers: BaseConnectionDragHandler[] = [], + public fTargetHandlers: BaseConnectionDragHandler[] = [], ) { this._onPointerDownPosition = { ...fNode.position }; + this._fBoundsLimiter = new PointBoundsLimiter(this._onPointerDownPosition, restrictions); } public onPointerMove(difference: IPoint): void { - const restrictedDifference = this._getDifference(difference); + const adjustCellSize = this._fComponentStore.fDraggable!.fCellSizeWhileDragging; + const differenceWithRestrictions = this._fBoundsLimiter.limit(difference, adjustCellSize); - this._redraw(this._getPosition(restrictedDifference)); - } + this._redraw(this._calculateNewPosition(differenceWithRestrictions)); - private _getPosition(difference: IPoint): IPoint { - return Point.fromPoint(this._onPointerDownPosition).add(difference); + this.fSourceHandlers.forEach((x) => x.setSourceDifference(differenceWithRestrictions)); + this.fTargetHandlers.forEach((x) => x.setTargetDifference(differenceWithRestrictions)); } - private _getDifference(difference: IPoint): IPoint { - return { - x: Math.min(Math.max(difference.x, this.restrictions.min.x), this.restrictions.max.x), - y: Math.min(Math.max(difference.y, this.restrictions.min.y), this.restrictions.max.y) - } + private _calculateNewPosition(difference: IPoint): IPoint { + return PointExtensions.sum(this._onPointerDownPosition, difference); } private _redraw(position: IPoint): void { @@ -41,18 +46,7 @@ export class NodeDragHandler implements IDraggableItem { this.fNode.positionChange.emit(this.fNode.position); } - public getDifferenceWithCellSize(difference: IPoint): IPoint { - const position = this._getPosition(this._getDifference(difference)); - - return Point.fromPoint(this._applyCellSize(position)).sub(this._onPointerDownPosition); - } - - private _applyCellSize(position: IPoint): IPoint { - const hCellSize = this._fComponentsStore.fDraggable!.hCellSize; - const vCellSize = this._fComponentsStore.fDraggable!.vCellSize; - return { - x: Math.round(position.x / hCellSize) * hCellSize, - y: Math.round(position.y / vCellSize) * vCellSize - }; + public calculateRestrictedDifference(difference: IPoint): IPoint { + return this._fBoundsLimiter.limit(difference, true); } } diff --git a/projects/f-flow/src/f-draggable/node/point-bounds-limiter.ts b/projects/f-flow/src/f-draggable/node/point-bounds-limiter.ts new file mode 100644 index 00000000..bfbe1b40 --- /dev/null +++ b/projects/f-flow/src/f-draggable/node/point-bounds-limiter.ts @@ -0,0 +1,60 @@ +import { IMinMaxPoint, IPoint } from '@foblex/2d'; +import { fInject } from '../f-injector'; +import { FComponentsStore } from '../../f-storage'; + +export class PointBoundsLimiter { + + private _fComponentsStore = fInject(FComponentsStore); + + private _vCellSize = this._fComponentsStore.fDraggable!.vCellSize; + private _hCellSize = this._fComponentsStore.fDraggable!.hCellSize; + + constructor( + private _onPointerDown: IPoint, + private readonly _limit: IMinMaxPoint + ) { + this._validate(_limit); + } + + public limit(difference: IPoint, adjustCellSize: boolean): IPoint { + const { min, max } = this._limit; + + const { x, y } = this._cellSizeStrategies[+adjustCellSize](difference); + + return { + x: this._clamp(x, min.x, max.x), + y: this._clamp(y, min.y, max.y), + }; + } + + private _cellSizeStrategies: Record IPoint> = { + 0: this._skipCellSize.bind(this), + 1: this._applyCellSize.bind(this) + }; + + private _applyCellSize(difference: IPoint): IPoint { + return { + x: this._snapToGrid(this._onPointerDown.x + difference.x, this._hCellSize) - this._onPointerDown.x, + y: this._snapToGrid(this._onPointerDown.y + difference.y, this._vCellSize) - this._onPointerDown.y + }; + } + + private _skipCellSize(difference: IPoint): IPoint { + return difference; + } + + private _clamp(value: number, min: number, max: number): number { + return Math.min(Math.max(value, min), max); + } + + private _snapToGrid(value: number, cellSize: number): number { + return Math.round(value / cellSize) * cellSize; + } + + private _validate(limit: IMinMaxPoint): void { + const { min, max } = limit; + if (min.x > max.x || min.y > max.y) { + throw new Error('Invalid restrictions: min values must be less than or equal to max values.'); + } + } +} diff --git a/projects/f-flow/src/f-draggable/node/source-connection.drag-handler.ts b/projects/f-flow/src/f-draggable/node/source-connection.drag-handler.ts new file mode 100644 index 00000000..fec5b7b1 --- /dev/null +++ b/projects/f-flow/src/f-draggable/node/source-connection.drag-handler.ts @@ -0,0 +1,15 @@ +import { BaseConnectionDragHandler } from './base-connection.drag-handler'; +import { FConnectionBase } from '../../f-connection'; +import { IPoint } from '@foblex/2d'; + +export class SourceConnectionDragHandler extends BaseConnectionDragHandler { + + constructor(fConnection: FConnectionBase) { + super(fConnection); + } + + public override setSourceDifference(difference: IPoint) { + super.setSourceDifference(difference); + this.redraw(); + } +} diff --git a/projects/f-flow/src/f-draggable/node/source-target-connection.drag-handler.ts b/projects/f-flow/src/f-draggable/node/source-target-connection.drag-handler.ts new file mode 100644 index 00000000..fa88e15b --- /dev/null +++ b/projects/f-flow/src/f-draggable/node/source-target-connection.drag-handler.ts @@ -0,0 +1,30 @@ +import { BaseConnectionDragHandler } from './base-connection.drag-handler'; +import { FConnectionBase } from '../../f-connection'; +import { IPoint } from '@foblex/2d'; + +export class SourceTargetConnectionDragHandler extends BaseConnectionDragHandler { + + private _callTracker = new Map(); + + constructor(fConnection: FConnectionBase) { + super(fConnection); + } + + public override setSourceDifference(difference: IPoint) { + super.setSourceDifference(difference); + this._checkAndTriggerAction(); + } + + public override setTargetDifference(difference: IPoint) { + super.setTargetDifference(difference); + this._checkAndTriggerAction(); + } + + private _checkAndTriggerAction() { + if ([...this._callTracker.values()].every(Boolean)) { + this._callTracker.clear(); + this.redraw(); + } + } +} + diff --git a/projects/f-flow/src/f-draggable/node/target-connection.drag-handler.ts b/projects/f-flow/src/f-draggable/node/target-connection.drag-handler.ts new file mode 100644 index 00000000..9751f4c0 --- /dev/null +++ b/projects/f-flow/src/f-draggable/node/target-connection.drag-handler.ts @@ -0,0 +1,15 @@ +import { FConnectionBase } from '../../f-connection'; +import { BaseConnectionDragHandler } from './base-connection.drag-handler'; +import { IPoint } from '@foblex/2d'; + +export class TargetConnectionDragHandler extends BaseConnectionDragHandler { + + constructor(fConnection: FConnectionBase) { + super(fConnection); + } + + public override setTargetDifference(difference: IPoint) { + super.setTargetDifference(difference); + this.redraw(); + } +} diff --git a/projects/f-flow/src/f-draggable/providers.ts b/projects/f-flow/src/f-draggable/providers.ts index e1036310..f43abf7e 100644 --- a/projects/f-flow/src/f-draggable/providers.ts +++ b/projects/f-flow/src/f-draggable/providers.ts @@ -3,7 +3,7 @@ import { CONNECTIONS_PROVIDERS } from './connections'; import { SINGLE_SELECT_PROVIDERS } from './single-select'; import { NODE_PROVIDERS } from './node'; import { NODE_RESIZE_PROVIDERS } from './node-resize'; -import { F_MINIMAP_DRAG_AND_DROP_PROVIDERS } from '../f-minimap/domain/providers'; +import { F_MINIMAP_DRAG_AND_DROP_PROVIDERS } from '../f-minimap'; import { F_EXTERNAL_ITEM_DRAG_AND_DROP_PROVIDERS } from '../f-external-item'; import { F_SELECTION_AREA_DRAG_AND_DROP_PROVIDERS } from '../f-selection-area'; import { DRAG_AND_DROP_COMMON_PROVIDERS } from './domain'; diff --git a/projects/f-flow/src/f-draggable/single-select/single-select.execution.ts b/projects/f-flow/src/f-draggable/single-select/single-select.execution.ts index 60cbef21..5ce0237b 100644 --- a/projects/f-flow/src/f-draggable/single-select/single-select.execution.ts +++ b/projects/f-flow/src/f-draggable/single-select/single-select.execution.ts @@ -1,110 +1,152 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { FExecutionRegister, FMediator, IExecution } from '@foblex/mediator'; import { SingleSelectRequest } from './single-select.request'; -import { UpdateItemAndChildrenLayersRequest } from '../../domain'; +import { isValidEventTrigger, UpdateItemAndChildrenLayersRequest } from '../../domain'; import { IPointerEvent } from '@foblex/drag-toolkit'; import { FConnectionBase } from '../../f-connection'; import { FComponentsStore } from '../../f-storage'; import { FDraggableDataContext } from '../f-draggable-data-context'; -import { EOperationSystem, PlatformService } from '@foblex/platform'; -import { ICanChangeSelection } from '../../mixins'; +import { ISelectable } from '../../mixins'; import { FNodeBase } from '../../f-node'; +/** + * Implements the functionality for selecting elements in a graphical interface. + * This class handles both single and multi-selection, updating the selection state + * of elements and managing related data. + * + * Logic flow: + * 1. **Validate the selection event**: + * - The event is considered valid if it occurs within the flow boundaries + * and there are no active draggable data operations. + * + * 2. **Determine the target element to select**: + * - The target element is determined based on the event’s target. + * It can be a node, a group of nodes, or a connection. + * - If no element is found, the current selection state is cleared. + * + * 3. **Update element layers**: + * - If an element is found, its visual layer and the layers of its child elements are updated. + * + * 4. **Single or multi-selection**: + * - If the event meets the criteria for multi-selection (e.g., a modifier key is pressed), + * multi-selection logic is applied. + * - Otherwise, single-selection logic is used. + * + * 5. **Single-selection logic**: + * - If the element is not selected and can be selected: + * - Clear the selection of all other elements. + * - Mark the current element as selected. + * - If the element cannot be selected, the current selection is cleared. + * + * 6. **Multi-selection logic**: + * - If the element is already selected, it is removed from the selection. + * - If the element is not selected and can be selected, it is added to the selection. + * + * 7. **Manage selection state**: + * - Adding or removing an element from the selection triggers the corresponding methods. + * - Selection state is tracked in the shared context for future use. + */ + @Injectable() @FExecutionRegister(SingleSelectRequest) export class SingleSelectExecution implements IExecution { - constructor( - private fPlatform: PlatformService, - private fComponentsStore: FComponentsStore, - private fDraggableDataContext: FDraggableDataContext, - private fMediator: FMediator - ) { - } + private _fMediator = inject(FMediator); + private _fComponentsStore = inject(FComponentsStore); + private _fDraggableDataContext = inject(FDraggableDataContext); public handle(request: SingleSelectRequest): void { - if(!this._isValid(request)) { + if (!this._isValid(request)) { return; } - const { event } = request; - const item = this.getSelectableItem(event); - if (item) { - this.fMediator.send(new UpdateItemAndChildrenLayersRequest(item, item.hostElement.parentElement!)); - } + const fItem = this._getItemToSelect(request.event); + + setTimeout(() => this._updateItemAndChildrenLayers(fItem)); - this.isMultiselectEnabled(event) ? this.multiSelect(item) : this.singleSelect(item); + this._isMultiSelect(request) ? this._multiSelect(fItem) : this._singleSelect(fItem); } private _isValid(request: SingleSelectRequest): boolean { - return this.fComponentsStore.fFlow!.hostElement.contains(request.event.targetElement) - && !this.fDraggableDataContext.draggableItems.length; + return this._isEventInFlowBounds(request.event) && this._fDraggableDataContext.isEmpty(); } - private getSelectableItem(event: IPointerEvent): ICanChangeSelection | undefined { - return this._findNode(event.targetElement) || this.getConnectionHandler(event.targetElement); + private _isEventInFlowBounds(event: IPointerEvent): boolean { + return this._fComponentsStore.fFlow!.hostElement.contains(event.targetElement); } - private _findNode(targetElement: HTMLElement): FNodeBase | undefined { - return this.fComponentsStore.fNodes.find(n => n.isContains(targetElement)); + private _getItemToSelect(event: IPointerEvent): ISelectable | undefined { + return this._getNodeOrGroup(event.targetElement) || this._getConnection(event.targetElement); } - private getConnectionHandler(element: HTMLElement | SVGElement): FConnectionBase | undefined { - return this.fComponentsStore.fConnections.find(c => c.isContains(element) || c.fConnectionCenter?.nativeElement?.contains(element)); + private _getNodeOrGroup(targetElement: HTMLElement): FNodeBase | undefined { + return this._fComponentsStore.fNodes.find((x) => (x).isContains(targetElement)); } - private isMultiselectEnabled(event: IPointerEvent): boolean { - return this.isCommandButton(this.fPlatform.getOS()!, event.originalEvent) || - this.isShiftPressed(event.originalEvent); + private _getConnection(element: HTMLElement | SVGElement): FConnectionBase | undefined { + return this._fComponentsStore.fConnections + .find(c => c.isContains(element) || c.fConnectionCenter?.nativeElement?.contains(element)); } - private isShiftPressed(event: { shiftKey: boolean }): boolean { - return event.shiftKey; + private _updateItemAndChildrenLayers(fItem?: ISelectable): void { + if (fItem) { + this._fMediator.execute( + new UpdateItemAndChildrenLayersRequest(fItem, fItem.hostElement.parentElement!) + ); + } } - private isCommandButton(platform: EOperationSystem, event: { metaKey: boolean, ctrlKey: boolean }): boolean { - return platform === EOperationSystem.MAC_OS ? event.metaKey : event.ctrlKey; + private _isMultiSelect(request: SingleSelectRequest): boolean { + return isValidEventTrigger(request.event.originalEvent, request.fMultiSelectTrigger) } - private singleSelect(item: ICanChangeSelection | undefined): void { - if (item) { - if (!item.isSelected() && !item.fSelectionDisabled) { - this.clearSelection(); - this.selectItem(item); - } else if(item.fSelectionDisabled) { - this.clearSelection(); + private _singleSelect(fItem?: ISelectable): void { + if (fItem) { + if (this._isItemNotSelectedAndSelectable(fItem)) { + this._clearSelection(); + this._selectItem(fItem); + } else if (fItem.fSelectionDisabled) { + this._clearSelection(); } } else { - this.clearSelection(); + this._clearSelection(); } } - private multiSelect(item: ICanChangeSelection | undefined): void { - if (item && !item.fSelectionDisabled) { - item.isSelected() ? this.deselectItem(item) : this.selectItem(item); - } + private _isItemNotSelectedAndSelectable(item: ISelectable): boolean { + return !item.isSelected() && !item.fSelectionDisabled; } - private deselectItem(item: ICanChangeSelection): void { - const index = this.fDraggableDataContext.selectedItems.indexOf(item); - if (index > -1) { - this.fDraggableDataContext.selectedItems.splice(index, 1); + private _clearSelection(): void { + this._fDraggableDataContext.selectedItems.forEach((x) => { + x.unmarkAsSelected(); + this._fDraggableDataContext.markSelectionAsChanged(); + }); + this._fDraggableDataContext.selectedItems = []; + } + + private _multiSelect(fItem?: ISelectable): void { + if (fItem && !fItem.fSelectionDisabled) { + fItem.isSelected() ? this._deselectItem(fItem) : this._selectItem(fItem); } - item.deselect(); - this.fDraggableDataContext.markSelectionAsChanged(); } - private selectItem(item: ICanChangeSelection): void { - this.fDraggableDataContext.selectedItems.push(item); - item.select(); - this.fDraggableDataContext.markSelectionAsChanged(); + private _deselectItem(fItem: ISelectable): void { + this._removeItemFromSelectedItems(fItem); + fItem.unmarkAsSelected(); + this._fDraggableDataContext.markSelectionAsChanged(); } - private clearSelection(): void { - this.fDraggableDataContext.selectedItems.forEach((x) => { - x.deselect(); - this.fDraggableDataContext.markSelectionAsChanged(); - }); - this.fDraggableDataContext.selectedItems = []; + private _removeItemFromSelectedItems(fItem: ISelectable): void { + const indexInSelection = this._fDraggableDataContext.selectedItems.indexOf(fItem); + if (indexInSelection > -1) { + this._fDraggableDataContext.selectedItems.splice(indexInSelection, 1); + } + } + + private _selectItem(fItem: ISelectable): void { + this._fDraggableDataContext.selectedItems.push(fItem); + fItem.markAsSelected(); + this._fDraggableDataContext.markSelectionAsChanged(); } } diff --git a/projects/f-flow/src/f-draggable/single-select/single-select.request.ts b/projects/f-flow/src/f-draggable/single-select/single-select.request.ts index 5306bb3c..d9f82b7b 100644 --- a/projects/f-flow/src/f-draggable/single-select/single-select.request.ts +++ b/projects/f-flow/src/f-draggable/single-select/single-select.request.ts @@ -1,9 +1,11 @@ import { IPointerEvent } from '@foblex/drag-toolkit'; +import { FEventTrigger } from '../../domain'; export class SingleSelectRequest { constructor( - public event: IPointerEvent + public event: IPointerEvent, + public fMultiSelectTrigger: FEventTrigger ) { } } diff --git a/projects/f-flow/src/f-flow/f-flow.component.ts b/projects/f-flow/src/f-flow/f-flow.component.ts index 5ac3af93..0f7b7dcb 100644 --- a/projects/f-flow/src/f-flow/f-flow.component.ts +++ b/projects/f-flow/src/f-flow/f-flow.component.ts @@ -150,7 +150,7 @@ export class FFlowComponent extends FFlowBase implements OnInit, AfterContentIni } public clearSelection(): void { - this._fMediator.send(new ClearSelectionRequest()); + this._fMediator.execute(new ClearSelectionRequest()); } public ngOnDestroy(): void { diff --git a/projects/f-flow/src/f-node/f-node-base.ts b/projects/f-flow/src/f-node/f-node-base.ts index bc72b9a7..011cd7c0 100644 --- a/projects/f-flow/src/f-node/f-node-base.ts +++ b/projects/f-flow/src/f-node/f-node-base.ts @@ -4,7 +4,7 @@ import { FConnectorBase } from '../f-connectors'; import { IHasHostElement } from '../i-has-host-element'; -import { ICanChangeSelection, mixinChangeSelection } from '../mixins'; +import { ISelectable, mixinChangeSelection } from '../mixins'; import { FChannel } from '../reactivity'; export const F_NODE = new InjectionToken('F_NODE'); @@ -17,7 +17,7 @@ const MIXIN_BASE = mixinChangeSelection( } }); -export abstract class FNodeBase extends MIXIN_BASE implements ICanChangeSelection, IHasHostElement { +export abstract class FNodeBase extends MIXIN_BASE implements ISelectable, IHasHostElement { public abstract override fId: string; diff --git a/projects/f-flow/src/f-node/f-node.directive.ts b/projects/f-flow/src/f-node/f-node.directive.ts index 054c312b..20b64dea 100644 --- a/projects/f-flow/src/f-node/f-node.directive.ts +++ b/projects/f-flow/src/f-node/f-node.directive.ts @@ -108,7 +108,7 @@ export class FNodeDirective extends FNodeBase implements OnInit, AfterViewInit, public override redraw(): void { super.redraw(); - this._fMediator.send(new NotifyTransformChangedRequest()); + this._fMediator.execute(new NotifyTransformChangedRequest()); } public ngAfterViewInit(): void { @@ -119,7 +119,7 @@ export class FNodeDirective extends FNodeBase implements OnInit, AfterViewInit, } private _listenStateSizeChanges(): void { - this._fMediator.send(new UpdateNodeWhenStateOrSizeChangedRequest(this, this._destroyRef)); + this._fMediator.execute(new UpdateNodeWhenStateOrSizeChangedRequest(this, this._destroyRef)); } public override refresh(): void { @@ -127,6 +127,6 @@ export class FNodeDirective extends FNodeBase implements OnInit, AfterViewInit, } public ngOnDestroy(): void { - this._fMediator.send(new RemoveNodeFromStoreRequest(this)); + this._fMediator.execute(new RemoveNodeFromStoreRequest(this)); } } diff --git a/projects/f-flow/src/f-node/f-resize-handle/e-f-resize-handle-type.ts b/projects/f-flow/src/f-node/f-resize-handle/e-f-resize-handle-type.ts index 080ca24c..03dfbf1b 100644 --- a/projects/f-flow/src/f-node/f-resize-handle/e-f-resize-handle-type.ts +++ b/projects/f-flow/src/f-node/f-resize-handle/e-f-resize-handle-type.ts @@ -1,10 +1,18 @@ export enum EFResizeHandleType { + LEFT = 'left', + LEFT_TOP = 'left-top', + TOP = 'top', + RIGHT_TOP = 'right-top', - LEFT_BOTTOM = 'left-bottom', + RIGHT = 'right', RIGHT_BOTTOM = 'right-bottom', + + BOTTOM = 'bottom', + + LEFT_BOTTOM = 'left-bottom', } diff --git a/projects/f-flow/src/f-node/f-resize-handle/f-resize-handle.directive.ts b/projects/f-flow/src/f-node/f-resize-handle/f-resize-handle.directive.ts index d67d2e2a..6f9454e4 100644 --- a/projects/f-flow/src/f-node/f-resize-handle/f-resize-handle.directive.ts +++ b/projects/f-flow/src/f-node/f-resize-handle/f-resize-handle.directive.ts @@ -11,7 +11,7 @@ export const F_RESIZE_HANDLE = new InjectionToken('F_RES selector: "[fResizeHandle]", host: { class: `f-resize-handle f-component`, - '[attr.data-f-resize-handle-type]': 'type', + '[attr.data-f-resize-handle-type]': 'type.toUpperCase()', }, providers: [ { provide: F_RESIZE_HANDLE, useExisting: FResizeHandleDirective } ], }) diff --git a/projects/f-flow/src/f-selection-area/domain/providers.ts b/projects/f-flow/src/f-selection-area/domain/providers.ts index ffa2fdd4..63adfc21 100644 --- a/projects/f-flow/src/f-selection-area/domain/providers.ts +++ b/projects/f-flow/src/f-selection-area/domain/providers.ts @@ -1,9 +1,9 @@ -import { SELECTION_AREA_FINALIZE_PROVIDERS } from './selection-area-finalize'; -import { SELECTION_AREA_PREPARATION_PROVIDERS } from './selection-area-preparation'; +import { SelectionAreaPreparationExecution } from './selection-area-preparation'; +import { SelectionAreaFinalizeExecution } from './selection-area-finalize'; export const F_SELECTION_AREA_DRAG_AND_DROP_PROVIDERS = [ - ...SELECTION_AREA_FINALIZE_PROVIDERS, + SelectionAreaPreparationExecution, - ...SELECTION_AREA_PREPARATION_PROVIDERS + SelectionAreaFinalizeExecution ]; diff --git a/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/index.ts b/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/index.ts index 94e632b8..5490e061 100644 --- a/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/index.ts +++ b/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/index.ts @@ -1,7 +1,3 @@ -export * from './providers'; - export * from './selection-area-finalize.execution'; export * from './selection-area-finalize.request'; - -export * from './selection-area-finalize.validator'; diff --git a/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/providers.ts b/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/providers.ts deleted file mode 100644 index 04ed6acb..00000000 --- a/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/providers.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { SelectionAreaFinalizeExecution } from './selection-area-finalize.execution'; -import { SelectionAreaFinalizeValidator } from './selection-area-finalize.validator'; - -export const SELECTION_AREA_FINALIZE_PROVIDERS = [ - - SelectionAreaFinalizeExecution, - - SelectionAreaFinalizeValidator, -]; diff --git a/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/selection-area-finalize.execution.ts b/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/selection-area-finalize.execution.ts index 6a53b54a..81c61f50 100644 --- a/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/selection-area-finalize.execution.ts +++ b/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/selection-area-finalize.execution.ts @@ -1,20 +1,27 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { SelectionAreaFinalizeRequest } from './selection-area-finalize.request'; import { FExecutionRegister, IExecution } from '@foblex/mediator'; import { FDraggableDataContext } from '../../../f-draggable'; +import { SelectionAreaDragHandle } from '../selection-area.drag-handle'; @Injectable() @FExecutionRegister(SelectionAreaFinalizeRequest) export class SelectionAreaFinalizeExecution implements IExecution { - constructor( - private fDraggableDataContext: FDraggableDataContext, - ) { - } + private _fDraggableDataContext = inject(FDraggableDataContext); public handle(request: SelectionAreaFinalizeRequest): void { - this.fDraggableDataContext.draggableItems.forEach((x) => { + if(!this._isValid()) { + return; + } + this._fDraggableDataContext.draggableItems.forEach((x) => { x.onPointerUp?.(); }); } + + private _isValid(): boolean { + return this._fDraggableDataContext.draggableItems.some((x) => + x instanceof SelectionAreaDragHandle + ); + } } diff --git a/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/selection-area-finalize.validator.ts b/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/selection-area-finalize.validator.ts deleted file mode 100644 index fa358138..00000000 --- a/projects/f-flow/src/f-selection-area/domain/selection-area-finalize/selection-area-finalize.validator.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Injectable } from '@angular/core'; -import { SelectionAreaFinalizeRequest } from './selection-area-finalize.request'; -import { FValidatorRegister, IValidator } from '@foblex/mediator'; -import { FDraggableDataContext } from '../../../f-draggable'; -import { SelectionAreaDragHandle } from '../selection-area.drag-handle'; - -@Injectable() -@FValidatorRegister(SelectionAreaFinalizeRequest) -export class SelectionAreaFinalizeValidator implements IValidator { - - constructor( - private fDraggableDataContext: FDraggableDataContext - ) { - } - - public handle(request: SelectionAreaFinalizeRequest): boolean { - return this.fDraggableDataContext.draggableItems.some((x) => - x instanceof SelectionAreaDragHandle - ); - } -} diff --git a/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/index.ts b/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/index.ts index 3176c834..b240e444 100644 --- a/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/index.ts +++ b/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/index.ts @@ -1,7 +1,3 @@ -export * from './providers'; - export * from './selection-area-preparation.execution'; export * from './selection-area-preparation.request'; - -export * from './selection-area-preparation.validator'; diff --git a/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/providers.ts b/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/providers.ts deleted file mode 100644 index 3e9c1cfd..00000000 --- a/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/providers.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { SelectionAreaPreparationExecution } from './selection-area-preparation.execution'; -import { SelectionAreaPreparationValidator } from './selection-area-preparation.validator'; - -export const SELECTION_AREA_PREPARATION_PROVIDERS = [ - - SelectionAreaPreparationExecution, - - SelectionAreaPreparationValidator, -]; diff --git a/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.execution.ts b/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.execution.ts index 0bbafb23..ac62e184 100644 --- a/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.execution.ts +++ b/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.execution.ts @@ -1,35 +1,41 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { SelectionAreaPreparationRequest } from './selection-area-preparation.request'; import { Point } from '@foblex/2d'; import { FExecutionRegister, FMediator, IExecution } from '@foblex/mediator'; import { FComponentsStore } from '../../../f-storage'; import { SelectionAreaDragHandle } from '../selection-area.drag-handle'; import { FDraggableDataContext } from '../../../f-draggable'; +import { isValidEventTrigger } from '../../../domain'; @Injectable() @FExecutionRegister(SelectionAreaPreparationRequest) export class SelectionAreaPreparationExecution implements IExecution { - private get flowHost(): HTMLElement { - return this.fComponentsStore.fFlow!.hostElement; - } + private _fMediator = inject(FMediator); + private _fComponentsStore = inject(FComponentsStore); + private _fDraggableDataContext = inject(FDraggableDataContext); - constructor( - private fComponentsStore: FComponentsStore, - private fDraggableDataContext: FDraggableDataContext, - private fMediator: FMediator - ) { + private get _fHost(): HTMLElement { + return this._fComponentsStore.fFlow!.hostElement; } public handle(request: SelectionAreaPreparationRequest): void { - this.fDraggableDataContext.draggableItems = [ + if (!this._isValid(request)) { + return; + } + this._fDraggableDataContext.draggableItems = [ new SelectionAreaDragHandle( - this.fComponentsStore, request.fSelectionArea, this.fDraggableDataContext, this.fMediator + this._fComponentsStore, request.fSelectionArea, this._fDraggableDataContext, this._fMediator ) ]; - this.fDraggableDataContext.onPointerDownScale = 1; - this.fDraggableDataContext.onPointerDownPosition = Point.fromPoint(request.event.getPosition()) - .elementTransform(this.flowHost); + this._fDraggableDataContext.onPointerDownScale = 1; + this._fDraggableDataContext.onPointerDownPosition = Point.fromPoint(request.event.getPosition()) + .elementTransform(this._fHost); + } + + private _isValid(request: SelectionAreaPreparationRequest): boolean { + return this._fDraggableDataContext.isEmpty() + && isValidEventTrigger(request.event.originalEvent, request.fTrigger); } } diff --git a/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.request.ts b/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.request.ts index 5f40e726..4d428eb1 100644 --- a/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.request.ts +++ b/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.request.ts @@ -1,11 +1,13 @@ import { IPointerEvent } from '@foblex/drag-toolkit'; import { FSelectionAreaBase } from '../../f-selection-area-base'; +import { FEventTrigger } from '../../../domain'; export class SelectionAreaPreparationRequest { constructor( public event: IPointerEvent, - public fSelectionArea: FSelectionAreaBase + public fSelectionArea: FSelectionAreaBase, + public fTrigger: FEventTrigger ) { } } diff --git a/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.validator.ts b/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.validator.ts deleted file mode 100644 index d94807ca..00000000 --- a/projects/f-flow/src/f-selection-area/domain/selection-area-preparation/selection-area-preparation.validator.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Injectable } from '@angular/core'; -import { SelectionAreaPreparationRequest } from './selection-area-preparation.request'; -import { FDraggableDataContext } from '../../../f-draggable'; -import { FValidatorRegister, IValidator } from '@foblex/mediator'; - -@Injectable() -@FValidatorRegister(SelectionAreaPreparationRequest) -export class SelectionAreaPreparationValidator implements IValidator { - - constructor( - private fDraggableDataContext: FDraggableDataContext - ) { - } - - public handle(request: SelectionAreaPreparationRequest): boolean { - return this.isDragHandlesEmpty() - && this.isShiftPressed(request.event.originalEvent); - } - - private isDragHandlesEmpty(): boolean { - return !this.fDraggableDataContext.draggableItems.length; - } - - private isShiftPressed(event: { shiftKey: boolean }): boolean { - return event.shiftKey; - } -} diff --git a/projects/f-flow/src/f-selection-area/domain/selection-area.drag-handle.ts b/projects/f-flow/src/f-selection-area/domain/selection-area.drag-handle.ts index 6d77a7d4..34698eba 100644 --- a/projects/f-flow/src/f-selection-area/domain/selection-area.drag-handle.ts +++ b/projects/f-flow/src/f-selection-area/domain/selection-area.drag-handle.ts @@ -1,73 +1,80 @@ -import { IPoint, Point, RectExtensions } from '@foblex/2d'; +import { IPoint, Point, PointExtensions, RectExtensions } from '@foblex/2d'; import { FComponentsStore, NotifyTransformChangedRequest } from '../../f-storage'; -import { GetCanBeSelectedItemsRequest, ICanBeSelected } from '../../domain'; +import { GetCanBeSelectedItemsRequest, ICanBeSelectedElementAndRect } from '../../domain'; import { FMediator } from '@foblex/mediator'; import { FDraggableDataContext, IDraggableItem } from '../../f-draggable'; import { FSelectionAreaBase } from '../f-selection-area-base'; -import { ICanChangeSelection } from '../../mixins'; +import { ISelectable } from '../../mixins'; export class SelectionAreaDragHandle implements IDraggableItem { - private _canBeSelected: ICanBeSelected[] = []; - private _selectedByMove: ICanChangeSelection[] = []; + private _canBeSelected: ICanBeSelectedElementAndRect[] = []; + private _selectedByMove: ISelectable[] = []; - private get canvasPosition(): Point { - return Point.fromPoint(this.fComponentsStore.fCanvas!.transform.position) - .add(this.fComponentsStore.fCanvas!.transform.scaledPosition); + private get _fCanvasPosition(): IPoint { + return Point.fromPoint(this._fComponentsStore.fCanvas!.transform.position) + .add(this._fComponentsStore.fCanvas!.transform.scaledPosition); } constructor( - private fComponentsStore: FComponentsStore, - private fSelectionArea: FSelectionAreaBase, - private fDraggableDataContext: FDraggableDataContext, - private fMediator: FMediator, + private _fComponentsStore: FComponentsStore, + private _fSelectionArea: FSelectionAreaBase, + private _fDraggableDataContext: FDraggableDataContext, + private _fMediator: FMediator, ) { } public prepareDragSequence(): void { - this._canBeSelected = this.fMediator.send(new GetCanBeSelectedItemsRequest()); + this._canBeSelected = this._fMediator.execute(new GetCanBeSelectedItemsRequest()); - this.fSelectionArea.show(); - this.fSelectionArea.draw( + this._fSelectionArea.show(); + this._fSelectionArea.draw( RectExtensions.initialize( - this.fDraggableDataContext.onPointerDownPosition.x, - this.fDraggableDataContext.onPointerDownPosition.y + this._fDraggableDataContext.onPointerDownPosition.x, + this._fDraggableDataContext.onPointerDownPosition.y ) ); } public onPointerMove(difference: IPoint): void { - const currentPoint = Point.fromPoint(difference).add(this.fDraggableDataContext.onPointerDownPosition); - const x: number = Math.min(this.fDraggableDataContext.onPointerDownPosition.x, currentPoint.x); - const y: number = Math.min(this.fDraggableDataContext.onPointerDownPosition.y, currentPoint.y); + const currentPoint = Point.fromPoint(difference).add(this._fDraggableDataContext.onPointerDownPosition); + + const point = this._getMinimumPoint(this._fDraggableDataContext.onPointerDownPosition, currentPoint); const width = Math.abs(difference.x); const height = Math.abs(difference.y); - this.fSelectionArea.draw( - RectExtensions.initialize(x, y, width, height) - ); + const fSelectionAreaRect = RectExtensions.initialize(point.x, point.y, width, height); + + this._fSelectionArea.draw(fSelectionAreaRect); this._selectedByMove = []; this._canBeSelected.forEach((item) => { - item.element.deselect(); + item.element.unmarkAsSelected(); - const itemRect = RectExtensions.addPoint(item.rect, this.canvasPosition); + const fItemRect = RectExtensions.addPoint(item.fRect, this._fCanvasPosition); - const isIntersect = RectExtensions.intersectionWithRect(itemRect, RectExtensions.initialize(x, y, width, height)); + const isIntersect = RectExtensions.intersectionWithRect(fItemRect, fSelectionAreaRect); if (isIntersect) { - item.element.select(); + item.element.markAsSelected(); this._selectedByMove.push(item.element); } }); - this.fMediator.send(new NotifyTransformChangedRequest()); + this._fMediator.execute(new NotifyTransformChangedRequest()); + } + + private _getMinimumPoint(point1: IPoint, point2: IPoint): IPoint { + return PointExtensions.initialize( + Math.min(point1.x, point2.x), + Math.min(point1.y, point2.y) + ); } public onPointerUp(): void { - this.fSelectionArea.hide(); - this.fDraggableDataContext.selectedItems.push(...this._selectedByMove); + this._fSelectionArea.hide(); + this._fDraggableDataContext.selectedItems.push(...this._selectedByMove); if (this._selectedByMove.length > 0) { - this.fDraggableDataContext.isSelectedChanged = true; + this._fDraggableDataContext.isSelectedChanged = true; } } } diff --git a/projects/f-flow/src/f-selection-area/f-selection-area.component.ts b/projects/f-flow/src/f-selection-area/f-selection-area.component.ts index fb045acc..bb97d73f 100644 --- a/projects/f-flow/src/f-selection-area/f-selection-area.component.ts +++ b/projects/f-flow/src/f-selection-area/f-selection-area.component.ts @@ -1,10 +1,11 @@ -import { Component, ElementRef, inject, OnInit } from '@angular/core'; +import { Component, ElementRef, inject, Input, OnInit } from '@angular/core'; import { FSelectionAreaBase } from './f-selection-area-base'; import { F_DRAG_AND_DROP_PLUGIN, IFDragAndDropPlugin } from '../f-draggable'; import { IPointerEvent } from '@foblex/drag-toolkit'; import { IRect } from '@foblex/2d'; import { FMediator } from '@foblex/mediator'; import { SelectionAreaFinalizeRequest, SelectionAreaPreparationRequest } from './domain'; +import { FEventTrigger, TriggerEvent } from '../domain'; @Component({ selector: "f-selection-area", @@ -22,6 +23,11 @@ export class FSelectionAreaComponent extends FSelectionAreaBase implements OnIni private _fMediator = inject(FMediator); private _elementReference = inject(ElementRef); + @Input() + public fTrigger: FEventTrigger = (event: TriggerEvent) => { + return event.shiftKey; + }; + public override get hostElement(): HTMLElement { return this._elementReference.nativeElement; } @@ -47,7 +53,7 @@ export class FSelectionAreaComponent extends FSelectionAreaBase implements OnIni } public onPointerDown(event: IPointerEvent): void { - this._fMediator.send(new SelectionAreaPreparationRequest(event, this)); + this._fMediator.send(new SelectionAreaPreparationRequest(event, this, this.fTrigger)); } public onPointerUp(event: IPointerEvent): void { diff --git a/projects/f-flow/src/mixins/change-selection/change-selection.ts b/projects/f-flow/src/mixins/change-selection/change-selection.ts index b671dd44..c5fc4d60 100644 --- a/projects/f-flow/src/mixins/change-selection/change-selection.ts +++ b/projects/f-flow/src/mixins/change-selection/change-selection.ts @@ -1,10 +1,10 @@ import { AbstractConstructor, Constructor } from '../constructor'; import { IHasHostElement } from '../../i-has-host-element'; -import { ICanChangeSelection } from './i-can-change-selection'; +import { ISelectable } from './i-selectable'; export const F_SELECTED_CLASS = 'f-selected'; -type CanChangeSelectionConstructor = Constructor & AbstractConstructor; +type CanChangeSelectionConstructor = Constructor & AbstractConstructor; export function mixinChangeSelection>(base: T): CanChangeSelectionConstructor & T; export function mixinChangeSelection>(base: T): CanChangeSelectionConstructor & T { @@ -14,13 +14,13 @@ export function mixinChangeSelection>(bas public fSelectionDisabled: boolean = false; - public deselect(): void { - this.deselectChild?.(); + public unmarkAsSelected(): void { + this.unmarkChildrenAsSelected?.(); this.hostElement.classList.remove(F_SELECTED_CLASS); } - public select(): void { - this.selectChild?.(); + public markAsSelected(): void { + this.markChildrenAsSelected?.(); if (!this.isSelected()) { this.hostElement.classList.add(F_SELECTED_CLASS); } @@ -30,10 +30,10 @@ export function mixinChangeSelection>(bas return this.hostElement.classList.contains(F_SELECTED_CLASS); } - public selectChild(): void { + public markChildrenAsSelected(): void { } - public deselectChild(): void { + public unmarkChildrenAsSelected(): void { } constructor(...args: any[]) { diff --git a/projects/f-flow/src/mixins/change-selection/i-can-change-selection.ts b/projects/f-flow/src/mixins/change-selection/i-can-change-selection.ts deleted file mode 100644 index 70235047..00000000 --- a/projects/f-flow/src/mixins/change-selection/i-can-change-selection.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface ICanChangeSelection { - - fId: string; - - fSelectionDisabled: boolean; - - hostElement: HTMLElement | SVGElement; - - select(): void; - - deselect(): void; - - isSelected(): boolean; - - selectChild?(): void; - - deselectChild?(): void; -} diff --git a/projects/f-flow/src/mixins/change-selection/i-selectable.ts b/projects/f-flow/src/mixins/change-selection/i-selectable.ts new file mode 100644 index 00000000..8d4075a4 --- /dev/null +++ b/projects/f-flow/src/mixins/change-selection/i-selectable.ts @@ -0,0 +1,18 @@ +export interface ISelectable { + + fId: string; + + fSelectionDisabled: boolean; + + hostElement: HTMLElement | SVGElement; + + markAsSelected(): void; + + unmarkAsSelected(): void; + + isSelected(): boolean; + + markChildrenAsSelected?(): void; + + unmarkChildrenAsSelected?(): void; +} diff --git a/projects/f-flow/src/mixins/change-selection/index.ts b/projects/f-flow/src/mixins/change-selection/index.ts index 7a38172a..9b9096d3 100644 --- a/projects/f-flow/src/mixins/change-selection/index.ts +++ b/projects/f-flow/src/mixins/change-selection/index.ts @@ -1,3 +1,3 @@ export * from './change-selection'; -export * from './i-can-change-selection'; +export * from './i-selectable'; diff --git a/projects/f-pro-examples/db-management-example/components/flow/db-management-flow.component.html b/projects/f-pro-examples/db-management-example/components/flow/db-management-flow.component.html index 25b0ad24..755c9756 100644 --- a/projects/f-pro-examples/db-management-example/components/flow/db-management-flow.component.html +++ b/projects/f-pro-examples/db-management-example/components/flow/db-management-flow.component.html @@ -1,4 +1,5 @@ - - + @@ -49,7 +50,20 @@
} - + @for (group of viewModel.groups; track group.id) { +
+
{{ group.name }}
+
+
+
+
+
+
+
+
+
+ } @for (table of viewModel.tables; track table.id) { } - @for (group of viewModel.groups; track group.id) { -
-
{{ group.name }}
-
-
-
-
-
- } +
diff --git a/projects/f-pro-examples/db-management-example/components/flow/db-management-flow.component.scss b/projects/f-pro-examples/db-management-example/components/flow/db-management-flow.component.scss index 18fd178b..d7cbc03a 100644 --- a/projects/f-pro-examples/db-management-example/components/flow/db-management-flow.component.scss +++ b/projects/f-pro-examples/db-management-example/components/flow/db-management-flow.component.scss @@ -8,8 +8,8 @@ ::ng-deep db-management-flow { .f-background { - circle { - fill: var(--db-background-component-color); + line { + stroke: var(--db-background-component-color); } } @@ -150,22 +150,34 @@ border: 1px solid var(--db-primary-1); border-radius: 1px; + &.f-resize-handle-left { + top: 50%; + left: var(--resize-handle-offset); + cursor: col-resize; + } + &.f-resize-handle-left-top { top: var(--resize-handle-offset); left: var(--resize-handle-offset); cursor: nwse-resize; } + &.f-resize-handle-top { + top: var(--resize-handle-offset); + left: 50%; + cursor: row-resize; + } + &.f-resize-handle-right-top { top: var(--resize-handle-offset); right: var(--resize-handle-offset); cursor: nesw-resize; } - &.f-resize-handle-left-bottom { - bottom: var(--resize-handle-offset); - left: var(--resize-handle-offset); - cursor: nesw-resize; + &.f-resize-handle-right { + top: 50%; + right: var(--resize-handle-offset); + cursor: col-resize; } &.f-resize-handle-right-bottom { @@ -173,4 +185,16 @@ right: var(--resize-handle-offset); cursor: nwse-resize; } + + &.f-resize-handle-bottom { + bottom: var(--resize-handle-offset); + left: 50%; + cursor: row-resize; + } + + &.f-resize-handle-left-bottom { + bottom: var(--resize-handle-offset); + left: var(--resize-handle-offset); + cursor: nesw-resize; + } } diff --git a/public/markdown/examples/environment.ts b/public/markdown/examples/environment.ts index 2d91a517..30552d65 100644 --- a/public/markdown/examples/environment.ts +++ b/public/markdown/examples/environment.ts @@ -260,6 +260,10 @@ function nodesGroup(): INavigationGroup { image_width: 806, image_height: 600, image_type: 'image/png', + badge: { + text: 'Updated', + type: 'warning' + } }, { link: 'grouping',