panel.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. /**
  2. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  3. * SPDX-License-Identifier: MIT
  4. */
  5. import { useEffect, startTransition, useState, useRef } from 'react';
  6. import { clsx } from 'clsx';
  7. import { Area } from '../../types';
  8. import { PanelEntity } from '../../services/panel-factory';
  9. import { usePanelManager } from '../../hooks/use-panel-manager';
  10. import { usePanelStore } from '../../hooks/use-panel';
  11. import { PanelContext } from '../../contexts';
  12. const PanelItem: React.FC<{ panel: PanelEntity }> = ({ panel }) => {
  13. const panelManager = usePanelManager();
  14. const ref = useRef<HTMLDivElement>(null);
  15. const isHorizontal = ['right', 'docked-right'].includes(panel.area);
  16. const { size, fullscreen, visible } = usePanelStore((s) => ({ size: s.size, fullscreen: s.fullscreen, visible: s.visible }));
  17. const [layerSize, setLayerSize] = useState(size);
  18. const [displayStyle, setDisplayStyle] = useState({});
  19. const currentSize = fullscreen ? layerSize : size;
  20. const sizeStyle = isHorizontal ? { width: currentSize } : { height: currentSize };
  21. const handleResize = (next: number) => {
  22. let nextSize = next;
  23. if (typeof panel.factory.maxSize === 'number' && nextSize > panel.factory.maxSize) {
  24. nextSize = panel.factory.maxSize;
  25. } else if (typeof panel.factory.minSize === 'number' && nextSize < panel.factory.minSize) {
  26. nextSize = panel.factory.minSize;
  27. }
  28. panel.store.setState({ size: nextSize });
  29. };
  30. useEffect(() => {
  31. /** The set size may be illegal and needs to be updated according to the real element rendered for the first time. */
  32. if (ref.current && !fullscreen) {
  33. const { width, height } = ref.current.getBoundingClientRect();
  34. const realSize = isHorizontal ? width : height;
  35. panel.store.setState({ size: realSize });
  36. }
  37. }, [fullscreen]);
  38. useEffect(() => {
  39. if (!fullscreen) {
  40. return;
  41. }
  42. const layer = panel.layer;
  43. if (!layer) {
  44. return;
  45. }
  46. const observer = new ResizeObserver(([entry]) => {
  47. const { width, height } = entry.contentRect;
  48. setLayerSize(isHorizontal ? width : height);
  49. });
  50. observer.observe(layer);
  51. return () => observer.disconnect();
  52. }, [fullscreen]);
  53. useEffect(() => {
  54. if (panel.keepDOM) {
  55. setDisplayStyle({ display: visible ? 'block' : 'none' });
  56. }
  57. }, [visible]);
  58. return (
  59. <div
  60. className={clsx(
  61. 'gedit-flow-panel-wrap',
  62. isHorizontal ? 'panel-horizontal' : 'panel-vertical'
  63. )}
  64. key={panel.id}
  65. ref={ref}
  66. style={{ ...displayStyle, ...panel.factory.style, ...panel.config.style, ...sizeStyle }}
  67. >
  68. {panel.resizable &&
  69. panelManager.config.resizeBarRender({
  70. size,
  71. direction: isHorizontal ? 'vertical' : 'horizontal',
  72. onResize: handleResize,
  73. })}
  74. {panel.renderer}
  75. </div>
  76. );
  77. };
  78. export const PanelArea: React.FC<{ area: Area }> = ({ area }) => {
  79. const panelManager = usePanelManager();
  80. const [panels, setPanels] = useState(panelManager.getPanels(area));
  81. useEffect(() => {
  82. const dispose = panelManager.onPanelsChange(() => {
  83. startTransition(() => {
  84. setPanels(panelManager.getPanels(area));
  85. });
  86. });
  87. return () => dispose.dispose();
  88. }, []);
  89. return (
  90. <>
  91. {panels.map((panel) => (
  92. <PanelContext.Provider value={panel} key={panel.id}>
  93. <PanelItem panel={panel} />
  94. </PanelContext.Provider>
  95. ))}
  96. </>
  97. );
  98. };