Skip to content

Commit 2e1ec8f

Browse files
authored
Merge pull request #1604 from merico-dev/panel-addon
feat: add panelAddon plugin
2 parents 4271700 + e14de1e commit 2e1ec8f

File tree

14 files changed

+178
-23
lines changed

14 files changed

+178
-23
lines changed

dashboard/src/components/panel/panel-render/panel-render-base.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { EmotionSx } from '@mantine/emotion';
33
import { observer } from 'mobx-react-lite';
44
import { ReactNode } from 'react';
55
import { PanelContextProvider } from '~/contexts/panel-context';
6+
import { PanelAddonProvider } from '~/components/plugins/panel-addon';
67
import { PanelRenderModelInstance } from '~/model';
78
import { DescriptionPopover } from './description-popover';
89
import './panel-render-base.css';
@@ -20,7 +21,6 @@ const baseStyle: EmotionSx = { border: '1px solid #e9ecef' };
2021

2122
export const PanelRenderBase = observer(({ panel, panelStyle, dropdownContent }: IPanelBase) => {
2223
const { ref, downloadPanelScreenshot } = useDownloadPanelScreenshot(panel);
23-
const titleHeight = panel.title.show ? '60px' : '28px';
2424
return (
2525
<PanelContextProvider
2626
value={{
@@ -40,12 +40,14 @@ export const PanelRenderBase = observer(({ panel, panelStyle, dropdownContent }:
4040
...panelStyle,
4141
}}
4242
>
43-
<Box className="panel-description-popover-wrapper">
44-
<DescriptionPopover />
45-
</Box>
46-
{dropdownContent}
47-
<PanelTitleBar />
48-
<PanelVizSection panel={panel} />
43+
<PanelAddonProvider>
44+
<Box className="panel-description-popover-wrapper">
45+
<DescriptionPopover />
46+
</Box>
47+
{dropdownContent}
48+
<PanelTitleBar />
49+
<PanelVizSection panel={panel} />
50+
</PanelAddonProvider>
4951
</Box>
5052
</PanelContextProvider>
5153
);

dashboard/src/components/panel/panel-render/viz/viz.tsx

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import { useElementSize } from '@mantine/hooks';
22
import { get } from 'lodash';
3+
import { createPortal } from 'react-dom';
34

45
import { Box } from '@mantine/core';
56
import { observer } from 'mobx-react-lite';
67
import { ReactNode, useContext } from 'react';
78
import { useConfigVizInstanceService } from '~/components/panel/use-config-viz-instance-service';
8-
import { ServiceLocatorProvider } from '~/components/plugins/service/service-locator/use-service-locator';
9+
import {
10+
ServiceLocatorProvider,
11+
useServiceLocator,
12+
} from '~/components/plugins/service/service-locator/use-service-locator';
913
import { WidthAndHeight } from '~/components/plugins/viz-manager/components';
1014
import { ErrorBoundary } from '~/utils';
11-
import { useRenderPanelContext } from '../../../../contexts';
12-
import { IViewPanelInfo, PluginContext } from '../../../plugins';
15+
import { usePanelAddonSlot } from '~/components/plugins/panel-addon';
16+
import { LayoutStateContext, useRenderPanelContext } from '../../../../contexts';
17+
import { IViewPanelInfo, PluginContext, tokens } from '../../../plugins';
1318
import { PluginVizViewComponent } from '../../plugin-adaptor';
1419
import './viz.css';
1520

@@ -40,6 +45,7 @@ function usePluginViz(data: TPanelData, measure: WidthAndHeight): ReactNode | nu
4045
variables={variables}
4146
vizManager={vizManager}
4247
/>
48+
<PanelVizAddons />
4349
</ServiceLocatorProvider>
4450
);
4551
} catch (e) {
@@ -64,3 +70,24 @@ export const Viz = observer(function _Viz({ data }: IViz) {
6470
</div>
6571
);
6672
});
73+
74+
export const PanelVizAddons = () => {
75+
const sl = useServiceLocator();
76+
const instance = sl.getRequired(tokens.instanceScope.vizInstance);
77+
const { inEditMode } = useContext(LayoutStateContext);
78+
const addonManager = sl.getRequired(tokens.panelAddonManager);
79+
const panelRoot = usePanelAddonSlot();
80+
if (!panelRoot) {
81+
return null;
82+
}
83+
return createPortal(
84+
<>
85+
{addonManager.createPanelAddonNode({
86+
viz: instance,
87+
isInEditMode: inEditMode,
88+
})}
89+
</>,
90+
panelRoot,
91+
'addon',
92+
);
93+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './panel-addon-manager';
2+
export * from './panel-addon-context';
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React, { useId } from 'react';
2+
3+
const PanelAddonContext = React.createContext<{ addonSlotId: string | null }>({ addonSlotId: null });
4+
5+
export function PanelAddonProvider({ children }: { children: React.ReactNode }) {
6+
const id = `panel-addon-slot-${useId()}`;
7+
return (
8+
<PanelAddonContext.Provider value={{ addonSlotId: id }}>
9+
<div style={{ position: 'static', top: 0, left: 0 }} id={id}></div>
10+
{children}
11+
</PanelAddonContext.Provider>
12+
);
13+
}
14+
15+
export function usePanelAddonSlot() {
16+
const { addonSlotId } = React.useContext(PanelAddonContext);
17+
if (!addonSlotId) {
18+
return null;
19+
}
20+
return document.getElementById(addonSlotId);
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { IPanelAddon, IPanelAddonRenderProps, IPluginManager } from '~/types/plugin';
2+
import React from 'react';
3+
4+
export class PanelAddonManager {
5+
constructor(private pluginManager: IPluginManager) {}
6+
7+
createPanelAddonNode(props: IPanelAddonRenderProps) {
8+
const addons = this.pluginManager.installedPlugins
9+
.flatMap((it) => it.manifest.panelAddon)
10+
.filter((it) => !!it) as IPanelAddon[];
11+
const nodes = addons.map((addon) => {
12+
return React.createElement(addon.addonRender, { ...props, key: addon.name });
13+
});
14+
return <>{nodes}</>;
15+
}
16+
}

dashboard/src/components/plugins/plugin-context.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createContext } from 'react';
22
import { Blue, Green, Orange, Red, RedGreen, YellowBlue } from '~/components/plugins/colors';
33
import { InstanceMigrator } from '~/components/plugins/instance-migrator';
44
import { token } from '~/components/plugins/service/service-locator';
5+
import { PanelAddonManager } from '~/components/plugins/panel-addon';
56

67
import { PanelModelInstance } from '~/dashboard-editor/model/panels';
78
import {
@@ -39,12 +40,12 @@ import { HorizontalBarChartVizComponent } from './viz-components/horizontal-bar-
3940
import { MericoEstimationChartVizComponent } from './viz-components/merico-estimation-chart';
4041
import { MericoStatsVizComponent } from './viz-components/merico-stats';
4142
import { MericoHeatmapVizComponent } from './viz-components/merico-heatmap';
42-
import _ from 'lodash';
4343

4444
export interface IPluginContextProps {
4545
pluginManager: IPluginManager;
4646
vizManager: VizManager;
4747
colorManager: IColorManager;
48+
panelAddonManager: PanelAddonManager;
4849
}
4950

5051
const basicColors = [
@@ -170,6 +171,7 @@ export const tokens = {
170171
pluginManager: token<IPluginManager>('pluginManager'),
171172
vizManager: token<VizManager>('vizManager'),
172173
colorManager: token<IColorManager>('colorManager'),
174+
panelAddonManager: token<PanelAddonManager>('panelAddonManager'),
173175
instanceScope: {
174176
panelModel: token<PanelModelInstance>('panelModel'),
175177
vizInstance: token<VizInstance>('vizInstance'),
@@ -189,7 +191,8 @@ export const createPluginContext = (): IPluginContextProps => {
189191
}
190192
const vizManager = new VizManager(pluginManager);
191193
const colorManager = new ColorManager(pluginManager);
192-
return { pluginManager, vizManager, colorManager };
194+
const panelAddonManager = new PanelAddonManager(pluginManager);
195+
return { pluginManager, vizManager, colorManager, panelAddonManager };
193196
};
194197

195198
export const PluginContext = createContext<IPluginContextProps>(createPluginContext());

dashboard/src/components/plugins/service/use-top-level-services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export function useTopLevelServices(pluginContext: IPluginContextProps) {
77
return services
88
.provideValue(tokens.pluginManager, pluginContext.pluginManager)
99
.provideValue(tokens.vizManager, pluginContext.vizManager)
10+
.provideValue(tokens.panelAddonManager, pluginContext.panelAddonManager)
1011
.provideValue(tokens.colorManager, pluginContext.colorManager);
1112
}, []);
1213
}

dashboard/src/components/plugins/viz-components/cartesian/viz-cartesian-chart.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { EChartsInstance } from 'echarts-for-react';
22
import ReactEChartsCore from 'echarts-for-react/lib/core';
33
import * as echarts from 'echarts/core';
44
import _, { defaults } from 'lodash';
5+
import { useLatest } from 'ahooks';
56
import { observer } from 'mobx-react-lite';
67
import React, { useCallback, useEffect, useMemo, useState } from 'react';
78
import { useStorageData } from '~/components/plugins/hooks';
@@ -33,13 +34,15 @@ function Chart({
3334
height,
3435
interactionManager,
3536
variables,
37+
onChartRenderFinished,
3638
}: {
3739
conf: ICartesianChartConf;
3840
data: TPanelData;
3941
width: number;
4042
height: number;
4143
interactionManager: IVizInteractionManager;
4244
variables: ITemplateVariable[];
45+
onChartRenderFinished: (chartOptions: unknown) => void;
4346
}) {
4447
const rowDataMap = useRowDataMap(data, conf.x_axis_data_key);
4548

@@ -60,13 +63,18 @@ function Chart({
6063
}, [conf, data]);
6164

6265
const echartsRef = React.useRef<EChartsInstance>();
66+
const onRenderFinishedRef = useLatest(onChartRenderFinished);
67+
const handleEChartsFinished = useCallback(() => {
68+
onRenderFinishedRef.current(echartsRef.current?.getOption());
69+
}, []);
6370
const onEvents = useMemo(() => {
6471
return {
6572
click: handleSeriesClick,
73+
finished: handleEChartsFinished,
6674
};
6775
}, [handleSeriesClick]);
6876

69-
const onChartReady = (echartsInstance: EChartsInstance) => {
77+
const handleChartReady = (echartsInstance: EChartsInstance) => {
7078
echartsRef.current = echartsInstance;
7179
updateRegressionLinesColor(echartsInstance);
7280
};
@@ -85,7 +93,7 @@ function Chart({
8593
option={option}
8694
style={{ width, height }}
8795
onEvents={onEvents}
88-
onChartReady={onChartReady}
96+
onChartReady={handleChartReady}
8997
notMerge
9098
theme="merico-light"
9199
/>
@@ -109,11 +117,16 @@ export const VizCartesianChart = observer(({ context, instance }: VizViewProps)
109117
const finalHeight = Math.max(0, getBoxContentHeight(height) - topStatsHeight - bottomStatsHeight);
110118
const finalWidth = getBoxContentWidth(width);
111119

120+
function handleChartRenderFinished(chartOptions: unknown) {
121+
instance.messageChannels.getChannel('viz').emit('rendered', chartOptions);
122+
}
123+
112124
return (
113125
<DefaultVizBox width={width} height={height}>
114126
<StatsAroundViz onHeightChange={setTopStatsHeight} value={conf.stats.top} context={context} />
115127

116128
<Chart
129+
onChartRenderFinished={handleChartRenderFinished}
117130
variables={variables}
118131
width={finalWidth}
119132
height={finalHeight}

dashboard/src/contexts/layout-state-context.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import _ from 'lodash';
21
import React from 'react';
32

43
export interface ILayoutStateContext {

dashboard/src/dashboard-editor/ui/settings/content/edit-panel/preview-panel.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import '~/components/panel/panel-render/panel-render-base.css';
55
import { PanelVizSection } from '~/components/panel/panel-render/viz/panel-viz-section';
66
import { useRenderPanelContext } from '~/contexts';
77
import { ErrorBoundary } from '~/utils';
8+
import { PanelAddonProvider } from '~/components/plugins/panel-addon';
89

910
const PreviewTitleBar = observer(() => {
1011
const { panel } = useRenderPanelContext();
@@ -38,11 +39,13 @@ export const PreviewPanel = observer(() => {
3839
height: '450px !important',
3940
}}
4041
>
41-
<Box className="panel-description-popover-wrapper">
42-
<DescriptionPopover />
43-
</Box>
44-
<PreviewTitleBar />
45-
<PanelVizSection panel={panel} />
42+
<PanelAddonProvider>
43+
<Box className="panel-description-popover-wrapper">
44+
<DescriptionPopover />
45+
</Box>
46+
<PreviewTitleBar />
47+
<PanelVizSection panel={panel} />
48+
</PanelAddonProvider>
4649
</Box>
4750
</ErrorBoundary>
4851
);

0 commit comments

Comments
 (0)