Skip to content

Commit

Permalink
Lift state (#54)
Browse files Browse the repository at this point in the history
* lift state

* lift state

* fix demo

* fix

* fix unit tests

* fix warning

* update docs
  • Loading branch information
intellild committed Sep 3, 2021
1 parent df9ea14 commit 4b979b9
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 293 deletions.
24 changes: 0 additions & 24 deletions packages/ReactDagEditor/docs/renderer-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,6 @@ interface IGraphProps<NodeData = unknown, EdgeData = unknown, PortData = unknown
* The ref of the svg element
*/
svgRef?: React.RefObject<SVGSVGElement>;
/**
* The default shape of the node
*
* @default "default"
*/
defaultNodeShape?: string;
/**
* The default shape of the edge
*
* @default "default"
*/
defaultEdgeShape?: string;
/**
* The default shape of the port
*
* @default "default"
*/
defaultPortShape?: string;
/**
* The default shape of the group
*
* @default "default"
*/
defaultGroupShape?: string;
/**
* The access key to focus the canvas
*
Expand Down
57 changes: 0 additions & 57 deletions packages/ReactDagEditor/docs/util-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,6 @@

Util components does not render anything. Use them for various of utility functionalities.

## `RegisterNode`

By using `RegisterNode` you can link the NodeConfig specified by "name" to the nodes in data by the field "shape".

## `RegisterNode` props

```typescript
interface IRegisterNodeProps {
/**
* Name of the custom node. The "shape" in your node model should have been registered as the name here.
*/
name: string;
/**
* The config could be a class that implements IRectConfig<ICanvasNode>
*/
config: IRectConfig<ICanvasNode>;
}
```

### NodeConfig.render

render decides the element to represent a node.
Expand All @@ -41,25 +22,6 @@ The hover view for a node

The min height/width constraint for node resizing.

## `RegisterEdge`

By using `RegisterEdge` you can link the EdgeConfig specified by "name" to the nodes in data by the field "shape".

## `RegisterEdge` props

```typescript
export interface IRegisterEdgeProps {
/**
* Name of the custom edge. The "shape" in your edge model should have been registered as the name here.
*/
name: string;
/**
* The config could be a class that implements IEdgeConfig
*/
config: IEdgeConfig;
}
```

### EdgeConfig.render

`render` decides the element to represent an edge.
Expand All @@ -68,25 +30,6 @@ export interface IRegisterEdgeProps {

In case the target/source node of an edge is outside of the view port while the edge is selected by mouse clicking, a hint will be rendered if this method is provided.

## `RegisterPort`

Similar to `RegisterNode` and `RegisterPort`, use this to customize the port elements.

## `RegisterPort` props

```typescript
interface IRegisterPortProps {
/**
* Name of the custom port. The "shape" in your port model should have been registered as the name here.
*/
name: string;
/**
* The config could be a class that implements IPortConfig
*/
config: IPortConfig;
}
```

### PortConfig.render

Similar to the render methods in `NodeConfig` / `EdgeConfig`, use `render` for the basic element to represent a port
Expand Down
21 changes: 0 additions & 21 deletions packages/ReactDagEditor/docs/wrapper-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,6 @@ All other components in the package react-dag-editor should be wrapped by this o
handleError?(error?: Error, errorInfo?: React.ErrorInfo, children?: React.ReactNode): React.ReactChild;
```
## `<GraphStateStore />`
Use this one to initialize the state and add custom middlewares.
Permit the wrapped components to access the state via propsAPI or built-in hooks.
## `<GraphStateStore />` props
```typescript
/**
* The propsAPI reference.
*/
propsAPIRef?: React.Ref<IPropsAPI<NodeData, EdgeData, PortData> | null>;
/**
* the initial graph data model.
*/
data?: GraphModel<NodeData, EdgeData, PortData>;
defaultTransformMatrix?: ITransformMatrix;
middleware?: IGraphReducer<NodeData, EdgeData, PortData, Action>;
onStateChanged?: IDispatchCallback<NodeData, EdgeData, PortData>;
```
## `<Item />`
Usually people want a palette aside the main canvas to build their own workflow. To implement this, wrap `<item />` around the a palette item then it will be able to drag-to-add to the canvas.
Expand Down
64 changes: 9 additions & 55 deletions packages/ReactDagEditor/src/components/Graph/GraphStateStore.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,22 @@
import * as React from "react";
import { ConnectingState } from "../../ConnectingState";
import { EMPTY_GAP, EMPTY_TRANSFORM_MATRIX, GraphConfigContext, IGraphReducer, ViewportContext } from "../../contexts";
import { GraphConfigContext, IDispatch, ViewportContext } from "../../contexts";
import { AlignmentLinesContext } from "../../contexts/AlignmentLinesContext";
import { GraphControllerContext } from "../../contexts/GraphControllerContext";
import { GraphStateContext, GraphValueContext } from "../../contexts/GraphStateContext";
import { GraphController } from "../../controllers/GraphController";
import { defaultFeatures, GraphFeatures } from "../../Features";
import { useConst } from "../../hooks/useConst";
import { useGraphReducer } from "../../hooks/useGraphReducer";
import { IGraphConfig } from "../../models/config/types";
import { IGap, ITransformMatrix } from "../../models/geometry";
import { GraphModel } from "../../models/GraphModel";
import type { GraphController } from "../../controllers/GraphController";
import type { IGraphState } from "../../models/state";

