Skip to content

Commit

Permalink
0.11.0 (#82)
Browse files Browse the repository at this point in the history
* feat: initial implementation of subflows

* feat: allow to select group node

* fix: improve layer order for direct children

* feat: keep child node in bounds of parent

* feat: compute global point on multiple levels

* fix: improve layer order for nested children

* fix: rounded group in safari

* refactor: make parentId private

* feat: add color field to default group

* feat: implement template group

* feat: add selectable class to template group

* feat: support handles on group nodes

* feat: add more type utils

* feat: set initial order for nodes

* feat: add optimization setting

* feat: allow to set null for parentId

* docs: add page about subflows

* up version
  • Loading branch information
artem-mangilev authored Aug 26, 2024
1 parent b9ec1dc commit 0680dcd
Show file tree
Hide file tree
Showing 19 changed files with 378 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Edge, Node, VflowModule } from 'projects/ngx-vflow-lib/src/public-api';

@Component({
template: `<vflow [nodes]="nodes" [edges]="edges">
<ng-template groupNode let-ctx>
<svg:rect
selectable
rx="5"
ry="5"
[attr.width]="ctx.node.width"
[attr.height]="ctx.node.height"
[style.stroke]="'red'"
[style.fill]="'red'"
[style.fill-opacity]="0.05"
[style.stroke-width]="ctx.selected() ? 3 : 1"
>
<handle type="source" position="right" />
</svg:rect>
</ng-template>
</vflow>`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [VflowModule]
})
export class SubflowsDemoComponent {
public nodes: Node[] = [
{
id: '1',
point: { x: 10, y: 10 },
type: 'default',
text: `1`,
parentId: '3'
},
{
id: '2',
point: { x: 90, y: 80 },
type: 'default',
// it's possible to pass html in this field
text: `<strong>2</strong>`,
parentId: '3'
},
{
id: '3',
point: { x: 10, y: 10 },
type: 'default-group',
width: 250,
height: 250
},
{
id: '4',
point: { x: 280, y: 10 },
type: 'default',
text: `4`,
},
{
id: '5',
point: { x: 10, y: 160 },
type: 'template-group',
width: 170,
height: 70,
parentId: '3'
},
{
id: '6',
point: { x: 10, y: 10 },
type: 'default',
text: `6`,
parentId: '5'
},
]

public edges: Edge[] = [
{
source: '1',
target: '2',
id: '1 -> 2'
},
{
source: '2',
target: '4',
id: '2 -> 4'
},
{
source: '5',
target: '4',
id: '5 -> 4'
},
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# {{ NgDocPage.title }}

A subflow is a node that can contain child nodes. Key things about subflows:

- To create a subflow, you need to use a `default-group` or `template-group` `type` on `Node` or `DynamicNode`.
- To associate a node with a subflow, set the `parentId` to the ID of the subflow.
- Nodes within a subflow have coordinates *relative* to that subflow.
- A subflow is itself a node, so it can act as a source or a target (this functionality is available only for `template-group` subflows).
- Use the `groupNode` directive on an `ng-template` to define your custom subflow. While a custom subflow can theoretically be any SVG structure, it's recommended to use the `<rect />` element.

{{ NgDocActions.demo("SubflowsDemoComponent", { expanded: true }) }}

## See also

- `DefaultGroupNode`
- `TemplateGroupNode`
- `DefaultDynamicGroupNode`
- `TemplateDynamicGroupNode`
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NgDocPage } from '@ng-doc/core';
import ExamplesCategory from '../../ng-doc.category'
import { SubflowsDemoComponent } from './demo/subflows-demo.component';

const TestPage: NgDocPage = {
title: `Subflows`,
mdFile: './index.md',
category: ExamplesCategory,
demos: { SubflowsDemoComponent },
order: 1
};

export default TestPage;
2 changes: 1 addition & 1 deletion projects/ngx-vflow-lib/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-vflow",
"version": "0.10.0",
"version": "0.11.0",
"license": "MIT",
"homepage": "https://www.ngx-vflow.org/",
"author": "Artem Mangilev",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<!-- Default node -->
<svg:foreignObject
*ngIf="nodeModel.node.type === 'default'"
class="selectable"
Expand All @@ -22,22 +23,24 @@
</div>
</svg:foreignObject>

<!-- Template node -->
<svg:foreignObject
*ngIf="nodeModel.node.type === 'html-template' && nodeHtmlTemplate"
*ngIf="nodeModel.node.type === 'html-template' && nodeTemplate"
class="selectable"
[attr.width]="nodeModel.size().width"
[attr.height]="nodeModel.size().height"
(mousedown)="pullNode()"
>
<div #htmlWrapper class="wrapper">
<ng-container
[ngTemplateOutlet]="nodeHtmlTemplate"
[ngTemplateOutlet]="nodeTemplate"
[ngTemplateOutletContext]="{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }"
[ngTemplateOutletInjector]="injector"
/>
</div>
</svg:foreignObject>

<!-- Component node -->
<svg:foreignObject
*ngIf="nodeModel.isComponentType"
class="selectable"
Expand All @@ -54,6 +57,33 @@
</div>
</svg:foreignObject>

<!-- Default group node -->
<svg:rect
*ngIf="nodeModel.node.type === 'default-group'"
class="default-group-node"
rx="5"
ry="5"
[class.default-group-node_selected]="nodeModel.selected()"
[attr.width]="nodeModel.size().width"
[attr.height]="nodeModel.size().height"
[style.stroke]="nodeModel.color()"
[style.fill]="nodeModel.color()"
(mousedown)="pullNode(); selectNode()"
/>

<svg:g
*ngIf="nodeModel.node.type === 'template-group' && groupNodeTemplate"
class="selectable"
(mousedown)="pullNode()"
>
<ng-container
[ngTemplateOutlet]="groupNodeTemplate"
[ngTemplateOutletContext]="{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }"
[ngTemplateOutletInjector]="injector"
/>
</svg:g>

<!-- Handles -->
<ng-container *ngFor="let handle of nodeModel.handles()">
<svg:circle
*ngIf="!handle.template"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
}
}

