diff --git a/src/frontend/src/components/plugins/PluginPanel.tsx b/src/frontend/src/components/plugins/PluginPanel.tsx new file mode 100644 index 0000000000..aef7959ed3 --- /dev/null +++ b/src/frontend/src/components/plugins/PluginPanel.tsx @@ -0,0 +1,93 @@ +import { t } from '@lingui/macro'; +import { Alert, Text } from '@mantine/core'; +import { AxiosInstance } from 'axios'; +import { useEffect, useRef } from 'react'; + +import { api } from '../../App'; +import { ModelType } from '../../enums/ModelType'; +import { PanelType } from '../nav/Panel'; + +interface PluginPanelProps extends PanelType { + src?: string; + params?: any; + targetModel?: ModelType | string; + targetId?: string | number | null; +} + +/* + * Definition of what we pass into a plugin panel + */ +interface PluginPanelParameters { + target: HTMLDivElement; + props: PluginPanelProps; + targetModel?: ModelType | string; + targetId?: number; + api: AxiosInstance; +} + +// Placeholder content for a panel with no content +function PanelNoContent() { + return ( + <Alert color="red" title={t`No Content`}> + <Text>{t`No content provided for this plugin`}</Text> + </Alert> + ); +} + +/** + * TODO: Provide more context information to the plugin renderer: + * + * - api instance + * - custom context data from server + */ + +/** + * A custom panel which can be used to display plugin content. + * + * - Content is loaded dynamically (via the API) when a page is first loaded + * - Content can be provided from an external javascript module, or with raw HTML + * + * If content is provided from an external source, it is expected to define a function `render_panel` which will render the content. + * const render_panel = (element: HTMLElement, params: any) => {...} + * + * Where: + * - `element` is the HTML element to render the content into + * - `params` is the set of run-time parameters to pass to the content rendering function + */ +export default function PluginPanel({ props }: { props: PluginPanelProps }) { + const ref = useRef<HTMLDivElement>(); + + const loadExternalSource = async () => { + // Load content from external source + const src = await import(/* @vite-ignore */ props.src ?? ''); + + // We expect the external source to define a function which will render the content + if (src && src.render_panel && typeof src.render_panel === 'function') { + src.render_panel({ + target: ref.current, + props: props, + api: api, + targetModel: props.targetModel, + targetId: props.targetId + }); + } + }; + + useEffect(() => { + if (props.src) { + // Load content from external source + loadExternalSource(); + } else if (props.content) { + // If content is provided directly, render it into the panel + // ref.current.innerHTML = props.content; + } else { + // Something... went wrong? + } + }, [props]); + + if (!props.content && !props.src) { + return <PanelNoContent />; + } + + return <div ref={ref as any}>{props.content}</div>; +} diff --git a/src/frontend/src/hooks/UsePluginPanels.tsx b/src/frontend/src/hooks/UsePluginPanels.tsx index 52382bc995..b37d311e74 100644 --- a/src/frontend/src/hooks/UsePluginPanels.tsx +++ b/src/frontend/src/hooks/UsePluginPanels.tsx @@ -5,6 +5,7 @@ import { useMemo } from 'react'; import { api } from '../App'; import { PanelType } from '../components/nav/Panel'; +import PluginPanel from '../components/plugins/PluginPanel'; import { ApiEndpoints } from '../enums/ApiEndpoints'; import { ModelType } from '../enums/ModelType'; import { identifierString } from '../functions/conversion'; @@ -16,15 +17,6 @@ export type PluginPanelState = { panels: PanelType[]; }; -// Placeholder content for a panel with no content -function PanelNoContent() { - return ( - <Alert color="red" title={t`No Content`}> - <Text>{t`No content provided for this plugin`}</Text> - </Alert> - ); -} - export function usePluginPanels({ targetModel, targetId @@ -39,6 +31,7 @@ export function usePluginPanels({ [globalSettings] ); + // API query to fetch information on available plugin panels const { isFetching, data } = useQuery({ enabled: pluginPanelsEnabled && !!targetModel, queryKey: [targetModel, targetId], @@ -70,7 +63,7 @@ export function usePluginPanels({ name: identifierString(`${pluginKey}-${panel.name}`), label: panel.label || t`Plugin Panel`, icon: <InvenTreeIcon icon={panel.icon ?? 'plugin'} />, - content: panel.content || <PanelNoContent /> + content: <PluginPanel props={panel} /> }; }) ?? [] );