diff --git a/src/frontend/src/components/buttons/AdminButton.tsx b/src/frontend/src/components/buttons/AdminButton.tsx index 6dbd5f565a..b4f02ec465 100644 --- a/src/frontend/src/components/buttons/AdminButton.tsx +++ b/src/frontend/src/components/buttons/AdminButton.tsx @@ -3,8 +3,8 @@ import { IconUserStar } from '@tabler/icons-react'; import { useCallback, useMemo } from 'react'; import type { ModelType } from '../../enums/ModelType'; +import { generateUrl } from '../../functions/urls'; import { useServerApiState } from '../../states/ApiState'; -import { useLocalState } from '../../states/LocalState'; import { useUserState } from '../../states/UserState'; import { ModelInformationDict } from '../render/ModelType'; import { ActionButton } from './ActionButton'; @@ -56,16 +56,14 @@ export default function AdminButton(props: Readonly) { const openAdmin = useCallback( (event: any) => { const modelDef = ModelInformationDict[props.model]; - const host = useLocalState.getState().host; if (!modelDef.admin_url) { return; } // Generate the URL for the admin interface - const url = new URL( - `${server.server.django_admin}${modelDef.admin_url}${props.id}/`, - host + const url = generateUrl( + `${server.server.django_admin}${modelDef.admin_url}${props.id}/` ); if (event?.ctrlKey || event?.shiftKey) { diff --git a/src/frontend/src/components/buttons/PrintingActions.tsx b/src/frontend/src/components/buttons/PrintingActions.tsx index 3e633ac5a7..aef0eb4d0f 100644 --- a/src/frontend/src/components/buttons/PrintingActions.tsx +++ b/src/frontend/src/components/buttons/PrintingActions.tsx @@ -8,9 +8,9 @@ import { api } from '../../App'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import type { ModelType } from '../../enums/ModelType'; import { extractAvailableFields } from '../../functions/forms'; +import { generateUrl } from '../../functions/urls'; import { useCreateApiFormModal } from '../../hooks/UseForm'; import { apiUrl } from '../../states/ApiState'; -import { useLocalState } from '../../states/LocalState'; import { useUserSettingsState } from '../../states/SettingsState'; import type { ApiFormFieldSet } from '../forms/fields/ApiFormField'; import { ActionDropdown } from '../items/ActionDropdown'; @@ -28,8 +28,6 @@ export function PrintingActions({ enableReports?: boolean; modelType?: ModelType; }) { - const { host } = useLocalState.getState(); - const userSettings = useUserSettingsState(); const enabled = useMemo(() => items.length > 0, [items]); @@ -116,7 +114,7 @@ export function PrintingActions({ if (response.output) { // An output file was generated - const url = new URL(response.output, host); + const url = generateUrl(response.output); window.open(url.toString(), '_blank'); } } @@ -154,7 +152,7 @@ export function PrintingActions({ if (response.output) { // An output file was generated - const url = new URL(response.output, host); + const url = generateUrl(response.output); window.open(url.toString(), '_blank'); } } diff --git a/src/frontend/src/components/images/ApiImage.tsx b/src/frontend/src/components/images/ApiImage.tsx index 92b50f1578..8227f7ae10 100644 --- a/src/frontend/src/components/images/ApiImage.tsx +++ b/src/frontend/src/components/images/ApiImage.tsx @@ -6,6 +6,7 @@ import { Image, type ImageProps, Skeleton, Stack } from '@mantine/core'; import { useMemo } from 'react'; +import { generateUrl } from '../../functions/urls'; import { useLocalState } from '../../states/LocalState'; interface ApiImageProps extends ImageProps { @@ -19,7 +20,7 @@ export function ApiImage(props: Readonly) { const { host } = useLocalState.getState(); const imageUrl = useMemo(() => { - return new URL(props.src, host).toString(); + return generateUrl(props.src, host); }, [host, props.src]); return ( diff --git a/src/frontend/src/components/items/AttachmentLink.tsx b/src/frontend/src/components/items/AttachmentLink.tsx index 3103c4f1dc..928c61809b 100644 --- a/src/frontend/src/components/items/AttachmentLink.tsx +++ b/src/frontend/src/components/items/AttachmentLink.tsx @@ -10,8 +10,7 @@ import { IconPhoto } from '@tabler/icons-react'; import { type ReactNode, useMemo } from 'react'; - -import { useLocalState } from '../../states/LocalState'; +import { generateUrl } from '../../functions/urls'; /** * Return an icon based on the provided filename @@ -61,16 +60,13 @@ export function AttachmentLink({ }>): ReactNode { const text = external ? attachment : attachment.split('/').pop(); - const host = useLocalState((s) => s.host); - const url = useMemo(() => { if (external) { return attachment; } - const u = new URL(attachment, host); - return u.toString(); - }, [host, attachment, external]); + return generateUrl(attachment); + }, [attachment, external]); return ( diff --git a/src/frontend/src/components/modals/AboutInvenTreeModal.tsx b/src/frontend/src/components/modals/AboutInvenTreeModal.tsx index 4b0f21d643..1862d60848 100644 --- a/src/frontend/src/components/modals/AboutInvenTreeModal.tsx +++ b/src/frontend/src/components/modals/AboutInvenTreeModal.tsx @@ -15,8 +15,8 @@ import { useQuery } from '@tanstack/react-query'; import { api } from '../../App'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; +import { generateUrl } from '../../functions/urls'; import { apiUrl, useServerApiState } from '../../states/ApiState'; -import { useLocalState } from '../../states/LocalState'; import { useUserState } from '../../states/UserState'; import { CopyButton } from '../buttons/CopyButton'; import { StylishText } from '../items/StylishText'; @@ -35,7 +35,6 @@ export function AboutInvenTreeModal({ modalBody: string; }>) { const [user] = useUserState((state) => [state.user]); - const { host } = useLocalState.getState(); const [server] = useServerApiState((state) => [state.server]); if (user?.is_staff != true) @@ -137,7 +136,7 @@ export function AboutInvenTreeModal({ { ref: 'api', title: API Version, - link: new URL('/api-doc/', host).toString(), + link: generateUrl('/api-doc/'), copy: true }, { diff --git a/src/frontend/src/components/plugins/PluginSource.tsx b/src/frontend/src/components/plugins/PluginSource.tsx index 33cb3e23d4..dff2c5c505 100644 --- a/src/frontend/src/components/plugins/PluginSource.tsx +++ b/src/frontend/src/components/plugins/PluginSource.tsx @@ -1,11 +1,9 @@ -import { useLocalState } from '../../states/LocalState'; +import { generateUrl } from '../../functions/urls'; /* * Load an external plugin source from a URL. */ export async function loadExternalPluginSource(source: string) { - const host = useLocalState.getState().host; - source = source.trim(); // If no source is provided, clear the plugin content @@ -13,7 +11,7 @@ export async function loadExternalPluginSource(source: string) { return null; } - const url = new URL(source, host).toString(); + const url = generateUrl(source); const module = await import(/* @vite-ignore */ url) .catch((error) => { diff --git a/src/frontend/src/functions/icons.tsx b/src/frontend/src/functions/icons.tsx index 6a89892f2d..54cb6041e3 100644 --- a/src/frontend/src/functions/icons.tsx +++ b/src/frontend/src/functions/icons.tsx @@ -126,6 +126,7 @@ const icons = { units: IconRulerMeasure, keywords: IconTag, status: IconInfoCircle, + status_custom_key: IconInfoCircle, edit: IconEdit, info: IconInfoCircle, exclamation: IconExclamationCircle, @@ -291,6 +292,3 @@ export function InvenTreeIcon(props: Readonly) { return ; } -function IconShapes(props: TablerIconProps): Element { - throw new Error('Function not implemented.'); -} diff --git a/src/frontend/src/functions/urls.tsx b/src/frontend/src/functions/urls.tsx index 5440a61ab8..8f84a60724 100644 --- a/src/frontend/src/functions/urls.tsx +++ b/src/frontend/src/functions/urls.tsx @@ -1,6 +1,7 @@ import { ModelInformationDict } from '../components/render/ModelType'; import type { ModelType } from '../enums/ModelType'; import { base_url } from '../main'; +import { useLocalState } from '../states/LocalState'; /** * Returns the detail view URL for a given model type @@ -30,3 +31,26 @@ export function getDetailUrl( console.error(`No detail URL found for model ${model} <${pk}>`); return ''; } + +/** + * Returns the edit view URL for a given model type + */ +export function generateUrl(url: string | URL, base?: string): string { + const { host } = useLocalState.getState(); + + let newUrl: string | URL = url; + + try { + if (base) { + newUrl = new URL(url, base).toString(); + } else if (host) { + newUrl = new URL(url, host).toString(); + } else { + newUrl = url.toString(); + } + } catch (e: any) { + console.error(`ERR: generateURL failed. url='${url}', base='${base}'`); + } + + return newUrl.toString(); +} diff --git a/src/frontend/src/states/IconState.tsx b/src/frontend/src/states/IconState.tsx index 613e83be97..008d93ffb4 100644 --- a/src/frontend/src/states/IconState.tsx +++ b/src/frontend/src/states/IconState.tsx @@ -2,8 +2,8 @@ import { create } from 'zustand'; import { api } from '../App'; import { ApiEndpoints } from '../enums/ApiEndpoints'; +import { generateUrl } from '../functions/urls'; import { apiUrl } from './ApiState'; -import { useLocalState } from './LocalState'; type IconPackage = { name: string; @@ -34,8 +34,6 @@ export const useIconState = create()((set, get) => ({ fetchIcons: async () => { if (get().hasLoaded) return; - const host = useLocalState.getState().host; - const packs = await api.get(apiUrl(ApiEndpoints.icons)); await Promise.all( @@ -43,8 +41,7 @@ export const useIconState = create()((set, get) => ({ const fontName = `inventree-icon-font-${pack.prefix}`; const src = Object.entries(pack.fonts as Record) .map( - ([format, url]) => - `url(${new URL(url, host).toString()}) format("${format}")` + ([format, url]) => `url(${generateUrl(url)}) format("${format}")` ) .join(',\n'); const font = new FontFace(fontName, `${src};`);