@flowgram.ai/panel-manager-plugin 0.1.0-alpha.23 → 0.1.0-alpha.25

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.
@@ -4,74 +4,112 @@
4
4
  */
5
5
 
6
6
  import { injectable, inject } from 'inversify';
7
- import { Playground } from '@flowgram.ai/core';
7
+ import { Emitter } from '@flowgram.ai/utils';
8
8
 
9
9
  import { PanelManagerConfig } from './panel-config';
10
- import type { Area, PanelFactory } from '../types';
11
- import { FloatPanel } from './float-panel';
10
+ import type { Area, PanelEntityConfig, PanelFactory } from '../types';
11
+ import { PanelEntity, PanelEntityFactory } from './panel-factory';
12
12
 
13
13
  @injectable()
14
14
  export class PanelManager {
15
- @inject(Playground) readonly playground: Playground;
16
-
17
15
  @inject(PanelManagerConfig) readonly config: PanelManagerConfig;
18
16
 
19
- readonly panelRegistry = new Map<string, PanelFactory<any>>();
17
+ @inject(PanelEntityFactory) readonly createPanel: PanelEntityFactory;
20
18
 
21
- right: FloatPanel;
19
+ readonly panelRegistry = new Map<string, PanelFactory<any>>();
22
20
 
23
- bottom: FloatPanel;
21
+ private panels = new Map<string, PanelEntity>();
24
22
 
25
- dockedRight: FloatPanel;
23
+ private onPanelsChangeEvent = new Emitter<void>();
26
24
 
27
- dockedBottom: FloatPanel;
25
+ public onPanelsChange = this.onPanelsChangeEvent.event;
28
26
 
29
27
  init() {
30
28
  this.config.factories.forEach((factory) => this.register(factory));
31
- this.right = new FloatPanel(this.config.right);
32
- this.bottom = new FloatPanel(this.config.bottom);
33
- this.dockedRight = new FloatPanel(this.config.dockedRight);
34
- this.dockedBottom = new FloatPanel(this.config.dockedBottom);
35
29
  }
36
30
 
31
+ /** registry panel factory */
37
32
  register<T extends any>(factory: PanelFactory<T>) {
38
33
  this.panelRegistry.set(factory.key, factory);
39
34
  }
40
35
 
41
- open(key: string, area: Area = 'right', options?: any) {
36
+ /** open panel */
37
+ public open(key: string, area: Area = 'right', options?: PanelEntityConfig) {
42
38
  const factory = this.panelRegistry.get(key);
43
39
  if (!factory) {
44
40
  return;
45
41
  }
46
- const panel = this.getPanel(area);
47
- panel.open(factory, options);
42
+
43
+ const sameKeyPanels = this.getPanels(area).filter((p) => p.key === key);
44
+ if (!factory.allowDuplicates && sameKeyPanels.length) {
45
+ sameKeyPanels.forEach((p) => this.remove(p.id));
46
+ }
47
+
48
+ const panel = this.createPanel({
49
+ factory,
50
+ config: {
51
+ area,
52
+ ...options,
53
+ },
54
+ });
55
+
56
+ this.panels.set(panel.id, panel);
57
+ this.trim(area);
58
+ this.onPanelsChangeEvent.fire();
59
+ }
60
+
61
+ /** close panel */
62
+ public close(key?: string) {
63
+ const panels = this.getPanels();
64
+ const closedPanels = key ? panels.filter((p) => p.key === key) : panels;
65
+ closedPanels.forEach((p) => this.remove(p.id));
66
+ this.onPanelsChangeEvent.fire();
67
+ }
68
+
69
+ private trim(area: Area) {
70
+ const panels = this.getPanels(area);
71
+ const areaConfig = this.getAreaConfig(area);
72
+ while (panels.length > areaConfig.max) {
73
+ const removed = panels.shift();
74
+ if (removed) {
75
+ this.remove(removed.id);
76
+ }
77
+ }
78
+ }
79
+
80
+ private remove(id: string) {
81
+ const panel = this.panels.get(id);
82
+ if (panel) {
83
+ panel.dispose();
84
+ this.panels.delete(id);
85
+ }
48
86
  }
49
87
 
50
- close(key?: string) {
51
- this.right.close(key);
52
- this.bottom.close(key);
53
- this.dockedRight.close(key);
54
- this.dockedBottom.close(key);
88
+ getPanels(area?: Area) {
89
+ const panels: PanelEntity[] = [];
90
+ this.panels.forEach((panel) => {
91
+ if (!area || panel.area === area) {
92
+ panels.push(panel);
93
+ }
94
+ });
95
+ return panels;
55
96
  }
56
97
 
57
- getPanel(area: Area) {
98
+ getAreaConfig(area: Area) {
58
99
  switch (area) {
59
100
  case 'docked-bottom':
60
- return this.dockedBottom;
101
+ return this.config.dockedBottom;
61
102
  case 'docked-right':
62
- return this.dockedRight;
103
+ return this.config.dockedRight;
63
104
  case 'bottom':
64
- return this.bottom;
105
+ return this.config.bottom;
65
106
  case 'right':
66
107
  default:
67
- return this.right;
108
+ return this.config.right;
68
109
  }
69
110
  }
70
111
 
71
112
  dispose() {
72
- this.right.dispose();
73
- this.bottom.dispose();
74
- this.dockedBottom.dispose();
75
- this.dockedRight.dispose();
113
+ this.onPanelsChangeEvent.dispose();
76
114
  }
77
115
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { injectable } from 'inversify';
7
+
8
+ export const PanelRestore = Symbol('PanelRestore');
9
+ export interface PanelRestore {
10
+ store: (k: string, v: any) => void;
11
+ restore: <T>(k: string) => T | undefined;
12
+ }
13
+
14
+ @injectable()
15
+ export class PanelRestoreImpl implements PanelRestore {
16
+ map = new Map<string, any>();
17
+
18
+ store(k: string, v: any) {
19
+ this.map.set(k, v);
20
+ }
21
+
22
+ restore<T>(k: string): T | undefined {
23
+ return this.map.get(k) as T;
24
+ }
25
+ }
package/src/types.ts CHANGED
@@ -13,6 +13,19 @@ export interface PanelConfig {
13
13
  export interface PanelFactory<T extends any> {
14
14
  key: string;
15
15
  defaultSize: number;
16
+ fullscreen?: boolean;
17
+ maxSize?: number;
18
+ minSize?: number;
16
19
  style?: React.CSSProperties;
20
+ /** Allows multiple panels with the same key to be rendered simultaneously */
21
+ allowDuplicates?: boolean;
22
+ resize?: boolean;
17
23
  render: (props: T) => React.ReactNode;
18
24
  }
25
+
26
+ export interface PanelEntityConfig<T extends any = any> {
27
+ defaultSize?: number;
28
+ fullscreen?: boolean;
29
+ style?: React.CSSProperties;
30
+ props?: T;
31
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export const merge = <T>(...objs: Partial<T>[]) => {
7
+ const result: any = {};
8
+
9
+ for (const obj of objs) {
10
+ if (!obj || typeof obj !== 'object') continue;
11
+
12
+ for (const key of Object.keys(obj)) {
13
+ const value = (obj as any)[key];
14
+
15
+ if (result[key] === undefined) {
16
+ result[key] = value;
17
+ }
18
+ }
19
+ }
20
+
21
+ return result as T;
22
+ };
@@ -1,59 +0,0 @@
1
- /**
2
- * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
- * SPDX-License-Identifier: MIT
4
- */
5
-
6
- import { useEffect, useRef, startTransition, useState, useCallback } from 'react';
7
-
8
- import { Area } from '../../types';
9
- import { usePanelManager } from '../../hooks/use-panel-manager';
10
- import { floatPanelWrap } from './css';
11
-
12
- export const FloatPanel: React.FC<{ area: Area }> = ({ area }) => {
13
- const [, setVersion] = useState(0);
14
- const panelManager = usePanelManager();
15
- const panel = useRef(panelManager.getPanel(area));
16
-
17
- const isHorizontal = ['right', 'docked-right'].includes(area);
18
-
19
- const render = () =>
20
- panel.current.elements.map((i) => (
21
- <div className="float-panel-wrap" key={i.key} style={{ ...floatPanelWrap, ...i.style }}>
22
- {i.el}
23
- </div>
24
- ));
25
- const node = useRef(render());
26
-
27
- useEffect(() => {
28
- const dispose = panel.current.onUpdate(() => {
29
- startTransition(() => {
30
- node.current = render();
31
- setVersion((v) => v + 1);
32
- });
33
- });
34
- return () => dispose.dispose();
35
- }, [panel]);
36
- const onResize = useCallback((newSize: number) => panel.current!.updateSize(newSize), []);
37
- const size = panel.current!.currentSize;
38
- const sizeStyle = isHorizontal
39
- ? { width: size, height: '100%' }
40
- : { height: size, width: '100%' };
41
-
42
- return (
43
- <div
44
- className="gedit-flow-panel"
45
- style={{
46
- position: 'relative',
47
- display: panel.current.visible ? 'block' : 'none',
48
- ...sizeStyle,
49
- }}
50
- >
51
- {panelManager.config.resizeBarRender({
52
- size,
53
- direction: isHorizontal ? 'vertical' : 'horizontal',
54
- onResize,
55
- })}
56
- {node.current}
57
- </div>
58
- );
59
- };
@@ -1,75 +0,0 @@
1
- /**
2
- * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
- * SPDX-License-Identifier: MIT
4
- */
5
-
6
- import { Emitter } from '@flowgram.ai/utils';
7
-
8
- import type { PanelFactory, PanelConfig } from '../types';
9
-
10
- export interface PanelElement {
11
- key: string;
12
- style?: React.CSSProperties;
13
- el: React.ReactNode;
14
- }
15
-
16
- const PANEL_SIZE_DEFAULT = 400;
17
-
18
- export class FloatPanel {
19
- elements: PanelElement[] = [];
20
-
21
- private onUpdateEmitter = new Emitter<void>();
22
-
23
- sizeMap = new Map<string, number>();
24
-
25
- onUpdate = this.onUpdateEmitter.event;
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
-
38
- constructor(private config: PanelConfig) {}
39
-
40
- open(factory: PanelFactory<any>, options: any) {
41
- const el = factory.render(options?.props);
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
- }
47
- if (idx >= 0) {
48
- this.elements[idx] = { el, key: factory.key, style: factory.style };
49
- } else {
50
- this.elements.push({ el, key: factory.key, style: factory.style });
51
- if (this.elements.length > this.config.max) {
52
- this.elements.shift();
53
- }
54
- }
55
- this.onUpdateEmitter.fire();
56
- }
57
-
58
- get visible() {
59
- return this.elements.length > 0;
60
- }
61
-
62
- close(key?: string) {
63
- if (!key) {
64
- this.elements = [];
65
- } else {
66
- this.elements = this.elements.filter((e) => e.key !== key);
67
- }
68
- this.onUpdateEmitter.fire();
69
- }
70
-
71
- dispose() {
72
- this.elements = [];
73
- this.onUpdateEmitter.dispose();
74
- }
75
- }