mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 03:56:43 +00:00
[PUI] Plugin panel context (#8190)
* Add server-side context for panel plugin rendering * Add "context" to PluginContext type * Pass server context through to client-side rendering * Bump API version
This commit is contained in:
parent
35362347a7
commit
194640f55a
@ -1,13 +1,16 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 259
|
INVENTREE_API_VERSION = 260
|
||||||
|
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v260 - 2024-09-26 : https://github.com/inventree/InvenTree/pull/8190
|
||||||
|
- Adds facility for server-side context data to be passed to client-side plugins
|
||||||
|
|
||||||
v259 - 2024-09-20 : https://github.com/inventree/InvenTree/pull/8137
|
v259 - 2024-09-20 : https://github.com/inventree/InvenTree/pull/8137
|
||||||
- Implements new API endpoint for enabling custom UI features via plugins
|
- Implements new API endpoint for enabling custom UI features via plugins
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ class CustomPanel(TypedDict):
|
|||||||
label: The label of the panel (required, human readable).
|
label: The label of the panel (required, human readable).
|
||||||
icon: The icon of the panel (optional, must be a valid icon identifier).
|
icon: The icon of the panel (optional, must be a valid icon identifier).
|
||||||
content: The content of the panel (optional, raw HTML).
|
content: The content of the panel (optional, raw HTML).
|
||||||
|
context: Optional context data (dict / JSON) which will be passed to the front-end rendering function
|
||||||
source: The source of the panel (optional, path to a JavaScript file).
|
source: The source of the panel (optional, path to a JavaScript file).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ class CustomPanel(TypedDict):
|
|||||||
label: str
|
label: str
|
||||||
icon: str
|
icon: str
|
||||||
content: str
|
content: str
|
||||||
|
context: dict
|
||||||
source: str
|
source: str
|
||||||
|
|
||||||
|
|
||||||
@ -87,6 +89,7 @@ class UserInterfaceMixin:
|
|||||||
'label': 'Panel Title', # The title of the panel (required, human readable)
|
'label': 'Panel Title', # The title of the panel (required, human readable)
|
||||||
'icon': 'icon-name', # Icon name (optional, must be a valid icon identifier)
|
'icon': 'icon-name', # Icon name (optional, must be a valid icon identifier)
|
||||||
'content': '<p>Panel content</p>', # HTML content to be rendered in the panel (optional)
|
'content': '<p>Panel content</p>', # HTML content to be rendered in the panel (optional)
|
||||||
|
'context': {'key': 'value'}, # Context data to be passed to the front-end rendering function (optional)
|
||||||
'source': 'static/plugin/panel.js', # Path to a JavaScript file to be loaded (optional)
|
'source': 'static/plugin/panel.js', # Path to a JavaScript file to be loaded (optional)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ class PluginPanelSerializer(serializers.Serializer):
|
|||||||
# Following fields are optional
|
# Following fields are optional
|
||||||
'icon',
|
'icon',
|
||||||
'content',
|
'content',
|
||||||
|
'context',
|
||||||
'source',
|
'source',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -43,6 +44,10 @@ class PluginPanelSerializer(serializers.Serializer):
|
|||||||
label=_('Panel Content (HTML)'), required=False, allow_blank=True
|
label=_('Panel Content (HTML)'), required=False, allow_blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
context = serializers.JSONField(
|
||||||
|
label=_('Panel Context (JSON)'), required=False, allow_null=True, default=None
|
||||||
|
)
|
||||||
|
|
||||||
source = serializers.CharField(
|
source = serializers.CharField(
|
||||||
label=_('Panel Source (javascript)'), required=False, allow_blank=True
|
label=_('Panel Source (javascript)'), required=False, allow_blank=True
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
"""Sample plugin which demonstrates user interface integrations."""
|
"""Sample plugin which demonstrates user interface integrations."""
|
||||||
|
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from InvenTree.version import INVENTREE_SW_VERSION
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
from plugin.helpers import render_template, render_text
|
from plugin.helpers import render_template, render_text
|
||||||
@ -15,7 +19,7 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
|||||||
SLUG = 'sampleui'
|
SLUG = 'sampleui'
|
||||||
TITLE = 'Sample User Interface Plugin'
|
TITLE = 'Sample User Interface Plugin'
|
||||||
DESCRIPTION = 'A sample plugin which demonstrates user interface integrations'
|
DESCRIPTION = 'A sample plugin which demonstrates user interface integrations'
|
||||||
VERSION = '1.0'
|
VERSION = '1.1'
|
||||||
|
|
||||||
SETTINGS = {
|
SETTINGS = {
|
||||||
'ENABLE_PART_PANELS': {
|
'ENABLE_PART_PANELS': {
|
||||||
@ -81,11 +85,18 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
|||||||
})
|
})
|
||||||
|
|
||||||
# A dynamic panel which will be injected into the UI (loaded from external file)
|
# A dynamic panel which will be injected into the UI (loaded from external file)
|
||||||
|
# Note that we additionally provide some "context" data to the front-end render function
|
||||||
if self.get_setting('ENABLE_DYNAMIC_PANEL'):
|
if self.get_setting('ENABLE_DYNAMIC_PANEL'):
|
||||||
panels.append({
|
panels.append({
|
||||||
'name': 'dynamic_panel',
|
'name': 'dynamic_panel',
|
||||||
'label': 'Dynamic Part Panel',
|
'label': 'Dynamic Part Panel',
|
||||||
'source': '/static/plugin/sample_panel.js',
|
'source': '/static/plugin/sample_panel.js',
|
||||||
|
'context': {
|
||||||
|
'version': INVENTREE_SW_VERSION,
|
||||||
|
'plugin_version': self.VERSION,
|
||||||
|
'random': random.randint(1, 100),
|
||||||
|
'time': time.time(),
|
||||||
|
},
|
||||||
'icon': 'part',
|
'icon': 'part',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
* as well as dynamically hidden, based on the provided context.
|
* as well as dynamically hidden, based on the provided context.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function renderPanel(target, context) {
|
export function renderPanel(target, data) {
|
||||||
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
console.error("No target provided to renderPanel");
|
console.error("No target provided to renderPanel");
|
||||||
@ -22,13 +22,22 @@ export function renderPanel(target, context) {
|
|||||||
<p>It can be hidden or displayed based on the provided context.</p>
|
<p>It can be hidden or displayed based on the provided context.</p>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h5>Context:</h5>
|
<h5>Client Context:</h5>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Username: ${context.user.username()}</li>
|
<li>Username: ${data.user.username()}</li>
|
||||||
<li>Is Staff: ${context.user.isStaff() ? "YES": "NO"}</li>
|
<li>Is Staff: ${data.user.isStaff() ? "YES": "NO"}</li>
|
||||||
<li>Model Type: ${context.model}</li>
|
<li>Model Type: ${data.model}</li>
|
||||||
<li>Instance ID: ${context.id}</li>
|
<li>Instance ID: ${data.id}</li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
<h5>Server Context:</h5>
|
||||||
|
<ul>
|
||||||
|
<li>Server Version: ${data.context.version}</li>
|
||||||
|
<li>Plugin Version: ${data.context.plugin_version}</li>
|
||||||
|
<li>Random Number: ${data.context.random}</li>
|
||||||
|
<li>Time: ${data.context.time}</li>
|
||||||
|
</ul>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ export type PluginPanelProps = {
|
|||||||
label: string;
|
label: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
|
context?: any;
|
||||||
source?: string;
|
source?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -82,7 +83,9 @@ export default function PluginPanelContent({
|
|||||||
func(ref.current, pluginContext);
|
func(ref.current, pluginContext);
|
||||||
setError('');
|
setError('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(t`Error occurred while rendering plugin content`);
|
setError(
|
||||||
|
t`Error occurred while rendering plugin content: ${error}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setError(t`Plugin did not provide panel rendering function`);
|
setError(t`Plugin did not provide panel rendering function`);
|
||||||
|
@ -111,6 +111,11 @@ export function usePluginPanels({
|
|||||||
);
|
);
|
||||||
const isHidden: boolean = panelState[identifier] ?? true;
|
const isHidden: boolean = panelState[identifier] ?? true;
|
||||||
|
|
||||||
|
const pluginContext: any = {
|
||||||
|
...contextData,
|
||||||
|
context: props.context
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: identifier,
|
name: identifier,
|
||||||
label: props.label,
|
label: props.label,
|
||||||
@ -118,7 +123,7 @@ export function usePluginPanels({
|
|||||||
content: (
|
content: (
|
||||||
<PluginPanelContent
|
<PluginPanelContent
|
||||||
pluginProps={props}
|
pluginProps={props}
|
||||||
pluginContext={contextData}
|
pluginContext={pluginContext}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
hidden: isHidden
|
hidden: isHidden
|
||||||
|
Loading…
x
Reference in New Issue
Block a user