Skip to content

Commit 6ca2be4

Browse files
authored
Merge pull request #1610 from merico-dev/client-panel-render
feat(dashboard): add ClientPanelRender
2 parents 15f45db + 4f8cb64 commit 6ca2be4

File tree

8 files changed

+196
-25
lines changed

8 files changed

+196
-25
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useDashboardContext } from '~/contexts';
2+
import { PanelRenderBase } from './panel-render-base';
3+
import { PanelVizFeatures } from './panel-viz-features';
4+
5+
export interface IClientPanelRenderProps {
6+
panelId: string;
7+
}
8+
9+
/**
10+
* Public API to render a panel on the dashboard user side.
11+
* This component should be rendered by the ReadOnlyDashboard/DashboardEditor component, you can use it with the panel addon.
12+
* @param props
13+
* @constructor
14+
*/
15+
export function ClientPanelRender(props: IClientPanelRenderProps) {
16+
const dashboardModel = useDashboardContext();
17+
const panel = dashboardModel.content.panels.findByID(props.panelId);
18+
if (!panel) {
19+
return null;
20+
}
21+
return (
22+
<PanelVizFeatures withAddon={false} withPanelTitle={false} withInteraction={false}>
23+
<PanelRenderBase panel={panel} panelStyle={{}} />
24+
</PanelVizFeatures>
25+
);
26+
}

dashboard/src/components/panel/panel-render/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './panel-render';
22
export * from './viz';
33
export * from './full-screen-render';
44
export * from './description-popover';
5+
export * from './client-panel-render';

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { Box } from '@mantine/core';
22
import { EmotionSx } from '@mantine/emotion';
33
import { observer } from 'mobx-react-lite';
4-
import { ReactNode } from 'react';
5-
import { PanelContextProvider } from '~/contexts/panel-context';
4+
import React, { ReactNode } from 'react';
65
import { PanelAddonProvider } from '~/components/plugins/panel-addon';
6+
import { PanelContextProvider } from '~/contexts/panel-context';
77
import { PanelRenderModelInstance } from '~/model';
88
import { DescriptionPopover } from './description-popover';
99
import './panel-render-base.css';
1010
import { PanelTitleBar } from './title-bar';
1111
import { useDownloadPanelScreenshot } from './use-download-panel-screenshot';
1212
import { PanelVizSection } from './viz';
13+
import { usePanelVizFeatures } from '~/components/panel/panel-render/panel-viz-features';
1314

