break it up further into canvas

This commit is contained in:
Dylan Vorster
2019-08-04 13:27:55 +02:00
parent 0f7930e132
commit dd5a0b7d8d
187 changed files with 1521 additions and 1543 deletions

View File

@ -1,43 +0,0 @@
import './sass/main.scss';
export * from './src/core-actions/AbstractAction';
export * from './src/core-actions/AbstractActionFactory';
export * from './src/core-actions/AbstractMouseAction';
export * from './src/actions/move-canvas/MoveCanvasActionFactory';
export * from './src/actions/move-canvas/MoveCanvasAction';
export * from './src/actions/selecting-items/SelectingAction';
export * from './src/actions/selecting-items/SelectingItemsActionFactory';
export * from './src/actions/move-items/MoveItemsAction';
export * from './src/actions/move-items/MoveItemsActionFactory';
export * from './src/core/BaseObserver';
export * from './src/core/FactoryBank';
export * from './src/core/AbstractFactory';
export * from './src/core/AbstractModelFactory';
export * from './src/core/AbstractReactFactory';
export * from './src/core-models/BaseModel';
export * from './src/core-models/BasePositionModel';
export * from './src/core-models/BaseEntity';
export * from './src/models/DiagramModel';
export * from './src/models/LabelModel';
export * from './src/models/LinkModel';
export * from './src/models/PointModel';
export * from './src/models/PortModel';
export * from './src/models/SelectionModel';
export * from './src/models/NodeModel';
export * from './src/widgets/BaseWidget';
export * from './src/widgets/DiagramWidget';
export * from './src/widgets/layers/LinkLayerWidget';
export * from './src/widgets/layers/NodeLayerWidget';
export * from './src/widgets/LinkWidget';
export * from './src/widgets/NodeWidget';
export * from './src/widgets/PortWidget';
export * from './src/DiagramEngine';
export * from './src/Toolkit';

View File

@ -1,13 +0,0 @@
.srd-diagram {
position: relative;
flex-grow: 1;
display: flex;
cursor: move;
overflow: hidden;
&__selector {
position: absolute;
background-color: rgba(0, 192, 255, 0.2);
border: solid 2px rgb(0, 192, 255);
}
}

View File

@ -1,11 +0,0 @@
.srd-link-layer {
position: absolute;
height: 100%;
width: 100%;
transform-origin: 0 0;
overflow: visible !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
}

View File

@ -1,11 +0,0 @@
.srd-node-layer {
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
pointer-events: none;
transform-origin: 0 0;
width: 100%;
height: 100%;
}

View File

@ -1,14 +0,0 @@
.srd-node {
position: absolute;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
user-select: none;
cursor: move;
pointer-events: all;
&--selected {
> * {
border-color: rgb(0, 192, 255) !important;
}
}
}

View File

@ -1,10 +0,0 @@
.srd-port {
width: 15px;
height: 15px;
background: rgba(white, 0.1);
&:hover,
&.selected {
background: rgb(192, 255, 0);
}
}

View File

@ -1,5 +0,0 @@
@import 'DiagramWidget';
@import 'LinkLayerWidget';
@import 'NodeLayerWidget';
@import 'NodeWidget';
@import 'PortWidget';

View File

@ -1,28 +0,0 @@
import { AbstractMouseAction } from '../../core-actions/AbstractMouseAction';
import { MouseEvent } from 'react';
import { DiagramEngine } from '../../DiagramEngine';
export class MoveCanvasAction extends AbstractMouseAction {
initialOffsetX: number;
initialOffsetY: number;
constructor(mouseX: number, mouseY: number, engine: DiagramEngine) {
super(mouseX, mouseY, engine);
this.initialOffsetX = this.model.getOffsetX();
this.initialOffsetY = this.model.getOffsetY();
}
fireMouseMove(event: MouseEvent) {
//translate the actual canvas
this.model.setOffset(
this.initialOffsetX + (event.clientX - this.mouseX),
this.initialOffsetY + (event.clientY - this.mouseY)
);
}
fireMouseUp(event) {}
fireMouseDown(event) {
this.model.clearSelection();
}
}

View File

@ -1,17 +0,0 @@
import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory';
import { MoveCanvasAction } from './MoveCanvasAction';
import { MouseEvent } from 'react';
export class MoveCanvasActionFactory extends AbstractActionFactory<MoveCanvasAction> {
constructor() {
super('move-canvas');
}
generateAction(event: MouseEvent): MoveCanvasAction {
return new MoveCanvasAction(event.clientX, event.clientY, this.engine);
}
activate(event: ActionFactoryActivationEvent): boolean {
return !event.selectedEntity && !event.mouseEvent.shiftKey;
}
}

View File

