jiangxujin 1 месяц назад
Родитель
Сommit
a92f0c83bf

+ 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);
 

+ 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';