Kaynağa Gözat

Merge pull request #996 from JxJuly/feat/panel-support-docked

feat: support docked panel
July 1 ay önce
ebeveyn
işleme
926e52bc3e
21 değiştirilmiş dosya ile 176 ekleme ve 80 silme
  1. 1 1
      apps/demo-free-layout/src/components/node-panel/index.tsx
  2. 4 1
      apps/demo-free-layout/src/editor.tsx
  3. 1 1
      apps/demo-free-layout/src/plugins/panel-manager-plugin/hooks.ts
  4. 3 0
      apps/demo-nextjs-antd/.eslintrc.js
  5. 9 9
      apps/demo-nextjs-antd/src/editor/components/line-add-button/index.tsx
  6. 4 3
      apps/demo-nextjs-antd/src/editor/form-components/form-item/index.tsx
  7. 6 2
      apps/demo-nextjs-antd/src/editor/nodes/default-form-meta.tsx
  8. 5 1
      apps/demo-nextjs-antd/src/editor/nodes/start/form-meta.tsx
  9. 1 0
      apps/docs/components/materials.tsx
  10. 1 0
      common/config/rush/command-line.json
  11. 43 40
      packages/plugins/panel-manager-plugin/src/components/panel-layer/css.ts
  12. 12 0
      packages/plugins/panel-manager-plugin/src/components/panel-layer/docked-panel-layer.tsx
  13. 11 6
      packages/plugins/panel-manager-plugin/src/components/panel-layer/float-panel.tsx
  14. 1 0
      packages/plugins/panel-manager-plugin/src/components/panel-layer/index.ts
  15. 24 11
      packages/plugins/panel-manager-plugin/src/components/panel-layer/panel-layer.tsx
  16. 6 3
      packages/plugins/panel-manager-plugin/src/components/resize-bar/index.tsx
  17. 1 0
      packages/plugins/panel-manager-plugin/src/index.ts
  18. 17 0
      packages/plugins/panel-manager-plugin/src/services/panel-config.ts
  19. 24 1
      packages/plugins/panel-manager-plugin/src/services/panel-manager.ts
  20. 1 1
      packages/plugins/panel-manager-plugin/src/types.ts
  21. 1 0
      packages/plugins/test-run-plugin/src/index.ts

+ 1 - 1
apps/demo-free-layout/src/components/node-panel/index.tsx

@@ -6,8 +6,8 @@
 import { useRef } from 'react';
 
 import { NodePanelRenderProps as NodePanelRenderPropsDefault } from '@flowgram.ai/free-node-panel-plugin';
+import { WorkflowPortEntity } from '@flowgram.ai/free-layout-editor';
 import { Popover } from '@douyinfe/semi-ui';
-import { WorkflowPortEntity } from '@flowgram.ai/free-layout-editor'
 
 import { NodePlaceholder } from './node-placeholder';
 import { NodeList } from './node-list';

+ 4 - 1
apps/demo-free-layout/src/editor.tsx

@@ -3,6 +3,7 @@
  * SPDX-License-Identifier: MIT
  */
 
+import { DockedPanelLayer } from '@flowgram.ai/panel-manager-plugin';
 import { EditorRenderer, FreeLayoutEditorProvider } from '@flowgram.ai/free-layout-editor';
 
 import '@flowgram.ai/free-layout-editor/index.css';