1415
interface IPanelBase {
1516
panel: PanelRenderModelInstance;
@@ -21,6 +22,8 @@ const baseStyle: EmotionSx = { border: '1px solid #e9ecef' };
2122

2223
export const PanelRenderBase = observer(({ panel, panelStyle, dropdownContent }: IPanelBase) => {
2324
const { ref, downloadPanelScreenshot } = useDownloadPanelScreenshot(panel);
25+
const { withAddon, withPanelTitle } = usePanelVizFeatures();
26+
const OptionalAddon = withAddon ? PanelAddonProvider : React.Fragment;
2427
return (
2528
<PanelContextProvider
2629
value={{
@@ -40,14 +43,18 @@ export const PanelRenderBase = observer(({ panel, panelStyle, dropdownContent }:
4043
...panelStyle,
4144
}}
4245
>
43-
<PanelAddonProvider>
44-
<Box className="panel-description-popover-wrapper">
45-
<DescriptionPopover />
46-
</Box>
47-
{dropdownContent}
48-
<PanelTitleBar />
46+
<OptionalAddon>
47+
{withPanelTitle && (
48+
<>
49+
<Box className="panel-description-popover-wrapper">
50+
<DescriptionPopover />
51+
</Box>
52+
{dropdownContent}
53+
<PanelTitleBar />
54+
</>
55+
)}
4956
<PanelVizSection panel={panel} />
50-
</PanelAddonProvider>
57+
</OptionalAddon>
5158
</Box>
5259
</PanelContextProvider>
5360
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
import { defaults } from 'lodash';
3+
4+
export interface IPanelVizFeatures {
5+
withInteraction: boolean;
6+
/**
7+
* Render panel title
8+
* @default true
9+
*/
10+
withPanelTitle: boolean;
11+
/**
12+
* Render panel addon from plugins
13+
* @default true
14+
*/
15+
withAddon: boolean;
16+
}
17+
18+
const defaultValue = {
19+
withInteraction: true,
20+
withAddon: true,
21+
withPanelTitle: true,
22+
};
23+
const PanelVizFeaturesContext = React.createContext<IPanelVizFeatures>(defaultValue);
24+
25+
export interface IPanelVizFeaturesProps extends Partial<IPanelVizFeatures> {
26+
children: React.ReactNode;
27+
}
28+
29+
export function PanelVizFeatures({ children, ...rest }: IPanelVizFeaturesProps) {
30+
const value = defaults({}, rest, defaultValue);
31+
return <PanelVizFeaturesContext.Provider value={value}>{children}</PanelVizFeaturesContext.Provider>;
32+
}
33+
34+
export function usePanelVizFeatures(): IPanelVizFeatures {
35+
return React.useContext(PanelVizFeaturesContext);
36+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { ErrorBoundary } from '~/utils';
1515
import { usePanelAddonSlot } from '~/components/plugins/panel-addon';
1616
import { LayoutStateContext, useRenderPanelContext } from '../../../../contexts';
1717
import { IViewPanelInfo, PluginContext, tokens } from '../../../plugins';
18+
import { usePanelVizFeatures } from '../panel-viz-features';
1819
import { PluginVizViewComponent } from '../../plugin-adaptor';
1920
import './viz.css';
2021

@@ -31,7 +32,8 @@ function usePluginViz(data: TPanelData, measure: WidthAndHeight): ReactNode | nu
3132
queryIDs,
3233
viz,
3334
};
34-
const configureService = useConfigVizInstanceService(panel);
35+
const { withInteraction } = usePanelVizFeatures();
36+
const configureService = useConfigVizInstanceService(panel, withInteraction);
3537
try {
3638
// ensure that the plugin is loaded
3739
vizManager.resolveComponent(viz.type);

dashboard/src/components/panel/use-config-viz-instance-service.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { InstanceMigrator } from '~/components/plugins/instance-migrator';
44
import { IServiceLocator } from '~/components/plugins/service/service-locator';
55
import { useRenderPanelContext } from '~/contexts';
66
import { InteractionManager, OPERATIONS } from '~/interactions';
7+
import { NullInteractionManager } from '~/interactions/null-interaction-manager';
78

8-
export function useConfigVizInstanceService(panel: IPanelInfo) {
9+
export function useConfigVizInstanceService(panel: IPanelInfo, withInteraction = true) {
910
const { panel: panelModel } = useRenderPanelContext();
1011
return useCallback(
1112
(services: IServiceLocator) => {
@@ -16,7 +17,11 @@ export function useConfigVizInstanceService(panel: IPanelInfo) {
1617
.provideFactory(tokens.instanceScope.vizInstance, () => vizManager.getOrCreateInstance(panel))
1718
.provideFactory(tokens.instanceScope.interactionManager, (services) => {
1819
const instance = services.getRequired(tokens.instanceScope.vizInstance);
19-
return new InteractionManager(instance, component, OPERATIONS);
20+
if (withInteraction) {
21+
return new InteractionManager(instance, component, OPERATIONS);
22+
} else {
23+
return new NullInteractionManager();
24+
}
2025
})
2126
.provideFactory(tokens.instanceScope.operationManager, (services) => {
2227
// todo: create operation manager with instance
@@ -28,6 +33,6 @@ export function useConfigVizInstanceService(panel: IPanelInfo) {
2833
.provideValue(tokens.instanceScope.panelModel, panelModel)
2934
.provideFactory(tokens.instanceScope.migrator, (services) => new InstanceMigrator(services));
3035
},
31-
[panel.viz.type, panel.viz.conf],
36+
[panel.viz.type, panel.viz.conf, withInteraction],
3237
);
3338
}
Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
1-
import { useCreation } from 'ahooks';
2-
import { InteractionManager } from '~/interactions/interaction-manager';
3-
import { OPERATIONS } from '~/interactions/operation/operations';
4-
import { IVizManager } from '~/components/plugins';
1+
import { IVizManager, tokens } from '~/components/plugins';
52
import { IVizInteractionManager, VizInstance } from '~/types/plugin';
3+
import { useServiceLocator } from '~/components/plugins/service/service-locator/use-service-locator';
64

7-
export const useCurrentInteractionManager = ({
8-
vizManager,
9-
instance,
10-
}: {
5+
export const useCurrentInteractionManager = ({}: {
116
vizManager: IVizManager;
127
instance: VizInstance;
138
}): IVizInteractionManager => {
14-
return useCreation(
15-
() => new InteractionManager(instance, vizManager.resolveComponent(instance.type), OPERATIONS),
16-
[instance, vizManager],
17-
);
9+
const sl = useServiceLocator();
10+
return sl.getRequired(tokens.instanceScope.interactionManager);
1811
};
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { JsonPluginStorage } from '~/components/plugins/json-plugin-storage';
2+
import {
3+
IDashboardOperation,
4+
IDashboardOperationSchema,
5+
ITrigger,
6+
ITriggerSchema,
7+
IVizInteraction,
8+
IVizInteractionManager,
9+
IVizOperationManager,
10+
IVizTriggerManager,
11+
PluginStorage,
12+
} from '~/types/plugin';
13+
14+
class NullTriggerManager implements IVizTriggerManager {
15+
getTriggerSchemaList(): ITriggerSchema[] {
16+
return [];
17+
}
18+
getTriggerList(): Promise<ITrigger[]> {
19+
return Promise.resolve([]);
20+
}
21+
removeTrigger(): Promise<void> {
22+
return Promise.resolve();
23+
}
24+
createOrGetTrigger(): Promise<ITrigger> {
25+
return Promise.resolve(nullTrigger);
26+
}
27+
retrieveTrigger(): Promise<ITrigger | undefined> {
28+
return Promise.resolve(nullTrigger);
29+
}
30+
watchTriggerSnapshotList(): () => void {
31+
return () => {
32+
return;
33+
};
34+
}
35+
needMigration(): Promise<boolean> {
36+
return Promise.resolve(false);
37+
}
38+
runMigration(): Promise<void> {
39+
return Promise.resolve();
40+
}
41+
}
42+
43+
class NullTrigger implements ITrigger {
44+
id = '';
45+
schemaRef = '';
46+
triggerData: PluginStorage = new JsonPluginStorage({});
47+
}
48+
const nullTrigger = new NullTrigger();
49+
50+
class NullOperationManager implements IVizOperationManager {
51+
getOperationSchemaList(): IDashboardOperationSchema[] {
52+
return [];
53+
}
54+
getOperationList(): Promise<IDashboardOperation[]> {
55+
return Promise.resolve([]);
56+
}
57+
removeOperation(): Promise<void> {
58+
return Promise.resolve();
59+
}
60+
createOrGetOperation(): Promise<IDashboardOperation> {
61+
return Promise.resolve(nullOperation);
62+
}
63+
runOperation(): Promise<void> {
64+
return Promise.resolve();
65+
}
66+
retrieveTrigger(): Promise<IDashboardOperation | undefined> {
67+
return Promise.resolve(nullOperation);
68+
}
69+
runMigration(): Promise<void> {
70+
return Promise.resolve();
71+
}
72+
needMigration(): Promise<boolean> {
73+
return Promise.resolve(false);
74+
}
75+
}
76+
77+
class NullDashboardOperation implements IDashboardOperation {
78+
id = '';
79+
schemaRef = '';
80+
operationData: PluginStorage = new JsonPluginStorage({});
81+
}
82+
const nullOperation = new NullDashboardOperation();
83+
84+
export class NullInteractionManager implements IVizInteractionManager {
85+
triggerManager: IVizTriggerManager = new NullTriggerManager();
86+
operationManager: IVizOperationManager = new NullOperationManager();
87+
getInteractionList(): Promise<IVizInteraction[]> {
88+
return Promise.resolve([]);
89+
}
90+
addInteraction(): Promise<void> {
91+
return Promise.resolve();
92+
}
93+
removeInteraction(): Promise<void> {
94+
return Promise.resolve();
95+
}
96+
runInteraction(): Promise<void> {
97+
return Promise.resolve();
98+
}
99+
100+
static instance = new NullInteractionManager();
101+
}

0 commit comments

Comments
 (0)