Add inline image add/remove

This commit is contained in:
april 2024-01-15 16:59:02 -06:00
parent 924252b38f
commit 9eb5465bc3
6 changed files with 213 additions and 26 deletions

View File

@ -25,7 +25,6 @@ import { IconPencil, IconTrash } from "@tabler/icons-react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import classes from "./route.module.css";
import { AircraftLogItem } from "@/ui/display/editable/aircraft-log-item";
import { DateLogItem } from "@/ui/display/editable/date-log-item";
import { HourLogItem } from "@/ui/display/editable/hour-log-item";
@ -33,6 +32,7 @@ import { IntLogItem } from "@/ui/display/editable/int-log-item";
import { ListLogItem } from "@/ui/display/editable/list-log-item";
import { TimeLogItem } from "@/ui/display/editable/time-log-item";
import { TextLogItem } from "@/ui/display/editable/text-log-item";
import ImageLogItem from "@/ui/display/editable/img-log-item";
export default function Flight() {
const params = useParams();
@ -140,24 +140,11 @@ export default function Flight() {
<Grid justify="center">
{imageIds.length > 0 ? (
<CollapsibleFieldset legend="Images" mt="sm" w="100%">
<Carousel
style={{ maxHeight: "700px" }}
withIndicators
slideGap="sm"
slideSize={{ base: "100%", sm: "80%" }}
classNames={classes}
>
{imageIds.map((img) => (
<Carousel.Slide key={randomId()}>
<SecureImage
key={randomId()}
id={img}
h="700px"
radius="lg"
<ImageLogItem
imageIds={imageIds}
id={params.id}
mah="700px"
/>
</Carousel.Slide>
))}
</Carousel>
</CollapsibleFieldset>
) : null}
<CollapsibleFieldset legend="About" mt="sm" w="100%">

View File

@ -0,0 +1,146 @@
import { Carousel } from "@mantine/carousel";
import classes from "./img-log-item.module.css";
import { randomId, useDisclosure } from "@mantine/hooks";
import SecureImage from "../secure-img";
import {
ActionIcon,
Button,
Group,
Loader,
Modal,
Stack,
Tooltip,
Text,
} from "@mantine/core";
import { IconPencil } from "@tabler/icons-react";
import ListInput from "@/ui/input/list-input";
import ImageUpload from "@/ui/input/image-upload";
import { useState } from "react";
import { useApi } from "@/util/api";
import { useMutation, useQueryClient } from "@tanstack/react-query";
export default function ImageLogItem({
imageIds,
id,
mah = "",
}: {
imageIds: string[];
id: string;
mah?: string;
}) {
const [editOpened, { open: openEdit, close: closeEdit }] =
useDisclosure(false);
const [existingImages, setExistingImages] = useState<string[]>(imageIds);
const [newImages, setNewImages] = useState<File[]>([]);
const client = useApi();
const queryClient = useQueryClient();
const updateValue = useMutation({
mutationFn: async () => {
const missing = imageIds.filter(
(item: string) => existingImages?.indexOf(item) < 0
);
for (const img of missing) {
await client.delete(`/img/${img}`);
}
await client
.patch(`/flights/${id}`, { images: existingImages })
.then((res) => res.data);
// Upload images
if (newImages.length > 0) {
const imageForm = new FormData();
for (const img of newImages ?? []) {
imageForm.append("images", img);
}
console.log(imageForm);
const img_id = await client.post(
`/flights/${id}/add_images`,
imageForm,
{ headers: { "Content-Type": "multipart/form-data" } }
);
if (!img_id) {
await queryClient.invalidateQueries({ queryKey: ["flights-list"] });
throw new Error("Image upload failed");
}
}
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [id] });
queryClient.invalidateQueries({ queryKey: ["flights-list"] });
closeEdit();
},
});
return (
<>
<Modal
opened={editOpened}
onClose={closeEdit}
title={`Edit Images`}
centered
>
<Stack>
<ListInput
label="Existing Images"
value={existingImages}
setValue={setExistingImages}
canAdd={false}
/>
<ImageUpload
value={newImages}
setValue={setNewImages}
label="Add Images"
mt="md"
placeholder="Images"
/>
<Group justify="flex-end">
{updateValue.isPending ? <Loader /> : null}
{updateValue.isError ? (
<Text c="red">{updateValue.error?.message}</Text>
) : null}
<Button
onClick={() => {
updateValue.mutate();
}}
leftSection={<IconPencil />}
>
Update
</Button>
</Group>
</Stack>
</Modal>
<Stack>
<Group justify="flex-end" py="0" my="0">
<Tooltip label="Edit Images">
<ActionIcon variant="transparent" onClick={openEdit}>
<IconPencil />
</ActionIcon>
</Tooltip>
</Group>
<Carousel
style={{ maxHeight: mah }}
withIndicators
slideGap="sm"
slideSize={{ base: "100%", sm: "80%" }}
classNames={classes}
>
{imageIds.map((img) => (
<Carousel.Slide key={randomId()}>
<SecureImage key={randomId()} id={img} h="700px" radius="lg" />
</Carousel.Slide>
))}
</Carousel>
</Stack>
</>
);
}

View File

@ -1,7 +1,6 @@
import {
AircraftFormSchema,
AircraftSchema,
FlightDisplaySchema,
FlightFormSchema,
} from "@/util/types";
import {
@ -29,7 +28,6 @@ import TimeInput from "./time-input";
import { ZeroIntInput } from "./int-input";
import ListInput from "./list-input";
import { IconPencil, IconPlaneTilt, IconPlus } from "@tabler/icons-react";
import { AxiosError } from "axios";
import { useDisclosure } from "@mantine/hooks";
import AircraftForm from "./aircraft-form";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

View File

@ -0,0 +1,52 @@
import { FlightFormSchema } from "@/util/types";
import { FileInput, FileInputProps, Pill } from "@mantine/core";
import { UseFormReturnType } from "@mantine/form";
import { randomId } from "@mantine/hooks";
import { IconPhoto } from "@tabler/icons-react";
import { Dispatch, SetStateAction } from "react";
export default function ImageUpload({
value,
setValue,
label = "",
placeholder = "",
mt = "",
}: {
value: File[];
setValue: Dispatch<SetStateAction<File[]>>;
label?: string;
placeholder?: string;
mt?: string;
}) {
const ValueComponent: FileInputProps["valueComponent"] = ({ value }) => {
if (value === null) {
return null;
}
if (Array.isArray(value)) {
return (
<Pill.Group>
{value.map((file) => (
<Pill key={randomId()}>{file.name}</Pill>
))}
</Pill.Group>
);
}
return <Pill>{value.name}</Pill>;
};
return (
<FileInput
label={label}
placeholder={placeholder}
multiple
mt={mt}
accept="image/*"
valueComponent={ValueComponent}
rightSectionPointerEvents="none"
rightSection={<IconPhoto />}
onChange={setValue}
/>
);
}

View File

@ -5,10 +5,12 @@ export default function ListInput({
label,
value,
setValue,
canAdd = true,
}: {
label: string;
value: string[];
setValue: Dispatch<SetStateAction<string[]>>;
canAdd?: boolean;
}) {
const [inputValue, setInputValue] = useState<string>("");
@ -43,11 +45,13 @@ export default function ListInput({
{item}
</Pill>
))}
{canAdd ? (
<PillsInput.Field
value={inputValue}
onChange={(event) => setInputValue(event.currentTarget.value)}
onKeyDown={handleKeyDown}
/>
) : null}
</Pill.Group>
</PillsInput>
);