Adds <PluginPanel> component for handling panel render

This commit is contained in:
Oliver Walters 2024-08-11 09:49:35 +00:00
parent a23bfdfc6f
commit f2153bfb60
2 changed files with 96 additions and 10 deletions

View File

@ -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>;
}

View File

@ -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} />
};
}) ?? []
);