@@ -17,7 +18,9 @@ export const Editor = () => {
     <div className="doc-free-feature-overview">
       <FreeLayoutEditorProvider {...editorProps}>
         <div className="demo-container">
-          <EditorRenderer className="demo-editor" />
+          <DockedPanelLayer>
+            <EditorRenderer className="demo-editor" />
+          </DockedPanelLayer>
         </div>
       </FreeLayoutEditorProvider>
     </div>

+ 1 - 1
apps/demo-free-layout/src/plugins/panel-manager-plugin/hooks.ts

@@ -25,7 +25,7 @@ export const useTestRunFormPanel = () => {
   const panelManager = usePanelManager();
 
   const open = () => {
-    panelManager.open(PanelType.TestRunFormPanel, 'right');
+    panelManager.open(PanelType.TestRunFormPanel, 'docked-right');
   };
   const close = () => panelManager.close(PanelType.TestRunFormPanel);
 

+ 3 - 0
apps/demo-nextjs-antd/.eslintrc.js

@@ -18,6 +18,9 @@ module.exports = defineConfig({
   rules: {
     'no-console': 'off',
     'react/prop-types': 'off',
+    'react-hooks/exhaustive-deps': 'off',
+    '@next/next/no-img-element': 'off',
+    'jsx-a11y/alt-text': 'off'
   },
   plugins: ['json'],
   extends: ['next', 'next/core-web-vitals'],

+ 9 - 9
apps/demo-nextjs-antd/src/editor/components/line-add-button/index.tsx

@@ -3,10 +3,13 @@
  * SPDX-License-Identifier: MIT
  */
 
-import { IconPlusCircle } from './button';
-import './index.scss';
-import { useVisible } from './use-visible';
+import { useCallback } from 'react';
 
+import {
+  WorkflowNodePanelService,
+  WorkflowNodePanelUtils,
+} from '@flowgram.ai/free-node-panel-plugin';
+import { LineRenderProps } from '@flowgram.ai/free-lines-plugin';
 import {
   HistoryService,
   WorkflowDocument,
@@ -17,14 +20,11 @@ import {
   delay,
   useService,
 } from '@flowgram.ai/free-layout-editor';
-import { LineRenderProps } from '@flowgram.ai/free-lines-plugin';
-import {
-  WorkflowNodePanelService,
-  WorkflowNodePanelUtils,
-} from '@flowgram.ai/free-node-panel-plugin';
 
-import { useCallback } from 'react';
+import { useVisible } from './use-visible';
+import { IconPlusCircle } from './button';
 
+import './index.scss';
 export const LineAddButton = (props: LineRenderProps) => {
   const { line, selected, hovered, color } = props;
   const visible = useVisible({ line, selected, hovered });

+ 4 - 3
apps/demo-nextjs-antd/src/editor/form-components/form-item/index.tsx

@@ -3,12 +3,13 @@
  * SPDX-License-Identifier: MIT
  */
 
-import { TypeTag } from '../type-tag';
+import React, { useCallback } from 'react';
 
-import './index.css';
 import { Tooltip, Typography } from 'antd';
 
-import React, { useCallback } from 'react';
+import { TypeTag } from '../type-tag';
+
+import './index.css';
 
 const { Text } = Typography;
 

+ 6 - 2
apps/demo-nextjs-antd/src/editor/nodes/default-form-meta.tsx

@@ -4,7 +4,11 @@
  */
 
 import { FormMeta, FormRenderProps, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
-import { autoRenameRefEffect, syncVariableTitle, provideJsonSchemaOutputs } from '@flowgram.ai/form-antd-materials';
+import {
+  autoRenameRefEffect,
+  syncVariableTitle,
+  provideJsonSchemaOutputs,
+} from '@flowgram.ai/form-antd-materials';
 
 import { FormContent, FormHeader, FormInputs, FormOutputs } from '@editor/form-components';
 import { FlowNodeJSON } from '../typings';
@@ -36,7 +40,7 @@ export const defaultFormMeta: FormMeta<FlowNodeJSON> = {
       return undefined;
     },
   },
-   effect: {
+  effect: {
     title: syncVariableTitle,
     outputs: provideJsonSchemaOutputs,
     inputsValues: autoRenameRefEffect,

+ 5 - 1
apps/demo-nextjs-antd/src/editor/nodes/start/form-meta.tsx

@@ -12,7 +12,11 @@ import {
   FormRenderProps,
   ValidateTrigger,
 } from '@flowgram.ai/free-layout-editor';
-import { JsonSchemaEditor, syncVariableTitle, provideJsonSchemaOutputs } from '@flowgram.ai/form-antd-materials';
+import {
+  JsonSchemaEditor,
+  syncVariableTitle,
+  provideJsonSchemaOutputs,
+} from '@flowgram.ai/form-antd-materials';
 
 import { FlowNodeJSON, JsonSchema } from '@editor/typings';
 import { useIsSidebar } from '@editor/hooks';

+ 1 - 0
apps/docs/components/materials.tsx

@@ -4,6 +4,7 @@
  */
 
 // @ts-expect-error
+// eslint-disable-next-line import/no-unresolved
 import { PackageManagerTabs, SourceCode } from '@theme';
 
 export function MaterialDisplay(props: any) {

+ 1 - 0
common/config/rush/command-line.json

@@ -268,6 +268,7 @@
 			"summary": "⭐️️ Run eslint check in packages",
 			"ignoreMissingScript": true,
 			"enableParallelism": true,
+			"allowWarningsInSuccessfulBuild": true,
 			"safeForSimultaneousRushProcesses": true
 		},
 		{

+ 43 - 40
packages/plugins/panel-manager-plugin/src/components/panel-layer/css.ts

@@ -4,59 +4,62 @@
  */
 
 export const globalCSS = `
-  .gedit-flow-panel-layer * {
+  .gedit-flow-panel-layer-wrap * {
     box-sizing: border-box;
   }
   .gedit-flow-panel-layer-wrap {
-    pointer-events: none;
     position: absolute;
     top: 0;
     left: 0;
     display: flex;
-    column-gap: 4px;
     width: 100%;
     height: 100%;
-    padding: 4px;
     overflow: hidden;
   }
-`;
-
-export const leftArea: React.CSSProperties = {
-  width: '100%',
-  minWidth: 0,
-  flexGrow: 0,
-  flexShrink: 1,
-
-  display: 'flex',
-  flexDirection: 'column',
-  rowGap: '4px',
-};
-
-export const rightArea: React.CSSProperties = {
-  height: '100%',
-  flexGrow: 1,
-  flexShrink: 0,
-  minWidth: 0,
+  .gedit-flow-panel-layer-wrap-docked {
 
-  display: 'flex',
-  columnGap: '4px',
-};
-
-export const mainArea: React.CSSProperties = {
-  position: 'relative',
-  overflow: 'hidden',
-  flexGrow: 0,
-  flexShrink: 1,
-  width: '100%',
-  height: '100%',
-};
+  }
+  .gedit-flow-panel-layer-wrap-floating {
+    column-gap: 4px;
+    padding: 4px;
+    pointer-events: none;
+  }
 
-export const bottomArea: React.CSSProperties = {
-  flexGrow: 1,
-  flexShrink: 0,
-  width: '100%',
-  minHeight: 0,
-};
+  .gedit-flow-panel-left-area {
+    width: 100%;
+    min-width: 0;
+    flex-grow: 0;
+    flex-shrink: 1;
+    display: flex;
+    flex-direction: column;
+  }
+  .gedit-flow-panel-layer-wrap-floating .gedit-flow-panel-left-area {
+    row-gap: 4px;
+  }
+  .gedit-flow-panel-right-area {
+    height: 100%;
+    flex-grow: 1;
+    flex-shrink: 0;
+    min-width: 0;
+    display: flex;
+    column-gap: 4px;
+  }
+  
+  .gedit-flow-panel-main-area {
+    position: relative;
+    overflow: hidden;
+    flex-grow: 0;
+    flex-shrink: 1;
+    width: 100%;
+    height: 100%;
+  }
+  .gedit-flow-panel-bottom-area {
+    flex-grow: 1;
+    flex-shrink: 0;
+    width: 100%;
+    min-height: 0;
+  }
+`;
 
 export const floatPanelWrap: React.CSSProperties = {
   pointerEvents: 'auto',

+ 12 - 0
packages/plugins/panel-manager-plugin/src/components/panel-layer/docked-panel-layer.tsx

@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { PanelLayer, PanelLayerProps } from './panel-layer';
+
+export type DockedPanelLayerProps = Omit<PanelLayerProps, 'mode'>;
+
+export const DockedPanelLayer: React.FC<DockedPanelLayerProps> = (props) => (
+  <PanelLayer mode="docked" {...props} />
+);

+ 11 - 6
packages/plugins/panel-manager-plugin/src/components/panel-layer/float-panel.tsx

@@ -8,12 +8,14 @@ import { useEffect, useRef, startTransition, useState, useCallback } from 'react
 import { Area } from '../../types';
 import { usePanelManager } from '../../hooks/use-panel-manager';
 import { floatPanelWrap } from './css';
-import { ResizeBar } from '../resize-bar';
 
 export const FloatPanel: React.FC<{ area: Area }> = ({ area }) => {
   const [, setVersion] = useState(0);
   const panelManager = usePanelManager();
   const panel = useRef(panelManager.getPanel(area));
+
+  const isHorizontal = ['right', 'docked-right'].includes(area);
+
   const render = () =>
     panel.current.elements.map((i) => (
       <div className="float-panel-wrap" key={i.key} style={{ ...floatPanelWrap, ...i.style }}>
@@ -33,8 +35,9 @@ export const FloatPanel: React.FC<{ area: Area }> = ({ area }) => {
   }, [panel]);
   const onResize = useCallback((newSize: number) => panel.current!.updateSize(newSize), []);
   const size = panel.current!.currentSize;
-  const sizeStyle =
-    area === 'right' ? { width: size, height: '100%' } : { height: size, width: '100%' };
+  const sizeStyle = isHorizontal
+    ? { width: size, height: '100%' }
+    : { height: size, width: '100%' };
 
   return (
     <div
@@ -45,9 +48,11 @@ export const FloatPanel: React.FC<{ area: Area }> = ({ area }) => {
         ...sizeStyle,
       }}
     >
-      {panelManager.config.autoResize && panel.current.elements.length > 0 && (
-        <ResizeBar size={size} isVertical={area === 'right'} onResize={onResize} />
-      )}
+      {panelManager.config.resizeBarRender({
+        size,
+        direction: isHorizontal ? 'vertical' : 'horizontal',
+        onResize,
+      })}
       {node.current}
     </div>
   );

+ 1 - 0
packages/plugins/panel-manager-plugin/src/components/panel-layer/index.ts

@@ -4,3 +4,4 @@
  */
 
 export { PanelLayer, type PanelLayerProps } from './panel-layer';
+export { DockedPanelLayer, type DockedPanelLayerProps } from './docked-panel-layer';

+ 24 - 11
packages/plugins/panel-manager-plugin/src/components/panel-layer/panel-layer.tsx

@@ -7,31 +7,44 @@ import clsx from 'clsx';
 
 import { useGlobalCSS } from '../../hooks/use-global-css';
 import { FloatPanel } from './float-panel';
-import { leftArea, rightArea, mainArea, bottomArea, globalCSS } from './css';
+import { globalCSS } from './css';
 
 export type PanelLayerProps = React.PropsWithChildren<{
+  /** 模式:悬浮|挤压 */
+  mode?: 'floating' | 'docked';
   className?: string;
   style?: React.CSSProperties;
 }>;
 
-export const PanelLayer: React.FC<PanelLayerProps> = ({ className, style, children }) => {
+export const PanelLayer: React.FC<PanelLayerProps> = ({
+  mode = 'floating',
+  className,
+  style,
+  children,
+}) => {
   useGlobalCSS({
     cssText: globalCSS,
     id: 'flow-panel-layer-css',
   });
 
   return (
-    <div className={clsx('gedit-flow-panel-layer-wrap', className)} style={style}>
-      <div className="gedit-flow-panel-left-area" style={leftArea}>
-        <div className="gedit-flow-panel-main-area" style={mainArea}>
-          {children}
-        </div>
-        <div className="gedit-flow-panel-bottom-area" style={bottomArea}>
-          <FloatPanel area="bottom" />
+    <div
+      className={clsx(
+        'gedit-flow-panel-layer-wrap',
+        mode === 'docked' && 'gedit-flow-panel-layer-wrap-docked',
+        mode === 'floating' && 'gedit-flow-panel-layer-wrap-floating',
+        className
+      )}
+      style={style}
+    >
+      <div className="gedit-flow-panel-left-area">
+        <div className="gedit-flow-panel-main-area">{children}</div>
+        <div className="gedit-flow-panel-bottom-area">
+          <FloatPanel area={mode === 'docked' ? 'docked-bottom' : 'bottom'} />
         </div>
       </div>
-      <div className="gedit-flow-panel-right-area" style={rightArea}>
-        <FloatPanel area="right" />
+      <div className="gedit-flow-panel-right-area">
+        <FloatPanel area={mode === 'docked' ? 'docked-right' : 'right'} />
       </div>
     </div>
   );

+ 6 - 3
packages/plugins/panel-manager-plugin/src/components/resize-bar/index.tsx

@@ -6,15 +6,18 @@
 import React, { useRef, useState } from 'react';
 
 interface Props {
-  onResize: (w: number) => void;
   size: number;
-  isVertical?: boolean;
+  direction?: 'vertical' | 'horizontal';
+  onResize: (w: number) => void;
 }
 
-export const ResizeBar: React.FC<Props> = ({ onResize, size, isVertical }) => {
+export const ResizeBar: React.FC<Props> = ({ onResize, size, direction }) => {
   const currentPoint = useRef<null | number>(null);
   const [isDragging, setIsDragging] = useState(false);
   const [isHovered, setIsHovered] = useState(false);
+
+  const isVertical = direction === 'vertical';
+
   return (
     <div
       onMouseDown={(e) => {

+ 1 - 0
packages/plugins/panel-manager-plugin/src/index.ts

@@ -12,6 +12,7 @@ export { PanelManager, type PanelManagerConfig } from './services';
 /** react hooks */
 export { usePanelManager } from './hooks/use-panel-manager';
 
+export { DockedPanelLayer, type DockedPanelLayerProps } from './components/panel-layer';
 export { ResizeBar } from './components/resize-bar';
 
 /** types */

+ 17 - 0
packages/plugins/panel-manager-plugin/src/services/panel-config.ts

@@ -6,14 +6,24 @@
 import { PluginContext } from '@flowgram.ai/core';
 
 import type { PanelFactory, PanelConfig } from '../types';
+import { ResizeBar } from '../components/resize-bar';
 import type { PanelLayerProps } from '../components/panel-layer';
 
 export interface PanelManagerConfig {
   factories: PanelFactory<any>[];
   right: PanelConfig;
   bottom: PanelConfig;
+  dockedRight: PanelConfig;
+  dockedBottom: PanelConfig;
   autoResize: boolean;
   layerProps: PanelLayerProps;
+  resizeBarRender: ({
+    size,
+  }: {
+    size: number;
+    direction?: 'vertical' | 'horizontal';
+    onResize: (size: number) => void;
+  }) => React.ReactNode;
   getPopupContainer: (ctx: PluginContext) => HTMLElement; // default playground.node.parentElement
 }
 
@@ -27,9 +37,16 @@ export const defineConfig = (config: Partial<PanelManagerConfig>) => {
     bottom: {
       max: 1,
     },
+    dockedRight: {
+      max: 1,
+    },
+    dockedBottom: {
+      max: 1,
+    },
     factories: [],
     autoResize: true,
     layerProps: {},
+    resizeBarRender: ResizeBar,
     getPopupContainer: (ctx: PluginContext) => ctx.playground.node.parentNode as HTMLElement,
   };
   return {

+ 24 - 1
packages/plugins/panel-manager-plugin/src/services/panel-manager.ts

@@ -4,6 +4,7 @@
  */
 
 import { injectable, inject } from 'inversify';
+import { Playground } from '@flowgram.ai/core';
 
 import { PanelManagerConfig } from './panel-config';
 import type { Area, PanelFactory } from '../types';
@@ -11,6 +12,8 @@ import { FloatPanel } from './float-panel';
 
 @injectable()
 export class PanelManager {
+  @inject(Playground) readonly playground: Playground;
+
   @inject(PanelManagerConfig) readonly config: PanelManagerConfig;
 
   readonly panelRegistry = new Map<string, PanelFactory<any>>();
@@ -19,10 +22,16 @@ export class PanelManager {
 
   bottom: FloatPanel;
 
+  dockedRight: FloatPanel;
+
+  dockedBottom: FloatPanel;
+
   init() {
     this.config.factories.forEach((factory) => this.register(factory));
     this.right = new FloatPanel(this.config.right);
     this.bottom = new FloatPanel(this.config.bottom);
+    this.dockedRight = new FloatPanel(this.config.dockedRight);
+    this.dockedBottom = new FloatPanel(this.config.dockedBottom);
   }
 
   register<T extends any>(factory: PanelFactory<T>) {
@@ -41,14 +50,28 @@ export class PanelManager {
   close(key?: string) {
     this.right.close(key);
     this.bottom.close(key);
+    this.dockedRight.close(key);
+    this.dockedBottom.close(key);
   }
 
   getPanel(area: Area) {
-    return area === 'right' ? this.right : this.bottom;
+    switch (area) {
+      case 'docked-bottom':
+        return this.dockedBottom;
+      case 'docked-right':
+        return this.dockedRight;
+      case 'bottom':
+        return this.bottom;
+      case 'right':
+      default:
+        return this.right;
+    }
   }
 
   dispose() {
     this.right.dispose();
     this.bottom.dispose();
+    this.dockedBottom.dispose();
+    this.dockedRight.dispose();
   }
 }

+ 1 - 1
packages/plugins/panel-manager-plugin/src/types.ts

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: MIT
  */
 
-export type Area = 'right' | 'bottom';
+export type Area = 'right' | 'bottom' | 'docked-right' | 'docked-bottom';
 
 export interface PanelConfig {
   /** max panel */

+ 1 - 0
packages/plugins/test-run-plugin/src/index.ts

@@ -19,4 +19,5 @@ export {
   type TestRunPipelinePlugin,
   TestRunPipelineEntity,
   type TestRunPipelineEntityCtx,
+  type TestRunConfig,
 } from './services';