Skip to content

Commit

Permalink
Merge pull request #1610 from merico-dev/client-panel-render
Browse files Browse the repository at this point in the history
feat(dashboard): add ClientPanelRender
  • Loading branch information
GerilLeto authored Jan 22, 2025
2 parents 15f45db + 4f8cb64 commit 6ca2be4
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useDashboardContext } from '~/contexts';
import { PanelRenderBase } from './panel-render-base';
import { PanelVizFeatures } from './panel-viz-features';

export interface IClientPanelRenderProps {
panelId: string;
}

/**
* Public API to render a panel on the dashboard user side.
* This component should be rendered by the ReadOnlyDashboard/DashboardEditor component, you can use it with the panel addon.
* @param props
* @constructor
*/
export function ClientPanelRender(props: IClientPanelRenderProps) {
const dashboardModel = useDashboardContext();
const panel = dashboardModel.content.panels.findByID(props.panelId);
if (!panel) {
return null;
}
return (
<PanelVizFeatures withAddon={false} withPanelTitle={false} withInteraction={false}>
<PanelRenderBase panel={panel} panelStyle={{}} />
</PanelVizFeatures>
);
}
1 change: 1 addition & 0 deletions dashboard/src/components/panel/panel-render/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './panel-render';
export * from './viz';
export * from './full-screen-render';
export * from './description-popover';
export * from './client-panel-render';
25 changes: 16 additions & 9 deletions dashboard/src/components/panel/panel-render/panel-render-base.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Box } from '@mantine/core';
import { EmotionSx } from '@mantine/emotion';
import { observer } from 'mobx-react-lite';
import { ReactNode } from 'react';
import { PanelContextProvider } from '~/contexts/panel-context';
import React, { ReactNode } from 'react';
import { PanelAddonProvider } from '~/components/plugins/panel-addon';
import { PanelContextProvider } from '~/contexts/panel-context';
import { PanelRenderModelInstance } from '~/model';
import { DescriptionPopover } from './description-popover';
import './panel-render-base.css';
import { PanelTitleBar } from './title-bar';
import { useDownloadPanelScreenshot } from './use-download-panel-screenshot';
import { PanelVizSection } from './viz';
import { usePanelVizFeatures } from '~/components/panel/panel-render/panel-viz-features';

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

