Skip to content

Commit

Permalink
feat: custom connections
Browse files Browse the repository at this point in the history
  • Loading branch information
Antamansid committed Nov 5, 2024
1 parent f3caffd commit 846eae8
Show file tree
Hide file tree
Showing 12 changed files with 1,105 additions and 42 deletions.
36 changes: 23 additions & 13 deletions src/components/canvas/connections/BlockConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { GraphLayer, TGraphLayerContext } from "../layers/graphLayer/GraphLayer"
import withBatchedConnection from "./batchMixins/withBatchedConnection";
import { bezierCurveLine, generateBezierParams, getArrowCoords, isPointInStroke } from "./bezierHelpers";
import { getLabelCoords } from "./labelHelper";
import { TConnectionRenderSettings } from "./batchMixins/withBatchedConnections";

Check failure on line 21 in src/components/canvas/connections/BlockConnection.ts

View workflow job for this annotation

GitHub Actions / Verify Files

`./batchMixins/withBatchedConnections` import should occur before import of `./bezierHelpers`

export type TConnectionProps = {
id: TConnectionId;
Expand Down Expand Up @@ -51,19 +52,19 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo

protected readonly unsubscribe: (() => void)[];

private sourceBlock: Block;
public sourceBlock: Block;

private sourceAnchor?: TAnchor;
public sourceAnchor?: TAnchor;

private targetBlock: Block;
public targetBlock: Block;

private targetAnchor?: TAnchor;
public targetAnchor?: TAnchor;

private hitBoxHash = 0;
public hitBoxHash = 0;

protected path2d?: Path2D;
public path2d?: Path2D;

private geometry = { x1: 0, x2: 0, y1: 0, y2: 0 };
public geometry = { x1: 0, x2: 0, y1: 0, y2: 0 };

private labelGeometry = { x: 0, y: 0, width: 0, height: 0 };

Expand Down Expand Up @@ -99,6 +100,7 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
}),
];
}


protected updateSourceAndTargetBlock() {
this.sourceBlock = this.connectedState.$sourceBlock.value?.getViewComponent();
Expand Down Expand Up @@ -136,18 +138,22 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
return;
}

this.shouldRender = this.context.camera.isLineVisible(
this.shouldRender = this.isConnectionVisible();
}

public isConnectionVisible() {
return this.context.camera.isLineVisible(
this.geometry.x1,
this.geometry.y1,
this.geometry.x2,
this.geometry.y2
);
}