@ -1,177 +0,0 @@
import { AbstractMouseAction } from '../../core-actions/AbstractMouseAction';
import { SelectionModel } from '../../models/SelectionModel';
import { PointModel } from '../../models/PointModel';
import { NodeModel } from '../../models/NodeModel';
import { DiagramEngine } from '../../DiagramEngine';
import { BasePositionModel } from '../../core-models/BasePositionModel';
import * as _ from 'lodash';
import { PortModel } from '../../models/PortModel';
import { LinkModel } from '../../models/LinkModel';
import { MouseEvent } from 'react';
import { ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory';
export class MoveItemsAction extends AbstractMouseAction {
selectionModels: SelectionModel[];
moved: boolean;
allowLooseLinks: boolean;
constructor(mouseX: number, mouseY: number, diagramEngine: DiagramEngine, allowLooseLinks: boolean) {
super(mouseX, mouseY, diagramEngine);
this.allowLooseLinks = allowLooseLinks;
this.moved = false;
}
fireMouseMove(event: MouseEvent) {
let amountX = event.clientX - this.mouseX;
let amountY = event.clientY - this.mouseY;
let amountZoom = this.model.getZoomLevel() / 100;
_.forEach(this.selectionModels, model => {
// in this case we need to also work out the relative grid position
if (model.model instanceof NodeModel || (model.model instanceof PointModel && !model.model.isConnectedToPort())) {
model.model.setPosition(
this.model.getGridPosition(model.initialX + amountX / amountZoom),
this.model.getGridPosition(model.initialY + amountY / amountZoom)
);
} else if (model.model instanceof PointModel) {
// we want points that are connected to ports, to not necessarily snap to grid
// this stuff needs to be pixel perfect, dont touch it
model.model.setPosition(
model.initialX + this.model.getGridPosition(amountX / amountZoom),
model.initialY + this.model.getGridPosition(amountY / amountZoom)
);
}
});
this.moved = true;
}
fireMouseUp(event: MouseEvent) {
const element = this.engine.getMouseElement(event);
_.forEach(this.selectionModels, model => {
//only care about points connecting to things
if (!(model.model instanceof PointModel)) {
return;
}
if (element && element.model instanceof PortModel && !this.engine.isModelLocked(element.model)) {
let link = model.model.getLink();
//if this was a valid link already and we are adding a node in the middle, create 2 links from the original
if (link.getTargetPort()) {
if (link.getTargetPort() !== element.model && link.getSourcePort() !== element.model) {
const targetPort = link.getTargetPort();
let newLink = link.clone({});
newLink.setSourcePort(element.model);
newLink.setTargetPort(targetPort);
link.setTargetPort(element.model);
link.getLastPoint().setPosition(this.engine.getPortCenter(element.model));
targetPort.removeLink(link);
newLink.removePointsBefore(newLink.getPoints()[link.getPointIndex(model.model)]);
link.removePointsAfter(model.model);
this.engine.getDiagramModel().addLink(newLink);
//if we are connecting to the same target or source, remove tweener points
} else if (link.getTargetPort() === element.model) {
link.removePointsAfter(model.model);
} else if (link.getSourcePort() === element.model) {
link.removePointsBefore(model.model);
}
}
// set the target port
else {
link.setTargetPort(element.model);
link.getLastPoint().setPosition(this.engine.getPortCenter(element.model));
}
}
});
//check for / remove any loose links in any models which have been moved
if (!this.allowLooseLinks && this.moved) {
_.forEach(this.selectionModels, model => {
//only care about points connecting to things
if (!(model.model instanceof PointModel)) {
return;
}
let selectedPoint: PointModel = model.model;
let link: LinkModel = selectedPoint.getLink();
if (link.getSourcePort() === null || link.getTargetPort() === null) {
link.remove();
}
});
}
//remove any invalid links
_.forEach(this.selectionModels, model => {
//only care about points connecting to things
if (!(model.model instanceof PointModel)) {
return;
}
let link: LinkModel = model.model.getLink();
let sourcePort: PortModel = link.getSourcePort();
let targetPort: PortModel = link.getTargetPort();
if (sourcePort !== null && targetPort !== null) {
if (!sourcePort.canLinkToPort(targetPort)) {
//link not allowed
link.remove();
} else if (
_.some(
_.values(targetPort.getLinks()),
(l: LinkModel) => l !== link && (l.getSourcePort() === sourcePort || l.getTargetPort() === sourcePort)
)
) {
//link is a duplicate
link.remove();
}
}
});
}
fireMouseDown(event: ActionFactoryActivationEvent) {
// clear selection first?
if (!event.selectedModel.isSelected()) {
this.model.clearSelection();
}
if (event.selectedModel instanceof PortModel) {
//its a port element, we want to drag a link
if (!this.engine.isModelLocked(event.selectedModel)) {
const portCenter = this.engine.getPortCenter(event.selectedModel);
const sourcePort = event.selectedModel;
const link = sourcePort.createLinkModel();
link.setSourcePort(sourcePort);
if (link) {
link.removeMiddlePoints();
if (link.getSourcePort() !== sourcePort) {
link.setSourcePort(sourcePort);
}
link.setTargetPort(null);
link.getFirstPoint().setPosition(portCenter);
link.getLastPoint().setPosition(portCenter);
this.model.clearSelection();
link.getLastPoint().setSelected(true);
this.model.addLink(link);
}
}
} else {
event.selectedModel.setSelected(true);
}
const selectedItems = this.model.getSelectedItems().filter(item => {
if (!(item instanceof BasePositionModel)) {
return false;
}
return !this.engine.isModelLocked(item);
});
this.selectionModels = selectedItems.map((item: PointModel | NodeModel) => {
return {
model: item,
initialX: item.getX(),
initialY: item.getY()
};
});
}
}

View File

@ -1,32 +0,0 @@
import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory';
import { MouseEvent } from 'react';
import { MoveItemsAction } from './MoveItemsAction';
export interface MoveItemsActionFactoryOptions {
allowLooseLinks?: boolean;
}
export class MoveItemsActionFactory extends AbstractActionFactory<MoveItemsAction> {
options: MoveItemsActionFactoryOptions;
static NAME = 'move-items';
constructor(options: MoveItemsActionFactoryOptions = {}) {
super(MoveItemsActionFactory.NAME);
this.options = {
...options,
allowLooseLinks: options.allowLooseLinks == null ? true : options.allowLooseLinks
};
}
generateAction(event: MouseEvent): MoveItemsAction {
return new MoveItemsAction(event.clientX, event.clientY, this.engine, this.options.allowLooseLinks);
}
activate(event: ActionFactoryActivationEvent): boolean {
if (event.selectedModel) {
return !this.engine.isModelLocked(event.selectedModel);
}
return false;
}
}

View File

@ -1,72 +0,0 @@
import { AbstractMouseAction } from '../../core-actions/AbstractMouseAction';
import { DiagramModel } from '../../models/DiagramModel';
import * as _ from 'lodash';
import { DiagramEngine } from '../../DiagramEngine';
import { MouseEvent } from 'react';
export class SelectingAction extends AbstractMouseAction {
mouseX2: number;
mouseY2: number;
constructor(mouseX: number, mouseY: number, engine: DiagramEngine) {
super(mouseX, mouseY, engine);
this.mouseX2 = mouseX;
this.mouseY2 = mouseY;
}
getBoxDimensions() {
return {
left: this.mouseX2 > this.mouseX ? this.mouseX : this.mouseX2,
top: this.mouseY2 > this.mouseY ? this.mouseY : this.mouseY2,
width: Math.abs(this.mouseX2 - this.mouseX),
height: Math.abs(this.mouseY2 - this.mouseY),
right: this.mouseX2 < this.mouseX ? this.mouseX : this.mouseX2,
bottom: this.mouseY2 < this.mouseY ? this.mouseY : this.mouseY2
};
}
containsElement(x: number, y: number, diagramModel: DiagramModel): boolean {
var z = diagramModel.getZoomLevel() / 100.0;
let dimensions = this.getBoxDimensions();
return (
x * z + diagramModel.getOffsetX() > dimensions.left &&
x * z + diagramModel.getOffsetX() < dimensions.right &&
y * z + diagramModel.getOffsetY() > dimensions.top &&
y * z + diagramModel.getOffsetY() < dimensions.bottom
);
}
fireMouseMove(event: MouseEvent) {
var relative = this.engine.getRelativePoint(event.clientX, event.clientY);
_.forEach(this.model.getNodes(), node => {
// TODO use geometry instead
if (this.containsElement(node.getX(), node.getY(), this.model)) {
node.setSelected(true);
}
});
_.forEach(this.model.getLinks(), link => {
var allSelected = true;
_.forEach(link.getPoints(), point => {
if (this.containsElement(point.getX(), point.getY(), this.model)) {
point.setSelected(true);
} else {
allSelected = false;
}
});
if (allSelected) {
link.setSelected(true);
}
});
this.mouseX2 = relative.x;
this.mouseY2 = relative.y;
}
fireMouseUp(event) {}
fireMouseDown(event) {}
}

View File

@ -1,17 +0,0 @@
import { AbstractActionFactory, ActionFactoryActivationEvent } from '../../core-actions/AbstractActionFactory';
import { MouseEvent } from 'react';
import { SelectingAction } from './SelectingAction';
export class SelectingItemsActionFactory extends AbstractActionFactory<SelectingAction> {
constructor() {
super('select-items');
}
generateAction(event: MouseEvent): SelectingAction {
return new SelectingAction(event.clientX, event.clientY, this.engine);
}
activate(event: ActionFactoryActivationEvent): boolean {
return !event.selectedEntity && event.mouseEvent.shiftKey;
}
}

View File

@ -1,12 +0,0 @@
import { DiagramEngine } from '../DiagramEngine';
import { DiagramModel } from '../models/DiagramModel';
export class AbstractAction {
engine: DiagramEngine;
model: DiagramModel;
constructor(engine: DiagramEngine) {
this.engine = engine;
this.model = engine.getDiagramModel();
}
}

View File

@ -1,16 +0,0 @@
import { AbstractAction } from './AbstractAction';
import { AbstractFactory } from '../core/AbstractFactory';
import { MouseEvent } from 'react';
import { BaseModel } from '../core-models/BaseModel';
export interface ActionFactoryActivationEvent {
selectedModel: BaseModel;
selectedEntity: HTMLElement;
mouseEvent: MouseEvent;
}
export abstract class AbstractActionFactory<T extends AbstractAction = AbstractAction> extends AbstractFactory {
abstract activate(event: ActionFactoryActivationEvent): boolean;
abstract generateAction(event: MouseEvent): T;
}

View File

@ -1,21 +0,0 @@
import { MouseEvent } from 'react';
import { AbstractAction } from './AbstractAction';
import { DiagramEngine } from '../DiagramEngine';
import { ActionFactoryActivationEvent } from './AbstractActionFactory';
export abstract class AbstractMouseAction extends AbstractAction {
protected mouseX: number;
protected mouseY: number;
constructor(mouseX: number, mouseY: number, engine: DiagramEngine) {
super(engine);
this.mouseX = mouseX;
this.mouseY = mouseY;
}
abstract fireMouseDown(event: ActionFactoryActivationEvent);
abstract fireMouseMove(event: MouseEvent);
abstract fireMouseUp(event: MouseEvent);
}

View File

@ -1,13 +0,0 @@
import { AbstractFactory } from './AbstractFactory';
import { BaseModel } from '../core-models/BaseModel';
export interface GenerateModelEvent {
initialConfig?: any;
}
export abstract class AbstractModelFactory<T extends BaseModel> extends AbstractFactory<T> {
/**
* Generates new models (the core factory pattern)
*/
abstract generateModel(event: GenerateModelEvent): T;
}

View File

@ -1,285 +0,0 @@
import {
BaseEntity,
BaseEntityEvent,
BaseEntityGenerics,
BaseEntityListener,
BaseEntityOptions,
BaseEntityType
} from '../core-models/BaseEntity';
import * as _ from 'lodash';
import { DiagramEngine } from '../DiagramEngine';
import { LinkModel } from './LinkModel';
import { NodeModel } from './NodeModel';
import { PortModel } from './PortModel';
import { BaseModel } from '../core-models/BaseModel';
import { PointModel } from './PointModel';
export interface DiagramListener extends BaseEntityListener {
nodesUpdated?(event: BaseEntityEvent & { node: NodeModel; isCreated: boolean }): void;
linksUpdated?(event: BaseEntityEvent & { link: LinkModel; isCreated: boolean }): void;
offsetUpdated?(event: BaseEntityEvent<DiagramModel> & { offsetX: number; offsetY: number }): void;
zoomUpdated?(event: BaseEntityEvent<DiagramModel> & { zoom: number }): void;
gridUpdated?(event: BaseEntityEvent<DiagramModel> & { size: number }): void;
}
export interface DiagramModelOptions extends BaseEntityOptions {
offsetX?: number;
offsetY?: number;
zoom?: number;
gridSize?: number;
}
export interface DiagramModelGenerics extends BaseEntityGenerics {
LISTENER: DiagramListener;
OPTIONS: DiagramModelOptions;
}
export class DiagramModel<G extends DiagramModelGenerics = DiagramModelGenerics> extends BaseEntity<G> {
//models
protected links: { [s: string]: LinkModel };
protected nodes: { [s: string]: NodeModel };
rendered: boolean;
constructor(options: G['OPTIONS'] = {}) {
super({
zoom: 100,
gridSize: 0,
offsetX: 0,
offsetY: 0,
...options
});
this.links = {};
this.nodes = {};
this.rendered = false;
}
setGridSize(size: number = 0) {
this.options.gridSize = size;
this.fireEvent({ size: size }, 'gridUpdated');
}
getGridPosition(pos) {
if (this.options.gridSize === 0) {
return pos;
}
return this.options.gridSize * Math.floor((pos + this.options.gridSize / 2) / this.options.gridSize);
}
deSerializeDiagram(object: any, diagramEngine: DiagramEngine) {
this.deSerialize(object, diagramEngine);
this.options.offsetX = object.offsetX;
this.options.offsetY = object.offsetY;
this.options.zoom = object.zoom;
this.options.gridSize = object.gridSize;
// deserialize nodes
_.forEach(object.nodes, (node: any) => {
let nodeOb = diagramEngine.getFactoryForNode(node.type).generateModel({ initialConfig: node });
nodeOb.setParent(this);
nodeOb.deSerialize(node, diagramEngine);
this.addNode(nodeOb);
});
// deserialze links
_.forEach(object.links, (link: any) => {
let linkOb = diagramEngine.getFactoryForLink(link.type).generateModel({ initialConfig: link });
linkOb.setParent(this);
linkOb.deSerialize(link, diagramEngine);
this.addLink(linkOb);
});
}
serializeDiagram() {
return {
...this.serialize(),
offsetX: this.options.offsetX,
offsetY: this.options.offsetY,
zoom: this.options.zoom,
gridSize: this.options.gridSize,
links: _.map(this.links, link => {
return link.serialize();
}),
nodes: _.map(this.nodes, node => {
return node.serialize();
})
};
}
clearSelection(ignore: BaseModel | null = null) {
_.forEach(this.getSelectedItems(), element => {
if (ignore && ignore.getID() === element.getID()) {
return;
}
element.setSelected(false); //TODO dont fire the listener
});
}
getSelectedItems(...filters: BaseEntityType[]): BaseModel[] {
if (!Array.isArray(filters)) {
filters = [filters];
}
var items = [];
// run through nodes
items = items.concat(
_.flatMap(this.nodes, node => {
return node.getSelectedEntities();
})
);
// find all the links
items = items.concat(
_.flatMap(this.links, link => {
return link.getSelectedEntities();
})
);
//find all points
items = items.concat(
_.flatMap(this.links, link => {
return _.flatMap(link.getPoints(), point => {
return point.getSelectedEntities();
});
})
);
items = _.uniq(items);
if (filters.length > 0) {
items = _.filter(_.uniq(items), (item: BaseModel<any>) => {
if (_.includes(filters, 'node') && item instanceof NodeModel) {
return true;
}
if (_.includes(filters, 'link') && item instanceof LinkModel) {
return true;
}
if (_.includes(filters, 'port') && item instanceof PortModel) {
return true;
}
if (_.includes(filters, 'point') && item instanceof PointModel) {
return true;
}
return false;
});
}
return items;
}
setZoomLevel(zoom: number) {
this.options.zoom = zoom;
this.fireEvent({ zoom }, 'zoomUpdated');
}
setOffset(offsetX: number, offsetY: number) {
this.options.offsetX = offsetX;
this.options.offsetY = offsetY;
this.fireEvent({ offsetX, offsetY }, 'offsetUpdated');
}
setOffsetX(offsetX: number) {
this.setOffset(offsetX, this.options.offsetY);
}
setOffsetY(offsetY: number) {
this.setOffset(this.options.offsetX, offsetY);
}
getOffsetY() {
return this.options.offsetY;
}
getOffsetX() {
return this.options.offsetX;
}
getZoomLevel() {
return this.options.zoom;
}
getNode(node: string | NodeModel): NodeModel | null {
if (node instanceof NodeModel) {
return node;
}
if (!this.nodes[node]) {
return null;
}
return this.nodes[node];
}
getLink(link: string | LinkModel): LinkModel | null {
if (link instanceof LinkModel) {
return link;
}
if (!this.links[link]) {
return null;
}
return this.links[link];
}
addAll(...models: BaseModel[]): BaseModel[] {
_.forEach(models, model => {
if (model instanceof LinkModel) {
this.addLink(model);
} else if (model instanceof NodeModel) {
this.addNode(model);
}
});
return models;
}
addLink(link: LinkModel): LinkModel {
link.registerListener({
entityRemoved: () => {
this.removeLink(link);
}
});
this.links[link.getID()] = link;
this.fireEvent(
{
link,
isCreated: true
},
'linksUpdated'
);
return link;
}
addNode(node: NodeModel): NodeModel {
node.registerListener({
entityRemoved: () => {
this.removeNode(node);
}
});
this.nodes[node.getID()] = node;
this.fireEvent({ node, isCreated: true }, 'nodesUpdated');
return node;
}
removeLink(link: LinkModel | string) {
link = this.getLink(link);
delete this.links[link.getID()];
this.fireEvent({ link, isCreated: false }, 'linksUpdated');
}
removeNode(node: NodeModel | string) {
node = this.getNode(node);
delete this.nodes[node.getID()];
this.fireEvent({ node, isCreated: false }, 'nodesUpdated');
}
getLinks(): { [s: string]: LinkModel } {
return this.links;
}
getNodes(): { [s: string]: NodeModel } {
return this.nodes;
}
}

View File

@ -1,7 +0,0 @@
import { BaseModel } from '../core-models/BaseModel';
export interface SelectionModel {
model: BaseModel;
initialX: number;
initialY: number;
}

View File

@ -1 +0,0 @@
export class ActionManager {}

View File

@ -1,41 +0,0 @@
import * as React from 'react';
export interface BaseWidgetProps {
/**
* Override the base class name
*/
baseClass?: string;
/**
* append additional classes
*/
className?: string;
/**
* Additional props to add
*/
extraProps?: any;
}
export class BaseWidget<P extends BaseWidgetProps = BaseWidgetProps, S = any> extends React.Component<P, S> {
className: string;
constructor(name: string, props: P) {
super(props);
this.className = name;
}
bem(selector: string): string {
return (this.props.baseClass || this.className) + selector + ' ';
}
getClassName(): string {
return (this.props.baseClass || this.className) + ' ' + (this.props.className ? this.props.className + ' ' : '');
}
getProps(): any {
return {
...((this.props.extraProps as any) || {}),
className: this.getClassName()
};
}
}

View File

@ -1,288 +0,0 @@
import * as React from 'react';
import { DiagramEngine } from '../DiagramEngine';
import * as _ from 'lodash';
import { LinkLayerWidget } from './layers/LinkLayerWidget';
import { NodeLayerWidget } from './layers/NodeLayerWidget';
import { AbstractMouseAction } from '../core-actions/AbstractMouseAction';
import { MoveItemsAction } from '../actions/move-items/MoveItemsAction';
import { SelectingAction } from '../actions/selecting-items/SelectingAction';
import { PointModel } from '../models/PointModel';
import { BaseWidget, BaseWidgetProps } from './BaseWidget';
import { MouseEvent } from 'react';
import { ActionFactoryActivationEvent } from '../core-actions/AbstractActionFactory';
import { AbstractAction } from '../core-actions/AbstractAction';
import { MoveItemsActionFactory } from '../actions/move-items/MoveItemsActionFactory';
export interface DiagramProps extends BaseWidgetProps {
diagramEngine: DiagramEngine;
// zoom
allowCanvasZoom?: boolean;
inverseZoom?: boolean;
actionStartedFiring?: (action: AbstractAction) => boolean;
actionStillFiring?: (action: AbstractAction) => void;
actionStoppedFiring?: (action: AbstractAction) => void;
deleteKeys?: number[];
}
export interface DiagramState {
action: AbstractAction;
diagramEngineListener: any;
}
export class DiagramWidget extends BaseWidget<DiagramProps, DiagramState> {
onKeyUpPointer: (this: Window, ev: KeyboardEvent) => void = null;
ref: React.RefObject<HTMLDivElement>;
constructor(props: DiagramProps) {
super('srd-diagram', props);
this.ref = React.createRef();
this.state = {
action: null,
diagramEngineListener: null
};
}
componentWillUnmount() {
this.props.diagramEngine.deregisterListener(this.state.diagramEngineListener);
this.props.diagramEngine.setCanvas(null);
window.removeEventListener('keyup', this.onKeyUpPointer);
window.removeEventListener('mouseUp', this.onMouseUp);
window.removeEventListener('mouseMove', this.onMouseMove);
}
componentWillReceiveProps(nextProps: DiagramProps) {
if (this.props.diagramEngine !== nextProps.diagramEngine) {
this.props.diagramEngine.deregisterListener(this.state.diagramEngineListener);
const diagramEngineListener = nextProps.diagramEngine.registerListener({
repaintCanvas: () => this.forceUpdate()
});
this.setState({ diagramEngineListener });
}
}
registerCanvas() {
this.props.diagramEngine.setCanvas(this.ref.current);
this.props.diagramEngine.iterateListeners(list => {
list.rendered && list.rendered();
});
}
componentDidUpdate() {
this.registerCanvas();
}
componentDidMount() {
this.onKeyUpPointer = this.onKeyUp.bind(this);
//add a keyboard listener
this.setState({
diagramEngineListener: this.props.diagramEngine.registerListener({
repaintCanvas: () => {
this.forceUpdate();
}
})
});
window.addEventListener('keyup', this.onKeyUpPointer, false);
// dont focus the window when in test mode - jsdom fails
if (process.env.NODE_ENV !== 'test') {
window.focus();
}
this.registerCanvas();
}
fireAction() {
if (this.state.action && this.props.actionStillFiring) {
this.props.actionStillFiring(this.state.action);
}
}
stopFiringAction(shouldSkipEvent?: boolean) {
if (this.props.actionStoppedFiring && !shouldSkipEvent) {
this.props.actionStoppedFiring(this.state.action);
}
this.setState({ action: null });
}
startFiringAction(action: AbstractAction) {
var setState = true;
if (this.props.actionStartedFiring) {
setState = this.props.actionStartedFiring(action);
}
if (setState) {
this.setState({ action: action });
}
}
onMouseUp = event => {
if (this.state.action && this.state.action instanceof AbstractMouseAction) {
this.state.action.fireMouseUp(event);
}
this.stopFiringAction();
document.removeEventListener('mousemove', this.onMouseMove);
document.removeEventListener('mouseup', this.onMouseUp);
};
onMouseMove = event => {
//select items so draw a bounding box
if (this.state.action) {
if (this.state.action && this.state.action instanceof AbstractMouseAction) {
this.state.action.fireMouseMove(event);
}
this.fireAction();
this.forceUpdate();
}
};
onKeyUp = event => {
//delete all selected
if ((this.props.deleteKeys || [46, 8]).indexOf(event.keyCode) !== -1) {
_.forEach(this.props.diagramEngine.getDiagramModel().getSelectedItems(), element => {
//only delete items which are not locked
if (!this.props.diagramEngine.isModelLocked(element)) {
element.remove();
}
});
this.forceUpdate();
}
};
drawSelectionBox() {
let dimensions = (this.state.action as SelectingAction).getBoxDimensions();
return (
<div
className={this.bem('__selector')}
style={{
top: dimensions.top,
left: dimensions.left,
width: dimensions.width,
height: dimensions.height
}}
/>
);
}
getActionForEvent(event: MouseEvent): AbstractAction {
event.persist();
const { diagramEngine } = this.props;
const model = diagramEngine.getMouseElement(event);
const activateEvent: ActionFactoryActivationEvent = {
selectedModel: model && model.model,
selectedEntity: model && (model.element as HTMLElement),
mouseEvent: event
};
for (let factory of diagramEngine.getActionFactories().getFactories()) {
if (factory.activate(activateEvent)) {
return factory.generateAction(event);
}
}
return null;
}
render() {
const diagramEngine = this.props.diagramEngine;
const diagramModel = diagramEngine.getDiagramModel();
return (
<div
{...this.getProps()}
ref={this.ref}
onWheel={event => {
const allow = this.props.allowCanvasZoom == null ? true : this.props.allowCanvasZoom;
if (allow) {
event.stopPropagation();
const oldZoomFactor = diagramModel.getZoomLevel() / 100;
let scrollDelta = this.props.inverseZoom ? -event.deltaY : event.deltaY;
//check if it is pinch gesture
if (event.ctrlKey && scrollDelta % 1 !== 0) {
/*Chrome and Firefox sends wheel event with deltaY that
have fractional part, also `ctrlKey` prop of the event is true
though ctrl isn't pressed
*/
scrollDelta /= 3;
} else {
scrollDelta /= 60;
}
if (diagramModel.getZoomLevel() + scrollDelta > 10) {
diagramModel.setZoomLevel(diagramModel.getZoomLevel() + scrollDelta);
}
const zoomFactor = diagramModel.getZoomLevel() / 100;
const boundingRect = event.currentTarget.getBoundingClientRect();
const clientWidth = boundingRect.width;
const clientHeight = boundingRect.height;
// compute difference between rect before and after scroll
const widthDiff = clientWidth * zoomFactor - clientWidth * oldZoomFactor;
const heightDiff = clientHeight * zoomFactor - clientHeight * oldZoomFactor;
// compute mouse coords relative to canvas
const clientX = event.clientX - boundingRect.left;
const clientY = event.clientY - boundingRect.top;
// compute width and height increment factor
const xFactor = (clientX - diagramModel.getOffsetX()) / oldZoomFactor / clientWidth;
const yFactor = (clientY - diagramModel.getOffsetY()) / oldZoomFactor / clientHeight;
diagramModel.setOffset(
diagramModel.getOffsetX() - widthDiff * xFactor,
diagramModel.getOffsetY() - heightDiff * yFactor
);
this.forceUpdate();
}
}}
onMouseDown={event => {
// try and get an action for this event
const action = this.getActionForEvent(event);
if (action) {
if (action instanceof AbstractMouseAction) {
const selected = diagramEngine.getMouseElement(event);
action.fireMouseDown({
mouseEvent: event,
selectedEntity: selected && (selected.element as HTMLElement),
selectedModel: selected && selected.model
});
}
this.startFiringAction(action);
}
document.addEventListener('mousemove', this.onMouseMove);
document.addEventListener('mouseup', this.onMouseUp);
}}>
<LinkLayerWidget
diagramEngine={diagramEngine}
pointAdded={(point: PointModel, event) => {
document.addEventListener('mousemove', this.onMouseMove);
document.addEventListener('mouseup', this.onMouseUp);
event.stopPropagation();
// TODO implement this better and more generic
let action: MoveItemsAction = null;
let fac: MoveItemsActionFactory = null;
try {
fac = diagramEngine.getActionFactories().getFactory<MoveItemsActionFactory>(MoveItemsActionFactory.NAME);
} catch (e) {}
if (fac) {
action = fac.generateAction(event);
action.fireMouseDown({
selectedModel: point,
selectedEntity: event.target as HTMLElement,
mouseEvent: event
});
this.startFiringAction(action);
}
}}
/>
<NodeLayerWidget diagramEngine={diagramEngine} />
{this.state.action instanceof SelectingAction && this.drawSelectionBox()}
</div>
);
}
}

View File

@ -1,48 +0,0 @@
import * as React from 'react';
import { DiagramEngine } from '../../DiagramEngine';
import { LinkWidget } from '../LinkWidget';
import * as _ from 'lodash';
import { PointModel } from '../../models/PointModel';
import { BaseWidget, BaseWidgetProps } from '../BaseWidget';
import { MouseEvent } from 'react';
export interface LinkLayerProps extends BaseWidgetProps {
diagramEngine: DiagramEngine;
pointAdded: (point: PointModel, event: MouseEvent) => any;
}
export class LinkLayerWidget extends BaseWidget<LinkLayerProps> {
constructor(props: LinkLayerProps) {
super('srd-link-layer', props);
}
render() {
var diagramModel = this.props.diagramEngine.getDiagramModel();
return (
<svg
{...this.getProps()}
style={{
transform:
'translate(' +
diagramModel.getOffsetX() +
'px,' +
diagramModel.getOffsetY() +
'px) scale(' +
diagramModel.getZoomLevel() / 100.0 +
')'
}}>
{//only perform these actions when we have a diagram
_.map(diagramModel.getLinks(), link => {
return (
<LinkWidget
pointAdded={this.props.pointAdded}
key={link.getID()}
link={link}
diagramEngine={this.props.diagramEngine}
/>
);
})}
</svg>
);
}
}

View File

@ -1,38 +0,0 @@
import * as React from 'react';
import { DiagramEngine } from '../../DiagramEngine';
import * as _ from 'lodash';
import { NodeWidget } from '../NodeWidget';
import { NodeModel } from '../../models/NodeModel';
import { BaseWidget, BaseWidgetProps } from '../BaseWidget';
export interface NodeLayerProps extends BaseWidgetProps {
diagramEngine: DiagramEngine;
}
export class NodeLayerWidget extends BaseWidget<NodeLayerProps> {
constructor(props: NodeLayerProps) {
super('srd-node-layer', props);
}
render() {
var diagramModel = this.props.diagramEngine.getDiagramModel();
return (
<div
{...this.getProps()}
style={{
transform:
'translate(' +
diagramModel.getOffsetX() +
'px,' +
diagramModel.getOffsetY() +
'px) scale(' +
diagramModel.getZoomLevel() / 100.0 +
')'
}}>
{_.map(diagramModel.getNodes(), (node: NodeModel) => {
return <NodeWidget key={node.getID()} diagramEngine={this.props.diagramEngine} node={node} />;
})}
</div>
);
}
}

View File

@ -1,8 +0,0 @@
const config = require('../webpack.shared')(__dirname);
module.exports = {
...config,
output: {
...config.output,
library: 'projectstorm/react-diagrams-core'
}
};

View File

@ -1,12 +0,0 @@
import * as React from 'react';
import { DiagramEngine, DiagramWidget } from '@projectstorm/react-diagrams';
export interface BodyWidgetProps {
engine: DiagramEngine;
}
export class BodyWidget extends React.Component<BodyWidgetProps> {
render() {
return <DiagramWidget className="diagram-container" diagramEngine={this.props.engine} />;
}
}

View File

@ -7,15 +7,7 @@
"type": "git",
"url": "https://github.com/projectstorm/react-diagrams.git"
},
"workspaces": [
"lib-all",
"lib-core",
"lib-defaults",
"lib-routing",
"lib-geometry",
"lib-demo-project",
"lib-demo-gallery"
],
"workspaces": ["packages/*"],
"keywords": [
"web",
"diagram",
@ -33,11 +25,11 @@
"watch": "yarn build:clean && lerna run watch --stream",
"publish:dev": "yarn build:prod && lerna publish --force-publish --dist-tag=next",
"publish:prod": "yarn build:prod && lerna publish --force-publish",
"publish:storybook": "cd lib-demo-gallery && yarn storybook:build && ../node_modules/.bin/storybook-to-ghpages --existing-output-dir .out",
"publish:storybook": "cd packages/diagrams-demo-gallery && yarn storybook:build && ../../node_modules/.bin/storybook-to-ghpages --existing-output-dir .out",
"build:clean": "lerna run clean --stream",
"test": "lerna run test --stream",
"test:ci": "lerna run test --stream -- --runInBand --ci ",
"pretty": "prettier --write \"lib-*/**/*.{ts,tsx,scss,js,jsx}\""
"pretty": "prettier --write \"packages/**/*.{ts,tsx,scss,js,jsx}\""
},
"peerDependencies": {
"closest": "^0.0.1",

View File

@ -8,10 +8,10 @@
"url": "https://github.com/projectstorm/react-diagrams.git"
},
"scripts": {
"start": "../node_modules/.bin/start-storybook",
"storybook:build": "../node_modules/.bin/build-storybook -c .storybook -o .out",
"github": "../node_modules/.bin/storybook-to-ghpages",
"test:run": "../node_modules/.bin/jest --no-cache",
"start": "../../node_modules/.bin/start-storybook",
"storybook:build": "../../node_modules/.bin/build-storybook -c .storybook -o .out",
"github": "../../node_modules/.bin/storybook-to-ghpages",
"test:run": "../../node_modules/.bin/jest --no-cache",
"test": "yarn build && yarn test:run"
},
"keywords": [

View File

@ -1,5 +1,5 @@
{
"extends": "../tsconfig",
"extends": "../../tsconfig",
"include": [
"./demos"
]

View File

@ -8,7 +8,7 @@
"url": "https://github.com/projectstorm/react-diagrams.git"
},
"scripts": {
"start": "../node_modules/.bin/webpack-dev-server"
"start": "../../node_modules/.bin/webpack-dev-server"
},
"keywords": [
"web",

View File

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 263 KiB

View File

@ -0,0 +1,13 @@
import * as React from 'react';
import { DiagramEngine } from '@projectstorm/react-diagrams';
import {CanvasWidget} from "@projectstorm/react-canvas-core";
export interface BodyWidgetProps {
engine: DiagramEngine;
}
export class BodyWidget extends React.Component<BodyWidgetProps> {
render() {
return <CanvasWidget className="diagram-container" engine={this.props.engine} />;
}
}

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { AbstractReactFactory } from '@projectstorm/react-diagrams';
import { JSCustomNodeModel } from './JSCustomNodeModel';
import { JSCustomNodeWidget } from './JSCustomNodeWidget';
import {AbstractReactFactory} from "@projectstorm/react-canvas-core";
export class JSCustomNodeFactory extends AbstractReactFactory {
constructor() {

View File

@ -5,8 +5,12 @@ export class JSCustomNodeWidget extends React.Component {
render() {
return (
<div className="custom-node">
<PortWidget engine={this.props.engine} port={this.props.node.getPort('in')} />
<PortWidget engine={this.props.engine} port={this.props.node.getPort('out')} />
<PortWidget engine={this.props.engine} port={this.props.node.getPort('in')}>
<div className="circle-port"/>
</PortWidget>
<PortWidget engine={this.props.engine} port={this.props.node.getPort('out')}>
<div className="circle-port"/>
</PortWidget>
<div className="custom-node-color" style={{ backgroundColor: this.props.node.color }} />
</div>
);

View File

@ -1,9 +1,10 @@
import * as React from 'react';
import { AbstractReactFactory } from '@projectstorm/react-diagrams';
import { TSCustomNodeModel } from './TSCustomNodeModel';
import { TSCustomNodeWidget } from './TSCustomNodeWidget';
import {AbstractReactFactory} from "@projectstorm/react-canvas-core";
import {DiagramEngine} from "@projectstorm/react-diagrams-core";
export class TSCustomNodeFactory extends AbstractReactFactory<TSCustomNodeModel> {
export class TSCustomNodeFactory extends AbstractReactFactory<TSCustomNodeModel, DiagramEngine> {
constructor() {
super('ts-custom-node');
}
@ -13,6 +14,6 @@ export class TSCustomNodeFactory extends AbstractReactFactory<TSCustomNodeModel>
}
generateReactWidget(event): JSX.Element {
return <TSCustomNodeWidget engine={this.engine} node={event.model} />;
return <TSCustomNodeWidget engine={this.engine as DiagramEngine} node={event.model} />;
}
}

View File

@ -1,4 +1,5 @@
import { DiagramEngine, NodeModel, DefaultPortModel, BaseModelOptions } from '@projectstorm/react-diagrams';
import { DiagramEngine, NodeModel, DefaultPortModel } from '@projectstorm/react-diagrams';
import {BaseModelOptions} from "@projectstorm/react-canvas-core";
export interface TSCustomNodeModelOptions extends BaseModelOptions {
color?: string;

View File

@ -1,13 +1,14 @@
import * as React from 'react';
import { DiagramEngine, PortWidget } from '@projectstorm/react-diagrams-core';
import { TSCustomNodeModel } from './TSCustomNodeModel';
import {DiagramEngine, PortWidget} from '@projectstorm/react-diagrams-core';
import {TSCustomNodeModel} from './TSCustomNodeModel';
export interface TSCustomNodeWidgetProps {
node: TSCustomNodeModel;
engine: DiagramEngine;
}
export interface TSCustomNodeWidgetState {}
export interface TSCustomNodeWidgetState {
}
export class TSCustomNodeWidget extends React.Component<TSCustomNodeWidgetProps, TSCustomNodeWidgetState> {
constructor(props: TSCustomNodeWidgetProps) {
@ -18,9 +19,13 @@ export class TSCustomNodeWidget extends React.Component<TSCustomNodeWidgetProps,
render() {
return (
<div className="custom-node">
<PortWidget engine={this.props.engine} port={this.props.node.getPort('in')} />
<PortWidget engine={this.props.engine} port={this.props.node.getPort('out')} />
<div className="custom-node-color" style={{ backgroundColor: this.props.node.color }} />
<PortWidget engine={this.props.engine} port={this.props.node.getPort('in')}>
<div className="circle-port"/>
</PortWidget>
<PortWidget engine={this.props.engine} port={this.props.node.getPort('out')}>
<div className="circle-port"/>
</PortWidget>
<div className="custom-node-color" style={{backgroundColor: this.props.node.color}}/>
</div>
);
}

View File

@ -34,3 +34,16 @@ html, body, #application{
transform: translate(-50%, -50%);
border-radius: 10px;
}
.circle-port{
width: 12px;
height: 12px;
margin: 2px;
border-radius: 4px;
background: darkgray;
cursor: pointer;
}
.circle-port:hover{
background: mediumpurple;
}

View File

@ -12,7 +12,7 @@ import { BodyWidget } from './BodyWidget';
const engine = createEngine();
// register the two engines
engine.getNodeFactories().registerFactory(new JSCustomNodeFactory());
engine.getNodeFactories().registerFactory(new JSCustomNodeFactory() as any);
engine.getNodeFactories().registerFactory(new TSCustomNodeFactory());
// create a diagram model
@ -36,7 +36,7 @@ model.addAll(node1, node2, link1);
//####################################################
// install the model into the engine
engine.setDiagramModel(model);
engine.setModel(model);
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(<BodyWidget engine={engine} />, document.querySelector('#application'));

View File

@ -1,5 +1,5 @@
{
"name": "@projectstorm/react-diagrams-geometry",
"name": "@projectstorm/geometry",
"version": "6.0.0-alpha.4.2",
"author": "dylanvorster",
"repository": {
@ -8,8 +8,8 @@
},
"scripts": {
"clean": "rm -rf ./dist",
"build": "../node_modules/.bin/webpack",
"build:prod": "NODE_ENV=production ../node_modules/.bin/webpack"
"build": "../../node_modules/.bin/webpack",
"build:prod": "NODE_ENV=production ../../node_modules/.bin/webpack"
},
"publishConfig": {
"access": "public"

View File

@ -1,5 +1,5 @@
{
"extends": "../tsconfig",
"extends": "../../tsconfig",
"compilerOptions": {
"declaration": true,
"declarationDir": "dist/@types"

View File

@ -1,4 +1,4 @@
const config = require('../webpack.shared')(__dirname);
const config = require('../../webpack.shared')(__dirname);
module.exports = {
...config,
output: {

View File

@ -0,0 +1,21 @@
export * from "./src/CanvasEngine"
export * from "./src/Toolkit"
export * from "./src/entities/canvas/CanvasModel"
export * from "./src/core/AbstractFactory"
export * from "./src/core/AbstractModelFactory"
export * from "./src/core/AbstractReactFactory"
export * from "./src/core/BaseObserver"
export * from "./src/core/FactoryBank"
export * from "./src/core-models/BaseEntity"
export * from "./src/core-models/BaseModel"
export * from "./src/core-models/BasePositionModel"
export * from "./src/entities/canvas/CanvasModel"
export * from "./src/entities/canvas/CanvasWidget"
export * from "./src/entities/layer/LayerModel"
export * from "./src/widgets/PeformanceWidget"
export * from "./src/entities/layer/TransformLayerWidget"

View File

@ -0,0 +1,39 @@
{
"name": "@projectstorm/react-canvas-core",
"version": "6.0.0-alpha.4.2",
"author": "dylanvorster",
"repository": {
"type": "git",
"url": "https://github.com/projectstorm/react-diagrams.git"
},
"scripts": {
"clean": "rm -rf ./dist",
"build": "../../node_modules/.bin/webpack",
"build:prod": "NODE_ENV=production ../../node_modules/.bin/webpack"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"web",
"diagram",
"diagrams",
"react",
"typescript",
"flowchart",
"simple",
"links",
"nodes"
],
"main": "./dist/index.js",
"typings": "./dist/@types/index",
"dependencies": {
"@projectstorm/geometry": "^6.0.0-alpha.4.2"
},
"peerDependencies": {
"closest": "^0.0.1",
"lodash": "4.*",
"react": "16.*"
},
"gitHead": "bb878657ba0c2f81764f32901fd96158a0f8352e"
}

View File

@ -0,0 +1,120 @@
import {CanvasModel} from "./entities/canvas/CanvasModel";
import {FactoryBank} from "./core/FactoryBank";
import {AbstractReactFactory} from "./core/AbstractReactFactory";
import {LayerModel} from "./entities/layer/LayerModel";
import {BaseListener, BaseObserver} from "./core/BaseObserver";
import {MouseEvent} from "react";
import {BaseModel} from "./core-models/BaseModel";
import {Point} from "@projectstorm/geometry";
import {ActionEventBus} from "./core-actions/ActionEventBus";
import {ZoomCanvasAction} from "./actions/ZoomCanvasAction";
import {DeleteItemsAction} from "./actions/DeleteItemsAction";
export interface CanvasEngineListener extends BaseListener {
canvasReady?(): void;
repaintCanvas?(): void;
rendered?(): void;
}
export class CanvasEngine<L extends CanvasEngineListener = CanvasEngineListener, M extends CanvasModel = CanvasModel> extends BaseObserver<L> {
protected model: M;
protected layerFactories: FactoryBank<AbstractReactFactory<LayerModel>>;
protected canvas: HTMLDivElement;
protected eventBus: ActionEventBus;
constructor() {
super();
this.model = null;
this.eventBus = new ActionEventBus(this);
this.layerFactories = new FactoryBank();
this.registerFactoryBank(this.layerFactories);
this.eventBus.registerAction(new ZoomCanvasAction());
this.eventBus.registerAction(new DeleteItemsAction());
}
getRelativeMousePoint(event): Point {
var point = this.getRelativePoint(event.clientX, event.clientY);
return new Point(
(point.x - this.model.getOffsetX()) / (this.model.getZoomLevel() / 100.0),
(point.y - this.model.getOffsetY()) / (this.model.getZoomLevel() / 100.0)
);
}
getRelativePoint(x, y): Point {
var canvasRect = this.canvas.getBoundingClientRect();
return new Point(x - canvasRect.left, y - canvasRect.top);
}
registerFactoryBank(factory: FactoryBank) {
factory.registerListener({
factoryAdded: event => {
event.factory.setDiagramEngine(this);
},
factoryRemoved: event => {
event.factory.setDiagramEngine(null);
}
});
}
getActionEventBus() {
return this.eventBus;
}
getLayerFactories() {
return this.layerFactories;
}
getFactoryForLayer<F extends AbstractReactFactory<LayerModel>>(layer: LayerModel) {
if (typeof layer === 'string') {
return this.layerFactories.getFactory(layer);
}
return this.layerFactories.getFactory(layer.getType());
}
setModel(model: M) {
this.model = model;
}
getModel(): M {
return this.model;
}
repaintCanvas() {
this.iterateListeners(listener => {
if (listener.repaintCanvas) {
listener.repaintCanvas();
}
});
}
setCanvas(canvas?: HTMLDivElement) {
if (this.canvas !== canvas) {
this.canvas = canvas;
if (canvas) {
this.fireEvent({}, 'canvasReady');
}
}
}
getCanvas(){
return this.canvas;
}
getMouseElement(event: MouseEvent): { model: BaseModel; element: Element } {
return null;
}
zoomToFit() {
const xFactor = this.canvas.clientWidth / this.canvas.scrollWidth;
const yFactor = this.canvas.clientHeight / this.canvas.scrollHeight;
const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
this.model.setZoomLevel(this.model.getZoomLevel() * zoomFactor);
this.model.setOffset(0, 0);
this.repaintCanvas();
}
}

View File

@ -1,5 +1,4 @@
import * as closest from 'closest';
import { PointModel } from './models/PointModel';
export class Toolkit {
static TESTING: boolean = false;
@ -23,9 +22,6 @@ export class Toolkit {
/**
* Finds the closest element as a polyfill
*
* @param {Element} element [description]
* @param {string} selector [description]
*/
public static closest(element: Element, selector: string) {
if (document.body.closest) {
@ -33,8 +29,4 @@ export class Toolkit {
}
return closest(element, selector);
}
public static generateLinePath(firstPoint: PointModel, lastPoint: PointModel): string {
return `M${firstPoint.getX()},${firstPoint.getY()} L ${lastPoint.getX()},${lastPoint.getY()}`;
}
}

Some files were not shown because too many files have changed in this diff Show More