chore: import upstream snapshot with attribution
CI / build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:09:51 +08:00
commit 0232b4e2bb
3528 changed files with 291404 additions and 0 deletions
@@ -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>
</>
),
},
};
},
}),
[]
);