initial work on group nodes support

This commit is contained in:
Dylan Vorster
2018-03-21 16:40:25 +02:00
parent cc40c398b7
commit 174ec45f8f
21 changed files with 243 additions and 25 deletions

View File

@ -0,0 +1,39 @@
import {
DiagramEngine,
DiagramModel,
DefaultNodeModel,
LinkModel,
DiagramWidget,
DefaultGroupNodeModel
} from "storm-react-diagrams";
import * as React from "react";
/**
* Tests the grid size
*/
export default () => {
var engine = new DiagramEngine();
engine.installDefaultFactories();
var model = new DiagramModel();
var node1 = new DefaultNodeModel("Node 1", "rgb(0,192,255)");
let port = node1.addOutPort("Out");
node1.setPosition(100, 100);
var node2 = new DefaultNodeModel("Node 2", "rgb(192,255,0)");
let port2 = node2.addInPort("In");
node2.setPosition(400, 100);
var node3 = new DefaultGroupNodeModel("Group");
node3.setPosition(50, 50);
let link1 = port.link(port2);
model.addAll(node1, node2, link1, node3);
engine.setDiagramModel(model);
//6) render the diagram!
return <DiagramWidget className="srd-demo-canvas" diagramEngine={engine} />;
};

View File

@ -38,6 +38,13 @@ storiesOf("Simple Usage", module)
"Simple flow example",
Helper.makeDemo(require("./demo-simple-flow/index").default(), require("!!raw-loader!./demo-simple-flow/index"))
)
.add(
"Group Nodes",
Helper.makeDemo(
require("./demo-grouped-nodes/index").default(),
require("!!raw-loader!./demo-grouped-nodes/index")
)
)
.add(
"Performance demo",
Helper.makeDemo(require("./demo-performance/index").default(), require("!!raw-loader!./demo-performance/index"))

View File

@ -16,6 +16,7 @@ import { DefaultPortFactory } from "./defaults/factories/DefaultPortFactory";
import { LabelModel } from "./models/LabelModel";
import { DefaultLabelFactory } from "./defaults/factories/DefaultLabelFactory";
import { Toolkit } from "./Toolkit";
import { DefaultGroupNodeFactory } from "./defaults/factories/DefaultGroupNodeFactory";
/**
* @author Dylan Vorster
*/
@ -81,6 +82,7 @@ export class DiagramEngine extends BaseEntity<DiagramEngineListener> {
this.registerLinkFactory(new DefaultLinkFactory());
this.registerPortFactory(new DefaultPortFactory());
this.registerLabelFactory(new DefaultLabelFactory());
this.registerNodeFactory(new DefaultGroupNodeFactory());
}
repaintCanvas() {

View File

@ -1,11 +1,13 @@
import { DiagramEngine } from "../DiagramEngine";
export class BaseAction {
mouseX: number;
mouseY: number;
ms: number;
constructor(mouseX: number, mouseY: number) {
this.mouseX = mouseX;
this.mouseY = mouseY;
constructor() {
this.ms = new Date().getTime();
}
actionWillFire(engine: DiagramEngine) {}
actionDidFire(engine: DiagramEngine) {}
}

View File

@ -0,0 +1,12 @@
import { BaseAction } from "./BaseAction";
export class MouseAction extends BaseAction {
mouseX: number;
mouseY: number;
constructor(mouseX: number, mouseY: number) {
super();
this.mouseX = mouseX;
this.mouseY = mouseY;
}
}

View File

@ -1,7 +1,7 @@
import { BaseAction } from "./BaseAction";
import { DiagramModel } from "../models/DiagramModel";
import { MouseAction } from "./MouseAction";
export class MoveCanvasAction extends BaseAction {
export class MoveCanvasAction extends MouseAction {
initialOffsetX: number;
initialOffsetY: number;

View File

@ -1,10 +1,10 @@
import { BaseAction } from "./BaseAction";
import { SelectionModel } from "../models/SelectionModel";
import { PointModel } from "../models/PointModel";
import { NodeModel } from "../models/NodeModel";
import { DiagramEngine } from "../DiagramEngine";
import { MouseAction } from "./MouseAction";
export class MoveItemsAction extends BaseAction {
export class MoveItemsAction extends MouseAction {
selectionModels: SelectionModel[];
moved: boolean;

View File

@ -0,0 +1,3 @@
import { BaseAction } from "./BaseAction";
export class RenderAction extends BaseAction {}

View File

@ -1,7 +1,7 @@
import { BaseAction } from "./BaseAction";
import { DiagramModel } from "../models/DiagramModel";
import { MouseAction } from "./MouseAction";
export class SelectingAction extends BaseAction {
export class SelectingAction extends MouseAction {
mouseX2: number;
mouseY2: number;

View File

@ -0,0 +1,19 @@
import { AbstractNodeFactory } from "../../factories/AbstractNodeFactory";
import { DefaultGroupNodeModel } from "../models/DefaultGroupNodeModel";
import { DiagramEngine } from "../../DiagramEngine";
import * as React from "react";
import { DefaultGroupNodeWidget } from "../widgets/DefaultGroupNodeWidget";
export class DefaultGroupNodeFactory extends AbstractNodeFactory<DefaultGroupNodeModel> {
constructor() {
super("default-group-node");
}
generateReactWidget(diagramEngine: DiagramEngine, node: DefaultGroupNodeModel): any {
return <DefaultGroupNodeWidget node={node} />;
}
getNewInstance(initialConfig?: any): DefaultGroupNodeModel {
return new DefaultGroupNodeModel("Default Group");
}
}

View File

@ -0,0 +1,58 @@
import { NodeModel } from "../../models/NodeModel";
import { DiagramEngine } from "../../DiagramEngine";
import { RenderAction } from "../../actions/RenderAction";
import { BaseAction } from "../../actions/BaseAction";
import * as _ from "lodash";
import { BaseModel } from "../../models/BaseModel";
export class DefaultGroupNodeModel extends NodeModel {
nodes: NodeModel[];
title: string;
constructor(title: string) {
super("default-group-node");
this.nodes = [];
this.title = title;
}
recollectChildNodes(engine: DiagramEngine) {
let diagramModel = engine.getDiagramModel();
this.nodes = [];
_.forEach(diagramModel.getNodes(), node => {
if (
node.id !== this.id &&
node.x > this.x &&
node.x < this.x + this.width &&
node.y > this.y &&
node.y < this.y + this.height
) {
this.nodes.push(node);
}
});
}
getSelectedEntities(): BaseModel[] {
let entities = super.getSelectedEntities();
_.forEach(this.nodes, node => {
entities.push(node);
_.forEach(node.ports, port => {
entities = entities.concat(
_.map(port.getLinks(), link => {
return link.getPointForPort(port);
})
);
});
});
return entities;
}
canvasActionFired(action: BaseAction, diagramEngine: DiagramEngine) {
super.canvasActionFired(action, diagramEngine);
if (action instanceof RenderAction) {
this.recollectChildNodes(diagramEngine);
}
}
}

View File

@ -0,0 +1,24 @@
import { BaseWidget, BaseWidgetProps } from "../../widgets/BaseWidget";
import * as React from "react";
import { DefaultGroupNodeModel } from "../models/DefaultGroupNodeModel";
export interface DefaultGroupNodeWidgetProps extends BaseWidgetProps {
node: DefaultGroupNodeModel;
}
export class DefaultGroupNodeWidget extends BaseWidget<DefaultGroupNodeWidgetProps> {
constructor(props: DefaultGroupNodeWidgetProps) {
super("srd-default-group-node", props);
}
render() {
return (
<div {...this.getProps()}>
<div className={this.bem("__title")}>
<div className={this.bem("__name")}>{this.props.node.title}</div>
</div>
<div className={this.bem("__container")} />
</div>
);
}
}

View File

@ -3,8 +3,8 @@ import { BaseModel } from "../models/BaseModel";
export abstract class AbstractFactory<T extends BaseModel> {
type: string;
constructor(name: string) {
this.type = name;
constructor(type: string) {
this.type = type;
}
getType(): string {

View File

@ -10,16 +10,19 @@ export * from "./defaults/models/DefaultNodeModel";
export * from "./defaults/models/DefaultPortModel";
export * from "./defaults/models/DefaultLinkModel";
export * from "./defaults/models/DefaultLabelModel";
export * from "./defaults/models/DefaultGroupNodeModel";
export * from "./defaults/factories/DefaultLinkFactory";
export * from "./defaults/factories/DefaultNodeFactory";
export * from "./defaults/factories/DefaultPortFactory";
export * from "./defaults/factories/DefaultLabelFactory";
export * from "./defaults/factories/DefaultGroupNodeFactory";
export * from "./defaults/widgets/DefaultLinkWidget";
export * from "./defaults/widgets/DefaultLabelWidget";
export * from "./defaults/widgets/DefaultNodeWidget";
export * from "./defaults/widgets/DefaultPortLabelWidget";
export * from "./defaults/widgets/DefaultGroupNodeWidget";
export * from "./factories/AbstractFactory";
export * from "./factories/AbstractLabelFactory";
@ -30,6 +33,7 @@ export * from "./factories/AbstractPortFactory";
export * from "./routing/PathFinding";
export * from "./actions/BaseAction";
export * from "./actions/MouseAction";
export * from "./actions/MoveCanvasAction";
export * from "./actions/MoveItemsAction";
export * from "./actions/SelectingAction";

View File

@ -2,6 +2,7 @@ import { BaseEntity, BaseListener } from "../BaseEntity";
import * as _ from "lodash";
import { BaseEvent } from "../BaseEntity";
import { DiagramEngine } from "../DiagramEngine";
import { BaseAction } from "../actions/BaseAction";
export interface BaseModelListener extends BaseListener {
selectionChanged?(event: BaseEvent<BaseModel> & { isSelected: boolean }): void;
@ -34,6 +35,8 @@ export class BaseModel<
this.parent = parent;
}
public canvasActionFired(action: BaseAction, engine: DiagramEngine) {}
public getSelectedEntities(): BaseModel<any, T>[] {
if (this.isSelected()) {
return [this];

View File

@ -3,6 +3,8 @@ import { PortModel } from "./PortModel";
import * as _ from "lodash";
import { DiagramEngine } from "../DiagramEngine";
import { DiagramModel } from "./DiagramModel";
import { BaseAction } from "../actions/BaseAction";
import { RenderAction } from "../actions/RenderAction";
export class NodeModel extends BaseModel<DiagramModel, BaseModelListener> {
x: number;
@ -37,7 +39,7 @@ export class NodeModel extends BaseModel<DiagramModel, BaseModelListener> {
this.y = y;
}
getSelectedEntities() {
getSelectedEntities(): BaseModel[] {
let entities = super.getSelectedEntities();
// add the points of each link that are selected here
@ -126,6 +128,13 @@ export class NodeModel extends BaseModel<DiagramModel, BaseModelListener> {
return port;
}
canvasActionFired(action: BaseAction, diagramEngine: DiagramEngine) {
super.canvasActionFired(action, diagramEngine);
if (action instanceof RenderAction) {
this.updateDimensions(diagramEngine.getNodeDimensions(this));
}
}
updateDimensions({ width, height }: { width: number; height: number }) {
this.width = width;
this.height = height;

View File

@ -0,0 +1,36 @@
.srd-default-group-node{
border: solid black 1px;
pointer-events: none;
&__title {
background: rgba(black, 0.3);
display: flex;
white-space: nowrap;
pointer-events: all;
> * {
align-self: center;
}
.fa {
padding: 5px;
opacity: 0.2;
cursor: pointer;
&:hover {
opacity: 1.0;
}
}
}
&__name {
flex-grow: 1;
padding: 5px 5px;
}
&__container{
min-height: 100px;
min-width: 100px;
}
}

View File

@ -8,4 +8,5 @@
@import "defaults/DefaultNodeWidget";
@import "defaults/DefaultPortWidget";
@import "defaults/DefaultLabelWidget";
@import "defaults/DefaultLinkWidget";
@import "defaults/DefaultLinkWidget";
@import "defaults/DefaultGroupNodeWidget";

View File

@ -194,10 +194,12 @@ export class DiagramWidget extends BaseWidget<DiagramProps, DiagramState> {
if (this.props.actionStoppedFiring && !shouldSkipEvent) {
this.props.actionStoppedFiring(this.state.action);
}
this.state.action && this.state.action.actionDidFire(this.props.diagramEngine);
this.setState({ action: null });
}
startFiringAction(action: BaseAction) {
action.actionWillFire(this.props.diagramEngine);
var setState = true;
if (this.props.actionStartedFiring) {
setState = this.props.actionStartedFiring(action);

View File

@ -4,6 +4,7 @@ import * as _ from "lodash";
import { NodeWidget } from "../NodeWidget";
import { NodeModel } from "../../models/NodeModel";
import { BaseWidget, BaseWidgetProps } from "../BaseWidget";
import { RenderAction } from "../../actions/RenderAction";
export interface NodeLayerProps extends BaseWidgetProps {
diagramEngine: DiagramEngine;
@ -17,18 +18,13 @@ export class NodeLayerWidget extends BaseWidget<NodeLayerProps, NodeLayerState>
this.state = {};
}
updateNodeDimensions = () => {
if (!this.props.diagramEngine.nodesRendered) {
const diagramModel = this.props.diagramEngine.getDiagramModel();
_.map(diagramModel.getNodes(), node => {
node.updateDimensions(this.props.diagramEngine.getNodeDimensions(node));
});
}
};
componentDidUpdate() {
this.updateNodeDimensions();
this.props.diagramEngine.nodesRendered = true;
let model = this.props.diagramEngine.getDiagramModel();
let action = new RenderAction();
_.map(model.getNodes(), node => {
node.canvasActionFired(action, this.props.diagramEngine);
});
}
render() {

View File

@ -12,6 +12,7 @@
"jsx": "react",
"baseUrl": ".",
"paths": {
"./src":["./src"],
"storm-react-diagrams": ["src/main.ts"]
},
"lib": [