Can set member photo
This commit is contained in:
@ -6,6 +6,7 @@ import {
|
||||
mdiFamilyTree,
|
||||
mdiHumanMaleFemale,
|
||||
mdiLockCheck,
|
||||
mdiRefresh,
|
||||
} from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
import HomeIcon from "@mui/icons-material/Home";
|
||||
@ -21,16 +22,15 @@ import {
|
||||
ListSubheader,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { mdiRefresh } from "@mdi/js";
|
||||
import React from "react";
|
||||
import { Outlet, useLocation, useParams } from "react-router-dom";
|
||||
import { Family, FamilyApi } from "../api/FamilyApi";
|
||||
import { MemberApi, MembersList } from "../api/MemberApi";
|
||||
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
|
||||
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
|
||||
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
|
||||
import { AsyncWidget } from "./AsyncWidget";
|
||||
import { RouterLink } from "./RouterLink";
|
||||
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
|
||||
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
|
||||
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
|
||||
import { Member, MemberApi, MembersList } from "../api/MemberApi";
|
||||
|
||||
interface FamilyContext {
|
||||
family: Family;
|
||||
|
15
geneit_app/src/widgets/MemberPhoto.tsx
Normal file
15
geneit_app/src/widgets/MemberPhoto.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { Avatar } from "@mui/material";
|
||||
import { Member } from "../api/MemberApi";
|
||||
|
||||
export function MemberPhoto(p: {
|
||||
member: Member;
|
||||
width: number;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<Avatar
|
||||
sx={{ width: `${p.width}px`, height: "auto", display: "inline-block" }}
|
||||
variant="rounded"
|
||||
src={p.member.photoURL}
|
||||
/>
|
||||
);
|
||||
}
|
98
geneit_app/src/widgets/forms/UploadPhotoButton.tsx
Normal file
98
geneit_app/src/widgets/forms/UploadPhotoButton.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import { Button } from "@mui/material";
|
||||
import { filesize } from "filesize";
|
||||
import React from "react";
|
||||
import { ServerApi } from "../../api/ServerApi";
|
||||
import { ImageCropperDialog } from "../../dialogs/ImageCropperDialog";
|
||||
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
|
||||
import { Area } from "react-easy-crop";
|
||||
import getCroppedImg from "../../utils/crop_image";
|
||||
|
||||
export function UploadPhotoButton(p: {
|
||||
label: string;
|
||||
onPhotoSelected: (b: Blob) => Promise<void>;
|
||||
}): React.ReactElement {
|
||||
const [processing, setProcessing] = React.useState(false);
|
||||
const [imageBlob, setImageBlob] = React.useState<Blob>();
|
||||
const [imageURL, setImageURL] = React.useState<string>();
|
||||
const alert = useAlert();
|
||||
|
||||
const uploadPhoto = async () => {
|
||||
try {
|
||||
// Create file element
|
||||
const fileEl = document.createElement("input");
|
||||
fileEl.type = "file";
|
||||
fileEl.accept =
|
||||
ServerApi.Config.constraints.photo_allowed_types.join(",");
|
||||
fileEl.click();
|
||||
|
||||
// Wait for a file to be chosen
|
||||
await new Promise((res, _rej) =>
|
||||
fileEl.addEventListener("change", () => res(null))
|
||||
);
|
||||
|
||||
if ((fileEl.files?.length ?? 0) === 0) return null;
|
||||
const file = fileEl.files![0];
|
||||
|
||||
// Check file size
|
||||
if (file.size > ServerApi.Config.constraints.photo_max_size) {
|
||||
await alert(
|
||||
`Le fichier sélectionné est trop lourd ! (taille maximale acceptée : ${filesize(
|
||||
ServerApi.Config.constraints.photo_max_size
|
||||
)})`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const tempURL = URL.createObjectURL(fileEl.files![0]);
|
||||
|
||||
setImageBlob(file);
|
||||
setImageURL(tempURL);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert("Failed to upload custom account image!");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const cancelCrop = () => {
|
||||
setImageURL(undefined);
|
||||
};
|
||||
|
||||
const submitCrop = async (a: Area | undefined) => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
let blob = imageBlob!;
|
||||
|
||||
if (a) {
|
||||
blob = await getCroppedImg(imageURL!, a!);
|
||||
}
|
||||
|
||||
await p.onPhotoSelected(blob);
|
||||
|
||||
setImageBlob(undefined);
|
||||
setImageURL(undefined);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert("Echec du traitement de la photo !");
|
||||
}
|
||||
|
||||
setProcessing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Upload button */}
|
||||
<Button onClick={uploadPhoto}>{p.label}</Button>
|
||||
|
||||
{/* Crop image dialog */}
|
||||
{imageURL && (
|
||||
<ImageCropperDialog
|
||||
processing={processing}
|
||||
src={imageURL!}
|
||||
onCancel={cancelCrop}
|
||||
onSubmit={submitCrop}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user