Skip to content

Commit

Permalink
Merge pull request #22 from tokarchyn/bugfix
Browse files Browse the repository at this point in the history
Do not use "global" variable
Recalculate diagram boundaries on zoomToFit
Fix rendering diagram with old settings
  • Loading branch information
tokarchyn authored Feb 11, 2023
2 parents 0bc1511 + 61e1725 commit 8e56499
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 68 deletions.
2 changes: 1 addition & 1 deletion lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"source": "src/index.ts",
"dependencies": {
"@use-gesture/react": "^10.2.21",
"mobx": "^6.4.0",
"mobx": "6.8.0",
"mobx-react-lite": "^3.4.0"
},
"scripts": {
Expand Down
56 changes: 24 additions & 32 deletions lib/src/components/DiagramContext.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,43 @@
import React, {
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import { RootStore } from 'states/rootStore';
import type { ISettings } from 'states/rootStore';
import type { INodeState } from 'states/nodeState';
import type { ILinkState } from 'states/linkState';
import { observer } from 'mobx-react-lite';
import React, { useEffect, useMemo } from 'react';
import type { ILinkState } from 'states/linkState';
import type { INodeState } from 'states/nodeState';
import type { ISettings } from 'states/rootStore';
import { RootStore } from 'states/rootStore';

export const RootStoreContext = React.createContext<RootStore | null>(null);

export const DiagramContext = observer((props: IDiagramContextProps) => {
const rootStore = useMemo(() => new RootStore(), []);
const [rootStore, initialSettings, initialState] = useMemo(
() => [
new RootStore(props.settings, props.initState),
props.settings,
props.initState,
],
[]
);

useEffect(() => {
rootStore.importSettings(props.settings);
}, [rootStore, props.settings]);
if (initialSettings !== props.settings) {
rootStore.importSettings(props.settings);
}
}, [rootStore, props.settings, initialSettings]);

useEffect(() => {
rootStore.importState(
props.initState?.nodes ?? [],
props.initState?.links ?? []
);
}, [rootStore, props.initState]);
if (initialState !== props.initState) {
rootStore.importState(
props.initState?.nodes ?? [],
props.initState?.links ?? []
);
}
}, [rootStore, props.initState, initialState]);

useEffect(() => {
if (props.storeRef) {
props.storeRef.current = rootStore;
}
}, [rootStore, props.storeRef]);

const lastRenderedImportRef = useRef(-1);

useLayoutEffect(() => {
if (
rootStore.diagramState.renderImportedRequestId >
lastRenderedImportRef.current
) {
rootStore.callbacks.importedStateRendered();
lastRenderedImportRef.current =
rootStore.diagramState.renderImportedRequestId;
}
}, [rootStore.diagramState.renderImportedRequestId]);

