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

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,114 @@
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
+ console.log('jxj', this.panels);
60
+ }
61
+
62
+ /** close panel */
63
+ public close(key?: string) {
64
+ const panels = this.getPanels();
65
+ const closedPanels = key ? panels.filter((p) => p.key === key) : panels;
66
+ closedPanels.forEach((p) => this.remove(p.id));
67
+ this.onPanelsChangeEvent.fire();
68
+ }
69
+
70
+ private trim(area: Area) {
71
+ const panels = this.getPanels(area);
72
+ const areaConfig = this.getAreaConfig(area);
73
+ console.log('jxj', areaConfig.max, panels.length);
74
+ while (panels.length > areaConfig.max) {
75
+ const removed = panels.shift();
76
+ if (removed) {
77
+ this.remove(removed.id);
78
+ }
79
+ }
80
+ }
81
+
82
+ private remove(id: string) {
83
+ const panel = this.panels.get(id);
84
+ if (panel) {
85
+ panel.dispose();
86
+ this.panels.delete(id);
87
+ }
48
88
  }
49
89
 
50
- close(key?: string) {
51
- this.right.close(key);
52
- this.bottom.close(key);
53
- this.dockedRight.close(key);
54
- this.dockedBottom.close(key);
90
+ getPanels(area?: Area) {
91
+ const panels: PanelEntity[] = [];
92
+ this.panels.forEach((panel) => {
93
+ if (!area || panel.area === area) {
94
+ panels.push(panel);
95
+ }
96
+ });
97
+ return panels;
55
98
  }
56
99
 
57
- getPanel(area: Area) {
100
+ getAreaConfig(area: Area) {
58
101
  switch (area) {
59
102
  case 'docked-bottom':
60
- return this.dockedBottom;
103
+ return this.config.dockedBottom;
61
104
  case 'docked-right':
62
- return this.dockedRight;
105
+ return this.config.dockedRight;
63
106
  case 'bottom':
64
- return this.bottom;
107
+ return this.config.bottom;
65
108
  case 'right':
66
109
  default:
67
- return this.right;
110
+ return this.config.right;
68
111
  }
69
112
  }
70
113
 
71
114
  dispose() {
72
- this.right.dispose();
73
- this.bottom.dispose();
74
- this.dockedBottom.dispose();
75
- this.dockedRight.dispose();
115
+ this.onPanelsChangeEvent.dispose();
76
116
  }
77
117
  }
@@ -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,17 @@ export interface PanelConfig {
13
13
  export interface PanelFactory<T extends any> {
14
14
  key: string;
15
15
  defaultSize: number;
16
+ maxSize?: number;
17
+ minSize?: number;
16
18
  style?: React.CSSProperties;
19
+ /** Allows multiple panels with the same key to be rendered simultaneously */
20
+ allowDuplicates?: boolean;
21
+ resize?: boolean;
17
22
  render: (props: T) => React.ReactNode;
18
23
  }
24
+
25
+ export interface PanelEntityConfig<T extends any = any> {
26
+ defaultSize?: number;
27
+ style?: React.CSSProperties;
28
+ props?: T;
29
+ }
@@ -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
- }