docs: free-layout-simple docs error (#245)

* chore: simple-layout-demo add cross-env

* docs: free-layout-simple docs error
This commit is contained in:
xiamidaxia 2025-05-19 15:52:36 +08:00 committed by GitHub
parent 766fdc1597
commit ec6e5abe23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 416 additions and 257 deletions

View File

@ -19,10 +19,10 @@
"build:fast": "exit 0", "build:fast": "exit 0",
"build:watch": "exit 0", "build:watch": "exit 0",
"clean": "rimraf dist", "clean": "rimraf dist",
"dev": "MODE=app NODE_ENV=development rsbuild dev --open", "dev": "cross-env MODE=app NODE_ENV=development rsbuild dev --open",
"lint": "eslint ./src --cache", "lint": "eslint ./src --cache",
"lint:fix": "eslint ./src --fix", "lint:fix": "eslint ./src --fix",
"start": "NODE_ENV=development rsbuild dev --open", "start": "cross-env NODE_ENV=development rsbuild dev --open",
"test": "exit", "test": "exit",
"test:cov": "exit", "test:cov": "exit",
"watch": "exit 0" "watch": "exit 0"

View File

@ -19,10 +19,10 @@
"build:fast": "exit 0", "build:fast": "exit 0",
"build:watch": "exit 0", "build:watch": "exit 0",
"clean": "rimraf dist", "clean": "rimraf dist",
"dev": "MODE=app NODE_ENV=development rsbuild dev --open", "dev": "cross-env MODE=app NODE_ENV=development rsbuild dev --open",
"lint": "eslint ./src --cache", "lint": "eslint ./src --cache",
"lint:fix": "eslint ./src --fix", "lint:fix": "eslint ./src --fix",
"start": "NODE_ENV=development rsbuild dev --open", "start": "cross-env NODE_ENV=development rsbuild dev --open",
"test": "exit", "test": "exit",
"test:cov": "exit", "test:cov": "exit",
"watch": "exit 0" "watch": "exit 0"

View File

@ -20,13 +20,7 @@ const indexCode = {
export const FixedLayoutSimplePreview = () => ( export const FixedLayoutSimplePreview = () => (
<PreviewEditor <PreviewEditor
files={{ files={{
'App.js': `import React from 'react'; 'editor.tsx': indexCode,
import { Editor } from './index.tsx';
const App = () => {
return <Editor />
}
export default App;`,
'index.tsx': indexCode,
'index.css': indexCssCode, 'index.css': indexCssCode,
'initial-data.ts': initialDataCode, 'initial-data.ts': initialDataCode,
'node-registries.ts': nodeRegistriesCode, 'node-registries.ts': nodeRegistriesCode,

View File

@ -4,14 +4,14 @@ import { FreeLayoutSimple } from '.';
import nodeRegistriesCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/node-registries.ts'; import nodeRegistriesCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/node-registries.ts';
import dataCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/initial-data.ts'; import dataCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/initial-data.ts';
import useEditorPropsCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/hooks/use-editor-props.tsx'; import useEditorPropsCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/hooks/use-editor-props.tsx';
import indexCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/editor.tsx'; import editorCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/editor.tsx';
import toolsCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/components/tools.tsx'; import toolsCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/components/tools.tsx';
import nodeAddPanelCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/components/node-add-panel.tsx'; import nodeAddPanelCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/components/node-add-panel.tsx';
import minimapCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/components/minimap.tsx'; import minimapCode from '!!raw-loader!@flowgram.ai/demo-free-layout-simple/src/components/minimap.tsx';
export const FreeLayoutSimplePreview = () => { export const FreeLayoutSimplePreview = () => {
const files = { const files = {
'index.tsx': indexCode, 'editor.tsx': editorCode,
'use-editor-props.tsx': useEditorPropsCode, 'use-editor-props.tsx': useEditorPropsCode,
'initial-data.ts': dataCode, 'initial-data.ts': dataCode,
'node-registries.ts': nodeRegistriesCode, 'node-registries.ts': nodeRegistriesCode,

View File

@ -127,40 +127,33 @@ Next, we need to define the behavior and appearance of different types of nodes:
// src/node-registries.ts // src/node-registries.ts
import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor'; import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
export const nodeRegistries: Record<string, WorkflowNodeRegistry> = { /**
// Start node * You can customize your own node registry
start: { */
export const nodeRegistries: WorkflowNodeRegistry[] = [
{
type: 'start', type: 'start',
meta: { meta: {
defaultWidth: 200, isStart: true, // Mark as start
defaultHeight: 100, deleteDisable: true, // The start node cannot be deleted
canDelete: false, // Prohibit deletion copyDisable: true, // The start node cannot be copied
backgroundColor: '#E6F7FF', defaultPorts: [{ type: 'output' }], // Used to define the input and output ports, the start node only has the output port
defaultExpanded: true,
}, },
}, },
// Custom node {
custom: {
type: 'custom',
meta: {
defaultWidth: 200,
defaultHeight: 100,
backgroundColor: '#FFF7E6',
defaultExpanded: true,
},
},
// End node
end: {
type: 'end', type: 'end',
meta: { meta: {
defaultWidth: 200, deleteDisable: true,
defaultHeight: 100, copyDisable: true,
canDelete: false, // Prohibit deletion defaultPorts: [{ type: 'input' }],
backgroundColor: '#FFF1F0',
defaultExpanded: true,
}, },
}, },
}; {
type: 'custom',
meta: {},
defaultPorts: [{ type: 'output' }, { type: 'input' }], // A normal node has two ports
},
];
``` ```
#### Step 3: Create Editor Configuration #### Step 3: Create Editor Configuration
@ -260,14 +253,29 @@ export const useEditorProps = () =>
canvasStyle: { canvasStyle: {
canvasWidth: 182, canvasWidth: 182,
canvasHeight: 102, canvasHeight: 102,
canvasPadding: 50,
canvasBackground: 'rgba(245, 245, 245, 1)', canvasBackground: 'rgba(245, 245, 245, 1)',
canvasBorderRadius: 10,
viewportBackground: 'rgba(235, 235, 235, 1)',
viewportBorderRadius: 4,
viewportBorderColor: 'rgba(201, 201, 201, 1)',
viewportBorderWidth: 1,
viewportBorderDashLength: 2,
nodeColor: 'rgba(255, 255, 255, 1)',
nodeBorderRadius: 2,
nodeBorderWidth: 0.145,
nodeBorderColor: 'rgba(6, 7, 9, 0.10)',
overlayColor: 'rgba(255, 255, 255, 0)',
}, },
inactiveDebounceTime: 1,
}), }),
// Auto-alignment plugin // Auto-alignment plugin
createFreeSnapPlugin({ createFreeSnapPlugin({
edgeColor: '#00B2B2', edgeColor: '#00B2B2',
alignColor: '#00B2B2', alignColor: '#00B2B2',
edgeLineWidth: 1, edgeLineWidth: 1,
alignLineWidth: 1,
alignCrossWidth: 8,
}), }),
], ],
}), }),
@ -311,37 +319,76 @@ export const NodeAddPanel: React.FC = () => {
#### Step 5: Create Toolbar and Minimap #### Step 5: Create Toolbar and Minimap
```tsx ```tsx
// src/components/tools.tsx
import React from 'react'; import React from 'react';
import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor'; import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
export const Tools: React.FC = () => { export const Tools: React.FC = () => {
const { zoomIn, zoomOut, resetZoom, undo, redo } = usePlaygroundTools();
const { history } = useClientContext(); const { history } = useClientContext();
const tools = usePlaygroundTools();
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
useEffect(() => {
const disposable = history.undoRedoService.onChange(() => {
setCanUndo(history.canUndo());
setCanRedo(history.canRedo());
});
return () => disposable.dispose();
}, [history]);
return ( return (
<div className="demo-free-tools"> <div
<button onClick={zoomIn}>Zoom In</button> style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}
<button onClick={zoomOut}>Zoom Out</button> >
<button onClick={resetZoom}>Reset Zoom</button> <button onClick={() => tools.zoomin()}>ZoomIn</button>
<button onClick={undo} disabled={!history?.canUndo()}>Undo</button> <button onClick={() => tools.zoomout()}>ZoomOut</button>
<button onClick={redo} disabled={!history?.canRedo()}>Redo</button> <button onClick={() => tools.fitView()}>Fitview</button>
<button onClick={() => tools.autoLayout()}>AutoLayout</button>
<button onClick={() => history.undo()} disabled={!canUndo}>
Undo
</button>
<button onClick={() => history.redo()} disabled={!canRedo}>
Redo
</button>
<span>{Math.floor(tools.zoom * 100)}%</span>
</div> </div>
); );
}; };
// src/components/minimap.tsx // src/components/minimap.tsx
import { FlowMinimapService, MinimapRender } from '@flowgram.ai/minimap-plugin';
import { useService } from '@flowgram.ai/free-layout-editor'; import { useService } from '@flowgram.ai/free-layout-editor';
import { MinimapService } from '@flowgram.ai/minimap-plugin';
export const Minimap: React.FC = () => {
const minimapService = useService<MinimapService>(MinimapService);
export const Minimap = () => {
const minimapService = useService(FlowMinimapService);
return ( return (
<div <div
className="demo-free-minimap" style={{
ref={minimapService?.setContainer} position: 'absolute',
/> left: 226,
bottom: 51,
zIndex: 100,
width: 198,
}}
>
<MinimapRender
service={minimapService}
containerStyles={{
pointerEvents: 'auto',
position: 'relative',
top: 'unset',
right: 'unset',
bottom: 'unset',
left: 'unset',
}}
inactiveStyle={{
opacity: 1,
scale: 1,
translateX: 0,
translateY: 0,
}}
/>
</div>
); );
}; };
``` ```
@ -380,85 +427,123 @@ export const Editor = () => {
```tsx ```tsx
// src/app.tsx // src/app.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Editor } from './editor'; import { Editor } from './editor';
export function App() { ReactDOM.render(<Editor />, document.getElementById('root'))
return (
<div className="app">
<h1>Free Layout Editor Example</h1>
<Editor />
</div>
);
}
``` ```
#### Step 8: Add Styles #### Step 8: Add Styles
```css ```css
/* src/index.css */ /* src/index.css */
.demo-free-container {
position: relative;
width: 100%;
height: 600px;
border: 1px solid #eee;
}
.demo-free-layout {
display: flex;
height: 100%;
}
.demo-free-sidebar {
width: 200px;
padding: 16px;
border-right: 1px solid #eee;
overflow-y: auto;
}
.demo-free-card {
margin-bottom: 8px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: grab;
background: #fff;
}
.demo-free-editor {
flex: 1;
height: 100%;
}
.demo-free-tools {
position: absolute;
top: 16px;
right: 16px;
display: flex;
gap: 8px;
z-index: 10;
}
.demo-free-minimap {
position: absolute;
right: 16px;
bottom: 16px;
z-index: 10;
}
.demo-free-node { .demo-free-node {
background: #fff; display: flex;
border-radius: 4px; min-width: 300px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); min-height: 100px;
flex-direction: column;
align-items: flex-start;
box-sizing: border-box;
border-radius: 8px;
border: 1px solid var(--light-usage-border-color-border, rgba(28, 31, 35, 0.08));
background: #fff;
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
} }
.demo-free-node-title { .demo-free-node-title {
padding: 8px 12px; background-color: #93bfe2;
font-weight: bold; width: 100%;
border-bottom: 1px solid #eee; border-radius: 8px 8px 0 0;
padding: 4px 12px;
}
.demo-free-node-content {
padding: 4px 12px;
flex-grow: 1;
width: 100%;
}
.demo-free-node::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -1;
background-color: white;
border-radius: 7px;
} }
.demo-free-node-content { .demo-free-node:hover:before {
padding: 8px 12px; -webkit-filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
}
.demo-free-node.activated:before,
.demo-free-node.selected:before {
outline: 2px solid var(--light-usage-primary-color-primary, #4d53e8);
-webkit-filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
}
.demo-free-sidebar {
height: 100%;
overflow-y: auto;
padding: 12px 16px 0;
box-sizing: border-box;
background: #f7f7fa;
border-right: 1px solid rgba(29, 28, 35, 0.08);
}
.demo-free-right-top-panel {
position: fixed;
right: 10px;
top: 70px;
width: 300px;
z-index: 999;
}
.demo-free-card {
width: 140px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 6px 8px 0 rgba(28, 31, 35, 0.03);
cursor: -webkit-grab;
cursor: grab;
line-height: 16px;
margin-bottom: 12px;
overflow: hidden;
padding: 16px;
position: relative;
color: black;
}
.demo-free-layout {
display: flex;
flex-direction: row;
flex-grow: 1;
}
.demo-free-editor {
flex-grow: 1;
position: relative;
height: 100%;
}
.demo-free-container {
position: absolute;
left: 0;
top: 0;
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
} }
``` ```
@ -515,24 +600,19 @@ Use `nodeRegistries` to define the behavior and appearance of different types of
// Node registration // Node registration
import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor'; import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
export const nodeRegistries: Record<string, WorkflowNodeRegistry> = { export const nodeRegistries: WorkflowNodeRegistry[] = [
// Start node definition // Start node definition
start: { {
type: 'start', type: 'start',
meta: { meta: {
defaultWidth: 200, isStart: true, // Mark as start
defaultHeight: 100, deleteDisable: true, // The start node cannot be deleted
canDelete: false, // Prohibit deletion copyDisable: true, // The start node cannot be copied
backgroundColor: '#fff', defaultPorts: [{ type: 'output' }], // Used to define the input and output ports, the start node only has the output port
defaultExpanded: true, // Default expanded
}, },
formMeta: {
// Node form definition
render: () => <>Form content</>
}
}, },
// More node types... // More node types...
}; ];
``` ```
### 3. Editor Components ### 3. Editor Components
@ -549,7 +629,7 @@ const editorProps = {
background: true, // Enable background grid background: true, // Enable background grid
readonly: false, // Non-readonly mode, allow editing readonly: false, // Non-readonly mode, allow editing
initialData: {...}, // Initial data: definition of nodes and edges initialData: {...}, // Initial data: definition of nodes and edges
nodeRegistries: {...}, // Node type registration nodeRegistries: [...], // Node type registration
nodeEngine: { nodeEngine: {
enable: true, // Enable node form engine enable: true, // Enable node form engine
}, },
@ -581,10 +661,10 @@ const dragService = useService<WorkflowDragService>(WorkflowDragService);
dragService.startDragCard('nodeType', event, { data: {...} }); dragService.startDragCard('nodeType', event, { data: {...} });
// Get editor context // Get editor context
const { document, services } = useClientContext(); const { document, playground } = useClientContext();
// Manipulate canvas // Manipulate canvas
document.fitView(); // Fit view document.fitView(); // Fit view
document.zoomTo(1.5); // Zoom canvas playground.config.zoomin(); // Zoom canvas
document.fromJSON(newData); // Update data document.fromJSON(newData); // Update data
``` ```

View File

@ -127,40 +127,33 @@ export const initialData: WorkflowJSON = {
// src/node-registries.ts // src/node-registries.ts
import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor'; import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
export const nodeRegistries: Record<string, WorkflowNodeRegistry> = { /**
// 开始节点 * 你可以自定义节点的注册器
start: { */
export const nodeRegistries: WorkflowNodeRegistry[] = [
{
type: 'start', type: 'start',
meta: { meta: {
defaultWidth: 200, isStart: true, // 开始节点标记
defaultHeight: 100, deleteDisable: true, // 开始节点不能被删除
canDelete: false, // 禁止删除 copyDisable: true, // 开始节点不能被 copy
backgroundColor: '#E6F7FF', defaultPorts: [{ type: 'output' }], // 定义 input 和 output 端口,开始节点只有 output 端口
defaultExpanded: true,
}, },
}, },
// 自定义节点 {
custom: {
type: 'custom',
meta: {
defaultWidth: 200,
defaultHeight: 100,
backgroundColor: '#FFF7E6',
defaultExpanded: true,
},
},
// 结束节点
end: {
type: 'end', type: 'end',
meta: { meta: {
defaultWidth: 200, deleteDisable: true,
defaultHeight: 100, copyDisable: true,
canDelete: false, // 禁止删除 defaultPorts: [{ type: 'input' }], // 结束节点只有 input 端口
backgroundColor: '#FFF1F0',
defaultExpanded: true,
}, },
}, },
}; {
type: 'custom',
meta: {},
defaultPorts: [{ type: 'output' }, { type: 'input' }], // 普通节点有两个端口
},
];
``` ```
#### 步骤三:创建编辑器配置 #### 步骤三:创建编辑器配置
@ -260,14 +253,29 @@ export const useEditorProps = () =>
canvasStyle: { canvasStyle: {
canvasWidth: 182, canvasWidth: 182,
canvasHeight: 102, canvasHeight: 102,
canvasPadding: 50,
canvasBackground: 'rgba(245, 245, 245, 1)', canvasBackground: 'rgba(245, 245, 245, 1)',
canvasBorderRadius: 10,
viewportBackground: 'rgba(235, 235, 235, 1)',
viewportBorderRadius: 4,
viewportBorderColor: 'rgba(201, 201, 201, 1)',
viewportBorderWidth: 1,
viewportBorderDashLength: 2,
nodeColor: 'rgba(255, 255, 255, 1)',
nodeBorderRadius: 2,
nodeBorderWidth: 0.145,
nodeBorderColor: 'rgba(6, 7, 9, 0.10)',
overlayColor: 'rgba(255, 255, 255, 0)',
}, },
inactiveDebounceTime: 1,
}), }),
// 自动对齐插件 // 自动对齐插件
createFreeSnapPlugin({ createFreeSnapPlugin({
edgeColor: '#00B2B2', edgeColor: '#00B2B2',
alignColor: '#00B2B2', alignColor: '#00B2B2',
edgeLineWidth: 1, edgeLineWidth: 1,
alignLineWidth: 1,
alignCrossWidth: 8,
}), }),
], ],
}), }),
@ -293,7 +301,7 @@ export const NodeAddPanel: React.FC = () => {
<div <div
key={nodeType} key={nodeType}
className="demo-free-card" className="demo-free-card"
onMouseDown={e => dragService.startDragCard('custom', e, { onMouseDown={e => dragService.startDragCard(nodeType, e, {
data: { data: {
title: nodeType, title: nodeType,
content: '拖拽创建的节点' content: '拖拽创建的节点'
@ -313,35 +321,76 @@ export const NodeAddPanel: React.FC = () => {
```tsx ```tsx
// src/components/tools.tsx // src/components/tools.tsx
import React from 'react'; import React from 'react';
import { useEffect, useState } from 'react';
import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor'; import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
export const Tools: React.FC = () => { export const Tools: React.FC = () => {
const { zoomIn, zoomOut, resetZoom, undo, redo } = usePlaygroundTools();
const { history } = useClientContext(); const { history } = useClientContext();
const tools = usePlaygroundTools();
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
useEffect(() => {
const disposable = history.undoRedoService.onChange(() => {
setCanUndo(history.canUndo());
setCanRedo(history.canRedo());
});
return () => disposable.dispose();
}, [history]);
return ( return (
<div className="demo-free-tools"> <div
<button onClick={zoomIn}>放大</button> style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}
<button onClick={zoomOut}>缩小</button> >
<button onClick={resetZoom}>重置缩放</button> <button onClick={() => tools.zoomin()}>ZoomIn</button>
<button onClick={undo} disabled={!history?.canUndo()}>撤销</button> <button onClick={() => tools.zoomout()}>ZoomOut</button>
<button onClick={redo} disabled={!history?.canRedo()}>重做</button> <button onClick={() => tools.fitView()}>Fitview</button>
<button onClick={() => tools.autoLayout()}>AutoLayout</button>
<button onClick={() => history.undo()} disabled={!canUndo}>
Undo
</button>
<button onClick={() => history.redo()} disabled={!canRedo}>
Redo
</button>
<span>{Math.floor(tools.zoom * 100)}%</span>
</div> </div>
); );
}; };
// src/components/minimap.tsx // src/components/minimap.tsx
import { FlowMinimapService, MinimapRender } from '@flowgram.ai/minimap-plugin';
import { useService } from '@flowgram.ai/free-layout-editor'; import { useService } from '@flowgram.ai/free-layout-editor';
import { MinimapService } from '@flowgram.ai/minimap-plugin';
export const Minimap: React.FC = () => {
const minimapService = useService<MinimapService>(MinimapService);
export const Minimap = () => {
const minimapService = useService(FlowMinimapService);
return ( return (
<div <div
className="demo-free-minimap" style={{
ref={minimapService?.setContainer} position: 'absolute',
/> left: 226,
bottom: 51,
zIndex: 100,
width: 198,
}}
>
<MinimapRender
service={minimapService}
containerStyles={{
pointerEvents: 'auto',
position: 'relative',
top: 'unset',
right: 'unset',
bottom: 'unset',
left: 'unset',
}}
inactiveStyle={{
opacity: 1,
scale: 1,
translateX: 0,
translateY: 0,
}}
/>
</div>
); );
}; };
``` ```
@ -380,86 +429,125 @@ export const Editor = () => {
```tsx ```tsx
// src/app.tsx // src/app.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Editor } from './editor'; import { Editor } from './editor';
export function App() { ReactDOM.render(<Editor />, document.getElementById('root'))
return (
<div className="app">
<h1>自由布局编辑器示例</h1>
<Editor />
</div>
);
}
``` ```
#### 步骤八:添加样式 #### 步骤八:添加样式
```css ```css
/* src/index.css */ /* src/index.css */
.demo-free-container {
position: relative;
width: 100%;
height: 600px;
border: 1px solid #eee;
}
.demo-free-layout {
display: flex;
height: 100%;
}
.demo-free-sidebar {
width: 200px;
padding: 16px;
border-right: 1px solid #eee;
overflow-y: auto;
}
.demo-free-card {
margin-bottom: 8px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: grab;
background: #fff;
}
.demo-free-editor {
flex: 1;
height: 100%;
}
.demo-free-tools {
position: absolute;
top: 16px;
right: 16px;
display: flex;
gap: 8px;
z-index: 10;
}
.demo-free-minimap {
position: absolute;
right: 16px;
bottom: 16px;
z-index: 10;
}
.demo-free-node { .demo-free-node {
background: #fff; display: flex;
border-radius: 4px; min-width: 300px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); min-height: 100px;
flex-direction: column;
align-items: flex-start;
box-sizing: border-box;
border-radius: 8px;
border: 1px solid var(--light-usage-border-color-border, rgba(28, 31, 35, 0.08));
background: #fff;
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
} }
.demo-free-node-title { .demo-free-node-title {
padding: 8px 12px; background-color: #93bfe2;
font-weight: bold; width: 100%;
border-bottom: 1px solid #eee; border-radius: 8px 8px 0 0;
padding: 4px 12px;
}
.demo-free-node-content {
padding: 4px 12px;
flex-grow: 1;
width: 100%;
}
.demo-free-node::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -1;
background-color: white;
border-radius: 7px;
} }
.demo-free-node-content { .demo-free-node:hover:before {
padding: 8px 12px; -webkit-filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
} }
.demo-free-node.activated:before,
.demo-free-node.selected:before {
outline: 2px solid var(--light-usage-primary-color-primary, #4d53e8);
-webkit-filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
}
.demo-free-sidebar {
height: 100%;
overflow-y: auto;
padding: 12px 16px 0;
box-sizing: border-box;
background: #f7f7fa;
border-right: 1px solid rgba(29, 28, 35, 0.08);
}
.demo-free-right-top-panel {
position: fixed;
right: 10px;
top: 70px;
width: 300px;
z-index: 999;
}
.demo-free-card {
width: 140px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 6px 8px 0 rgba(28, 31, 35, 0.03);
cursor: -webkit-grab;
cursor: grab;
line-height: 16px;
margin-bottom: 12px;
overflow: hidden;
padding: 16px;
position: relative;
color: black;
}
.demo-free-layout {
display: flex;
flex-direction: row;
flex-grow: 1;
}
.demo-free-editor {
flex-grow: 1;
position: relative;
height: 100%;
}
.demo-free-container {
position: absolute;
left: 0;
top: 0;
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
}
``` ```
### 4. 运行项目 ### 4. 运行项目
@ -515,24 +603,19 @@ const initialData: WorkflowJSON = {
// 节点注册 // 节点注册
import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor'; import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
export const nodeRegistries: Record<string, WorkflowNodeRegistry> = { export const nodeRegistries: WorkflowNodeRegistry[] = [
// 开始节点定义 // 开始节点定义
start: { {
type: 'start', type: 'start',
meta: { meta: {
defaultWidth: 200, isStart: true, // Mark as start
defaultHeight: 100, deleteDisable: true, // The start node cannot be deleted
canDelete: false, // 禁止删除 copyDisable: true, // The start node cannot be copied
backgroundColor: '#fff', defaultPorts: [{ type: 'output' }], // Used to define the input and output ports, the start node only has the output port
defaultExpanded: true, // 默认展开
}, },
formMeta: {
// 节点表单定义
render: () => <>表单内容</>
}
}, },
// 更多节点类型... // 更多节点类型...
}; ];
``` ```
### 3. 编辑器组件 ### 3. 编辑器组件
@ -549,7 +632,7 @@ const editorProps = {
background: true, // 启用背景网格 background: true, // 启用背景网格
readonly: false, // 非只读模式,允许编辑 readonly: false, // 非只读模式,允许编辑
initialData: {...}, // 初始化数据:节点和边的定义 initialData: {...}, // 初始化数据:节点和边的定义
nodeRegistries: {...}, // 节点类型注册 nodeRegistries: [...], // 节点类型注册
nodeEngine: { nodeEngine: {
enable: true, // 启用节点表单引擎 enable: true, // 启用节点表单引擎
}, },
@ -581,10 +664,10 @@ const dragService = useService<WorkflowDragService>(WorkflowDragService);
dragService.startDragCard('nodeType', event, { data: {...} }); dragService.startDragCard('nodeType', event, { data: {...} });
// 获取编辑器上下文 // 获取编辑器上下文
const { document, services } = useClientContext(); const { document, playground } = useClientContext();
// 操作画布 // 操作画布
document.fitView(); // 适应视图 document.fitView(); // 适应视图
document.zoomTo(1.5); // 缩放画布 playground.config.zoomin(); // 缩放画布
document.fromJSON(newData); // 更新数据 document.fromJSON(newData); // 更新数据
``` ```

View File

@ -8,6 +8,7 @@
"build": "tsc -b --force", "build": "tsc -b --force",
"dev": "npm run build -- -w", "dev": "npm run build -- -w",
"lint": "eslint ./src --cache", "lint": "eslint ./src --cache",
"watch": "exit",
"test": "exit", "test": "exit",
"test:cov": "exit" "test:cov": "exit"
}, },

View File

@ -9,6 +9,7 @@
"build": "exit", "build": "exit",
"test": "exit", "test": "exit",
"lint": "exit", "lint": "exit",
"watch": "exit",
"test:cov": "exit 0" "test:cov": "exit 0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -28,7 +28,7 @@
"build:watch": "npm run build:fast -- --dts-resolve", "build:watch": "npm run build:fast -- --dts-resolve",
"clean": "rimraf dist", "clean": "rimraf dist",
"test": "vitest run", "test": "vitest run",
"test:cov": "exit 0", "test:cov": "vitest run --coverage",
"ts-check": "tsc --noEmit", "ts-check": "tsc --noEmit",
"watch": "npm run build:fast -- --dts-resolve --watch --ignore-watch dist" "watch": "npm run build:fast -- --dts-resolve --watch --ignore-watch dist"
}, },

View File

@ -33,7 +33,7 @@ export function fromNodeJSON(
return; return;
} }
return WorkflowDocumentOptionsDefault.fromNodeJSON?.(node, json, isFirstCreate); return WorkflowDocumentOptionsDefault.fromNodeJSON!(node, json, isFirstCreate);
} }
export function toNodeJSON(opts: FreeLayoutProps, node: FlowNodeEntity): FlowNodeJSON { export function toNodeJSON(opts: FreeLayoutProps, node: FlowNodeEntity): FlowNodeJSON {