public computeRenderSettings(props, state: TConnectionState) {
public computeRenderSettings(props, state: TConnectionState): TConnectionRenderSettings {
let lineWidth = 2;
let zIndex = 1;
let lineDash: number[] | boolean = false;
let lineDash: number[] | undefined;
const strokeStyle = this.getStrokeColor(state);

if (state.selected) {
Expand Down Expand Up @@ -180,14 +186,18 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
protected didRender() {
super.didRender();

const hash = this.geometry.x1 + this.geometry.y1 + this.geometry.x2 + this.geometry.y2;
const hash = this.calculateHitBoxHash();

if (this.hitBoxHash !== hash) {
this.hitBoxHash = hash;
this.updateHitBox();
}
}

public calculateHitBoxHash () {
return this.geometry.x1 + this.geometry.y1 + this.geometry.x2 + this.geometry.y2;
}

public handleEvent(event: MouseEvent | KeyboardEvent) {
event.stopPropagation();

Expand All @@ -208,7 +218,7 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
}
}

private updateHitBox = () => {
public updateHitBox = () => {
const labelGeometry = this.labelGeometry;
let minX: number;
let maxX: number;
Expand Down Expand Up @@ -294,7 +304,7 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
}
}

private updateGeometry(sourceBlock: Block, targetBlock: Block) {
public updateGeometry(sourceBlock: Block, targetBlock: Block) {
if (!sourceBlock || !targetBlock) return;
const scale = this.context.camera.getCameraScale();
const isSchematicView = scale < this.context.constants.connection.SCALES[1];
Expand Down
14 changes: 11 additions & 3 deletions src/components/canvas/connections/BlockConnections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export class BlockConnections extends withBatchedConnections(Component) {

public removeFromRenderOrder = this.removeFromRenderOrder.bind(this);

constructor(props: {}, parent: GraphLayer) {
protected connectionsView = {};

public constructor(props: {}, parent: GraphLayer) {

Check failure on line 21 in src/components/canvas/connections/BlockConnections.ts

View workflow job for this annotation

GitHub Actions / Verify Files

Public accessibility modifier on method definition constructor
super(props, parent);
this.unsubscribe = this.subscribe();
}
Expand All @@ -28,6 +30,7 @@ export class BlockConnections extends withBatchedConnections(Component) {

protected subscribe() {
this.connections = this.context.graph.rootStore.connectionsList.$connections.value;
this.connectionsView = this.context.graph.rootStore.settings.getConfigFlag("connectionComponents");

const r1 = this.context.graph.rootStore.settings.$connectionsSettings.subscribe(() => {
this.scheduleUpdate();
Expand All @@ -38,7 +41,12 @@ export class BlockConnections extends withBatchedConnections(Component) {
this.scheduleUpdate();
});

return [r1, r2];
const r3 = this.context.graph.rootStore.settings.$connectionComponents.subscribe((connectionsView) => {
this.connectionsView = connectionsView;
this.scheduleUpdate();
});

return [r1, r2, r3];
}

protected unmount() {
Expand All @@ -60,7 +68,7 @@ export class BlockConnections extends withBatchedConnections(Component) {
showConnectionLabels: settings.showConnectionLabels,
showConnectionArrows: settings.showConnectionArrows,
};
return BlockConnection.create(props, { key: String(connection.id) });
return (this.connectionsView[connection.$state.value.is] || BlockConnection).create(props, { key: String(connection.id) });
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { TGraphLayerContext } from "../../layers/graphLayer/GraphLayer";
import { IWithBatchedConnection } from "./withBatchedConnection";

export interface IWithBatchedConnections {
addInRenderOrder(comp: unknown, settings: object): void;
addInRenderOrder(comp: unknown, settings: TConnectionRenderSettings): void;
removeFromRenderOrder(comp: unknown): void;
}

type ConnectionComponent = Component & IWithBatchedConnection;
type StyleSettings = object & { zIndex: number };
export type TConnectionRenderSettings = Partial<
Pick<CanvasRenderingContext2D, "fillStyle" | "lineWidth" | "strokeStyle">
> & { zIndex: number; lineDash?: number[] };

// default export because https://github.com/microsoft/TypeScript/issues/30355#issuecomment-671095933
export default <T extends Constructor<Component>>(superclass: T): T & Constructor<IWithBatchedConnections> =>
Expand All @@ -18,15 +20,15 @@ export default <T extends Constructor<Component>>(superclass: T): T & Constructo

private mapKeyToComps = new Map<string, Set<ConnectionComponent>>();

private mapKeyToSettings = new Map<string, StyleSettings>();
private mapKeyToSettings = new Map<string, TConnectionRenderSettings>();

private mapCompToComps = new WeakMap<ConnectionComponent, Set<ConnectionComponent>>();

private wasModifyed = false;
private wasModified = false;

private sortedCouplesKeySetting: [string, StyleSettings][] = [];
private sortedCouplesKeySetting: [string, TConnectionRenderSettings][] = [];

public addInRenderOrder(comp: ConnectionComponent, settings: StyleSettings) {
public addInRenderOrder(comp: ConnectionComponent, settings: TConnectionRenderSettings) {
const key = JSON.stringify(settings);
let comps = this.mapCompToComps.get(comp);

Expand All @@ -46,7 +48,7 @@ export default <T extends Constructor<Component>>(superclass: T): T & Constructo
}

this.mapCompToComps.set(comp, comps);
this.wasModifyed = true;
this.wasModified = true;
this.performRender();
}

Expand All @@ -61,40 +63,35 @@ export default <T extends Constructor<Component>>(superclass: T): T & Constructo
protected willIterate() {
super.willIterate();

if (this.wasModifyed) {
this.wasModifyed = false;
if (this.wasModified) {
this.wasModified = false;
this.sortedCouplesKeySetting = Array.from(this.mapKeyToSettings.entries()).sort((item1, item2) => {
return item2[1].zIndex - item1[1].zIndex;
});
}
}

protected render() {
let i;
let j;
let key;
let settings;
let comps;

for (i = 0; i < this.sortedCouplesKeySetting.length; i += 1) {
key = this.sortedCouplesKeySetting[i][0];
settings = this.sortedCouplesKeySetting[i][1];
for (let i = 0; i < this.sortedCouplesKeySetting.length; i += 1) {
const key = this.sortedCouplesKeySetting[i][0];
const settings = this.sortedCouplesKeySetting[i][1];

// because original will be changed during addInRenderOrder while ._iterate() below
comps = [...this.mapKeyToComps.get(key)];
const comps = [...this.mapKeyToComps.get(key)];

this.context.ctx.beginPath();

for (j = 0; j < comps.length; j += 1) {
for (let j = 0; j < comps.length; j += 1) {
comps[j]._iterate();
}

if ("lineDash" in settings && settings.lineDash) {
this.context.ctx.setLineDash(settings.lineDash);
}

Object.assign(this.context.ctx, settings);
this.context.ctx.stroke();

if (settings.strokeStyle) this.context.ctx.stroke();
if (settings.fillStyle) this.context.ctx.fill();

if ("lineDash" in settings) {
this.context.ctx.setLineDash([]);
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
export { Anchor, type TAnchor } from "./components/canvas/anchors";
export { Block as CanvasBlock, type TBlock } from "./components/canvas/blocks/Block";
export { generateBezierParams, isPointInStroke } from "./components/canvas/connections/bezierHelpers";
export { BlockConnection as CanvasConnection, type TConnectionProps } from "./components/canvas/connections/BlockConnection";
export { type GraphLayer } from "./components/canvas/layers/graphLayer/GraphLayer";
export * from "./graph";
export type { TGraphColors, TGraphConstants } from "./graphConfig";
export * from "./plugins";
export * from "./react-component";
export { ECameraScaleLevel } from "./services/camera/CameraService";
export { HitBoxData } from "./services/HitTest";
export * from "./services/Layer";
export * from "./store";
export { EAnchorType } from "./store/anchor/Anchor";
export type { BlockState, TBlockId } from "./store/block/Block";
export type { ConnectionState, TConnection, TConnectionId } from "./store/connection/ConnectionState";
export { ECanChangeBlockGeometry } from "./store/settings";
export { addEventListeners } from "./utils/functions";
export { dragListener } from "./utils/functions/dragListener";
export * from "./utils/renderers/text";
export { EVENTS } from "./utils/types/events";
export { type TPoint, type TRect } from "./utils/types/shapes";
Expand Down
2 changes: 1 addition & 1 deletion src/react-component/Block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const GraphBlock = <T extends TBlock>({
}: {
graph: Graph;
block: T;
children: React.ReactNode;
children?: React.ReactNode;
className?: string;
containerClassName?: string;
}) => {
Expand Down
5 changes: 3 additions & 2 deletions src/react-component/hooks/useGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { batch } from "@preact/signals-core";

import { ZoomConfig } from "../../api/PublicGraphApi";
import type { TBlock } from "../../components/canvas/blocks/Block";
import { Graph, GraphState, TGraphConfig } from "../../graph";
import type { TGraphZoomTarget } from "../../graph";
import { Graph, GraphState, TGraphConfig } from "../../graph";
import type { TGraphColors, TGraphConstants } from "../../graphConfig";
import type { Layer } from "../../services/Layer";
import type { TConnection } from "../../store/connection/ConnectionState";
import { TGraphSettingsConfig } from "../../store/settings";
import { useFn } from "../../utils/hooks/useFn";
import { RecursivePartial } from "../../utils/types/helpers";

Expand Down Expand Up @@ -57,7 +58,7 @@ export function useGraph(config: HookGraphParams) {
return {
graph,
api: graph.api,
setSettings: useFn((settings) => graph.updateSettings(settings)),
setSettings: useFn((settings: Partial<TGraphSettingsConfig>) => graph.updateSettings(settings)),
start: useFn(() => {
if (graph.state !== GraphState.READY) {
graph.start();
Expand Down
1 change: 1 addition & 0 deletions src/store/connection/ConnectionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type TConnectionId = string | number | symbol;

export type TConnection = {
id?: TConnectionId;
is?: string;
sourceBlockId: TBlockId;
targetBlockId: TBlockId;
sourceAnchorId?: string;
Expand Down
8 changes: 7 additions & 1 deletion src/store/settings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { computed, signal } from "@preact/signals-core";

import type { Block, TBlock } from "../components/canvas/blocks/Block";
import { type BlockConnection } from "../components/canvas/connections/BlockConnection";

Check failure on line 4 in src/store/settings.ts

View workflow job for this annotation

GitHub Actions / Verify Files

Prefer using a top-level type-only import instead of inline type specifiers

import { RootStore } from "./index";

Expand All @@ -24,6 +25,7 @@ export type TGraphSettingsConfig<B extends TBlock = TBlock> = {
connectivityComponentOnClickRaise: boolean;
showConnectionLabels: boolean;
blockComponents: Record<string, typeof Block<B>>;
connectionComponents: Record<string, typeof BlockConnection>;
};

const getInitState: TGraphSettingsConfig = {
Expand All @@ -40,6 +42,7 @@ const getInitState: TGraphSettingsConfig = {
connectivityComponentOnClickRaise: true,
showConnectionLabels: false,
blockComponents: {},
connectionComponents: {},
};

export class GraphEditorSettings {
Expand All @@ -49,7 +52,10 @@ export class GraphEditorSettings {
return this.$settings.value.blockComponents;
});

constructor(public rootStore: RootStore) {}
public $connectionComponents = computed(() => {
return this.$settings.value.connectionComponents;
});
public constructor(public rootStore: RootStore) {}

Check failure on line 58 in src/store/settings.ts

View workflow job for this annotation

GitHub Actions / Verify Files

Public accessibility modifier on method definition constructor

public setupSettings(config: Partial<TGraphSettingsConfig>) {
this.$settings.value = Object.assign({}, this.$settings.value, config);
Expand Down
Loading

0 comments on commit 846eae8

Please sign in to comment.