return (
<RootStoreContext.Provider value={rootStore}>
{props.children}
Expand Down
42 changes: 38 additions & 4 deletions lib/src/components/DiagramInner.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { ReactNode, useEffect } from 'react';
import React, {
ReactNode,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import { LinksLayer } from 'components/link/LinksLayer';
import { NodesLayer } from 'components/node/NodesLayer';
import { useDiagramUserInteraction } from 'hooks/userInteractions/useDiagramUserInteraction';
Expand All @@ -23,9 +29,35 @@ export const DigramInner = observer<IDiagramInnerProps>((props) => {
rootStore.nodesStore.nodes.forEach((n) => n.recalculatePortsOffset());
});

const offset = rootStore.diagramState.offset;
const zoom = rootStore.diagramState.zoom;
const transform = generateTransform(offset, zoom);
const [{ offset, zoom }, setOffsetZoom] = useState({
offset: rootStore.diagramState.offset,
zoom: rootStore.diagramState.zoom,
});

useEffect(() => {
setOffsetZoom({
offset: rootStore.diagramState.offset,
zoom: rootStore.diagramState.zoom,
});
}, [rootStore.diagramState.offset, rootStore.diagramState.zoom]);

const lastRenderedImportRef = useRef(-1);

useLayoutEffect(() => {
if (
rootStore.diagramState.importGenerationId > lastRenderedImportRef.current
) {
lastRenderedImportRef.current = rootStore.diagramState.importGenerationId;

rootStore.callbacks.importedStateRendered();

setOffsetZoom({
offset: rootStore.diagramState.offset,
zoom: rootStore.diagramState.zoom,
});
}
}, [rootStore.diagramState.importGenerationId]);

let className = 'react_fast_diagram_DiagramInner';
if (
!rootStore.diagramSettings.userInteraction.arePointerInteractionsDisabled
Expand All @@ -34,6 +66,8 @@ export const DigramInner = observer<IDiagramInnerProps>((props) => {
className += ' react_fast_diagram_touch_action_disabled';
}

const transform = generateTransform(offset, zoom);

return (
<div
ref={rootStore.diagramState.ref}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/hooks/userInteractions/useLinkUserInteraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const useLinkUserInteraction = (
selectionHandledRef.current = false;

if (linkState.isSelectionEnabled) {
selectionTimeoutRef.current = global.setTimeout(() => {
selectionTimeoutRef.current = setTimeout(() => {
if (!selectionHandledRef.current) {
selectionHandledRef.current = true;
rootStore.selectionState.switch(linkState);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/hooks/userInteractions/useNodeUserInteraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const useNodeUserInteraction = (nodeState: NodeState) => {
if (interactionActiveRef.current) {
cancelSelectOnLongTap();
if (nodeState.isSelectionEnabled) {
selectOnLongTapRef.current = global.setTimeout(() => {
selectOnLongTapRef.current = setTimeout(() => {
if (selectOnLongTapRef.current) {
selectOnLongTapRef.current = null;
// It can happen if user simultaneously tap two nodes and will start move one of them
Expand Down
40 changes: 28 additions & 12 deletions lib/src/states/diagramSettings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeAutoObservable } from 'mobx';
import { action, makeAutoObservable, observable, trace } from 'mobx';
import { Point } from 'utils/point';
import { createSvgBackground } from 'components/background/SvgBackground';
import { VisualComponentWithDefault } from 'states/visualComponentWithDefault';
Expand All @@ -8,24 +8,40 @@ import {
IComponentDefinition,
VisualComponent,
} from 'states/visualComponentState';
import { IUserInteraction, UserInteractionSettings } from 'states/userInteractionSettings';
import {
IUserInteraction,
UserInteractionSettings,
} from 'states/userInteractionSettings';

export class DiagramSettings {
private _backgroundComponentState: VisualComponentWithDefault<IBackgroundComponentProps>;
private _miniControlComponentState: VisualComponentWithDefault<IMiniControlComponentProps>;
private _zoomInterval: Point = defaultZoomInterval;
private _zoomToFitSettings: IZoomToFitSettings = defaultZoomToFitSettings;
private _zoomInterval: Point;
private _zoomToFitSettings: IZoomToFitSettings;
private _userInteraction: UserInteractionSettings;

constructor() {
this._backgroundComponentState = new VisualComponentWithDefault<IBackgroundComponentProps>(
createSvgBackground()
);
this._miniControlComponentState = new VisualComponentWithDefault<IMiniControlComponentProps>(
createDefaultMiniControl()
);
this._backgroundComponentState =
new VisualComponentWithDefault<IBackgroundComponentProps>(
createSvgBackground()
);
this._miniControlComponentState =
new VisualComponentWithDefault<IMiniControlComponentProps>(
createDefaultMiniControl()
);
this._userInteraction = new UserInteractionSettings();
makeAutoObservable(this);

this.import({
zoomInterval: defaultZoomInterval,
zoomToFitSettings: defaultZoomToFitSettings,
});

makeAutoObservable(this, {
// Probably some bug in mobx, without this annotation warning is thrown saying "cannot change _zoomInterval outside action"
setZoomInterval: action,
// @ts-ignore
_zoomInterval: observable.ref,
});
}

import = (obj?: IDiagramSettings) => {
Expand Down Expand Up @@ -68,7 +84,7 @@ const defaultZoomInterval: Point = [0.1, 3];
const defaultZoomToFitSettings: IZoomToFitSettings = {
padding: [30, 30],
zoomInterval: [0.1, 1.5],
callOnImportState: true
callOnImportState: true,
};

export interface IDiagramSettings {
Expand Down
15 changes: 8 additions & 7 deletions lib/src/states/diagramState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import { BoundingBox, clampValue, deepCopy } from 'utils/common';
import { addPoints, multiplyPoint, Point, subtractPoints } from 'utils/point';

export class DiagramState
implements IUserInteractionTranslate, IUserInteractionTranslateAndZoom {
implements IUserInteractionTranslate, IUserInteractionTranslateAndZoom
{
private _offset: Point;
private _zoom: number;
private _ref: HtmlElementRefState;
private _rootStore: RootStore;

private _renderImportedRequestId: number = -1;
private _importGenerationId: number = -1;

constructor(rootStore: RootStore) {
this._ref = new HtmlElementRefState(null, this);
Expand Down Expand Up @@ -49,12 +50,12 @@ export class DiagramState
);
};

reportWhenImportedStateRendered = () => {
this._renderImportedRequestId ++;
incrementImportGenerationId = () => {
this._importGenerationId++;
};

get renderImportedRequestId() {
return this._renderImportedRequestId;
get importGenerationId() {
return this._importGenerationId;
}

zoomIn = () => this.zoomIntoCenter(1 / 0.8);
Expand Down Expand Up @@ -144,7 +145,7 @@ export class DiagramState
zoomToFit = () => {
const nodesBoundingBox = this._getNodesBoundingBoxWithPadding();

const diagramSize = this.ref.boundingRect?.size;
const diagramSize = this.ref.getRealBoundingRect()?.size;
if (!diagramSize) {
console.warn('Cannot retrieve diagram size');
return;
Expand Down
4 changes: 4 additions & 0 deletions lib/src/states/htmlElementRefState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export class HtmlElementRefState {

get boundingRect() {
this._triggerSizePositionRecalculation | 1;
return this.getRealBoundingRect();
}

getRealBoundingRect() {
if (this.current) {
const rect = this.current.getBoundingClientRect();
const zoom = this._diagramState.getRenderedZoom();
Expand Down
8 changes: 5 additions & 3 deletions lib/src/states/portState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ const positionToLinkDirection: {
// https://stackoverflow.com/questions/21912684/how-to-get-value-of-translatex-and-translatey
// https://gist.github.com/aderaaij/a6b666bf756b2db1596b366da921755d
function getTranslate(item: HTMLElement): Point {
const transArr: Point = [0, 0];
let transArr: Point = [0, 0];
if (!window.getComputedStyle) {
return transArr;
}
Expand All @@ -369,8 +369,10 @@ function getTranslate(item: HTMLElement): Point {
// consider also to add matrix3d(a, b, 0, 0, c, d, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1)
let mat = transform.match(/^matrix\((.+)\)$/);
if (mat) {
transArr[0] = parseFloat(mat[1].split(', ')[4]);
transArr[1] = parseFloat(mat[1].split(', ')[5]);
transArr = [
parseFloat(mat[1].split(', ')[4]),
parseFloat(mat[1].split(', ')[5]),
];
}

return transArr;
Expand Down
10 changes: 8 additions & 2 deletions lib/src/states/rootStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PortsSettings, IPortsSettings } from 'states/portsSettings';
import { SelectionState } from 'states/selectionState';
import { DragState } from 'states/dragState';
import { CommandExecutor } from 'states/commandExecutor';
import { IDiagramInitState } from 'components/DiagramContext';

export class RootStore {
private _diagramState: DiagramState;
Expand All @@ -27,7 +28,7 @@ export class RootStore {
private _linksSettings: LinksSettings;
private _callbacks: Callbacks;

constructor() {
constructor(settings?: ISettings, state?: IDiagramInitState) {
this._diagramSettings = new DiagramSettings();
this._nodesSettings = new NodesSettings();
this._linksSettings = new LinksSettings();
Expand All @@ -41,6 +42,11 @@ export class RootStore {
this._linksStore = new LinksStore(this);
this._selectionState = new SelectionState();
this._dragState = new DragState(this._selectionState, this._callbacks);

this.importSettings(settings);
if (state) {
this.importState(state.nodes, state.links);
}
}

get diagramState() {
Expand Down Expand Up @@ -88,9 +94,9 @@ export class RootStore {
}

importState = (nodes?: INodeState[], links?: ILinkState[]) => {
this._diagramState.incrementImportGenerationId();
this._nodesStore.import(nodes);
this._linksStore.import(links);
this._diagramState.reportWhenImportedStateRendered();
};

export = (): { nodes: INodeExport[]; links: ILinkState[] } => ({
Expand Down
2 changes: 1 addition & 1 deletion lib/src/utils/point.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type Point = [number, number];
export type Point = readonly [number, number];

export function isPoint(value: any): value is Point {
return (
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7633,10 +7633,10 @@ mobx-react-lite@^3.4.0:
resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.4.0.tgz#d59156a96889cdadad751e5e4dab95f28926dfff"
integrity sha512-bRuZp3C0itgLKHu/VNxi66DN/XVkQG7xtoBVWxpvC5FhAqbOCP21+nPhULjnzEqd7xBMybp6KwytdUpZKEgpIQ==

mobx@^6.4.0:
version "6.6.0"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.6.0.tgz#617ca1f3b745a781fa89c5eb94a773e3cbeff8ae"
integrity sha512-MNTKevLH/6DShLZcmSL351+JgiJPO56A4GUpoiDQ3/yZ0mAtclNLdHK9q4BcQhibx8/JSDupfTpbX2NZPemlRg==
mobx@6.8.0:
version "6.8.0"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.8.0.tgz#59051755fdb5c8a9f3f2e0a9b6abaf86bab7f843"
integrity sha512-+o/DrHa4zykFMSKfS8Z+CPSEg5LW9tSNGTuN8o6MF1GKxlfkSHSeJn5UtgxvPkGgaouplnrLXCF+duAsmm6FHQ==

module-details-from-path@^1.0.3:
version "1.0.3"
Expand Down

0 comments on commit 8e56499

Please sign in to comment.