mirror of
https://github.com/projectstorm/react-diagrams.git
synced 2025-08-14 16:51:29 +08:00
initial work on group nodes support
This commit is contained in:
39
demos/demo-grouped-nodes/index.tsx
Normal file
39
demos/demo-grouped-nodes/index.tsx
Normal 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} />;
|
||||
};
|
@ -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"))
|
||||
|
@ -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() {
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
12
src/actions/MouseAction.ts
Normal file
12
src/actions/MouseAction.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
3
src/actions/RenderAction.ts
Normal file
3
src/actions/RenderAction.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { BaseAction } from "./BaseAction";
|
||||
|
||||
export class RenderAction extends BaseAction {}
|
@ -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;
|
||||
|
||||
|
19
src/defaults/factories/DefaultGroupNodeFactory.tsx
Normal file
19
src/defaults/factories/DefaultGroupNodeFactory.tsx
Normal 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");
|
||||
}
|
||||
}
|
58
src/defaults/models/DefaultGroupNodeModel.ts
Normal file
58
src/defaults/models/DefaultGroupNodeModel.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
24
src/defaults/widgets/DefaultGroupNodeWidget.tsx
Normal file
24
src/defaults/widgets/DefaultGroupNodeWidget.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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";
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
|
36
src/sass/defaults/_DefaultGroupNodeWidget.scss
Normal file
36
src/sass/defaults/_DefaultGroupNodeWidget.scss
Normal 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;
|
||||
|
||||
}
|
||||
}
|
@ -8,4 +8,5 @@
|
||||
@import "defaults/DefaultNodeWidget";
|
||||
@import "defaults/DefaultPortWidget";
|
||||
@import "defaults/DefaultLabelWidget";
|
||||
@import "defaults/DefaultLinkWidget";
|
||||
@import "defaults/DefaultLinkWidget";
|
||||
@import "defaults/DefaultGroupNodeWidget";
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -12,6 +12,7 @@
|
||||
"jsx": "react",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"./src":["./src"],
|
||||
"storm-react-diagrams": ["src/main.ts"]
|
||||
},
|
||||
"lib": [
|
||||
|
Reference in New Issue
Block a user