@flowgram.ai/panel-manager-plugin 0.4.19 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,11 +3,12 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
- import { useEffect, useRef, startTransition, useState } from 'react';
6
+ import { useEffect, useRef, startTransition, useState, useCallback } from 'react';
7
7
 
8
8
  import { Area } from '../../types';
9
9
  import { usePanelManager } from '../../hooks/use-panel-manager';
10
10
  import { floatPanelWrap } from './css';
11
+ import { ResizeBar } from '../resize-bar';
11
12
 
12
13
  export const FloatPanel: React.FC<{ area: Area }> = ({ area }) => {
13
14
  const [, setVersion] = useState(0);
@@ -15,12 +16,7 @@ export const FloatPanel: React.FC<{ area: Area }> = ({ area }) => {
15
16
  const panel = useRef(panelManager.getPanel(area));
16
17
  const render = () =>
17
18
  panel.current.elements.map((i) => (
18
- <div
19
- className="float-panel-wrap"
20
- key={i.key}
21
- style={floatPanelWrap}
22
- onMouseDown={(e) => e.stopPropagation()}
23
- >
19
+ <div className="float-panel-wrap" key={i.key} style={{ ...floatPanelWrap, ...i.style }}>
24
20
  {i.el}
25
21
  </div>
26
22
  ));
@@ -35,6 +31,24 @@ export const FloatPanel: React.FC<{ area: Area }> = ({ area }) => {
35
31
  });
36
32
  return () => dispose.dispose();
37
33
  }, [panel]);
34
+ const onResize = useCallback((newSize: number) => panel.current!.updateSize(newSize), []);
35
+ const size = panel.current!.currentSize;
36
+ const sizeStyle =
37
+ area === 'right' ? { width: size, height: '100%' } : { height: size, width: '100%' };
38
38
 
39
- return <>{node.current}</>;
39
+ return (
40
+ <div
41
+ className="gedit-flow-panel"
42
+ style={{
43
+ position: 'relative',
44
+ display: panel.current.visible ? 'block' : 'none',
45
+ ...sizeStyle,
46
+ }}
47
+ >
48
+ {panelManager.config.autoResize && (
49
+ <ResizeBar size={size} isVertical={area === 'right'} onResize={onResize} />
50
+ )}
51
+ {node.current}
52
+ </div>
53
+ );
40
54
  };
@@ -4,19 +4,20 @@
4
4
  */
5
5
 
6
6
  import { FloatPanel } from './float-panel';
7
- import { panelLayer, leftArea, rightArea, mainArea, bottomArea } from './css';
7
+ import { panelLayer, leftArea, rightArea, mainArea, bottomArea, globalCSS } from './css';
8
8
 
