mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-26 07:59:10 -05:00
feat: implements onSceneUpdated callback
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { Box } from '@mui/material';
|
||||
import { theme } from 'src/styles/theme';
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
GroupInput,
|
||||
Scene
|
||||
} from 'src/types';
|
||||
import { sceneToSceneInput } from 'src/utils';
|
||||
import { useSceneStore, SceneProvider } from 'src/stores/sceneStore';
|
||||
import { GlobalStyles } from 'src/styles/GlobalStyles';
|
||||
import { Renderer } from 'src/components/Renderer/Renderer';
|
||||
@@ -24,7 +26,7 @@ interface Props {
|
||||
zoom?: number;
|
||||
};
|
||||
interactionsEnabled?: boolean;
|
||||
onSceneUpdated?: (scene: SceneInput, prevScene: SceneInput) => void;
|
||||
onSceneUpdated?: (scene: SceneInput) => void;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
}
|
||||
@@ -37,6 +39,9 @@ const App = ({
|
||||
onSceneUpdated
|
||||
}: Props) => {
|
||||
useWindowUtils();
|
||||
const scene = useSceneStore(({ nodes, connectors, groups, icons }) => {
|
||||
return { nodes, connectors, groups, icons };
|
||||
}, shallow);
|
||||
const sceneActions = useSceneStore((state) => {
|
||||
return state.actions;
|
||||
});
|
||||
@@ -58,6 +63,13 @@ const App = ({
|
||||
}
|
||||
}, [initialScene, sceneActions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onSceneUpdated) {
|
||||
const sceneInput = sceneToSceneInput(scene);
|
||||
onSceneUpdated(sceneInput);
|
||||
}
|
||||
}, [scene, onSceneUpdated]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<GlobalStyles />
|
||||
|
||||
33
src/examples/Callbacks/Callbacks.tsx
Normal file
33
src/examples/Callbacks/Callbacks.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import Isoflow from 'src/Isoflow';
|
||||
import { icons } from '../icons';
|
||||
|
||||
export const Callbacks = () => {
|
||||
const onSceneUpdated = useCallback(() => {
|
||||
console.log('Scene updated');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Isoflow
|
||||
initialScene={{
|
||||
icons,
|
||||
nodes: [
|
||||
{
|
||||
id: 'server',
|
||||
label: 'Callbacks example',
|
||||
labelHeight: 40,
|
||||
iconId: 'server',
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
],
|
||||
connectors: [],
|
||||
groups: []
|
||||
}}
|
||||
onSceneUpdated={onSceneUpdated}
|
||||
height="100%"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -64,7 +64,6 @@ export const CustomNode = () => {
|
||||
groups: [
|
||||
{
|
||||
id: 'group1',
|
||||
label: 'Group 1',
|
||||
nodeIds: ['server', 'database']
|
||||
}
|
||||
],
|
||||
|
||||
@@ -2,8 +2,10 @@ import React, { useState, useMemo } from 'react';
|
||||
import { Box, Select, MenuItem, useTheme } from '@mui/material';
|
||||
import { BasicEditor } from './BasicEditor/BasicEditor';
|
||||
import { CustomNode } from './CustomNode/CustomNode';
|
||||
import { Callbacks } from './Callbacks/Callbacks';
|
||||
|
||||
const examples = [
|
||||
{ name: 'Callbacks', component: Callbacks },
|
||||
{ name: 'Live Diagrams', component: CustomNode },
|
||||
{ name: 'Basic Editor', component: BasicEditor }
|
||||
];
|
||||
|
||||
@@ -87,7 +87,10 @@ export const SceneProvider = ({ children }: ProviderProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export function useSceneStore<T>(selector: (state: SceneStore) => T) {
|
||||
export function useSceneStore<T>(
|
||||
selector: (state: SceneStore) => T,
|
||||
equalityFn?: (left: T, right: T) => boolean
|
||||
) {
|
||||
const store = useContext(SceneContext);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -100,6 +103,6 @@ export function useSceneStore<T>(selector: (state: SceneStore) => T) {
|
||||
throw new Error('Missing provider in the tree');
|
||||
}
|
||||
|
||||
const value = useStore(store, selector);
|
||||
const value = useStore(store, selector, equalityFn);
|
||||
return value;
|
||||
}
|
||||
|
||||
2
src/tests/fixtures/scene.ts
vendored
2
src/tests/fixtures/scene.ts
vendored
@@ -46,5 +46,5 @@ export const scene: SceneInput = {
|
||||
{ id: 'connector1', label: 'Connector1', from: 'node1', to: 'node2' },
|
||||
{ id: 'connector2', label: 'Connector2', from: 'node2', to: 'node3' }
|
||||
],
|
||||
groups: [{ id: 'group1', label: 'Group1', nodeIds: ['node1', 'node2'] }]
|
||||
groups: [{ id: 'group1', nodeIds: ['node1', 'node2'] }]
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface Node {
|
||||
export interface Connector {
|
||||
type: SceneItemTypeEnum.CONNECTOR;
|
||||
id: string;
|
||||
label: string;
|
||||
color: string;
|
||||
from: string;
|
||||
to: string;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import chroma from 'chroma-js';
|
||||
import { SceneInput, Scene } from 'src/types';
|
||||
|
||||
export const clamp = (num: number, min: number, max: number) => {
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
@@ -14,26 +13,6 @@ export const roundToOneDecimalPlace = (num: number) => {
|
||||
return Math.round(num * 10) / 10;
|
||||
};
|
||||
|
||||
export const sceneToSceneInput = (scene: Scene): SceneInput => {
|
||||
const nodes: SceneInput['nodes'] = scene.nodes.map((node) => {
|
||||
return {
|
||||
id: node.id,
|
||||
position: node.position,
|
||||
label: node.label,
|
||||
labelHeight: node.labelHeight,
|
||||
color: node.color,
|
||||
iconId: node.iconId
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
nodes,
|
||||
connectors: [],
|
||||
groups: [],
|
||||
icons: scene.icons
|
||||
} as SceneInput;
|
||||
};
|
||||
|
||||
interface GetColorVariantOpts {
|
||||
alpha?: number;
|
||||
grade?: number;
|
||||
|
||||
@@ -40,6 +40,7 @@ export const connectorInputToConnector = (
|
||||
return {
|
||||
type: SceneItemTypeEnum.CONNECTOR,
|
||||
id: connectorInput.id,
|
||||
label: connectorInput.label ?? '',
|
||||
color: connectorInput.color ?? DEFAULT_COLOR,
|
||||
from: connectorInput.from,
|
||||
to: connectorInput.to
|
||||
@@ -67,3 +68,49 @@ export const sceneInputtoScene = (sceneInput: SceneInput): Scene => {
|
||||
icons: sceneInput.icons
|
||||
} as Scene;
|
||||
};
|
||||
|
||||
export const nodeToNodeInput = (node: Node): NodeInput => {
|
||||
return {
|
||||
id: node.id,
|
||||
position: node.position,
|
||||
label: node.label,
|
||||
labelHeight: node.labelHeight,
|
||||
color: node.color,
|
||||
iconId: node.iconId
|
||||
};
|
||||
};
|
||||
|
||||
export const connectorToConnectorInput = (
|
||||
connector: Connector
|
||||
): ConnectorInput => {
|
||||
return {
|
||||
id: connector.id,
|
||||
label: connector.label,
|
||||
from: connector.from,
|
||||
to: connector.to,
|
||||
color: connector.color
|
||||
};
|
||||
};
|
||||
|
||||
export const groupToGroupInput = (group: Group): GroupInput => {
|
||||
return {
|
||||
id: group.id,
|
||||
nodeIds: group.nodeIds,
|
||||
color: group.color
|
||||
};
|
||||
};
|
||||
|
||||
export const sceneToSceneInput = (scene: Scene): SceneInput => {
|
||||
const nodes: NodeInput[] = scene.nodes.map(nodeInputToNode);
|
||||
const connectors: ConnectorInput[] = scene.connectors.map(
|
||||
connectorToConnectorInput
|
||||
);
|
||||
const groups: GroupInput[] = scene.groups.map(groupToGroupInput);
|
||||
|
||||
return {
|
||||
nodes,
|
||||
connectors,
|
||||
groups,
|
||||
icons: scene.icons
|
||||
} as SceneInput;
|
||||
};
|
||||
|
||||
@@ -29,7 +29,6 @@ export const connectorInput = z.object({
|
||||
|
||||
export const groupInput = z.object({
|
||||
id: z.string(),
|
||||
label: z.string().nullable(),
|
||||
color: z.string().optional(),
|
||||
nodeIds: z.array(z.string())
|
||||
});
|
||||
|
||||
@@ -54,7 +54,6 @@ describe('scene validation works correctly', () => {
|
||||
const { nodes } = scene;
|
||||
const invalidGroup: GroupInput = {
|
||||
id: 'invalidGroup',
|
||||
label: null,
|
||||
nodeIds: ['invalidNode', 'node1']
|
||||
};
|
||||
const groups: GroupInput[] = [...scene.groups, invalidGroup];
|
||||
|
||||
Reference in New Issue
Block a user