Skip to content

Commit c9de1cf

Browse files
New: Add state setters with options (#95)
* New: Add state setters with options * Chore: Remove leftover comments * Chore: Refactor state setter logic * Chore: Refactor state types
1 parent 0ee733d commit c9de1cf

File tree

5 files changed

+132
-11
lines changed

5 files changed

+132
-11
lines changed

src/models/edge.ts

+44-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { INodeBase, INode } from './node';
2-
import { GraphObjectState } from './state';
2+
import { GraphObjectState, IGraphObjectStateOptions, IGraphObjectStateParameters } from './state';
33
import { Color, IPosition, ICircle, getDistanceToLine } from '../common';
4-
import { isArrayOfNumbers, isFunction } from '../utils/type.utils';
4+
import { isArrayOfNumbers, isFunction, isNumber, isPlainObject } from '../utils/type.utils';
55
import { IObserver, ISubject, Subject } from '../utils/observer.utils';
66
import { patchProperties } from '../utils/object.utils';
77

@@ -120,7 +120,9 @@ export interface IEdge<N extends INodeBase, E extends IEdgeBase> extends ISubjec
120120
patchStyle(style: IEdgeStyle): void;
121121
patchStyle(callback: (edge: IEdge<N, E>) => IEdgeStyle): void;
122122
setState(state: number): void;
123+
setState(state: IGraphObjectStateParameters): void;
123124
setState(callback: (edge: IEdge<N, E>) => number): void;
125+
setState(callback: (edge: IEdge<N, E>) => IGraphObjectStateParameters): void;
124126
}
125127

126128
export interface IEdgeSettings {
@@ -400,15 +402,52 @@ abstract class Edge<N extends INodeBase, E extends IEdgeBase> extends Subject im
400402
}
401403

402404
setState(state: number): void;
405+
setState(state: IGraphObjectStateParameters): void;
403406
setState(callback: (edge: IEdge<N, E>) => number): void;
404-
setState(arg: number | ((edge: IEdge<N, E>) => number)): void {
407+
setState(callback: (edge: IEdge<N, E>) => IGraphObjectStateParameters): void;
408+
setState(
409+
arg:
410+
| number
411+
| IGraphObjectStateParameters
412+
| ((edge: IEdge<N, E>) => number)
413+
| ((edge: IEdge<N, E>) => IGraphObjectStateParameters),
414+
): void {
415+
let result: number | IGraphObjectStateParameters;
416+
405417
if (isFunction(arg)) {
406-
this._state = (arg as (edge: IEdge<N, E>) => number)(this);
418+
result = (arg as (edge: IEdge<N, E>) => number | IGraphObjectStateParameters)(this);
407419
} else {
408-
this._state = arg as number;
420+
result = arg;
421+
}
422+
423+
if (isNumber(result)) {
424+
this._state = result;
425+
} else if (isPlainObject(result)) {
426+
const options = result.options;
427+
428+
this._state = this._handleState(result.state, options);
429+
430+
if (options) {
431+
this.notifyListeners({
432+
id: this.id,
433+
type: 'edge',
434+
options: options,
435+
});
436+
437+
return;
438+
}
409439
}
440+
410441
this.notifyListeners();
411442
}
443+
444+
private _handleState(state: number, options?: Partial<IGraphObjectStateOptions>): number {
445+
if (options?.isToggle && this._state === state) {
446+
return GraphObjectState.NONE;
447+
} else {
448+
return state;
449+
}
450+
}
412451
}
413452

