Kaynağa Gözat

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

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

* docs: free-layout-simple docs error
xiamidaxia 7 ay önce
ebeveyn
işleme
ec6e5abe23

+ 2 - 2
apps/demo-free-layout-simple/package.json

@@ -19,10 +19,10 @@
     "build:fast": "exit 0",
     "build:watch": "exit 0",
     "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:fix": "eslint ./src --fix",
-    "start": "NODE_ENV=development rsbuild dev --open",
+    "start": "cross-env NODE_ENV=development rsbuild dev --open",
     "test": "exit",
     "test:cov": "exit",
     "watch": "exit 0"

+ 2 - 2
apps/demo-react-16/package.json

@@ -19,10 +19,10 @@
     "build:fast": "exit 0",
     "build:watch": "exit 0",
     "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:fix": "eslint ./src --fix",
-    "start": "NODE_ENV=development rsbuild dev --open",
+    "start": "cross-env NODE_ENV=development rsbuild dev --open",
     "test": "exit",
     "test:cov": "exit",
     "watch": "exit 0"

+ 1 - 7
apps/docs/components/fixed-layout-simple/preview.tsx

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

+ 2 - 2
apps/docs/components/free-layout-simple/preview.tsx

@@ -4,14 +4,14 @@ import { FreeLayoutSimple } from '.';
 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 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 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';
 
 export const FreeLayoutSimplePreview = () => {
   const files = {
-    'index.tsx': indexCode,
+    'editor.tsx': editorCode,
     'use-editor-props.tsx': useEditorPropsCode,
     'initial-data.ts': dataCode,
     'node-registries.ts': nodeRegistriesCode,

+ 189 - 109
apps/docs/src/en/examples/free-layout/free-layout-simple.mdx

@@ -127,40 +127,33 @@ Next, we need to define the behavior and appearance of different types of nodes:
 // src/node-registries.ts
 import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
 
-export const nodeRegistries: Record<string, WorkflowNodeRegistry> = {
-  // Start node
-  start: {
+/**
+ * You can customize your own node registry
+ */
+export const nodeRegistries: WorkflowNodeRegistry[] = [
+  {
     type: 'start',
     meta: {
-      defaultWidth: 200,
-      defaultHeight: 100,
-      canDelete: false,  // Prohibit deletion
-      backgroundColor: '#E6F7FF',
-      defaultExpanded: true,
+      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
     },
   },
-  // Custom node
-  custom: {
-    type: 'custom',
-    meta: {
-      defaultWidth: 200,
-      defaultHeight: 100,
-      backgroundColor: '#FFF7E6',
-      defaultExpanded: true,
-    },
-  },
-  // End node
-  end: {
+  {
     type: 'end',
     meta: {
-      defaultWidth: 200,
-      defaultHeight: 100,
-      canDelete: false,  // Prohibit deletion
-      backgroundColor: '#FFF1F0',
-      defaultExpanded: true,
+      deleteDisable: true,
+      copyDisable: true,
+      defaultPorts: [{ type: 'input' }],
     },
   },
-};
+  {
+    type: 'custom',
+    meta: {},
+    defaultPorts: [{ type: 'output' }, { type: 'input' }], // A normal node has two ports
+  },
+];
 ```
 
 #### Step 3: Create Editor Configuration
@@ -260,14 +253,29 @@ export const useEditorProps = () =>
           canvasStyle: {
             canvasWidth: 182,
             canvasHeight: 102,
+            canvasPadding: 50,
             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
         createFreeSnapPlugin({
           edgeColor: '#00B2B2',
           alignColor: '#00B2B2',
           edgeLineWidth: 1,
+          alignLineWidth: 1,
+          alignCrossWidth: 8,
         }),
       ],
     }),
@@ -311,37 +319,76 @@ export const NodeAddPanel: React.FC = () => {
 #### Step 5: Create Toolbar and Minimap
 
 ```tsx
-// src/components/tools.tsx
 import React from 'react';
 import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
 
 export const Tools: React.FC = () => {
-  const { zoomIn, zoomOut, resetZoom, undo, redo } = usePlaygroundTools();
   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 (
-    <div className="demo-free-tools">
-      <button onClick={zoomIn}>Zoom In</button>
-      <button onClick={zoomOut}>Zoom Out</button>
-      <button onClick={resetZoom}>Reset Zoom</button>
-      <button onClick={undo} disabled={!history?.canUndo()}>Undo</button>
-      <button onClick={redo} disabled={!history?.canRedo()}>Redo</button>
+    <div
+      style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}
+    >
+      <button onClick={() => tools.zoomin()}>ZoomIn</button>
+      <button onClick={() => tools.zoomout()}>ZoomOut</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>
   );
 };
 
 // src/components/minimap.tsx
+import { FlowMinimapService, MinimapRender } from '@flowgram.ai/minimap-plugin';
 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 (
     <div
-      className="demo-free-minimap"
-      ref={minimapService?.setContainer}
-    />
+      style={{
+        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
 // src/app.tsx
+import React from 'react';
+import ReactDOM from 'react-dom';
+
 import { Editor } from './editor';
 
-export function App() {
-  return (
-    <div className="app">
-      <h1>Free Layout Editor Example</h1>
-      <Editor />
-    </div>
-  );
-}
+ReactDOM.render(<Editor />, document.getElementById('root'))
 ```
 
 #### Step 8: Add Styles
 
 ```css
 /* src/index.css */
-.demo-free-container {
-  position: relative;
-  width: 100%;
-  height: 600px;
-  border: 1px solid #eee;
+.demo-free-node {
+    display: flex;
+    min-width: 300px;
+    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-layout {
-  display: flex;
-  height: 100%;
+.demo-free-node-title {
+    background-color: #93bfe2;
+    width: 100%;
+    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-sidebar {
-  width: 200px;
-  padding: 16px;
-  border-right: 1px solid #eee;
-  overflow-y: auto;
+.demo-free-node:hover:before {
+    -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-card {
-  margin-bottom: 8px;
-  padding: 8px 12px;
-  border: 1px solid #ddd;
-  border-radius: 4px;
-  cursor: grab;
-  background: #fff;
+.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-editor {
-  flex: 1;
-  height: 100%;
+.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-tools {
-  position: absolute;
-  top: 16px;
-  right: 16px;
-  display: flex;
-  gap: 8px;
-  z-index: 10;
+.demo-free-right-top-panel {
+    position: fixed;
+    right: 10px;
+    top: 70px;
+    width: 300px;
+    z-index: 999;
 }
 
-.demo-free-minimap {
-  position: absolute;
-  right: 16px;
-  bottom: 16px;
-  z-index: 10;
+.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-node {
-  background: #fff;
-  border-radius: 4px;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+.demo-free-layout {
+    display: flex;
+    flex-direction: row;
+    flex-grow: 1;
 }
 
-.demo-free-node-title {
-  padding: 8px 12px;
-  font-weight: bold;
-  border-bottom: 1px solid #eee;
+.demo-free-editor {
+    flex-grow: 1;
+    position: relative;
+    height: 100%;
 }
 
-.demo-free-node-content {
-  padding: 8px 12px;
+.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
 import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
 
-export const nodeRegistries: Record<string, WorkflowNodeRegistry> = {
+export const nodeRegistries: WorkflowNodeRegistry[] = [
   // Start node definition
-  start: {
+  {
     type: 'start',
     meta: {
-      defaultWidth: 200,
-      defaultHeight: 100,
-      canDelete: false,      // Prohibit deletion
-      backgroundColor: '#fff',
-      defaultExpanded: true, // Default expanded
+      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
     },
-    formMeta: {
-      // Node form definition
-      render: () => <>Form content</>
-    }
   },
   // More node types...
-};
+];
 ```
 
 ### 3. Editor Components
@@ -549,7 +629,7 @@ const editorProps = {
   background: true,       // Enable background grid
   readonly: false,        // Non-readonly mode, allow editing
   initialData: {...},     // Initial data: definition of nodes and edges
-  nodeRegistries: {...},  // Node type registration
+  nodeRegistries: [...],  // Node type registration
   nodeEngine: {
     enable: true,         // Enable node form engine
   },
@@ -581,10 +661,10 @@ const dragService = useService<WorkflowDragService>(WorkflowDragService);
 dragService.startDragCard('nodeType', event, { data: {...} });
 
 // Get editor context
-const { document, services } = useClientContext();
+const { document, playground } = useClientContext();
 // Manipulate canvas
 document.fitView();                 // Fit view
-document.zoomTo(1.5);               // Zoom canvas
+playground.config.zoomin();               // Zoom canvas
 document.fromJSON(newData);         // Update data
 ```
 

+ 192 - 109
apps/docs/src/zh/examples/free-layout/free-layout-simple.mdx

@@ -127,40 +127,33 @@ export const initialData: WorkflowJSON = {
 // src/node-registries.ts
 import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
 
-export const nodeRegistries: Record<string, WorkflowNodeRegistry> = {
-  // 开始节点
-  start: {
+/**
+ * 你可以自定义节点的注册器
+ */
+export const nodeRegistries: WorkflowNodeRegistry[] = [
+  {
     type: 'start',
     meta: {
-      defaultWidth: 200,
-      defaultHeight: 100,
-      canDelete: false,  // 禁止删除
-      backgroundColor: '#E6F7FF',
-      defaultExpanded: true,
+      isStart: true, // 开始节点标记
+      deleteDisable: true, // 开始节点不能被删除
+      copyDisable: true, // 开始节点不能被 copy
+      defaultPorts: [{ type: 'output' }], // 定义 input 和 output 端口,开始节点只有 output 端口
     },
   },
-  // 自定义节点
-  custom: {
-    type: 'custom',
-    meta: {
-      defaultWidth: 200,
-      defaultHeight: 100,
-      backgroundColor: '#FFF7E6',
-      defaultExpanded: true,
-    },
-  },
-  // 结束节点
-  end: {
+  {
     type: 'end',
     meta: {
-      defaultWidth: 200,
-      defaultHeight: 100,
-      canDelete: false,  // 禁止删除
-      backgroundColor: '#FFF1F0',
-      defaultExpanded: true,
+      deleteDisable: true,
+      copyDisable: true,
+      defaultPorts: [{ type: 'input' }], // 结束节点只有 input 端口
     },
   },
-};
+  {
+    type: 'custom',
+    meta: {},
+    defaultPorts: [{ type: 'output' }, { type: 'input' }], // 普通节点有两个端口
+  },
+];
 ```
 
 #### 步骤三:创建编辑器配置
@@ -260,14 +253,29 @@ export const useEditorProps = () =>
           canvasStyle: {
             canvasWidth: 182,
             canvasHeight: 102,
+            canvasPadding: 50,
             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({
           edgeColor: '#00B2B2',
           alignColor: '#00B2B2',
           edgeLineWidth: 1,
+          alignLineWidth: 1,
+          alignCrossWidth: 8,
         }),
       ],
     }),
@@ -293,7 +301,7 @@ export const NodeAddPanel: React.FC = () => {
         <div
           key={nodeType}
           className="demo-free-card"
-          onMouseDown={e => dragService.startDragCard('custom', e, {
+          onMouseDown={e => dragService.startDragCard(nodeType, e, {
             data: {
               title: nodeType,
               content: '拖拽创建的节点'
@@ -313,35 +321,76 @@ export const NodeAddPanel: React.FC = () => {
 ```tsx
 // src/components/tools.tsx
 import React from 'react';
+import { useEffect, useState } from 'react';
 import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
 
 export const Tools: React.FC = () => {
-  const { zoomIn, zoomOut, resetZoom, undo, redo } = usePlaygroundTools();
   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 (
-    <div className="demo-free-tools">
-      <button onClick={zoomIn}>放大</button>
-      <button onClick={zoomOut}>缩小</button>
-      <button onClick={resetZoom}>重置缩放</button>
-      <button onClick={undo} disabled={!history?.canUndo()}>撤销</button>
-      <button onClick={redo} disabled={!history?.canRedo()}>重做</button>
+    <div
+      style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}
+    >
+      <button onClick={() => tools.zoomin()}>ZoomIn</button>
+      <button onClick={() => tools.zoomout()}>ZoomOut</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>
   );
 };
 
 // src/components/minimap.tsx
+import { FlowMinimapService, MinimapRender } from '@flowgram.ai/minimap-plugin';
 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 (
     <div
-      className="demo-free-minimap"
-      ref={minimapService?.setContainer}
-    />
+      style={{
+        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
 // src/app.tsx
+import React from 'react';
+import ReactDOM from 'react-dom';
+
 import { Editor } from './editor';
 
-export function App() {
-  return (
-    <div className="app">
-      <h1>自由布局编辑器示例</h1>
-      <Editor />
-    </div>
-  );
-}
+ReactDOM.render(<Editor />, document.getElementById('root'))
 ```
 
 #### 步骤八:添加样式
 
 ```css
 /* src/index.css */
-.demo-free-container {
-  position: relative;
-  width: 100%;
-  height: 600px;
-  border: 1px solid #eee;
+.demo-free-node {
+    display: flex;
+    min-width: 300px;
+    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-layout {
-  display: flex;
-  height: 100%;
+.demo-free-node-title {
+    background-color: #93bfe2;
+    width: 100%;
+    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-sidebar {
-  width: 200px;
-  padding: 16px;
-  border-right: 1px solid #eee;
-  overflow-y: auto;
+.demo-free-node:hover:before {
+    -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-card {
-  margin-bottom: 8px;
-  padding: 8px 12px;
-  border: 1px solid #ddd;
-  border-radius: 4px;
-  cursor: grab;
-  background: #fff;
+.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-editor {
-  flex: 1;
-  height: 100%;
+.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-tools {
-  position: absolute;
-  top: 16px;
-  right: 16px;
-  display: flex;
-  gap: 8px;
-  z-index: 10;
+.demo-free-right-top-panel {
+    position: fixed;
+    right: 10px;
+    top: 70px;
+    width: 300px;
+    z-index: 999;
 }
 
-.demo-free-minimap {
-  position: absolute;
-  right: 16px;
-  bottom: 16px;
-  z-index: 10;
+.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-node {
-  background: #fff;
-  border-radius: 4px;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+.demo-free-layout {
+    display: flex;
+    flex-direction: row;
+    flex-grow: 1;
 }
 
-.demo-free-node-title {
-  padding: 8px 12px;
-  font-weight: bold;
-  border-bottom: 1px solid #eee;
+.demo-free-editor {
+    flex-grow: 1;
+    position: relative;
+    height: 100%;
 }
 
-.demo-free-node-content {
-  padding: 8px 12px;
+.demo-free-container {
+    position: absolute;
+    left: 0;
+    top: 0;
+    display: flex;
+    width: 100%;
+    height: 100%;
+    flex-direction: column;
 }
+
 ```
 
 ### 4. 运行项目
@@ -515,24 +603,19 @@ const initialData: WorkflowJSON = {
 // 节点注册
 import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
 
-export const nodeRegistries: Record<string, WorkflowNodeRegistry> = {
+export const nodeRegistries: WorkflowNodeRegistry[] = [
   // 开始节点定义
-  start: {
+  {
     type: 'start',
     meta: {
-      defaultWidth: 200,
-      defaultHeight: 100,
-      canDelete: false,      // 禁止删除
-      backgroundColor: '#fff',
-      defaultExpanded: true, // 默认展开
+      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
     },
-    formMeta: {
-      // 节点表单定义
-      render: () => <>表单内容</>
-    }
   },
   // 更多节点类型...
-};
+];
 ```
 
 ### 3. 编辑器组件
@@ -549,7 +632,7 @@ const editorProps = {
   background: true,       // 启用背景网格
   readonly: false,        // 非只读模式,允许编辑
   initialData: {...},     // 初始化数据:节点和边的定义
-  nodeRegistries: {...},  // 节点类型注册
+  nodeRegistries: [...],  // 节点类型注册
   nodeEngine: {
     enable: true,         // 启用节点表单引擎
   },
@@ -581,10 +664,10 @@ const dragService = useService<WorkflowDragService>(WorkflowDragService);
 dragService.startDragCard('nodeType', event, { data: {...} });
 
 // 获取编辑器上下文
-const { document, services } = useClientContext();
+const { document, playground } = useClientContext();
 // 操作画布
 document.fitView();                 // 适应视图
-document.zoomTo(1.5);               // 缩放画布
+playground.config.zoomin();               // 缩放画布
 document.fromJSON(newData);         // 更新数据
 ```
 

+ 1 - 0
config/eslint-config/package.json

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

+ 1 - 0
config/ts-config/package.json

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

+ 1 - 1
packages/client/free-layout-editor/package.json

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

+ 1 - 1
packages/client/free-layout-editor/src/preset/node-serialize.ts

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