.default-group-node {
stroke-width: 1.5px;
fill-opacity: 0.05;

&_selected {
stroke-width: 2px;
}
}

.default-handle {
stroke: #fff;
fill: #1b262c;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { FlowSettingsService } from '../../services/flow-settings.service';
import { SelectionService } from '../../services/selection.service';
import { ConnectionControllerDirective } from '../../directives/connection-controller.directive';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { isDynamicNode } from '../../interfaces/node.interface';

export type HandleState = 'valid' | 'invalid' | 'idle'

Expand All @@ -39,7 +38,10 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, WithInje
public nodeModel!: NodeModel

@Input()
public nodeHtmlTemplate?: TemplateRef<any>
public nodeTemplate?: TemplateRef<any>

@Input()
public groupNodeTemplate?: TemplateRef<any>

@ViewChild('nodeContent')
public nodeContentRef!: ElementRef<SVGGraphicsElement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
*ngFor="let model of nodeModels(); trackBy: trackNodes"
node
[nodeModel]="model"
[nodeHtmlTemplate]="nodeHtmlDirective?.templateRef"
[nodeTemplate]="nodeTemplateDirective?.templateRef"
[groupNodeTemplate]="groupNodeTemplateDirective?.templateRef"
[attr.transform]="model.pointTransform()"
/>
</svg:g>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AfterContentInit, ChangeDetectionStrategy, Component, ContentChild, EventEmitter, Injector, Input, OnChanges, Output, Signal, SimpleChanges, ViewChild, computed, effect, inject, runInInjectionContext } from '@angular/core';
import { AfterContentInit, ChangeDetectionStrategy, Component, ContentChild, EventEmitter, Injector, Input, OnChanges, OnInit, Output, Signal, SimpleChanges, ViewChild, computed, effect, inject, runInInjectionContext } from '@angular/core';
import { DynamicNode, Node } from '../../interfaces/node.interface';
import { MapContextDirective } from '../../directives/map-context.directive';
import { DraggableService } from '../../services/draggable.service';
Expand All @@ -7,7 +7,7 @@ import { ViewportService } from '../../services/viewport.service';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { Edge } from '../../interfaces/edge.interface';
import { EdgeModel } from '../../models/edge.model';
import { ConnectionTemplateDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, NodeHtmlTemplateDirective } from '../../directives/template.directive';
import { ConnectionTemplateDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, GroupNodeTemplateDirective, NodeHtmlTemplateDirective } from '../../directives/template.directive';
import { HandlePositions } from '../../interfaces/handle-positions.interface';
import { addNodesToEdges } from '../../utils/add-nodes-to-edges';
import { skip } from 'rxjs';
Expand All @@ -31,6 +31,7 @@ import { ComponentEventBusService } from '../../services/component-event-bus.ser
import { Background } from '../../types/background.type';
import { SpacePointContextDirective } from '../../directives/space-point-context.directive';
import { FitViewOptions } from '../../interfaces/fit-view-options.interface';
import { Optimization } from '../../interfaces/optimization.interface';

const connectionControllerHostDirective = {
directive: ConnectionControllerDirective,
Expand Down Expand Up @@ -91,7 +92,7 @@ const changesControllerHostDirective = {
changesControllerHostDirective
]
})
export class VflowComponent {
export class VflowComponent implements OnInit {
// #region DI
private viewportService = inject(ViewportService)
private flowEntitiesService = inject(FlowEntitiesService)
Expand Down Expand Up @@ -152,6 +153,11 @@ export class VflowComponent {
@Input()
public background: Background | string = '#fff'

@Input()
public optimization: Optimization = {
computeLayersOnInit: true
}

/**
* Global rule if you can or can't select entities
*/
Expand Down Expand Up @@ -219,7 +225,10 @@ export class VflowComponent {

// #region TEMPLATES
@ContentChild(NodeHtmlTemplateDirective)
protected nodeHtmlDirective?: NodeHtmlTemplateDirective
protected nodeTemplateDirective?: NodeHtmlTemplateDirective

@ContentChild(GroupNodeTemplateDirective)
protected groupNodeTemplateDirective?: GroupNodeTemplateDirective

@ContentChild(EdgeTemplateDirective)
protected edgeTemplateDirective?: EdgeTemplateDirective
Expand Down Expand Up @@ -278,6 +287,10 @@ export class VflowComponent {

protected markers = this.flowEntitiesService.markers

public ngOnInit(): void {
this.setInitialNodesOrder()
}

// #region METHODS_API
/**
* Change viewport to specified state
Expand Down Expand Up @@ -341,6 +354,19 @@ export class VflowComponent {
protected trackEdges(idx: number, { edge }: EdgeModel) {
return edge
}
}

private setInitialNodesOrder() {
if (this.optimization.computeLayersOnInit) {

this.nodeModels().forEach(model => {
switch (model.node.type) {
case 'default-group':
case 'template-group': {
this.nodeRenderingService.pullNode(model)
}
}
})
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export class NodeHtmlTemplateDirective {
public templateRef = inject(TemplateRef)
}

@Directive({ selector: 'ng-template[groupNode]' })
export class GroupNodeTemplateDirective {
public templateRef = inject(TemplateRef)
}

@Directive({ selector: 'ng-template[handle]' })
export class HandleTemplateDirective {
public templateRef = inject(TemplateRef)
Expand Down
Loading

0 comments on commit 0680dcd

Please sign in to comment.