2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

[PUI] download image (#8230)

* Add placeholder for download image button

* Implement image download functionality

* Increase timeout

* Show timeout notification

* Icon cleanup
This commit is contained in:
Oliver 2024-10-06 21:49:13 +11:00 committed by GitHub
parent c914d1c5af
commit a323bf0007
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 93 additions and 26 deletions

View File

@ -19,6 +19,8 @@ import { api } from '../../App';
import { UserRoles } from '../../enums/Roles';
import { cancelEvent } from '../../functions/events';
import { InvenTreeIcon } from '../../functions/icons';
import { useEditApiFormModal } from '../../hooks/UseForm';
import { useGlobalSettingsState } from '../../states/SettingsState';
import { useUserState } from '../../states/UserState';
import { PartThumbTable } from '../../tables/part/PartThumbTable';
import { vars } from '../../theme';
@ -42,11 +44,13 @@ export type DetailImageProps = {
* Actions for Detail Images.
* If true, the button type will be visible
* @param {boolean} selectExisting - PART ONLY. Allows selecting existing images as part image
* @param {boolean} downloadImage - Allows downloading image from a remote URL
* @param {boolean} uploadFile - Allows uploading a new image
* @param {boolean} deleteFile - Allows deleting the current image
*/
export type DetailImageButtonProps = {
selectExisting?: boolean;
downloadImage?: boolean;
uploadFile?: boolean;
deleteFile?: boolean;
};
@ -245,7 +249,8 @@ function ImageActionButtons({
apiPath,
hasImage,
pk,
setImage
setImage,
downloadImage
}: Readonly<{
actions?: DetailImageButtonProps;
visible: boolean;
@ -253,7 +258,10 @@ function ImageActionButtons({
hasImage: boolean;
pk: string;
setImage: (image: string) => void;
downloadImage: () => void;
}>) {
const globalSettings = useGlobalSettingsState();
return (
<>
{visible && (
@ -284,6 +292,25 @@ function ImageActionButtons({
}}
/>
)}
{actions.downloadImage &&
globalSettings.isSet('INVENTREE_DOWNLOAD_FROM_URL') && (
<ActionButton
icon={
<InvenTreeIcon
icon="download"
iconProps={{ color: 'white' }}
/>
}
tooltip={t`Download remote image`}
variant="outline"
size="lg"
tooltipAlignment="top"
onClick={(event: any) => {
cancelEvent(event);
downloadImage();
}}
/>
)}
{actions.uploadFile && (
<ActionButton
icon={
@ -341,6 +368,21 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
const permissions = useUserState();
const downloadImage = useEditApiFormModal({
url: props.apiPath,
title: t`Download Image`,
fields: {
remote_image: {}
},
timeout: 10000,
successMessage: t`Image downloaded successfully`,
onFormSuccess: (response: any) => {
if (response.image) {
setAndRefresh(response.image);
}
}
});
const hasOverlay: boolean = useMemo(() => {
return (
props.imageActions?.selectExisting ||
@ -359,6 +401,8 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
};
return (
<>
{downloadImage.modal}
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos="relative">
<>
<ApiImage
@ -367,7 +411,9 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
maw={IMAGE_DIMENSION}
onClick={expandImage}
/>
{permissions.hasChangeRole(props.appRole) && hasOverlay && hovered && (
{permissions.hasChangeRole(props.appRole) &&
hasOverlay &&
hovered && (
<Overlay color="black" opacity={0.8} onClick={expandImage}>
<ImageActionButtons
visible={hovered}
@ -376,10 +422,12 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
hasImage={props.src ? true : false}
pk={props.pk}
setImage={setAndRefresh}
downloadImage={downloadImage.open}
/>
</Overlay>
)}
</>
</AspectRatio>
</>
);
}

View File

@ -33,7 +33,10 @@ import {
extractAvailableFields,
mapFields
} from '../../functions/forms';
import { invalidResponse } from '../../functions/notifications';
import {
invalidResponse,
showTimeoutNotification
} from '../../functions/notifications';
import { getDetailUrl } from '../../functions/urls';
import { TableState } from '../../hooks/UseTable';
import { PathParams } from '../../states/ApiState';
@ -540,7 +543,7 @@ export function ApiForm({
break;
}
} else {
invalidResponse(0);
showTimeoutNotification();
props.onFormError?.();
}

View File

@ -31,6 +31,7 @@ import {
IconEdit,
IconExclamationCircle,
IconExternalLink,
IconFileDownload,
IconFileUpload,
IconFlag,
IconFlagShare,
@ -143,6 +144,7 @@ const icons = {
notes: IconNotes,
photo: IconPhoto,
upload: IconFileUpload,
download: IconFileDownload,
reject: IconX,
refresh: IconRefresh,
select_image: IconGridDots,

View File

@ -39,6 +39,17 @@ export function invalidResponse(returnCode: number) {
});
}
/**
* Display a notification on timeout
*/
export function showTimeoutNotification() {
notifications.show({
title: t`Timeout`,
message: t`The request timed out`,
color: 'red'
});
}
/*
* Display a login / logout notification message.
* Any existing login notification(s) will be hidden.

View File

@ -153,6 +153,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
refresh={refreshInstance}
imageActions={{
uploadFile: true,
downloadImage: true,
deleteFile: true
}}
/>

View File

@ -511,6 +511,7 @@ export default function PartDetail() {
appRole={UserRoles.part}
imageActions={{
selectExisting: true,
downloadImage: true,
uploadFile: true,
deleteFile: true
}}
@ -531,7 +532,7 @@ export default function PartDetail() {
) : (
<Skeleton />
);
}, [part, instanceQuery]);
}, [globalSettings, part, instanceQuery]);
// Part data panels (recalculate when part data changes)
const partPanels: PanelType[] = useMemo(() => {

View File

@ -95,6 +95,7 @@ export default function ReturnOrderDetail() {
type: 'text',
name: 'customer_reference',
label: t`Customer Reference`,
icon: 'customer',
copy: true,
hidden: !order.customer_reference
},