panel-manager.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. /**
  2. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  3. * SPDX-License-Identifier: MIT
  4. */
  5. import { injectable, inject } from 'inversify';
  6. import { Emitter } from '@flowgram.ai/utils';
  7. import { PanelManagerConfig } from './panel-config';
  8. import type { Area, PanelEntityConfig, PanelFactory } from '../types';
  9. import { PanelEntity, PanelEntityFactory } from './panel-factory';
  10. @injectable()
  11. export class PanelManager {
  12. @inject(PanelManagerConfig) readonly config: PanelManagerConfig;
  13. @inject(PanelEntityFactory) readonly createPanel: PanelEntityFactory;
  14. readonly panelRegistry = new Map<string, PanelFactory<any>>();
  15. private panels = new Map<string, PanelEntity>();
  16. private onPanelsChangeEvent = new Emitter<void>();
  17. public onPanelsChange = this.onPanelsChangeEvent.event;
  18. init() {
  19. this.config.factories.forEach((factory) => this.register(factory));
  20. }
  21. /** registry panel factory */
  22. register<T extends any>(factory: PanelFactory<T>) {
  23. this.panelRegistry.set(factory.key, factory);
  24. }
  25. /** open panel */
  26. public open(key: string, area: Area = 'right', options?: PanelEntityConfig) {
  27. const factory = this.panelRegistry.get(key);
  28. if (!factory) {
  29. return;
  30. }
  31. const sameKeyPanels = this.getPanels(area).filter((p) => p.key === key);
  32. if (factory.keepDOM && sameKeyPanels.length) {
  33. const [panel] = sameKeyPanels;
  34. // move to last
  35. this.panels.delete(panel.id);
  36. this.panels.set(panel.id, panel);
  37. panel.visible = true;
  38. } else {
  39. if (!factory.allowDuplicates && sameKeyPanels.length) {
  40. sameKeyPanels.forEach((p) => this.remove(p.id));
  41. }
  42. const panel = this.createPanel({
  43. factory,
  44. config: {
  45. area,
  46. ...options,
  47. },
  48. });
  49. this.panels.set(panel.id, panel);
  50. }
  51. this.trim(area);
  52. this.onPanelsChangeEvent.fire();
  53. }
  54. /** close panel */
  55. public close(key?: string) {
  56. const panels = this.getPanels();
  57. const closedPanels = key ? panels.filter((p) => p.key === key) : panels;
  58. closedPanels.forEach((panel) => {
  59. this.remove(panel.id);
  60. });
  61. this.onPanelsChangeEvent.fire();
  62. }
  63. private trim(area: Area) {
  64. /** 1. general panel; 2. keepDOM visible panel */
  65. const panels = this.getPanels(area).filter((p) => !p.keepDOM || p.visible);
  66. const areaConfig = this.getAreaConfig(area);
  67. while (panels.length > areaConfig.max) {
  68. const removed = panels.shift();
  69. if (removed) {
  70. this.remove(removed.id);
  71. }
  72. }
  73. }
  74. private remove(id: string) {
  75. const panel = this.panels.get(id);
  76. if (!panel) {
  77. return;
  78. }
  79. if (panel.keepDOM) {
  80. panel.visible = false;
  81. } else {
  82. panel.dispose();
  83. this.panels.delete(id);
  84. }
  85. }
  86. getPanels(area?: Area) {
  87. const panels: PanelEntity[] = [];
  88. this.panels.forEach((panel) => {
  89. if (!area || panel.area === area) {
  90. panels.push(panel);
  91. }
  92. });
  93. return panels;
  94. }
  95. getAreaConfig(area: Area) {
  96. switch (area) {
  97. case 'docked-bottom':
  98. return this.config.dockedBottom;
  99. case 'docked-right':
  100. return this.config.dockedRight;
  101. case 'bottom':
  102. return this.config.bottom;
  103. case 'right':
  104. default:
  105. return this.config.right;
  106. }
  107. }
  108. dispose() {
  109. this.onPanelsChangeEvent.dispose();
  110. }
  111. }