2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-16 03:55:41 +00:00

provide exportable types

This commit is contained in:
wolflu05
2024-09-18 10:09:06 +02:00
parent 0918fd8fa6
commit 15962e2f57
8 changed files with 133 additions and 89 deletions

View File

@ -9,7 +9,6 @@ import { useMemo } from 'react';
import { NavigateFunction, useNavigate } from 'react-router-dom'; import { NavigateFunction, useNavigate } from 'react-router-dom';
import { api } from '../../App'; import { api } from '../../App';
import { ModelType } from '../../enums/ModelType';
import { useLocalState } from '../../states/LocalState'; import { useLocalState } from '../../states/LocalState';
import { import {
SettingsStateProps, SettingsStateProps,
@ -18,13 +17,10 @@ import {
} from '../../states/SettingsState'; } from '../../states/SettingsState';
import { UserStateProps, useUserState } from '../../states/UserState'; import { UserStateProps, useUserState } from '../../states/UserState';
/* /**
* A set of properties which are passed to a plugin, * A set of properties which are passed to a plugin,
* for rendering an element in the user interface. * for rendering an element in the user interface.
* *
* @param model - The model type for the plugin (e.g. 'part' / 'purchaseorder')
* @param id - The ID (primary key) of the model instance for the plugin
* @param instance - The model instance data (if available)
* @param api - The Axios API instance (see ../states/ApiState.tsx) * @param api - The Axios API instance (see ../states/ApiState.tsx)
* @param user - The current user instance (see ../states/UserState.tsx) * @param user - The current user instance (see ../states/UserState.tsx)
* @param userSettings - The current user settings (see ../states/SettingsState.tsx) * @param userSettings - The current user settings (see ../states/SettingsState.tsx)
@ -33,10 +29,7 @@ import { UserStateProps, useUserState } from '../../states/UserState';
* @param theme - The current Mantine theme * @param theme - The current Mantine theme
* @param colorScheme - The current Mantine color scheme (e.g. 'light' / 'dark') * @param colorScheme - The current Mantine color scheme (e.g. 'light' / 'dark')
*/ */
export type PluginContext = { export type InvenTreeContext = {
model?: ModelType | string;
id?: string | number | null;
instance?: any;
api: AxiosInstance; api: AxiosInstance;
user: UserStateProps; user: UserStateProps;
userSettings: SettingsStateProps; userSettings: SettingsStateProps;
@ -47,7 +40,7 @@ export type PluginContext = {
colorScheme: MantineColorScheme; colorScheme: MantineColorScheme;
}; };
export const usePluginContext = () => { export const useInvenTreeContext = () => {
const host = useLocalState.getState().host; const host = useLocalState.getState().host;
const navigate = useNavigate(); const navigate = useNavigate();
const user = useUserState(); const user = useUserState();
@ -56,7 +49,7 @@ export const usePluginContext = () => {
const globalSettings = useGlobalSettingsState(); const globalSettings = useGlobalSettingsState();
const userSettings = useUserSettingsState(); const userSettings = useUserSettingsState();
const contextData: PluginContext = useMemo(() => { const contextData = useMemo<InvenTreeContext>(() => {
return { return {
user: user, user: user,
host: host, host: host,

View File

@ -3,7 +3,7 @@ import { Alert, Stack, Text } from '@mantine/core';
import { IconExclamationCircle } from '@tabler/icons-react'; import { IconExclamationCircle } from '@tabler/icons-react';
import { ReactNode, useEffect, useRef, useState } from 'react'; import { ReactNode, useEffect, useRef, useState } from 'react';
import { PluginContext } from './PluginContext'; import { InvenTreeContext } from './PluginContext';
import { findExternalPluginFunction } from './PluginSource'; import { findExternalPluginFunction } from './PluginSource';
// Definition of the plugin panel properties, provided by the server API // Definition of the plugin panel properties, provided by the server API
@ -21,7 +21,7 @@ export async function isPluginPanelHidden({
pluginContext pluginContext
}: { }: {
pluginProps: PluginPanelProps; pluginProps: PluginPanelProps;
pluginContext: PluginContext; pluginContext: InvenTreeContext;
}): Promise<boolean> { }): Promise<boolean> {
if (!pluginProps.source) { if (!pluginProps.source) {
// No custom source supplied - panel is not hidden // No custom source supplied - panel is not hidden
@ -66,7 +66,7 @@ export default function PluginPanelContent({
pluginContext pluginContext
}: Readonly<{ }: Readonly<{
pluginProps: PluginPanelProps; pluginProps: PluginPanelProps;
pluginContext: PluginContext; pluginContext: InvenTreeContext;
}>): ReactNode { }>): ReactNode {
const ref = useRef<HTMLDivElement>(); const ref = useRef<HTMLDivElement>();

View File

@ -36,7 +36,7 @@ export async function loadExternalPluginSource(source: string) {
export async function findExternalPluginFunction( export async function findExternalPluginFunction(
source: string, source: string,
functionName: string functionName: string
) { ): Promise<Function | null> {
const module = await loadExternalPluginSource(source); const module = await loadExternalPluginSource(source);
if (module && module[functionName]) { if (module && module[functionName]) {

View File

@ -11,36 +11,15 @@ import {
import { TemplateI } from '../../tables/settings/TemplateTable'; import { TemplateI } from '../../tables/settings/TemplateTable';
import { EditorComponent } from '../editors/TemplateEditor/TemplateEditor'; import { EditorComponent } from '../editors/TemplateEditor/TemplateEditor';
import {
PluginUIFuncWithoutInvenTreeContextType,
TemplateEditorUIFeature
} from './PluginUIFeatureTypes';
// Definition of the plugin ui feature properties, provided by the server API export const getPluginTemplateEditor = (
export type PluginUIFeatureProps = ( func: PluginUIFuncWithoutInvenTreeContextType<TemplateEditorUIFeature>,
| { template: TemplateI
feature_type: 'template_editor'; ) =>
options: {
title: string;
slug: string;
};
}
| {
feature_type: 'template_preview';
options: {
title: string;
slug: string;
};
}
) & {
source: string;
};
export type TemplateEditorRenderContextType = {
registerHandlers: (params: {
setCode: (code: string) => void;
getCode: () => string;
}) => void;
template: TemplateI;
};
export const getPluginTemplateEditor = (func: any, template: any) =>
forwardRef((props, ref) => { forwardRef((props, ref) => {
const elRef = useRef<HTMLDivElement>(); const elRef = useRef<HTMLDivElement>();
const [error, setError] = useState<string | undefined>(undefined); const [error, setError] = useState<string | undefined>(undefined);
@ -64,7 +43,7 @@ export const getPluginTemplateEditor = (func: any, template: any) =>
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
await func(elRef.current, { await func(elRef.current!, {
registerHandlers: ({ getCode, setCode }) => { registerHandlers: ({ getCode, setCode }) => {
setCodeRef.current = setCode; setCodeRef.current = setCode;
getCodeRef.current = getCode; getCodeRef.current = getCode;
@ -74,7 +53,7 @@ export const getPluginTemplateEditor = (func: any, template: any) =>
} }
}, },
template template
} as TemplateEditorRenderContextType); });
} catch (error) { } catch (error) {
setError(t`Error occurred while rendering the template editor.`); setError(t`Error occurred while rendering the template editor.`);
console.error(error); console.error(error);

View File

@ -0,0 +1,67 @@
import { InvenTreeIconType } from '../../functions/icons';
import { TemplateI } from '../../tables/settings/TemplateTable';
import { InvenTreeContext } from './PluginContext';
// #region Type Helpers
export type BaseUIFeature = {
featureType: string;
requestContext: Record<string, any>;
responseOptions: Record<string, any>;
renderContext: Record<string, any>;
};
export type PluginUIGetFeatureType<T extends BaseUIFeature> = (
ref: HTMLDivElement,
params: {
renderContext: T['renderContext'];
inventreeContext: InvenTreeContext;
}
) => void;
export type PluginUIFuncWithoutInvenTreeContextType<T extends BaseUIFeature> = (
ref: HTMLDivElement,
renderContext: T['renderContext']
) => void;
export type PluginUIFeatureAPIResponse<T extends BaseUIFeature> = {
feature_type: T['featureType'];
options: T['responseOptions'];
source: string;
};
// #region Types
export type TemplateEditorUIFeature = {
featureType: 'template_editor';
requestContext: {
template_type: string;
template_model: string;
};
responseOptions: {
key: string;
title: string;
icon: InvenTreeIconType;
};
renderContext: {
registerHandlers: (params: {
setCode: (code: string) => void;
getCode: () => string;
}) => void;
template: TemplateI;
};
};
export type TemplatePreviewUIFeature = {
featureType: 'template_preview';
requestContext: {
template_type: string;
template_model: string;
};
responseOptions: {
key: string;
title: string;
icon: InvenTreeIconType;
};
renderContext: {
template: TemplateI;
};
};

View File

@ -4,8 +4,8 @@ import { useEffect, useMemo, useState } from 'react';
import { api } from '../App'; import { api } from '../App';
import { PanelType } from '../components/nav/Panel'; import { PanelType } from '../components/nav/Panel';
import { import {
PluginContext, InvenTreeContext,
usePluginContext useInvenTreeContext
} from '../components/plugins/PluginContext'; } from '../components/plugins/PluginContext';
import PluginPanelContent, { import PluginPanelContent, {
PluginPanelProps, PluginPanelProps,
@ -18,6 +18,17 @@ import { InvenTreeIcon, InvenTreeIconType } from '../functions/icons';
import { apiUrl } from '../states/ApiState'; import { apiUrl } from '../states/ApiState';
import { useGlobalSettingsState } from '../states/SettingsState'; import { useGlobalSettingsState } from '../states/SettingsState';
/**
* @param model - The model type for the plugin (e.g. 'part' / 'purchaseorder')
* @param id - The ID (primary key) of the model instance for the plugin
* @param instance - The model instance data (if available)
*/
export type PluginPanelContext = InvenTreeContext & {
model?: ModelType | string;
id?: string | number | null;
instance?: any;
};
export function usePluginPanels({ export function usePluginPanels({
instance, instance,
model, model,
@ -59,13 +70,13 @@ export function usePluginPanels({
}); });
// Cache the context data which is delivered to the plugins // Cache the context data which is delivered to the plugins
const pluginContext = usePluginContext(); const inventreeContext = useInvenTreeContext();
const contextData: PluginContext = useMemo(() => { const contextData = useMemo<PluginPanelContext>(() => {
return { return {
model: model, model: model,
id: id, id: id,
instance: instance, instance: instance,
...pluginContext ...inventreeContext
}; };
}, [model, id, instance]); }, [model, id, instance]);

View File

@ -2,20 +2,24 @@ import { useQuery } from '@tanstack/react-query';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { api } from '../App'; import { api } from '../App';
import { import { useInvenTreeContext } from '../components/plugins/PluginContext';
PluginContext,
usePluginContext
} from '../components/plugins/PluginContext';
import { findExternalPluginFunction } from '../components/plugins/PluginSource'; import { findExternalPluginFunction } from '../components/plugins/PluginSource';
import {
BaseUIFeature,
PluginUIFeatureAPIResponse,
PluginUIFuncWithoutInvenTreeContextType
} from '../components/plugins/PluginUIFeatureTypes';
import { ApiEndpoints } from '../enums/ApiEndpoints'; import { ApiEndpoints } from '../enums/ApiEndpoints';
import { apiUrl } from '../states/ApiState'; import { apiUrl } from '../states/ApiState';
import { useGlobalSettingsState } from '../states/SettingsState'; import { useGlobalSettingsState } from '../states/SettingsState';
export function usePluginUIFeature< export function usePluginUIFeature<UIFeatureT extends BaseUIFeature>({
RequestContextT extends Record<string, any>, featureType,
ResponseOptionsT extends Record<string, any>, context
RenderContextT extends Record<string, any> }: {
>({ featureType, context }: { featureType: string; context: RequestContextT }) { featureType: UIFeatureT['featureType'];
context: UIFeatureT['requestContext'];
}) {
const globalSettings = useGlobalSettingsState(); const globalSettings = useGlobalSettingsState();
const pluginUiFeaturesEnabled: boolean = useMemo( const pluginUiFeaturesEnabled: boolean = useMemo(
@ -24,7 +28,9 @@ export function usePluginUIFeature<
); );
// API query to fetch initial information on available plugin panels // API query to fetch initial information on available plugin panels
const { data: pluginData } = useQuery({ const { data: pluginData } = useQuery<
PluginUIFeatureAPIResponse<UIFeatureT>[]
>({
enabled: pluginUiFeaturesEnabled && !!featureType, enabled: pluginUiFeaturesEnabled && !!featureType,
queryKey: ['custom-ui-features', featureType, JSON.stringify(context)], queryKey: ['custom-ui-features', featureType, JSON.stringify(context)],
queryFn: async () => { queryFn: async () => {
@ -53,31 +59,30 @@ export function usePluginUIFeature<
}); });
// Cache the context data which is delivered to the plugins // Cache the context data which is delivered to the plugins
const pluginContext = usePluginContext(); const inventreeContext = useInvenTreeContext();
return useMemo< return useMemo<
{ {
options: ResponseOptionsT; options: UIFeatureT['responseOptions'];
func: ( func: PluginUIFuncWithoutInvenTreeContextType<UIFeatureT>;
ref: HTMLDivElement,
params: { renderContext: RenderContextT; pluginContext: PluginContext }
) => void;
}[] }[]
>(() => { >(() => {
return ( return (
pluginData?.map((feature: any) => ({ pluginData?.map((feature) => ({
options: feature.options, options: feature.options,
func: async (ref: HTMLDivElement, renderContext: RenderContextT) => { func: (async (ref, renderContext) => {
const func = await findExternalPluginFunction( const func = await findExternalPluginFunction(
feature.source, feature.source,
'getFeature' 'getFeature'
); );
if (!func) return;
return func(ref, { return func(ref, {
renderContext, renderContext,
pluginContext inventreeContext
}); });
} }) as PluginUIFuncWithoutInvenTreeContextType<UIFeatureT>
})) || [] })) || []
); );
}, [pluginData, pluginContext]); }, [pluginData, inventreeContext]);
} }

View File

@ -14,12 +14,11 @@ import { Editor } from '../../components/editors/TemplateEditor/TemplateEditor';
import { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; import { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
import { AttachmentLink } from '../../components/items/AttachmentLink'; import { AttachmentLink } from '../../components/items/AttachmentLink';
import { DetailDrawer } from '../../components/nav/DetailDrawer'; import { DetailDrawer } from '../../components/nav/DetailDrawer';
import { import { getPluginTemplateEditor } from '../../components/plugins/PluginUIFeature';
TemplateEditorRenderContextType, import { TemplateEditorUIFeature } from '../../components/plugins/PluginUIFeatureTypes';
getPluginTemplateEditor
} from '../../components/plugins/PluginUIFeature';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
import { GetIcon } from '../../functions/icons';
import { notYetImplemented } from '../../functions/notifications'; import { notYetImplemented } from '../../functions/notifications';
import { useFilters } from '../../hooks/UseFilter'; import { useFilters } from '../../hooks/UseFilter';
import { import {
@ -82,17 +81,7 @@ export function TemplateDrawer({
throwError: true throwError: true
}); });
const extraEditors = usePluginUIFeature< const extraEditors = usePluginUIFeature<TemplateEditorUIFeature>({
{
template_type: string;
template_model: string;
},
{
title: string;
slug: string;
},
TemplateEditorRenderContextType
>({
featureType: 'template_editor', featureType: 'template_editor',
context: { template_type: templateType, template_model: modelType } context: { template_type: templateType, template_model: modelType }
}); });
@ -107,9 +96,9 @@ export function TemplateDrawer({
...(extraEditors?.map( ...(extraEditors?.map(
(editor) => (editor) =>
({ ({
key: editor.options.slug, key: editor.options.key,
name: editor.options.title, name: editor.options.title,
icon: IconFileCode, icon: GetIcon(editor.options.icon),
component: getPluginTemplateEditor(editor.func, template) component: getPluginTemplateEditor(editor.func, template)
} as Editor) } as Editor)
) || []) ) || [])