mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-27 19:16:44 +00:00
feat: New / Refactor "Nav Mixin" (#9283)
* [FR/P-UI] New / Refactor "Nav Mixin" Fixes #5269 * remove logging * fix sample item that causes issues * Add test coverage * Update src/frontend/src/components/plugins/PluginUIFeatureTypes.ts Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com> * [FR/P-UI] New / Refactor "Nav Mixin" Fixes #5269 * fix style * remove requirement for source * fix import * bump api version --------- Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com>
This commit is contained in:
parent
058aa190d9
commit
6b0a082b5a
@ -1,13 +1,16 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 338
|
||||
INVENTREE_API_VERSION = 339
|
||||
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v339 -> 2025-04-15 : https://github.com/inventree/InvenTree/pull/9283
|
||||
- Remove need for source in /plugins/ui/features
|
||||
|
||||
v338 -> 2025-04-15 : https://github.com/inventree/InvenTree/pull/9333
|
||||
- Adds oAuth2 support for the API
|
||||
|
||||
|
@ -19,6 +19,7 @@ FeatureType = Literal[
|
||||
'panel', # Custom panels
|
||||
'template_editor', # Custom template editor
|
||||
'template_preview', # Custom template preview
|
||||
'navigation', # Custom navigation items
|
||||
]
|
||||
|
||||
|
||||
@ -102,6 +103,7 @@ class UserInterfaceMixin:
|
||||
'panel': self.get_ui_panels,
|
||||
'template_editor': self.get_ui_template_editors,
|
||||
'template_preview': self.get_ui_template_previews,
|
||||
'navigation': self.get_ui_navigation_items,
|
||||
}
|
||||
|
||||
if feature_type in feature_map:
|
||||
@ -169,3 +171,18 @@ class UserInterfaceMixin:
|
||||
"""
|
||||
# Default implementation returns an empty list
|
||||
return []
|
||||
|
||||
def get_ui_navigation_items(
|
||||
self, request: Request, context: dict, **kwargs
|
||||
) -> list[UIFeature]:
|
||||
"""Return a list of custom navigation items to be injected into the UI.
|
||||
|
||||
Args:
|
||||
request: HTTPRequest object (including user information)
|
||||
context: Additional context data provided by the UI (query parameters)
|
||||
|
||||
Returns:
|
||||
list: A list of custom navigation items to be injected into the UI
|
||||
"""
|
||||
# Default implementation returns an empty list
|
||||
return []
|
||||
|
@ -61,5 +61,5 @@ class PluginUIFeatureSerializer(serializers.Serializer):
|
||||
context = serializers.DictField(label=_('Feature Context'), default=None)
|
||||
|
||||
source = serializers.CharField(
|
||||
label=_('Feature Source (javascript)'), required=True, allow_blank=False
|
||||
label=_('Feature Source (javascript)'), required=False, allow_blank=True
|
||||
)
|
||||
|
@ -223,3 +223,13 @@ class UserInterfaceMixinTests(InvenTreeAPITestCase):
|
||||
|
||||
# Set the setting back to True for subsequent tests
|
||||
InvenTreeSetting.set_setting('ENABLE_PLUGINS_INTERFACE', True, change_user=None)
|
||||
|
||||
def test_ui_navigation_items(self):
|
||||
"""Test that the sample UI plugin provides custom navigation items."""
|
||||
response = self.get(
|
||||
reverse('api-plugin-ui-feature-list', kwargs={'feature': 'navigation'})
|
||||
)
|
||||
self.assertEqual(1, len(response.data))
|
||||
self.assertEqual(response.data[0]['plugin_name'], 'sampleui')
|
||||
self.assertEqual(response.data[0]['key'], 'sample-nav-item')
|
||||
self.assertEqual(response.data[0]['title'], 'Sample Nav Item')
|
||||
|
@ -197,6 +197,17 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
||||
}
|
||||
]
|
||||
|
||||
def get_ui_navigation_items(self, request, context, **kwargs):
|
||||
"""Return a list of custom navigation items."""
|
||||
return [
|
||||
{
|
||||
'key': 'sample-nav-item',
|
||||
'title': 'Sample Nav Item',
|
||||
'icon': 'ti:menu',
|
||||
'options': {'url': '/sample/page/'},
|
||||
}
|
||||
]
|
||||
|
||||
def get_admin_context(self) -> dict:
|
||||
"""Return custom context data which can be rendered in the admin panel."""
|
||||
return {'apple': 'banana', 'foo': 'bar', 'hello': 'world'}
|
||||
|
@ -7,8 +7,7 @@ import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import type { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { getDetailUrl, navigateToLink } from '@lib/functions/Navigation';
|
||||
import type { TableFilter } from '@lib/types/Filters';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { ActionIcon, Group, Text } from '@mantine/core';
|
||||
|
@ -1,3 +1,7 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
ActionIcon,
|
||||
Container,
|
||||
@ -12,13 +16,10 @@ import { IconBell, IconSearch } from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { type ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
import { useMatch, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { api } from '../../App';
|
||||
import type { NavigationUIFeature } from '../../components/plugins/PluginUIFeatureTypes';
|
||||
import { getNavTabs } from '../../defaults/links';
|
||||
import { usePluginUIFeature } from '../../hooks/UsePluginUIFeature';
|
||||
import * as classes from '../../main.css';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
@ -180,10 +181,18 @@ function NavTabs() {
|
||||
[userSettings]
|
||||
);
|
||||
|
||||
const extraNavs = usePluginUIFeature<NavigationUIFeature>({
|
||||
featureType: 'navigation',
|
||||
context: {}
|
||||
});
|
||||
|
||||
const tabs: ReactNode[] = useMemo(() => {
|
||||
const _tabs: ReactNode[] = [];
|
||||
|
||||
navTabs.forEach((tab) => {
|
||||
const mainNavTabs = getNavTabs(user);
|
||||
|
||||
// static content
|
||||
mainNavTabs.forEach((tab) => {
|
||||
if (tab.role && !user.hasViewRole(tab.role)) {
|
||||
return;
|
||||
}
|
||||
@ -206,9 +215,23 @@ function NavTabs() {
|
||||
</Tabs.Tab>
|
||||
);
|
||||
});
|
||||
// dynamic content
|
||||
extraNavs.forEach((nav) => {
|
||||
_tabs.push(
|
||||
<Tabs.Tab
|
||||
value={nav.options.title}
|
||||
key={nav.options.key}
|
||||
onClick={(event: any) =>
|
||||
navigateToLink(nav.options.options.url, navigate, event)
|
||||
}
|
||||
>
|
||||
{nav.options.title}
|
||||
</Tabs.Tab>
|
||||
);
|
||||
});
|
||||
|
||||
return _tabs;
|
||||
}, [navTabs, user, withIcons]);
|
||||
}, [extraNavs, navTabs, user, withIcons]);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
|
@ -28,7 +28,8 @@ export enum PluginUIFeatureType {
|
||||
dashboard = 'dashboard',
|
||||
panel = 'panel',
|
||||
template_editor = 'template_editor',
|
||||
template_preview = 'template_preview'
|
||||
template_preview = 'template_preview',
|
||||
navigation = 'navigation'
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,3 +76,11 @@ export type TemplatePreviewUIFeature = {
|
||||
};
|
||||
featureReturnType: undefined;
|
||||
};
|
||||
|
||||
export type NavigationUIFeature = {
|
||||
featureType: 'navigation';
|
||||
requestContext: {};
|
||||
responseOptions: PluginUIFeature;
|
||||
featureContext: {};
|
||||
featureReturnType: undefined;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user