This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import '@flowgram.ai/free-layout-editor/index.css';
|
||||
import { FreeLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
const FlowGramApp = () => (
|
||||
<FreeLayoutEditorProvider>
|
||||
<EditorRenderer />
|
||||
</FreeLayoutEditorProvider>
|
||||
);
|
||||
|
||||
export default FlowGramApp;
|
||||
@@ -0,0 +1,65 @@
|
||||
import '@flowgram.ai/free-layout-editor/index.css';
|
||||
|
||||
import {
|
||||
FreeLayoutEditorProvider,
|
||||
EditorRenderer,
|
||||
useNodeRender,
|
||||
WorkflowNodeProps,
|
||||
WorkflowNodeRenderer,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
const NodeRender = (props: WorkflowNodeProps) => {
|
||||
const { form, selected } = useNodeRender();
|
||||
return (
|
||||
<WorkflowNodeRenderer
|
||||
style={{
|
||||
width: 280,
|
||||
minHeight: 88,
|
||||
height: 'auto',
|
||||
background: '#fff',
|
||||
border: '1px solid rgba(6, 7, 9, 0.15)',
|
||||
borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
padding: 12,
|
||||
cursor: 'move',
|
||||
}}
|
||||
node={props.node}
|
||||
>
|
||||
{form?.render()}
|
||||
</WorkflowNodeRenderer>
|
||||
);
|
||||
};
|
||||
|
||||
const FlowGramApp = () => (
|
||||
<FreeLayoutEditorProvider
|
||||
materials={{
|
||||
renderDefaultNode: NodeRender,
|
||||
}}
|
||||
nodeRegistries={[
|
||||
{
|
||||
type: 'custom',
|
||||
},
|
||||
]}
|
||||
initialData={{
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 250, y: 100 },
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
}}
|
||||
>
|
||||
<EditorRenderer />
|
||||
</FreeLayoutEditorProvider>
|
||||
);
|
||||
|
||||
export default FlowGramApp;
|
||||
@@ -0,0 +1,82 @@
|
||||
import '@flowgram.ai/free-layout-editor/index.css';
|
||||
|
||||
import {
|
||||
FreeLayoutEditorProvider,
|
||||
EditorRenderer,
|
||||
useNodeRender,
|
||||
WorkflowNodeProps,
|
||||
WorkflowNodeRenderer,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
const NodeRender = (props: WorkflowNodeProps) => {
|
||||
const { form, selected } = useNodeRender();
|
||||
return (
|
||||
<WorkflowNodeRenderer
|
||||
style={{
|
||||
width: 280,
|
||||
minHeight: 88,
|
||||
height: 'auto',
|
||||
background: '#fff',
|
||||
border: '1px solid rgba(6, 7, 9, 0.15)',
|
||||
borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
padding: 12,
|
||||
cursor: 'move',
|
||||
}}
|
||||
node={props.node}
|
||||
>
|
||||
{form?.render()}
|
||||
</WorkflowNodeRenderer>
|
||||
);
|
||||
};
|
||||
|
||||
const FlowGramApp = () => (
|
||||
<FreeLayoutEditorProvider
|
||||
onAllLayersRendered={(ctx) => {
|
||||
ctx.tools.fitView(false);
|
||||
}}
|
||||
materials={{
|
||||
renderDefaultNode: NodeRender,
|
||||
}}
|
||||
nodeRegistries={[
|
||||
{
|
||||
type: 'custom',
|
||||
},
|
||||
]}
|
||||
canDeleteNode={() => true}
|
||||
canDeleteLine={() => true}
|
||||
initialData={{
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: 0 },
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '2',
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<EditorRenderer />
|
||||
</FreeLayoutEditorProvider>
|
||||
);
|
||||
|
||||
export default FlowGramApp;
|
||||
@@ -0,0 +1,126 @@
|
||||
import '@flowgram.ai/free-layout-editor/index.css';
|
||||
|
||||
import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
|
||||
import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
|
||||
import {
|
||||
FreeLayoutEditorProvider,
|
||||
EditorRenderer,
|
||||
useNodeRender,
|
||||
WorkflowNodeProps,
|
||||
WorkflowNodeRenderer,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
const NodeRender = (props: WorkflowNodeProps) => {
|
||||
const { form, selected } = useNodeRender();
|
||||
return (
|
||||
<WorkflowNodeRenderer
|
||||
style={{
|
||||
width: 280,
|
||||
minHeight: 88,
|
||||
height: 'auto',
|
||||
background: '#fff',
|
||||
border: '1px solid rgba(6, 7, 9, 0.15)',
|
||||
borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
padding: 12,
|
||||
cursor: 'move',
|
||||
}}
|
||||
node={props.node}
|
||||
>
|
||||
{form?.render()}
|
||||
</WorkflowNodeRenderer>
|
||||
);
|
||||
};
|
||||
|
||||
const FlowGramApp = () => (
|
||||
<FreeLayoutEditorProvider
|
||||
plugins={() => [createMinimapPlugin({}), createFreeSnapPlugin({})]}
|
||||
onAllLayersRendered={(ctx) => {
|
||||
ctx.tools.fitView(false);
|
||||
}}
|
||||
materials={{
|
||||
renderDefaultNode: NodeRender,
|
||||
}}
|
||||
nodeRegistries={[
|
||||
{
|
||||
type: 'custom',
|
||||
},
|
||||
]}
|
||||
canDeleteNode={() => true}
|
||||
canDeleteLine={() => true}
|
||||
initialData={{
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: -200 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: 0 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: 200 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 800, y: 0 },
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '2',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '3',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '4',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '2',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '3',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '4',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<EditorRenderer />
|
||||
</FreeLayoutEditorProvider>
|
||||
);
|
||||
|
||||
export default FlowGramApp;
|
||||
@@ -0,0 +1,16 @@
|
||||
import '@flowgram.ai/free-layout-editor/index.css';
|
||||
|
||||
import { FreeLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
import { useEditorProps } from './use-editor-props';
|
||||
|
||||
const FlowGramApp = () => {
|
||||
const editorProps = useEditorProps();
|
||||
return (
|
||||
<FreeLayoutEditorProvider {...editorProps}>
|
||||
<EditorRenderer />
|
||||
</FreeLayoutEditorProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlowGramApp;
|
||||
@@ -0,0 +1,67 @@
|
||||
import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
export const initialData: WorkflowJSON = {
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: -200 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: 0 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: 200 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 800, y: 0 },
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '2',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '3',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '4',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '2',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '3',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '4',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
/**
|
||||
* You can customize your own node registry
|
||||
* 你可以自定义节点的注册器
|
||||
*/
|
||||
export const nodeRegistries: WorkflowNodeRegistry[] = [
|
||||
{
|
||||
type: 'custom',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,34 @@
|
||||
import '@flowgram.ai/free-layout-editor/index.css';
|
||||
|
||||
import {
|
||||
useNodeRender,
|
||||
WorkflowNodeProps,
|
||||
WorkflowNodeRenderer,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
export const NodeRender = (props: WorkflowNodeProps) => {
|
||||
const { form, selected } = useNodeRender();
|
||||
return (
|
||||
<WorkflowNodeRenderer
|
||||
style={{
|
||||
width: 280,
|
||||
minHeight: 88,
|
||||
height: 'auto',
|
||||
background: '#fff',
|
||||
border: '1px solid rgba(6, 7, 9, 0.15)',
|
||||
borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
padding: 12,
|
||||
cursor: 'move',
|
||||
}}
|
||||
node={props.node}
|
||||
>
|
||||
{form?.render()}
|
||||
</WorkflowNodeRenderer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
|
||||
import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
|
||||
import { FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
import { NodeRender } from './node-render';
|
||||
import { nodeRegistries } from './node-registries';
|
||||
import { initialData } from './initial-data';
|
||||
|
||||
export const useEditorProps = () =>
|
||||
useMemo<FreeLayoutProps>(
|
||||
() => ({
|
||||
plugins: () => [createMinimapPlugin({}), createFreeSnapPlugin({})],
|
||||
onAllLayersRendered: (ctx) => {
|
||||
ctx.tools.fitView(false);
|
||||
},
|
||||
materials: {
|
||||
renderDefaultNode: NodeRender,
|
||||
},
|
||||
nodeRegistries,
|
||||
canDeleteNode: () => true,
|
||||
canDeleteLine: () => true,
|
||||
initialData,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
import '@flowgram.ai/free-layout-editor/index.css';
|
||||
|
||||
import { FreeLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
import { useEditorProps } from './use-editor-props';
|
||||
|
||||
const FlowGramApp = () => {
|
||||
const editorProps = useEditorProps();
|
||||
return (
|
||||
<FreeLayoutEditorProvider {...editorProps}>
|
||||
<EditorRenderer />
|
||||
</FreeLayoutEditorProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlowGramApp;
|
||||
@@ -0,0 +1,82 @@
|
||||
import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
export const initialData: WorkflowJSON = {
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'start',
|
||||
meta: {
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
data: {
|
||||
title: 'Start Node',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: -200 },
|
||||
},
|
||||
data: {
|
||||
title: 'Custom Node A',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: 0 },
|
||||
},
|
||||
data: {
|
||||
title: 'Custom Node B',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: 200 },
|
||||
},
|
||||
data: {
|
||||
title: 'Custom Node C',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'end',
|
||||
meta: {
|
||||
position: { x: 800, y: 0 },
|
||||
},
|
||||
data: {
|
||||
title: 'End Node',
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '2',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '3',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '4',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '2',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '3',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '4',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
/**
|
||||
* You can customize your own node registry
|
||||
* 你可以自定义节点的注册器
|
||||
*/
|
||||
export const nodeRegistries: WorkflowNodeRegistry[] = [
|
||||
{
|
||||
type: 'start',
|
||||
meta: {
|
||||
isStart: true, // Mark as start
|
||||
deleteDisable: true, // The start node cannot be deleted
|
||||
copyDisable: true, // The start node cannot be copied
|
||||
defaultPorts: [{ type: 'output' }], // Used to define the input and output ports, the start node only has the output port
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'end',
|
||||
meta: {
|
||||
deleteDisable: true,
|
||||
copyDisable: true,
|
||||
defaultPorts: [{ type: 'input' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'custom',
|
||||
meta: {},
|
||||
defaultPorts: [{ type: 'output' }, { type: 'input' }], // A normal node has two ports
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,34 @@
|
||||
import '@flowgram.ai/free-layout-editor/index.css';
|
||||
|
||||
import {
|
||||
useNodeRender,
|
||||
WorkflowNodeProps,
|
||||
WorkflowNodeRenderer,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
export const NodeRender = (props: WorkflowNodeProps) => {
|
||||
const { form, selected } = useNodeRender();
|
||||
return (
|
||||
<WorkflowNodeRenderer
|
||||
style={{
|
||||
width: 280,
|
||||
minHeight: 88,
|
||||
height: 'auto',
|
||||
background: '#fff',
|
||||
border: '1px solid rgba(6, 7, 9, 0.15)',
|
||||
borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
padding: 12,
|
||||
cursor: 'move',
|
||||
}}
|
||||
node={props.node}
|
||||
>
|
||||
{form?.render()}
|
||||
</WorkflowNodeRenderer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
|
||||
import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
|
||||
import { Field, FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
import { NodeRender } from './node-render';
|
||||
import { nodeRegistries } from './node-registries';
|
||||
import { initialData } from './initial-data';
|
||||
|
||||
export const useEditorProps = () =>
|
||||
useMemo<FreeLayoutProps>(
|
||||
() => ({
|
||||
plugins: () => [createMinimapPlugin({}), createFreeSnapPlugin({})],
|
||||
onAllLayersRendered: (ctx) => {
|
||||
ctx.tools.fitView(false);
|
||||
},
|
||||
materials: {
|
||||
renderDefaultNode: NodeRender,
|
||||
},
|
||||
nodeRegistries,
|
||||
canDeleteNode: () => true,
|
||||
canDeleteLine: () => true,
|
||||
initialData,
|
||||
/**
|
||||
* Node engine enable, you can configure formMeta in the FlowNodeRegistry
|
||||
*/
|
||||
nodeEngine: {
|
||||
enable: true,
|
||||
},
|
||||
/**
|
||||
* Redo/Undo enable
|
||||
*/
|
||||
history: {
|
||||
enable: true,
|
||||
enableChangeNode: true, // Listen Node engine data change
|
||||
},
|
||||
getNodeDefaultRegistry(type) {
|
||||
return {
|
||||
type,
|
||||
meta: {
|
||||
defaultExpanded: true,
|
||||
},
|
||||
formMeta: {
|
||||
/**
|
||||
* Render form
|
||||
*/
|
||||
render: () => (
|
||||
<>
|
||||
<Field<string> name="title">{({ field }) => <div>{field.value}</div>}</Field>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
useService,
|
||||
WorkflowDocument,
|
||||
WorkflowNodeEntity,
|
||||
WorkflowSelectService,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
export const AddNode = () => {
|
||||
const workflowDocument = useService(WorkflowDocument);
|
||||
const selectService = useService(WorkflowSelectService);
|
||||
|
||||
return (
|
||||
<div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 8, display: 'flex', gap: 8 }}>
|
||||
<button
|
||||
style={{
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: '50%',
|
||||
cursor: 'pointer',
|
||||
padding: '4px',
|
||||
color: '#ffffff',
|
||||
background: '#7e72e8',
|
||||
width: 70,
|
||||
height: 70,
|
||||
fontSize: 14,
|
||||
}}
|
||||
onClick={() => {
|
||||
const node: WorkflowNodeEntity = workflowDocument.createWorkflowNodeByType(
|
||||
'custom',
|
||||
undefined, // position undefined means create node in center of canvas - position undefined 可以在画布中间创建节点
|
||||
{
|
||||
data: {
|
||||
title: 'New Node',
|
||||
},
|
||||
}
|
||||
);
|
||||
selectService.selectNode(node);
|
||||
}}
|
||||
>
|
||||
+ Node
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import '@flowgram.ai/free-layout-editor/index.css';
|
||||
|
||||
import { FreeLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
import { useEditorProps } from './use-editor-props';
|
||||
import { Tools } from './tools';
|
||||
import { Minimap } from './minimap';
|
||||
import { AddNode } from './add-node';
|
||||
|
||||
const FlowGramApp = () => {
|
||||
const editorProps = useEditorProps();
|
||||
return (
|
||||
<FreeLayoutEditorProvider {...editorProps}>
|
||||
<EditorRenderer />
|
||||
<Tools />
|
||||
<Minimap />
|
||||
<AddNode />
|
||||
</FreeLayoutEditorProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlowGramApp;
|
||||
@@ -0,0 +1,82 @@
|
||||
import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
export const initialData: WorkflowJSON = {
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'start',
|
||||
meta: {
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
data: {
|
||||
title: 'Start Node',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: -200 },
|
||||
},
|
||||
data: {
|
||||
title: 'Custom Node A',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: 0 },
|
||||
},
|
||||
data: {
|
||||
title: 'Custom Node B',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'custom',
|
||||
meta: {
|
||||
position: { x: 400, y: 200 },
|
||||
},
|
||||
data: {
|
||||
title: 'Custom Node C',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'end',
|
||||
meta: {
|
||||
position: { x: 800, y: 0 },
|
||||
},
|
||||
data: {
|
||||
title: 'End Node',
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '2',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '3',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
targetNodeID: '4',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '2',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '3',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
{
|
||||
sourceNodeID: '4',
|
||||
targetNodeID: '5',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { MinimapRender } from '@flowgram.ai/minimap-plugin';
|
||||
|
||||
export const Minimap = () => (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 16,
|
||||
bottom: 72,
|
||||
zIndex: 100,
|
||||
width: 118,
|
||||
}}
|
||||
>
|
||||
<MinimapRender
|
||||
containerStyles={{
|
||||
pointerEvents: 'auto',
|
||||
position: 'relative',
|
||||
top: 'unset',
|
||||
right: 'unset',
|
||||
bottom: 'unset',
|
||||
left: 'unset',
|
||||
}}
|
||||
inactiveStyle={{
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
translateX: 0,
|
||||
translateY: 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
/**
|
||||
* You can customize your own node registry
|
||||
* 你可以自定义节点的注册器
|
||||
*/
|
||||
export const nodeRegistries: WorkflowNodeRegistry[] = [
|
||||
{
|
||||
type: 'start',
|
||||
meta: {
|
||||
isStart: true, // Mark as start
|
||||
deleteDisable: true, // The start node cannot be deleted
|
||||
copyDisable: true, // The start node cannot be copied
|
||||
defaultPorts: [{ type: 'output' }], // Used to define the input and output ports, the start node only has the output port
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'end',
|
||||
meta: {
|
||||
deleteDisable: true,
|
||||
copyDisable: true,
|
||||
defaultPorts: [{ type: 'input' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'custom',
|
||||
meta: {},
|
||||
defaultPorts: [{ type: 'output' }, { type: 'input' }], // A normal node has two ports
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,34 @@
|
||||
import '@flowgram.ai/free-layout-editor/index.css';
|
||||
|
||||
import {
|
||||
useNodeRender,
|
||||
WorkflowNodeProps,
|
||||
WorkflowNodeRenderer,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
export const NodeRender = (props: WorkflowNodeProps) => {
|
||||
const { form, selected } = useNodeRender();
|
||||
return (
|
||||
<WorkflowNodeRenderer
|
||||
style={{
|
||||
width: 280,
|
||||
minHeight: 88,
|
||||
height: 'auto',
|
||||
background: '#fff',
|
||||
border: '1px solid rgba(6, 7, 9, 0.15)',
|
||||
borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
padding: 12,
|
||||
cursor: 'move',
|
||||
}}
|
||||
node={props.node}
|
||||
>
|
||||
{form?.render()}
|
||||
</WorkflowNodeRenderer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { CSSProperties, useEffect, useState } from 'react';
|
||||
|
||||
import { usePlaygroundTools, useClientContext, LineType } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
export const Tools = () => {
|
||||
const { history } = useClientContext();
|
||||
const tools = usePlaygroundTools();
|
||||
const [canUndo, setCanUndo] = useState(false);
|
||||
const [canRedo, setCanRedo] = useState(false);
|
||||
|
||||
const buttonStyle: CSSProperties = {
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
padding: '4px',
|
||||
color: '#141414',
|
||||
background: '#e1e3e4',
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = history.undoRedoService.onChange(() => {
|
||||
setCanUndo(history.canUndo());
|
||||
setCanRedo(history.canRedo());
|
||||
});
|
||||
return () => disposable.dispose();
|
||||
}, [history]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ position: 'absolute', zIndex: 10, bottom: 34, right: 16, display: 'flex', gap: 8 }}
|
||||
>
|
||||
<button style={buttonStyle} onClick={() => tools.zoomin()}>
|
||||
ZoomIn
|
||||
</button>
|
||||
<button style={buttonStyle} onClick={() => tools.zoomout()}>
|
||||
ZoomOut
|
||||
</button>
|
||||
<span
|
||||
style={{
|
||||
...buttonStyle,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'default',
|
||||
width: 40,
|
||||
}}
|
||||
>
|
||||
{Math.floor(tools.zoom * 100)}%
|
||||
</span>
|
||||
<button style={buttonStyle} onClick={() => tools.fitView()}>
|
||||
FitView
|
||||
</button>
|
||||
<button style={buttonStyle} onClick={() => tools.autoLayout()}>
|
||||
AutoLayout
|
||||
</button>
|
||||
<button
|
||||
style={buttonStyle}
|
||||
onClick={() =>
|
||||
tools.switchLineType(
|
||||
tools.lineType === LineType.BEZIER ? LineType.LINE_CHART : LineType.BEZIER
|
||||
)
|
||||
}
|
||||
>
|
||||
{tools.lineType === LineType.BEZIER ? 'Bezier' : 'Fold'}
|
||||
</button>
|
||||
<button
|
||||
style={{
|
||||
...buttonStyle,
|
||||
cursor: canUndo ? 'pointer' : 'not-allowed',
|
||||
color: canUndo ? '#141414' : '#b1b1b1',
|
||||
}}
|
||||
onClick={() => history.undo()}
|
||||
disabled={!canUndo}
|
||||
>
|
||||
Undo
|
||||
</button>
|
||||
<button
|
||||
style={{
|
||||
...buttonStyle,
|
||||
cursor: canRedo ? 'pointer' : 'not-allowed',
|
||||
color: canRedo ? '#141414' : '#b1b1b1',
|
||||
}}
|
||||
onClick={() => history.redo()}
|
||||
disabled={!canRedo}
|
||||
>
|
||||
Redo
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
|
||||
import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
|
||||
import { Field, FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
|
||||
|
||||
import { NodeRender } from './node-render';
|
||||
import { nodeRegistries } from './node-registries';
|
||||
import { initialData } from './initial-data';
|
||||
|
||||
export const useEditorProps = () =>
|
||||
useMemo<FreeLayoutProps>(
|
||||
() => ({
|
||||
plugins: () => [
|
||||
createMinimapPlugin({
|
||||
disableLayer: true,
|
||||
canvasStyle: {
|
||||
canvasWidth: 100,
|
||||
canvasHeight: 50,
|
||||
canvasPadding: 50,
|
||||
},
|
||||
}),
|
||||
createFreeSnapPlugin({}),
|
||||
],
|
||||
onAllLayersRendered: (ctx) => {
|
||||
ctx.tools.fitView(false);
|
||||
},
|
||||
materials: {
|
||||
renderDefaultNode: NodeRender,
|
||||
},
|
||||
nodeRegistries,
|
||||
canDeleteNode: () => true,
|
||||
canDeleteLine: () => true,
|
||||
initialData,
|
||||
/**
|
||||
* Node engine enable, you can configure formMeta in the FlowNodeRegistry
|
||||
*/
|
||||
nodeEngine: {
|
||||
enable: true,
|
||||
},
|
||||
/**
|
||||
* Redo/Undo enable
|
||||
*/
|
||||
history: {
|
||||
enable: true,
|
||||
enableChangeNode: true, // Listen Node engine data change
|
||||
},
|
||||
getNodeDefaultRegistry(type) {
|
||||
return {
|
||||
type,
|
||||
meta: {
|
||||
defaultExpanded: true,
|
||||
},
|
||||
formMeta: {
|
||||
/**
|
||||
* Render form
|
||||
*/
|
||||
render: () => (
|
||||
<>
|
||||
<Field<string> name="title">{({ field }) => <div>{field.value}</div>}</Field>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
Reference in New Issue
Block a user