export const PanelRenderBase = observer(({ panel, panelStyle, dropdownContent }: IPanelBase) => {
const { ref, downloadPanelScreenshot } = useDownloadPanelScreenshot(panel);
const { withAddon, withPanelTitle } = usePanelVizFeatures();
const OptionalAddon = withAddon ? PanelAddonProvider : React.Fragment;
return (
<PanelContextProvider
value={{
Expand All @@ -40,14 +43,18 @@ export const PanelRenderBase = observer(({ panel, panelStyle, dropdownContent }:
...panelStyle,
}}
>
<PanelAddonProvider>
<Box className="panel-description-popover-wrapper">
<DescriptionPopover />
</Box>
{dropdownContent}
<PanelTitleBar />
<OptionalAddon>
{withPanelTitle && (
<>
<Box className="panel-description-popover-wrapper">
<DescriptionPopover />
</Box>
{dropdownContent}
<PanelTitleBar />
</>
)}
<PanelVizSection panel={panel} />
</PanelAddonProvider>
</OptionalAddon>
</Box>
</PanelContextProvider>
);
Expand Down
36 changes: 36 additions & 0 deletions dashboard/src/components/panel/panel-render/panel-viz-features.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { defaults } from 'lodash';

export interface IPanelVizFeatures {
withInteraction: boolean;
/**
* Render panel title
* @default true
*/
withPanelTitle: boolean;
/**
* Render panel addon from plugins
* @default true
*/
withAddon: boolean;
}

const defaultValue = {
withInteraction: true,
withAddon: true,
withPanelTitle: true,
};
const PanelVizFeaturesContext = React.createContext<IPanelVizFeatures>(defaultValue);

export interface IPanelVizFeaturesProps extends Partial<IPanelVizFeatures> {
children: React.ReactNode;
}

export function PanelVizFeatures({ children, ...rest }: IPanelVizFeaturesProps) {
const value = defaults({}, rest, defaultValue);
return <PanelVizFeaturesContext.Provider value={value}>{children}</PanelVizFeaturesContext.Provider>;
}

export function usePanelVizFeatures(): IPanelVizFeatures {
return React.useContext(PanelVizFeaturesContext);
}
4 changes: 3 additions & 1 deletion dashboard/src/components/panel/panel-render/viz/viz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ErrorBoundary } from '~/utils';
import { usePanelAddonSlot } from '~/components/plugins/panel-addon';
import { LayoutStateContext, useRenderPanelContext } from '../../../../contexts';
import { IViewPanelInfo, PluginContext, tokens } from '../../../plugins';
import { usePanelVizFeatures } from '../panel-viz-features';
import { PluginVizViewComponent } from '../../plugin-adaptor';
import './viz.css';

Expand All @@ -31,7 +32,8 @@ function usePluginViz(data: TPanelData, measure: WidthAndHeight): ReactNode | nu
queryIDs,
viz,
};
const configureService = useConfigVizInstanceService(panel);
const { withInteraction } = usePanelVizFeatures();
const configureService = useConfigVizInstanceService(panel, withInteraction);
try {
// ensure that the plugin is loaded
vizManager.resolveComponent(viz.type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { InstanceMigrator } from '~/components/plugins/instance-migrator';
import { IServiceLocator } from '~/components/plugins/service/service-locator';
import { useRenderPanelContext } from '~/contexts';
import { InteractionManager, OPERATIONS } from '~/interactions';
import { NullInteractionManager } from '~/interactions/null-interaction-manager';

export function useConfigVizInstanceService(panel: IPanelInfo) {
export function useConfigVizInstanceService(panel: IPanelInfo, withInteraction = true) {
const { panel: panelModel } = useRenderPanelContext();
return useCallback(
(services: IServiceLocator) => {
Expand All @@ -16,7 +17,11 @@ export function useConfigVizInstanceService(panel: IPanelInfo) {
.provideFactory(tokens.instanceScope.vizInstance, () => vizManager.getOrCreateInstance(panel))
.provideFactory(tokens.instanceScope.interactionManager, (services) => {
const instance = services.getRequired(tokens.instanceScope.vizInstance);
return new InteractionManager(instance, component, OPERATIONS);
if (withInteraction) {
return new InteractionManager(instance, component, OPERATIONS);
} else {
return new NullInteractionManager();
}
})
.provideFactory(tokens.instanceScope.operationManager, (services) => {
// todo: create operation manager with instance
Expand All @@ -28,6 +33,6 @@ export function useConfigVizInstanceService(panel: IPanelInfo) {
.provideValue(tokens.instanceScope.panelModel, panelModel)
.provideFactory(tokens.instanceScope.migrator, (services) => new InstanceMigrator(services));
},
[panel.viz.type, panel.viz.conf],
[panel.viz.type, panel.viz.conf, withInteraction],
);
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { useCreation } from 'ahooks';
import { InteractionManager } from '~/interactions/interaction-manager';
import { OPERATIONS } from '~/interactions/operation/operations';
import { IVizManager } from '~/components/plugins';
import { IVizManager, tokens } from '~/components/plugins';
import { IVizInteractionManager, VizInstance } from '~/types/plugin';
import { useServiceLocator } from '~/components/plugins/service/service-locator/use-service-locator';

export const useCurrentInteractionManager = ({
vizManager,
instance,
}: {
export const useCurrentInteractionManager = ({}: {
vizManager: IVizManager;
instance: VizInstance;
}): IVizInteractionManager => {
return useCreation(
() => new InteractionManager(instance, vizManager.resolveComponent(instance.type), OPERATIONS),
[instance, vizManager],
);
const sl = useServiceLocator();
return sl.getRequired(tokens.instanceScope.interactionManager);
};
101 changes: 101 additions & 0 deletions dashboard/src/interactions/null-interaction-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { JsonPluginStorage } from '~/components/plugins/json-plugin-storage';
import {
IDashboardOperation,
IDashboardOperationSchema,
ITrigger,
ITriggerSchema,
IVizInteraction,
IVizInteractionManager,
IVizOperationManager,
IVizTriggerManager,
PluginStorage,
} from '~/types/plugin';

class NullTriggerManager implements IVizTriggerManager {
getTriggerSchemaList(): ITriggerSchema[] {
return [];
}
getTriggerList(): Promise<ITrigger[]> {
return Promise.resolve([]);
}
removeTrigger(): Promise<void> {
return Promise.resolve();
}
createOrGetTrigger(): Promise<ITrigger> {
return Promise.resolve(nullTrigger);
}
retrieveTrigger(): Promise<ITrigger | undefined> {
return Promise.resolve(nullTrigger);
}
watchTriggerSnapshotList(): () => void {
return () => {
return;
};
}
needMigration(): Promise<boolean> {
return Promise.resolve(false);
}
runMigration(): Promise<void> {
return Promise.resolve();
}
}

class NullTrigger implements ITrigger {
id = '';
schemaRef = '';
triggerData: PluginStorage = new JsonPluginStorage({});
}
const nullTrigger = new NullTrigger();

class NullOperationManager implements IVizOperationManager {
getOperationSchemaList(): IDashboardOperationSchema[] {
return [];
}
getOperationList(): Promise<IDashboardOperation[]> {
return Promise.resolve([]);
}
removeOperation(): Promise<void> {
return Promise.resolve();
}
createOrGetOperation(): Promise<IDashboardOperation> {
return Promise.resolve(nullOperation);
}
runOperation(): Promise<void> {
return Promise.resolve();
}
retrieveTrigger(): Promise<IDashboardOperation | undefined> {
return Promise.resolve(nullOperation);
}
runMigration(): Promise<void> {
return Promise.resolve();
}
needMigration(): Promise<boolean> {
return Promise.resolve(false);
}
}

class NullDashboardOperation implements IDashboardOperation {
id = '';
schemaRef = '';
operationData: PluginStorage = new JsonPluginStorage({});
}
const nullOperation = new NullDashboardOperation();

export class NullInteractionManager implements IVizInteractionManager {
triggerManager: IVizTriggerManager = new NullTriggerManager();
operationManager: IVizOperationManager = new NullOperationManager();
getInteractionList(): Promise<IVizInteraction[]> {
return Promise.resolve([]);
}
addInteraction(): Promise<void> {
return Promise.resolve();
}
removeInteraction(): Promise<void> {
return Promise.resolve();
}
runInteraction(): Promise<void> {
return Promise.resolve();
}

static instance = new NullInteractionManager();
}

0 comments on commit 6ca2be4

Please sign in to comment.