414453
const getEdgeType = <N extends INodeBase, E extends IEdgeBase>(data: IEdgeData<N, E>): EdgeType => {

src/models/graph.ts

+22
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,28 @@ export class Graph<N extends INodeBase, E extends IEdgeBase> extends Subject imp
381381
// Arrow function is used because they inherit the context from the enclosing scope
382382
// which is important for the callback to notify listeners as expected
383383
private _update: IObserver = (data?: IObserverDataPayload): void => {
384+
if (data && 'type' in data && 'options' in data && 'isSingle' in data.options) {
385+
if (data.type === 'node' && data.options.isSingle) {
386+
const nodes = this._nodes.getAll();
387+
388+
for (let i = 0; i < nodes.length; i++) {
389+
if (nodes[i].id !== data.id) {
390+
nodes[i].clearState();
391+
}
392+
}
393+
}
394+
395+
if (data.type === 'edge' && data.options.isSingle) {
396+
const edges = this._edges.getAll();
397+
398+
for (let i = 0; i < edges.length; i++) {
399+
if (edges[i].id !== data.id) {
400+
edges[i].clearState();
401+
}
402+
}
403+
}
404+
}
405+
384406
this.notifyListeners(data);
385407
};
386408

src/models/node.ts

+44-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { IEdge, IEdgeBase } from './edge';
22
import { Color, IPosition, IRectangle, isPointInRectangle } from '../common';
33
import { ImageHandler } from '../services/images';
4-
import { GraphObjectState } from './state';
4+
import { GraphObjectState, IGraphObjectStateOptions, IGraphObjectStateParameters } from './state';
55
import { IObserver, ISubject, Subject } from '../utils/observer.utils';
66
import { patchProperties } from '../utils/object.utils';
7-
import { isFunction } from '../utils/type.utils';
7+
import { isFunction, isNumber, isPlainObject } from '../utils/type.utils';
88

99
/**
1010
* Node baseline object with required fields
@@ -122,7 +122,9 @@ export interface INode<N extends INodeBase, E extends IEdgeBase> extends ISubjec
122122
patchStyle(style: INodeStyle): void;
123123
patchStyle(callback: (node: INode<N, E>) => INodeStyle): void;
124124
setState(state: number): void;
125+
setState(state: IGraphObjectStateParameters): void;
125126
setState(callback: (node: INode<N, E>) => number): void;
127+
setState(callback: (node: INode<N, E>) => IGraphObjectStateParameters): void;
126128
}
127129

128130
// TODO: Dirty solution: Find another way to listen for global images, maybe through
@@ -504,17 +506,54 @@ export class Node<N extends INodeBase, E extends IEdgeBase> extends Subject impl
504506
}
505507

506508
setState(state: number): void;
509+
setState(state: IGraphObjectStateParameters): void;
507510
setState(callback: (node: INode<N, E>) => number): void;
508-
setState(arg: number | ((node: INode<N, E>) => number)): void {
511+
setState(callback: (node: INode<N, E>) => IGraphObjectStateParameters): void;
512+
setState(
513+
arg:
514+
| number
515+
| IGraphObjectStateParameters
516+
| ((node: INode<N, E>) => number)
517+
| ((node: INode<N, E>) => IGraphObjectStateParameters),
518+
): void {
519+
let result: number | IGraphObjectStateParameters;
520+
509521
if (isFunction(arg)) {
510-
this._state = (arg as (node: INode<N, E>) => number)(this);
522+
result = (arg as (node: INode<N, E>) => number | IGraphObjectStateParameters)(this);
511523
} else {
512-
this._state = arg as number;
524+
result = arg;
525+
}
526+
527+
if (isNumber(result)) {
528+
this._state = result;
529+
} else if (isPlainObject(result)) {
530+
const options = result.options;
531+
532+
this._state = this._handleState(result.state, options);
533+
534+
if (options) {
535+
this.notifyListeners({
536+
id: this.id,
537+
type: 'node',
538+
options: options,
539+
});
540+
541+
return;
542+
}
513543
}
544+
514545
this.notifyListeners();
515546
}
516547

517548
protected _isPointInBoundingBox(point: IPosition): boolean {
518549
return isPointInRectangle(this.getBoundingBox(), point);
519550
}
551+
552+
private _handleState(state: number, options?: Partial<IGraphObjectStateOptions>): number {
553+
if (options?.isToggle && this._state === state) {
554+
return GraphObjectState.NONE;
555+
} else {
556+
return state;
557+
}
558+
}
520559
}

src/models/state.ts

+18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
1+
import { GraphObject } from '../utils/observer.utils';
2+
13
// Enum is dismissed so user can define custom additional events (numbers)
24
export const GraphObjectState = {
35
NONE: 0,
46
SELECTED: 1,
57
HOVERED: 2,
68
};
9+
10+
export interface IGraphObjectStateOptions {
11+
isToggle: boolean;
12+
isSingle: boolean;
13+
}
14+
15+
export interface IGraphObjectStateParameters {
16+
state: number;
17+
options?: Partial<IGraphObjectStateOptions>;
18+
}
19+
20+
export interface ISetStateDataPayload {
21+
id: any;
22+
type: GraphObject;
23+
options: Partial<IGraphObjectStateOptions>;
24+
}

src/utils/observer.utils.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { INodeCoordinates, INodePosition } from '../models/node';
2+
import { ISetStateDataPayload } from '../models/state';
23

3-
export type IObserverDataPayload = INodePosition | INodeCoordinates;
4+
export type GraphObject = 'node' | 'edge';
5+
6+
export type IObserverDataPayload = INodePosition | INodeCoordinates | ISetStateDataPayload;
47

58
// Using callbacks here to ensure that the Observer update is abstracted from the user
69
export type IObserver = (data?: IObserverDataPayload) => void;

0 commit comments

Comments
 (0)