export interface IGraphStateStoreProps<NodeData = unknown, EdgeData = unknown, PortData = unknown, Action = never> {
/**
* the initial graph data model.
*/
data?: GraphModel<NodeData, EdgeData, PortData>;
defaultTransformMatrix?: ITransformMatrix;
middleware?: IGraphReducer<NodeData, EdgeData, PortData, Action>;
features?: ReadonlySet<GraphFeatures>;
canvasBoundaryPadding?: IGap;
graphConfig: IGraphConfig;
state: IGraphState<NodeData, EdgeData, PortData>;
dispatch: IDispatch<NodeData, EdgeData, PortData, Action>;
graphController: GraphController;
}

export function GraphStateStore<NodeData = unknown, EdgeData = unknown, PortData = unknown, Action = never>(
props: React.PropsWithChildren<IGraphStateStoreProps<NodeData, EdgeData, PortData, Action>>
): React.ReactElement {
const {
defaultTransformMatrix = EMPTY_TRANSFORM_MATRIX,
middleware,
features = defaultFeatures,
canvasBoundaryPadding = EMPTY_GAP,
graphConfig
} = props;

const [state, dispatch] = useGraphReducer(
{
data: props.data,
transformMatrix: defaultTransformMatrix,
graphConfig,
features,
canvasBoundaryPadding,
nodeMinVisibleSize: {
width: 5,
height: 5
},
nodeMaxVisibleSize: {
width: Infinity,
height: Infinity
}
},
middleware
);

const graphController = useConst(() => new GraphController(state, dispatch));
graphController.UNSAFE_latestState = state;
React.useLayoutEffect(() => {
graphController.state = state;
graphController.dispatch = dispatch;
// TODO: fix the next line after state is lifted and everything is merged into top level `ReactDagEditor`
// graphController.getGlobalEventTargetImpl = getGlobalEventTarget;
}, [dispatch, graphController, state]);

const { graphController, state, dispatch, children } = props;
const contextValue = React.useMemo(
() => ({
state,
Expand All @@ -72,14 +26,14 @@ export function GraphStateStore<NodeData = unknown, EdgeData = unknown, PortData
);

return (
<GraphConfigContext.Provider value={graphConfig}>
<GraphConfigContext.Provider value={state.settings.graphConfig}>
<GraphControllerContext.Provider value={graphController}>
<ConnectingState data={state.data.present} connectState={state.connectState}>
<GraphStateContext.Provider value={contextValue}>
<ViewportContext.Provider value={state.viewport}>
<GraphValueContext.Provider value={state.data.present}>
<AlignmentLinesContext.Provider value={state.alignmentLines}>
{props.children}
{children}
</AlignmentLinesContext.Provider>
</GraphValueContext.Provider>
</ViewportContext.Provider>
Expand Down
42 changes: 32 additions & 10 deletions packages/ReactDagEditor/src/components/ReactDagEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import * as React from "react";
import { ContextMenuConfig, ContextMenuConfigContext } from "../contexts";
import { ContextMenuConfig, ContextMenuConfigContext, IDispatch } from "../contexts";
import { GraphController } from "../controllers/GraphController";
import { useConst } from "../hooks/useConst";
import type { IGraphState } from "../models/state";
import { Debug } from "../utils/debug";
import { noop } from "../utils/noop";
import { ErrorBoundary } from "./ErrorBoundary/ErrorBoundary";
import { GraphStateStore } from "./Graph/GraphStateStore";
import { IThemeProviderProps, ThemeProvider } from "./ThemeProvider";

/**
* ReactDagEditor props
*/
export interface IReactDagEditorProps extends IThemeProviderProps {
export interface IReactDagEditorProps<NodeData = unknown, EdgeData = unknown, PortData = unknown, Action = never>
extends IThemeProviderProps {
/**
* Additional css styles to apply to the container element.
*/
Expand All @@ -17,6 +22,8 @@ export interface IReactDagEditorProps extends IThemeProviderProps {
* Additional css class to apply to the container element.
*/
className?: string;
state: IGraphState<NodeData, EdgeData, PortData>;
dispatch: IDispatch<NodeData, EdgeData, PortData, Action>;
/**
* Fired when there is invalid data or config. The invalid data or config will be ignored to avoid crashing your app.
*/
Expand Down Expand Up @@ -48,17 +55,32 @@ export const ReactDagEditor: React.FunctionComponent<IReactDagEditorProps> = pro

const handleError = props.handleError?.bind(null);

const { theme, setTheme } = props;
const { theme, setTheme, state, dispatch, getGlobalEventTarget } = props;

const graphController = useConst(() => new GraphController(state, dispatch));
graphController.UNSAFE_latestState = state;
React.useLayoutEffect(() => {
graphController.state = state;
graphController.dispatch = dispatch;
graphController.getGlobalEventTargetImpl = getGlobalEventTarget;
}, [dispatch, getGlobalEventTarget, graphController, state]);
React.useEffect(() => {
return () => {
graphController.dispatch = noop;
};
}, [graphController]);

return (
<ErrorBoundary renderOnError={handleError}>
<ContextMenuConfigContext.Provider value={useConst(() => new ContextMenuConfig())}>
<ThemeProvider theme={theme} setTheme={setTheme}>
<div style={props.style} className={props.className}>
{props.children}
</div>
</ThemeProvider>
</ContextMenuConfigContext.Provider>
<GraphStateStore state={state} dispatch={dispatch} graphController={graphController}>
<ContextMenuConfigContext.Provider value={useConst(() => new ContextMenuConfig())}>
<ThemeProvider theme={theme} setTheme={setTheme}>
<div style={props.style} className={props.className}>
{props.children}
</div>
</ThemeProvider>
</ContextMenuConfigContext.Provider>
</GraphStateStore>
</ErrorBoundary>
);
};
1 change: 0 additions & 1 deletion packages/ReactDagEditor/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ export * from "./StaticGraph/StaticGraph";
export * from "./Graph/IGraphProps";
export * from "./RegisterComponent";
export { defaultGetPositionFromEvent } from "../controllers";
export * from "./Graph/GraphStateStore";
18 changes: 10 additions & 8 deletions packages/ReactDagEditor/src/contexts/GraphStateContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { GraphConfigBuilder } from "../models/config/GraphConfigBuilder";
import type { IEvent } from "../models/event";
import type { IGap, IRectSize, ITransformMatrix, IViewport } from "../models/geometry";
import { GraphModel } from "../models/GraphModel";
import { GraphBehavior, IGraphState } from "../models/state";
import { GraphBehavior, IGraphSettings, IGraphState } from "../models/state";
import { Debug } from "../utils/debug";
import { resetUndoStack } from "../utils/history";

Expand Down Expand Up @@ -35,14 +35,16 @@ export const DEFAULT_NODE_MAX_VISIBLE_SIZE: IRectSize = {
height: NODE_MAX_VISIBLE_LENGTH
};

export const DEFAULT_GRAPH_SETTINGS: IGraphSettings = {
features: defaultFeatures,
graphConfig: GraphConfigBuilder.default().build(),
canvasBoundaryPadding: EMPTY_GAP,
nodeMinVisibleSize: DEFAULT_NODE_MIN_VISIBLE_SIZE,
nodeMaxVisibleSize: DEFAULT_NODE_MAX_VISIBLE_SIZE
};

export const EMPTY_GRAPH_STATE: IGraphState = {
settings: {
features: defaultFeatures,
graphConfig: GraphConfigBuilder.default().build(),
canvasBoundaryPadding: EMPTY_GAP,
nodeMinVisibleSize: DEFAULT_NODE_MIN_VISIBLE_SIZE,
nodeMaxVisibleSize: DEFAULT_NODE_MAX_VISIBLE_SIZE
},
settings: DEFAULT_GRAPH_SETTINGS,
behavior: GraphBehavior.default,
data: resetUndoStack(GraphModel.empty()),
viewport: {
Expand Down
28 changes: 12 additions & 16 deletions packages/ReactDagEditor/src/hooks/useGraphReducer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import * as React from "react";
import { emptyDummyNodes } from "../components/dummyNodes";
import { emptySelectBoxPosition } from "../components/Graph/SelectBox";
import { EMPTY_TRANSFORM_MATRIX, IDispatch, IDispatchCallback, IGraphReactReducer, IGraphReducer } from "../contexts";
import {
DEFAULT_GRAPH_SETTINGS,
EMPTY_TRANSFORM_MATRIX,
IDispatch,
IDispatchCallback,
IGraphReactReducer,
IGraphReducer
} from "../contexts";
import { ITransformMatrix } from "../models/geometry";
import { GraphModel } from "../models/GraphModel";
import { GraphBehavior, IGraphSettings, IGraphState } from "../models/state";
Expand All @@ -20,7 +27,7 @@ import { batchedUpdates } from "../utils/batchedUpdates";
import { useConst } from "./useConst";

export interface IGraphReducerInitializerParams<NodeData = unknown, EdgeData = unknown, PortData = unknown>
extends IGraphSettings<NodeData, EdgeData, PortData> {
extends Partial<IGraphSettings<NodeData, EdgeData, PortData>> {
data?: GraphModel<NodeData, EdgeData, PortData>;
transformMatrix?: ITransformMatrix;
}
Expand Down Expand Up @@ -52,21 +59,10 @@ export function useGraphReducer<NodeData = unknown, EdgeData = unknown, PortData
const [state, dispatchImpl] = React.useReducer(
reducer,
params,
({
data,
transformMatrix,
features,
graphConfig,
canvasBoundaryPadding,
nodeMaxVisibleSize,
nodeMinVisibleSize
}) => ({
({ data, transformMatrix, ...settings }): IGraphState => ({
settings: {
features,
graphConfig,
canvasBoundaryPadding,
nodeMaxVisibleSize,
nodeMinVisibleSize
...DEFAULT_GRAPH_SETTINGS,
...settings
},
data: resetUndoStack(data ?? GraphModel.empty()),
viewport: {
Expand Down

0 comments on commit 4b979b9

Please sign in to comment.