mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-16 12:05:53 +00:00
provide exportable types
This commit is contained in:
@ -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,
|
||||||
|
@ -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>();
|
||||||
|
|
||||||
|
@ -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]) {
|
||||||
|
@ -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);
|
||||||
|
67
src/frontend/src/components/plugins/PluginUIFeatureTypes.ts
Normal file
67
src/frontend/src/components/plugins/PluginUIFeatureTypes.ts
Normal 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;
|
||||||
|
};
|
||||||
|
};
|
@ -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]);
|
||||||
|
|
||||||
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
) || [])
|
) || [])
|
||||||
|
Reference in New Issue
Block a user