mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36: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 information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# 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."""
|
"""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 = """
|
||||||
|
|
||||||
|
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
|
v338 -> 2025-04-15 : https://github.com/inventree/InvenTree/pull/9333
|
||||||
- Adds oAuth2 support for the API
|
- Adds oAuth2 support for the API
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ FeatureType = Literal[
|
|||||||
'panel', # Custom panels
|
'panel', # Custom panels
|
||||||
'template_editor', # Custom template editor
|
'template_editor', # Custom template editor
|
||||||
'template_preview', # Custom template preview
|
'template_preview', # Custom template preview
|
||||||
|
'navigation', # Custom navigation items
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -102,6 +103,7 @@ class UserInterfaceMixin:
|
|||||||
'panel': self.get_ui_panels,
|
'panel': self.get_ui_panels,
|
||||||
'template_editor': self.get_ui_template_editors,
|
'template_editor': self.get_ui_template_editors,
|
||||||
'template_preview': self.get_ui_template_previews,
|
'template_preview': self.get_ui_template_previews,
|
||||||
|
'navigation': self.get_ui_navigation_items,
|
||||||
}
|
}
|
||||||
|
|
||||||
if feature_type in feature_map:
|
if feature_type in feature_map:
|
||||||
@ -169,3 +171,18 @@ class UserInterfaceMixin:
|
|||||||
"""
|
"""
|
||||||
# Default implementation returns an empty list
|
# Default implementation returns an empty list
|
||||||
return []
|
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)
|
context = serializers.DictField(label=_('Feature Context'), default=None)
|
||||||
|
|
||||||
source = serializers.CharField(
|
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
|
# Set the setting back to True for subsequent tests
|
||||||
InvenTreeSetting.set_setting('ENABLE_PLUGINS_INTERFACE', True, change_user=None)
|
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:
|
def get_admin_context(self) -> dict:
|
||||||
"""Return custom context data which can be rendered in the admin panel."""
|
"""Return custom context data which can be rendered in the admin panel."""
|
||||||
return {'apple': 'banana', 'foo': 'bar', 'hello': 'world'}
|
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 { ModelType } from '@lib/enums/ModelType';
|
||||||
import type { UserRoles } from '@lib/enums/Roles';
|
import type { UserRoles } from '@lib/enums/Roles';
|
||||||
import { apiUrl } from '@lib/functions/Api';
|
import { apiUrl } from '@lib/functions/Api';
|
||||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
import { getDetailUrl, navigateToLink } from '@lib/functions/Navigation';
|
||||||
import { navigateToLink } from '@lib/functions/Navigation';
|
|
||||||
import type { TableFilter } from '@lib/types/Filters';
|
import type { TableFilter } from '@lib/types/Filters';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { ActionIcon, Group, Text } from '@mantine/core';
|
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 {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Container,
|
Container,
|
||||||
@ -12,13 +16,10 @@ import { IconBell, IconSearch } from '@tabler/icons-react';
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { type ReactNode, useEffect, useMemo, useState } from 'react';
|
import { type ReactNode, useEffect, useMemo, useState } from 'react';
|
||||||
import { useMatch, useNavigate } from 'react-router-dom';
|
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 { api } from '../../App';
|
||||||
|
import type { NavigationUIFeature } from '../../components/plugins/PluginUIFeatureTypes';
|
||||||
import { getNavTabs } from '../../defaults/links';
|
import { getNavTabs } from '../../defaults/links';
|
||||||
|
import { usePluginUIFeature } from '../../hooks/UsePluginUIFeature';
|
||||||
import * as classes from '../../main.css';
|
import * as classes from '../../main.css';
|
||||||
import { useServerApiState } from '../../states/ApiState';
|
import { useServerApiState } from '../../states/ApiState';
|
||||||
import { useLocalState } from '../../states/LocalState';
|
import { useLocalState } from '../../states/LocalState';
|
||||||
@ -180,10 +181,18 @@ function NavTabs() {
|
|||||||
[userSettings]
|
[userSettings]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const extraNavs = usePluginUIFeature<NavigationUIFeature>({
|
||||||
|
featureType: 'navigation',
|
||||||
|
context: {}
|
||||||
|
});
|
||||||
|
|
||||||
const tabs: ReactNode[] = useMemo(() => {
|
const tabs: ReactNode[] = useMemo(() => {
|
||||||
const _tabs: ReactNode[] = [];
|
const _tabs: ReactNode[] = [];
|
||||||
|
|
||||||
navTabs.forEach((tab) => {
|
const mainNavTabs = getNavTabs(user);
|
||||||
|
|
||||||
|
// static content
|
||||||
|
mainNavTabs.forEach((tab) => {
|
||||||
if (tab.role && !user.hasViewRole(tab.role)) {
|
if (tab.role && !user.hasViewRole(tab.role)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -206,9 +215,23 @@ function NavTabs() {
|
|||||||
</Tabs.Tab>
|
</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;
|
return _tabs;
|
||||||
}, [navTabs, user, withIcons]);
|
}, [extraNavs, navTabs, user, withIcons]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
|
@ -28,7 +28,8 @@ export enum PluginUIFeatureType {
|
|||||||
dashboard = 'dashboard',
|
dashboard = 'dashboard',
|
||||||
panel = 'panel',
|
panel = 'panel',
|
||||||
template_editor = 'template_editor',
|
template_editor = 'template_editor',
|
||||||
template_preview = 'template_preview'
|
template_preview = 'template_preview',
|
||||||
|
navigation = 'navigation'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,3 +76,11 @@ export type TemplatePreviewUIFeature = {
|
|||||||
};
|
};
|
||||||
featureReturnType: undefined;
|
featureReturnType: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NavigationUIFeature = {
|
||||||
|
featureType: 'navigation';
|
||||||
|
requestContext: {};
|
||||||
|
responseOptions: PluginUIFeature;
|
||||||
|
featureContext: {};
|
||||||
|
featureReturnType: undefined;
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user