9
9
  export const PanelLayer: React.FC<React.PropsWithChildren> = ({ children }) => (
10
- <div className="panel-layer" style={panelLayer}>
11
- <div className="left-area" style={leftArea}>
12
- <div className="main-area" style={mainArea}>
10
+ <div style={panelLayer}>
11
+ <style dangerouslySetInnerHTML={{ __html: globalCSS }} />
12
+ <div className="gedit-flow-panel-left-area" style={leftArea}>
13
+ <div className="gedit-flow-panel-main-area" style={mainArea}>
13
14
  {children}
14
15
  </div>
15
- <div className="bottom-area" style={bottomArea}>
16
+ <div className="gedit-flow-panel-bottom-area" style={bottomArea}>
16
17
  <FloatPanel area="bottom" />
17
18
  </div>
18
19
  </div>
19
- <div className="right-area" style={rightArea}>
20
+ <div className="gedit-flow-panel-right-area" style={rightArea}>
20
21
  <FloatPanel area="right" />
21
22
  </div>
22
23
  </div>
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import React, { useRef, useState } from 'react';
7
+
8
+ interface Props {
9
+ onResize: (w: number) => void;
10
+ size: number;
11
+ isVertical?: boolean;
12
+ }
13
+
14
+ export const ResizeBar: React.FC<Props> = ({ onResize, size, isVertical }) => {
15
+ const currentPoint = useRef<null | number>(null);
16
+ const [isDragging, setIsDragging] = useState(false);
17
+ const [isHovered, setIsHovered] = useState(false);
18
+ return (
19
+ <div
20
+ onMouseDown={(e) => {
21
+ currentPoint.current = isVertical ? e.clientX : e.clientY;
22
+ e.stopPropagation();
23
+ e.preventDefault();
24
+ setIsDragging(true);
25
+ const mouseUp = () => {
26
+ currentPoint.current = null;
27
+ document.body.removeEventListener('mouseup', mouseUp);
28
+ document.body.removeEventListener('mousemove', mouseMove);
29
+ setIsDragging(false);
30
+ };
31
+ const mouseMove = (e: MouseEvent) => {
32
+ const delta = currentPoint.current! - (isVertical ? e.clientX : e.clientY);
33
+ onResize(size + delta);
34
+ };
35
+ document.body.addEventListener('mouseup', mouseUp);
36
+ document.body.addEventListener('mousemove', mouseMove);
37
+ }}
38
+ onMouseEnter={() => setIsHovered(true)}
39
+ onMouseLeave={() => setIsHovered(false)}
40
+ style={{
41
+ position: 'absolute',
42
+ top: 0,
43
+ left: 0,
44
+ zIndex: 999,
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ justifyContent: 'center',
48
+ pointerEvents: 'auto',
49
+ ...(isVertical
50
+ ? {
51
+ cursor: 'ew-resize',
52
+ height: '100%',
53
+ marginLeft: -5,
54
+ width: 10,
55
+ }
56
+ : {
57
+ cursor: 'ns-resize',
58
+ width: '100%',
59
+ marginTop: -5,
60
+ height: 10,
61
+ }),
62
+ }}
63
+ >
64
+ <div
65
+ style={{
66
+ ...(isVertical
67
+ ? {
68
+ width: 3,
69
+ height: '100%',
70
+ }
71
+ : {
72
+ height: 3,
73
+ width: '100%',
74
+ }),
75
+ backgroundColor: isDragging || isHovered ? 'var(--g-playground-line)' : 'transparent',
76
+ }}
77
+ />
78
+ </div>
79
+ );
80
+ };
@@ -9,14 +9,12 @@ import { defineConfig } from './services/panel-config';
9
9
  import { PanelManager, PanelManagerConfig, PanelLayer } from './services';
10
10
 
11
11
  export const createPanelManagerPlugin = definePluginCreator<Partial<PanelManagerConfig>>({
12
- onBind: ({ bind }) => {
12
+ onBind: ({ bind }, opt) => {
13
13
  bind(PanelManager).to(PanelManager).inSingletonScope();
14
- bind(PanelManagerConfig).toConstantValue(defineConfig({}));
14
+ bind(PanelManagerConfig).toConstantValue(defineConfig(opt));
15
15
  },
16
16
  onInit(ctx, opt) {
17
17
  ctx.playground.registerLayer(PanelLayer);
18
- const config = defineConfig(opt);
19
- ctx.container.rebind(PanelManagerConfig).toConstantValue(config);
20
18
  const panelManager = ctx.container.get<PanelManager>(PanelManager);
21
19
  panelManager.init();
22
20
  },
package/src/index.ts CHANGED
@@ -12,5 +12,7 @@ export { PanelManager } from './services';
12
12
  /** react hooks */
13
13
  export { usePanelManager } from './hooks/use-panel-manager';
14
14
 
15
+ export { ResizeBar } from './components/resize-bar';
16
+
15
17
  /** types */
16
18
  export type { Area, PanelFactory } from './types';
@@ -9,25 +9,45 @@ import type { PanelFactory, PanelConfig } from '../types';
9
9
 
10
10
  export interface PanelElement {
11
11
  key: string;
12
+ style?: React.CSSProperties;
12
13
  el: React.ReactNode;
13
14
  }
14
15
 
16
+ const PANEL_SIZE_DEFAULT = 400;
17
+
15
18
  export class FloatPanel {
16
19
  elements: PanelElement[] = [];
17
20
 
18
21
  private onUpdateEmitter = new Emitter<void>();
19
22
 
23
+ sizeMap = new Map<string, number>();
24
+
20
25
  onUpdate = this.onUpdateEmitter.event;
21
26
 
27
+ currentFactoryKey = '';
28
+
29
+ updateSize(newSize: number) {
30
+ this.sizeMap.set(this.currentFactoryKey, newSize);
31
+ this.onUpdateEmitter.fire();
32
+ }
33
+
34
+ get currentSize(): number {
35
+ return this.sizeMap.get(this.currentFactoryKey) || PANEL_SIZE_DEFAULT;
36
+ }
37
+
22
38
  constructor(private config: PanelConfig) {}
23
39
 
24
40
  open(factory: PanelFactory<any>, options: any) {
25
41
  const el = factory.render(options?.props);
26
42
  const idx = this.elements.findIndex((e) => e.key === factory.key);
43
+ this.currentFactoryKey = factory.key;
44
+ if (!this.sizeMap.has(factory.key)) {
45
+ this.sizeMap.set(factory.key, factory.defaultSize || PANEL_SIZE_DEFAULT);
46
+ }
27
47
  if (idx >= 0) {
28
- this.elements[idx] = { el, key: factory.key };
48
+ this.elements[idx] = { el, key: factory.key, style: factory.style };
29
49
  } else {
30
- this.elements.push({ el, key: factory.key });
50
+ this.elements.push({ el, key: factory.key, style: factory.style });
31
51
  if (this.elements.length > this.config.max) {
32
52
  this.elements.shift();
33
53
  }
@@ -35,6 +55,10 @@ export class FloatPanel {
35
55
  this.onUpdateEmitter.fire();
36
56
  }
37
57
 
58
+ get visible() {
59
+ return this.elements.length > 0;
60
+ }
61
+
38
62
  close(key?: string) {
39
63
  if (!key) {
40
64
  this.elements = [];
@@ -3,12 +3,16 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
+ import { PluginContext } from '@flowgram.ai/core';
7
+
6
8
  import type { PanelFactory, PanelConfig } from '../types';
7
9
 
8
10
  export interface PanelManagerConfig {
9
11
  factories: PanelFactory<any>[];
10
12
  right: PanelConfig;
11
13
  bottom: PanelConfig;
14
+ getPopupContainer: (ctx: PluginContext) => HTMLElement; // default playground.node.parentElement
15
+ autoResize: boolean;
12
16
  }
13
17
 
14
18
  export const PanelManagerConfig = Symbol('PanelManagerConfig');
@@ -22,6 +26,8 @@ export const defineConfig = (config: Partial<PanelManagerConfig>) => {
22
26
  max: 1,
23
27
  },
24
28
  factories: [],
29
+ getPopupContainer: (ctx: PluginContext) => ctx.playground.node.parentNode as HTMLElement,
30
+ autoResize: true,
25
31
  };
26
32
  return {
27
33
  ...defaultConfig,
@@ -3,42 +3,50 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
+ import ReactDOM from 'react-dom';
6
7
  import { createElement } from 'react';
7
8
 
8
- import { injectable } from 'inversify';
9
- import { domUtils } from '@flowgram.ai/utils';
10
- import { Layer } from '@flowgram.ai/core';
9
+ import { injectable, inject } from 'inversify';
10
+ import { domUtils, Disposable } from '@flowgram.ai/utils';
11
+ import { Layer, PluginContext } from '@flowgram.ai/core';
11
12
 
12
13
  import { PanelLayer as PanelLayerComp } from '../components/panel-layer';
14
+ import { PanelManagerConfig } from './panel-config';
13
15
 
14
16
  @injectable()
15
17
  export class PanelLayer extends Layer {
16
- node = domUtils.createDivWithClass('gedit-flow-panel-layer');
18
+ @inject(PanelManagerConfig) private readonly panelConfig: PanelManagerConfig;
19
+
20
+ @inject(PluginContext) private readonly pluginContext: PluginContext;
21
+
22
+ readonly panelRoot = domUtils.createDivWithClass('gedit-flow-panel-layer');
17
23
 
18
24
  layout: JSX.Element | null = null;
19
25
 
20
26
  onReady(): void {
27
+ this.panelConfig.getPopupContainer(this.pluginContext).appendChild(this.panelRoot);
28
+ this.toDispose.push(
29
+ Disposable.create(() => {
30
+ // Remove from PopupContainer
31
+ this.panelRoot.remove();
32
+ })
33
+ );
21
34
  const commonStyle = {
22
35
  pointerEvents: 'none',
23
- zIndex: 11,
36
+ width: '100%',
37
+ height: '100%',
38
+ position: 'absolute',
39
+ left: 0,
40
+ top: 0,
41
+ zIndex: 100,
24
42
  };
25
- domUtils.setStyle(this.node, commonStyle);
26
- this.config.onDataChange(() => {
27
- const { width, height, scrollX, scrollY } = this.config.config;
28
- domUtils.setStyle(this.node, {
29
- ...commonStyle,
30
- width,
31
- height,
32
- left: scrollX,
33
- top: scrollY,
34
- });
35
- });
43
+ domUtils.setStyle(this.panelRoot, commonStyle);
36
44
  }
37
45
 
38
46
  render(): JSX.Element {
39
47
  if (!this.layout) {
40
48
  this.layout = createElement(PanelLayerComp);
41
49
  }
42
- return this.layout;
50
+ return ReactDOM.createPortal(this.layout, this.panelRoot);
43
51
  }
44
52
  }
@@ -11,7 +11,7 @@ import { FloatPanel } from './float-panel';
11
11
 
12
12
  @injectable()
13
13
  export class PanelManager {
14
- @inject(PanelManagerConfig) private readonly config: PanelManagerConfig;
14
+ @inject(PanelManagerConfig) readonly config: PanelManagerConfig;
15
15
 
16
16
  readonly panelRegistry = new Map<string, PanelFactory<any>>();
17
17
 
@@ -38,9 +38,9 @@ export class PanelManager {
38
38
  panel.open(factory, options);
39
39
  }
40
40
 
41
- close(key: string, area: Area = 'right') {
42
- const panel = this.getPanel(area);
43
- panel.close(key);
41
+ close(key?: string) {
42
+ this.right.close(key);
43
+ this.bottom.close(key);
44
44
  }
45
45
 
46
46
  getPanel(area: Area) {
package/src/types.ts CHANGED
@@ -12,5 +12,7 @@ export interface PanelConfig {
12
12
 
13
13
  export interface PanelFactory<T extends any> {
14
14
  key: string;
15
+ defaultSize: number;
16
+ style?: React.CSSProperties;
15
17
  render: (props: T) => React.ReactNode;
16
18
  }