Files
mtdb_movie/resources/client/admin/titles/title-editor/title-images-editor.tsx
maher 703f50a09d
Some checks failed
Build / run (push) Has been cancelled
first commit
2025-10-29 11:42:25 +01:00

122 lines
3.8 KiB
TypeScript
Executable File

import {TitleBackdrop} from '@app/titles/title-poster/title-backdrop';
import {DialogTrigger} from '@common/ui/overlays/dialog/dialog-trigger';
import {ImageZoomDialog} from '@common/ui/overlays/dialog/image-zoom-dialog';
import {Button} from '@common/ui/buttons/button';
import {Trans} from '@common/i18n/trans';
import {IconButton} from '@common/ui/buttons/icon-button';
import {ZoomOutMapIcon} from '@common/icons/material/ZoomOutMap';
import {useDeleteImage} from '@app/admin/titles/requests/use-delete-image';
import {UploadInputType} from '@common/uploads/types/upload-input-config';
import {FileUploadProvider} from '@common/uploads/uploader/file-upload-provider';
import {openUploadWindow} from '@common/uploads/utils/open-upload-window';
import {useUploadImage} from '@app/admin/titles/requests/use-upload-image';
import {useOutletContext, useParams} from 'react-router-dom';
import {AddIcon} from '@common/icons/material/Add';
import {validateUpload} from '@common/uploads/uploader/validate-upload';
import {toast} from '@common/ui/toast/toast';
import {TitleEditorLayout} from '@app/admin/titles/title-editor/title-editor-layout';
import {Title} from '@app/titles/models/title';
import {IllustratedMessage} from '@common/ui/images/illustrated-message';
import React from 'react';
import {ImageIcon} from '@common/icons/material/Image';
export function TitleImagesEditor() {
const title = useOutletContext<Title>();
return (
<TitleEditorLayout>
<FileUploadProvider>
<UploadButton />
</FileUploadProvider>
<div className="mt-24 grid grid-cols-2 gap-24 md:grid-cols-3">
{title.images.map((image, index) => (
<div key={image.id}>
<TitleBackdrop src={image.url} srcSize="md" className="rounded" />
<div className="mt-6 flex items-center justify-between gap-14">
<DeleteButton imageId={image.id} />
<DialogTrigger type="modal">
<IconButton variant="outline" size="xs">
<ZoomOutMapIcon />
</IconButton>
<ImageZoomDialog
images={title.images.map(img => img.url)}
defaultActiveIndex={index}
/>
</DialogTrigger>
</div>
</div>
))}
</div>
{!title.images.length && <NoImagesMessage />}
</TitleEditorLayout>
);
}
function NoImagesMessage() {
return (
<IllustratedMessage
className="mt-40"
imageMargin="mb-8"
image={
<div className="text-muted">
<ImageIcon size="xl" />
</div>
}
imageHeight="h-auto"
title={<Trans message="No images have been added yet" />}
/>
);
}
const MAX_IMAGE_SIZE = 5000000;
function UploadButton() {
const {titleId} = useParams();
const uploadImage = useUploadImage();
const selectAndUploadFile = async () => {
const files = await openUploadWindow({
types: [UploadInputType.image],
});
const errorMessage = validateUpload(files[0], {
maxFileSize: MAX_IMAGE_SIZE,
});
if (errorMessage) {
toast.danger(errorMessage);
return;
}
uploadImage.mutate({
file: files[0].native,
titleId: titleId!,
});
};
return (
<Button
variant="outline"
color="primary"
startIcon={<AddIcon />}
disabled={uploadImage.isPending}
onClick={() => selectAndUploadFile()}
>
<Trans message="Upload image" />
</Button>
);
}
interface ImageItemProps {
imageId: number;
}
function DeleteButton({imageId}: ImageItemProps) {
const deleteImage = useDeleteImage(imageId);
return (
<Button
variant="outline"
size="xs"
disabled={deleteImage.isPending}
onClick={() => deleteImage.mutate()}
>
<Trans message="Delete" />
</Button>
);
}