2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-09-13 14:11:37 +00:00

Walkthrough Update for v1.0 release (#10159)

* Updated walkthrough for Inventree 1.0 release

* fix: correct style object syntax in AttachmentCarousel component

* fix: wrap carousel code in raw tags for proper rendering
This commit is contained in:
Red Echidna UK
2025-08-24 03:56:18 +01:00
committed by GitHub
parent 8f95e61035
commit ac1662c5a0
15 changed files with 171 additions and 131 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 KiB

View File

Before

Width:  |  Height:  |  Size: 296 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 KiB

View File

@@ -9,14 +9,15 @@ A walkthrough showing how to build a part panel plugin.
By the end of the walkthrough, you will have created a plugin that adds a new part panel to display an image carousel from the images attached to the current part.
![Attachment Carousel in Inventree panel screenshot](../assets/images/plugin/plugin_attachment_carousel.png "Attachment Carousel in Inventree panel screenshot")
![Attachment Carousel in Inventree panel screenshot](../assets/images/plugin/plugin_walkthrough_attachment_carousel.png "Attachment Carousel in Inventree panel screenshot")
#### Prerequisites
This is a basic walkthrough and not a guide on how to code in Python or React. It is presumed you have the following,
* A running Inventree instance
* A running Inventree instance and/or a [devcontainer](https://docs.inventree.org/en/stable/develop/devcontainer/)
* The [Inventree Plugin Creator](https://github.com/inventree/plugin-creator) installed
* [Visual Studio Code (VS Code)](https://code.visualstudio.com/) (or an IDE of your choosing)
* [Node Package Manager](https://www.npmjs.com/) installed
* Basic Python knowledge
* Basic React knowledge
* Optional:
@@ -37,42 +38,50 @@ After the plugin wizard has launched, enter the name as `Attachment Carousel` an
At the plugin structure information questions,
* Select `UserInterfaceMixin`
* Only select `UserInterfaceMixin` and `SettingsMixin`
* `Add User Interface Support`
* Select `Custom panel items`
* Select `None` for the `DevOps support`
* Only select `Custom panel items`
* Select `No` for `Enable translation support`
* Select `No` for `Git Integration`
![Plugin Wizard Screenshot showing mixins](../assets/images/plugin/plugin_wizard_mixins.png "Plugin Wizard Screenshot showing mixins")
![Plugin Wizard Screenshot showing items](../assets/images/plugin/plugin_wizard_items.png "Plugin Wizard Screenshot showing items")
After the Plugin Creator has finished you should have a new project that looks like this,
After the Plugin Creator has finished you should have a new project that looks like this (seen here in VS Code),
![VS Code screenshot](../assets/images/plugin/plugin_vscode_layout.png "VS Code screenshot")
![VS Code screenshot](../assets/images/plugin/plugin_walkthrough_vscode_layout.png "VS Code screenshot")
#### A Brief Overview of the Environment
`attachment_carousel` contains several files, but the main file is `core.py` which is the Python entry point for the plugin. Once created, this folder will also contain the bundled frontend code and any [static file assets](./index.md#static-files).
`frontend/src` contains the frontend React TypeScript code,
* `App.tsx` - The application component
* `main.tsx` - The standalone development entry point (this is only required for testing)
* `Panel.tsx` - The Inventree panel entry point
`frontend/src` contains `Panel.tsx' which is the frontend React TypeScript code.
### Testing the Plugin Environment
To ensure the environment is working as expected, from the terminal navigate to the `frontend` directory and run either the development server (if using the development environment) **or** build the project and install the plugin.
To ensure the environment is working as expected, from the terminal run the development Vite server,
Development Environment
``` Bash
npm install
npm run dev
```
This should launch the local development server,
![Vite screenshot](../assets/images/plugin/plugin_vite_running.png "Vite screenshot")
Generic Build and install
If you browse to the address provided you should see the default plugin website,
``` Bash
npm install
npm run build
```
![Vite website screenshot](../assets/images/plugin/plugin_vite_dev_site.png "Vite website screenshot")
Copy the built `attachment_carousel` diretory to the `inventree-data/plugins` directory and enable it via the admin interface.
![Attachment Carousel in Inventree panel screenshot](../assets/images/plugin/plugin_walkthrough_default.png "Attachment Carousel in Inventree panel screenshot")
!!! info "Tip"
If you do not see the `Attachment Carousel` panel listed, make sure you have `Enable interface integration` turned on (it is disabled by default)
![Inventree Plugin Settings screenshot](../assets/images/plugin/plugin_walkthrough_interface_integration.png "Inventree Plugin Settings screenshot")
### Creating the Carousel
@@ -84,32 +93,46 @@ As the Mantine UI carousel is not added to the plugin project by default, add it
npm install @mantine/carousel
```
Edit `main.tsx` to add the carousel CSS,
Edit `Panel.tsx` to add the carousel references,
``` TypeScript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import { MantineProvider } from '@mantine/core'
import "@mantine/core/styles.css";
import '@mantine/carousel/styles.css';
import AttachmentCarousel from './AttachmentCarousel.tsx';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<MantineProvider>
<App />
</MantineProvider>
</StrictMode>,
)
// Import for type checking
import { checkPluginVersion, type InvenTreePluginContext } from '@inventreedb/ui';
/**
* Render a custom panel with the provided context.
* Refer to the InvenTree documentation for the context interface
* https://docs.inventree.org/en/latest/plugins/mixins/ui/#plugin-context
*/
function AttachmentCarouselPanel({context}: {context: InvenTreePluginContext;}) {
console.log(context);
return (
<>
<MantineProvider>
<AttachmentCarousel />
</MantineProvider>
</>
);
}
// This is the function which is called by InvenTree to render the actual panel component
export function renderAttachmentCarouselPanel(context: InvenTreePluginContext) {
checkPluginVersion(context);
return <AttachmentCarouselPanel context={context} />;
}
```
Edit `App.tsx` to add the relevant carousel code,
Create a new file called `AttachmentCarousel.tsx` and add the carousel code,
``` TypeScript
{% raw %}
import { Carousel } from '@mantine/carousel';
import { Image, AspectRatio } from '@mantine/core';
export default function App() {
export default function AttachmentCarousel() {
const indicators = true
const loop = true
@@ -128,55 +151,16 @@ export default function App() {
return (
<AspectRatio ratio={1920 / 1080} >
<Carousel withIndicators={indicators} loop={loop}>{slides}</Carousel>
<Carousel withIndicators={indicators} emblaOptions={{loop:loop}} styles={{control: {backgroundColor: 'var(--mantine-color-white)', color: 'var(--mantine-color-black)'}, indicator: {backgroundColor: 'var(--mantine-color-white)'}}}>{slides}</Carousel>
</AspectRatio>
);
}
{% endraw %}
```
Run the Vite development environment again and browse to the website. You should now see the Mantine UI carousel.
As detailed earlier, run/build the plugin. You should now see that the plugin has been update to show a carousel.
![Mantine UI carousel screenshot](../assets/images/plugin/plugin_vite_mantine_carousel.png "Mantine UI carousel screenshot")
### Adding The Carousel to an Inventree Panel
As previously mentioned, `Panel.tsx` is the default entry point from the Inventree Python backend. Therefore, change `Panel.tsx` to render our carousel component rather than the example code,
``` TypeScript
import { MantineProvider } from '@mantine/core';
import { createRoot } from 'react-dom/client';
import App from './App';
import '@mantine/carousel/styles.css';
/**
* Render the AttachmentCarouselPanel component.
*
* @param target - The target HTML element to render the panel into
* @param context - The context object to pass to the panel
*/
export function renderAttachmentCarouselPanel(target: HTMLElement, context: any) {
createRoot(target).render(
<MantineProvider theme={context.theme} defaultColorScheme={context.colorScheme}>
<App />
</MantineProvider>
);
}
```
Generate the Vite bundle via terminal,
``` Bash
vite run build --emptyOutDir
```
Depending on how you are running Inventree, [install the plugin](./install.md), enable it, and view the resulting panel,
![Attachment Carousel Screenshot](../assets/images/plugin/plugin_attachment_carousel_icon_wrong.png "Attachment Carousel Screenshot")
!!! info "Tip"
If you do not see the `Attachment Carousel` panel listed, make sure you have `Enable interface integration` turned on (it is disabled by default)
![Attachment Carousel in Inventree panel screenshot](../assets/images/plugin/plugin_interface_integration.png "Attachment Carousel in Inventree panel screenshot")
![Mantine UI carousel screenshot](../assets/images/plugin/plugin_walkthrough_attachment_carousel_picsum.png "Mantine UI carousel screenshot")
### Connecting the Carousel to Inventree Part Attachments via the Frontend
@@ -188,70 +172,96 @@ Install TanStack Query in our project via the terminal
npm install @tanstack/react-query
```
Update `App.tsx` to use TanStack Query and query for all part attachments,
Update `Panel.tsx` to add a TanStack `QueryClientProvider` and send the context to the attachment carousel,
``` diff
import { MantineProvider } from '@mantine/core'
import AttachmentCarousel from './AttachmentCarousel';
+import { QueryClientProvider } from '@tanstack/react-query';
...
function AttachmentCarouselPanel({context}: {context: InvenTreePluginContext;}) {
- console.log(context);
return (
<>
+ <QueryClientProvider client={context.queryClient}>
<MantineProvider>
- <AttachmentCarousel />
+ <AttachmentCarousel context={context} />
</MantineProvider>
+ </QueryClientProvider>
</>
);
}
...
```
!!! info "More Info"
By using the `context` api query we do not need to pass an API key or have the user login to perform the API call, it just happens automatically.
By providing the `context`, we can use the context api query to avoid the need to pass an API key or have the user login to perform the API call, it just happens automatically.
Update `Attachment_carousel.tsx` to use TanStack Query and query for all part attachments,
``` TypeScript
{% raw %}
import { Carousel } from '@mantine/carousel';
import { Image, AspectRatio } from '@mantine/core';
import { useQuery } from '@tanstack/react-query';
export default function Test({ context }: { context: any }) {
export default function AttachmentCarousel({context}: { context: any }) {
const indicators = true
const loop = true
const vaildAttachmentTypes: string[] = ["jpg", "jpeg"] // Add more items to the array if you want to support more file types.
let imageUrls: string[] = new Array();
const { data: attachments } = useQuery({
queryKey: ["attachment"],
queryFn: async () => {
const response = await context.api?.get('api/attachment/', {
params: {
model_type: context.model,
model_id: context.id,
is_file: true,
limit: 100
}
})
return await response.data.results
}
})
if (attachments) {
attachments.forEach((element: { attachment: string; filename: string; }) => {
if (vaildAttachmentTypes.includes(element.filename.split('.').pop() || "")) {
imageUrls.push(context.globalSettings.getSetting("INVENTREE_BASE_URL") + element.attachment);
const indicators = true
const loop = true
const vaildAttachmentTypes: string[] = ["jpg", "jpeg"] // Add more items to the array if you want to support more file types.
let imageUrls: string[] = new Array();
const { data: attachments } = useQuery({
queryKey: ["attachment"],
queryFn: async () => {
const response = await context.api?.get('api/attachment/', {
params: {
model_type: context.model,
model_id: context.id,
is_file: true,
limit: 100
}
})
return await response.data.results
}
});
}
else {
imageUrls = [""];
}
const slides = imageUrls.map((url) => (
<Carousel.Slide key={url}>
<Image src={url} />
</Carousel.Slide>
));
})
if (attachments) {
attachments.forEach((element: { attachment: string; filename: string; }) => {
if (vaildAttachmentTypes.includes(element.filename.split('.').pop() || "")) {
imageUrls.push(context.globalSettings.getSetting("INVENTREE_BASE_URL") + element.attachment);
}
});
}
else {
imageUrls = [""];
}
const slides = imageUrls.map((url) => (
<Carousel.Slide key={url}>
<Image src={url} />
</Carousel.Slide>
));
return (
<AspectRatio ratio={1920 / 1080} >
<Carousel withIndicators={indicators} loop={loop}>{slides}</Carousel>
<Carousel withIndicators={indicators} emblaOptions={{loop:loop}} styles={{control: {backgroundColor: 'var(--mantine-color-white)', color: 'var(--mantine-color-black)'}, indicator: {backgroundColor: 'var(--mantine-color-white)'}}}>{slides}</Carousel>
</AspectRatio>
);
}
{% endraw %}
```
Upload some appropriately sized images in the specified file type (see code), re-bundle the plugin, and update it in Inventree.
You should now see the carousel displaying images from the attachments on the part. Upload images to different parts and see how the carousel changes based on the part you are viewing.
### Connecting the Carousel to Inventree Part Attachments via the Backend
An alternative to doing the API query on the frontend is to do it via the backend and supply the image URLs via additional context data. The advantage of doing the query this way is the panel can now be shown or hidden based on if any images are found on the part, and the images could also be checked to ensure they are images and not corrupted etc. The downside is there is more backend processing, and the context variable could become quite large when passed to the frontend. Of course, queries could be done on the backend and frontend and not pass the data via the context. It is for the developer of the plugin to decide which approach is best...
An alternative to doing the API query on the frontend is to do it via the backend and supply the image URLs via additional context data. The advantage of doing the query this way is the panel can now be shown or hidden based on if any images are found on the part, and the images could also be checked to ensure they are images and not corrupted etc. The downsides are, there is more backend processing, the context variable could become quite large when passed to the frontend and the carousel will not automatically see new attachments as the backend is only run when the page is first loaded. Of course, queries could be done on the backend and frontend and not pass the data via the context. It is for the developer of the plugin to decide which approach is best...
Back to the walkthrough, open `core.py` in the `attachment_carousel` folder and make the following changes,
@@ -282,7 +292,7 @@ Back to the walkthrough, open `core.py` in the `attachment_carousel` folder and
+ if attachments.count() > 0:
+ for attachment in attachments:
+ attachments_urls.append(base_url + '/media/' + attachment.attachment.name)
+ attachment_urls.append(base_url + '/media/' + attachment.attachment.name)
+ if len(attachment_urls) > 0:
panels.append({
@@ -294,6 +304,7 @@ Back to the walkthrough, open `core.py` in the `attachment_carousel` folder and
'context': {
# Provide additional context data to the panel
'settings': self.get_settings_dict(),
- 'foo': 'bar'
+ 'attachments': attachment_urls,
}
})
@@ -303,7 +314,7 @@ Back to the walkthrough, open `core.py` in the `attachment_carousel` folder and
Now the backend collects the attachment details and passes them to the frontend, whilst also only displaying the panel if attachments are found.
Next, modify `App.tsx` to use this new information,
Next, modify `AttachmentCarousel.tsx` to use this new information,
``` Diff
import { Carousel } from '@mantine/carousel';
@@ -358,6 +369,11 @@ Next, modify `App.tsx` to use this new information,
```
Again, re-bundle the plugin, and update it in Inventree.
You should now see the carousel displaying images from the attachments on the part, but this time they are passed via context from the backend.
### Changing the Panel Icon
Inventree uses [Tabler icons](https://tabler.io/icons) and it is easy to change the panel's icon to something more suitable. Simply find the [Tabler icon](https://tabler.io/icons) you would like and update the icon reference in `core.py`,
@@ -378,7 +394,7 @@ panels.append({
})
```
![Attachment Carousel in Inventree panel with updated icon screenshot](../assets/images/plugin/plugin_attachment_carousel_icon.png "Attachment Carousel in Inventree panel screenshot updated icon screenshot")
![Attachment Carousel in Inventree panel with updated icon screenshot](../assets/images/plugin/plugin_walkthrough_attachment_carousel.png "Attachment Carousel in Inventree panel screenshot updated icon screenshot")
### Adding Plugin Admin Options
@@ -414,7 +430,7 @@ Make the following changes to `core.py`,
...
```
Edit `App.tsx` to use the new options,
Edit `AttachmentCarousel.tsx` to use the new options,
``` Diff
export default function App({ context }: { context: any }) {
@@ -428,13 +444,13 @@ export default function App({ context }: { context: any }) {
Now you have two options that change the behaviour of the plugin.
![Attachment Carousel Inventree Admin interface screenshot](../assets/images/plugin/plugin_admin_interface.png "Attachment Carousel Inventree Admin interface screenshot")
![Attachment Carousel Inventree Admin interface screenshot](../assets/images/plugin/plugin_walkthrough_admin_interface.png "Attachment Carousel Inventree Admin interface screenshot")
### Using CSS to Enhance the User Experience
### Using CSS to Enhance the End User Experience
When `loop` is set to `disabled` the carousel still displays the previous and next buttons for the first and last images. This behaviour can be changed with some CSS.
Create a new file called `App.css` and add the following,
Create a new file called `AttachmentCarousel.css` and add the following,
``` CSS
.control {
@@ -452,23 +468,47 @@ Create a new file called `App.css` and add the following,
}
```
Update `AttachmentCarousel.tsx` to reference to this stylesheet and the default Mantine Carousel styles,
```diff
import { Carousel } from '@mantine/carousel';
import { Image, AspectRatio } from '@mantine/core';
+ import '@mantine/carousel/styles.css'; // Import Mantine Carousel styles
+ import './AttachmentCarousel.css'; // Import custom styles for the carousel
...
```
Update `AttachmentCarousel.tsx` to use the new styles,
```diff
...
{% raw %}
<AspectRatio ratio={1920 / 1080} >
-<Carousel withIndicators={indicators} emblaOptions={{loop:loop}} styles={{control: {backgroundColor: 'var(--mantine-color-white)', color: 'var(--mantine-color-black)'}, indicator: {backgroundColor: 'var(--mantine-color-white)'}}}>{slides}</Carousel>
+<Carousel withIndicators={indicators} emblaOptions={{loop:loop}} classNames={{control: 'control'}}>{slides}</Carousel>
</AspectRatio>
{% endraw %}
...
```
Vite will automatically bundle all the CSS files in the project to `static/assets/style.css`, but it will not automatically add a reference to the stylesheet. Add the reference manually to `Panel.tsx`,
``` TypeScript
``` diff
...
export function renderAttachmentCarouselPanel(target: HTMLElement, context: any) {
createRoot(target).render(
function AttachmentCarouselPanel({context}: {context: InvenTreePluginContext;}) {
console.log(context);
return (
<>
<link rel="stylesheet" href={`${context.host}static/plugins/${context.context.slug}/assets/style.css`} />
<QueryClientProvider client={queryClient}>
<MantineProvider theme={context.theme} defaultColorScheme={context.colorScheme}>
<App context={context} />
+ <link rel="stylesheet" href={`${context.host}static/plugins/${context.context.slug}/assets/style.css`} />
<QueryClientProvider client={context.queryClient}>
<MantineProvider>
<AttachmentCarousel context={context} />
</MantineProvider>
</QueryClientProvider>
</>
);
}
...
```
The reference requires the host and the [plugin-name](./index.md##static-files). Rather than statically coding these references, the host reference may be retrieved via the context and the plugin-name may be passed via additional context data. In this example it is passed as the `slug`, as by default the slug is the plugin name. Add this additional context data via `core.py`,
@@ -483,7 +523,7 @@ panels.append({
'context': {
# Provide additional context data to the panel
'settings': self.get_settings_dict(),
'attachments': attachments_info,
'attachments': attachments_urls,
+ 'slug': self.SLUG
}
})
@@ -491,4 +531,4 @@ panels.append({
Update the plugin in Inventree and the walkthrough is complete!
![Attachment Carousel in Inventree panel screenshot](../assets/images/plugin/plugin_attachment_carousel.png "Attachment Carousel in Inventree panel screenshot")
![Attachment Carousel in Inventree panel screenshot](../assets/images/plugin/plugin_walkthrough_attachment_carousel.png "Attachment Carousel in Inventree panel screenshot")