keycloak-d4science-spi-parent/keycloak-d4science-theme/src.v3/js/apps/account-ui/src/d4science-page/AvatarForm.tsx

198 lines
6.8 KiB
TypeScript

import {
Form,
FormGroup,
ActionGroup,
FileUpload,
Avatar,
Button,
HelperText,
HelperTextItem
} from "@patternfly/react-core";
import { useState, CSSProperties } from "react";
import { useTranslation } from "react-i18next";
import { useEnvironment } from "../root/KeycloakContext";
import { useAlerts, HelpItem } from "ui-shared";
interface AvatarFormProps {
accountUrl: string;
}
interface AvatarFormState {
errors: any;
imageBlob: any;
filename: string;
avatarUrl: string;
avatarSrc: string;
noAvatarSrc: string;
}
export const AvatarForm = ({accountUrl} : AvatarFormProps) => {
const { t } = useTranslation();
const context = useEnvironment();
const urlparts = accountUrl.indexOf('?') > 0 ? accountUrl.split('?') : accountUrl;
const currentAvatarUrl = Array.isArray(urlparts) ? urlparts[0] + "-avatar?" + urlparts[1] : urlparts + "-avatar";
const noavatar = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMiwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzNiAzNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzYgMzY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRjBGMEYwO30KCS5zdDF7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRDJEMkQyO30KCS5zdDJ7ZmlsbDojQjhCQkJFO30KCS5zdDN7ZmlsbDojRDJEMkQyO30KPC9zdHlsZT4KPHJlY3QgY2xhc3M9InN0MCIgd2lkdGg9IjM2IiBoZWlnaHQ9IjM2Ii8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0xNy43LDIwLjFjLTMuNSwwLTYuNC0yLjktNi40LTYuNHMyLjktNi40LDYuNC02LjRzNi40LDIuOSw2LjQsNi40UzIxLjMsMjAuMSwxNy43LDIwLjF6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMy4zLDM2bDAtNi43Yy0yLDAuNC0yLjksMS40LTMuMSwzLjVMMTAuMSwzNkgxMy4zeiIvPgo8cGF0aCBjbGFzcz0ic3QzIiBkPSJNMTAuMSwzNmwwLjEtMy4yYzAuMi0yLjEsMS4xLTMuMSwzLjEtMy41bDAsNi43aDkuNGwwLTYuN2MyLDAuNCwyLjksMS40LDMuMSwzLjVsMC4xLDMuMmg0LjcKCWMtMC40LTMuOS0xLjMtOS0yLjktMTFjLTEuMS0xLjQtMi4zLTIuMi0zLjUtMi42cy0xLjgtMC42LTYuMy0wLjZzLTYuMSwwLjctNi4xLDAuN2MtMS4yLDAuNC0yLjQsMS4yLTMuNCwyLjYKCUM2LjcsMjcsNS44LDMyLjIsNS40LDM2SDEwLjF6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0yNS45LDM2bC0wLjEtMy4yYy0wLjItMi4xLTEuMS0zLjEtMy4xLTMuNWwwLDYuN0gyNS45eiIvPgo8L3N2Zz4=";
const initialState = {
errors: {avatar: ""},
imageBlob: null,
filename: "",
avatarUrl: currentAvatarUrl,
avatarSrc: currentAvatarUrl,
noAvatarSrc: noavatar
};
const [state, setState] = useState<AvatarFormState>(initialState);
const { addAlert, addError } = useAlerts();
const reader = new FileReader();
let currentFilename = "";
reader.onloadend = (event: any) => {
var imgData = String(event.target!.result)
imageScale(imgData, (blob: Blob) => {
setState({
errors: {avatar: ""},
imageBlob: blob,
filename: currentFilename,
avatarUrl: currentAvatarUrl,
avatarSrc: URL.createObjectURL(blob),
noAvatarSrc: noavatar
})
})
};
const handleFileInputChange = (_: any, file: File) => {
if (file != null && file.name != "") {
if (file.type.startsWith("image")) {
currentFilename = file.name;
reader.readAsDataURL(file);
} else {
console.error("Wrong file type: " + file.type);
}
}
};
const handleClear = (_: any) => {
setState(initialState);
};
const imageScale = (imgData: string, callback: any) => {
var img = new Image()
img.src = imgData
img.onload = (event: Event) => {
var canvas = document.createElement("canvas")
var ctx = canvas.getContext("2d")
ctx!.drawImage(img, 0, 0)
var MAX_WIDTH = 250
var MAX_HEIGHT = 250
var width = img.width
var height = img.height
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width
width = MAX_WIDTH
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height
height = MAX_HEIGHT
}
}
canvas.width = width
canvas.height = height
ctx = canvas.getContext("2d")
ctx!.drawImage(img, 0, 0, width, height)
canvas.toBlob(callback)
}
}
const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault()
const form = event.target as HTMLFormElement
var formData = new FormData(form)
formData.append("image", state.imageBlob)
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (200 <= xhr.status && xhr.status <= 204) {
addAlert(t("avatarUpdatedMessage"));
// force reload avatar
setState(initialState);
} else {
addError(xhr.response);
}
}
}
xhr.open(form.method, form.action, true);
xhr.send(formData);
}
const handleError = (event: any) => {
setState({
errors: {avatar: t('error-noAvatarFound')},
imageBlob: null,
filename: "",
avatarUrl: "",
avatarSrc: "",
noAvatarSrc: noavatar
})
};
const { filename, avatarUrl, avatarSrc, noAvatarSrc } = state;
const avatarStyle = {
objectFit: 'cover',
width: '150px', height: '150px',
border: '1px solid lightgray',
boxShadow: 'lightgray 6px 3px 10px 2px'
} as CSSProperties;
return (
<Form id="avatarForm" method="post"
action={avatarUrl} encType="multipart/form-data"
onSubmit={handleSubmit}
>
<FormGroup label={t('avatarLabel')} fieldId="avatar-current-or-preview">
<HelperText>
{state.errors.avatar !== ""
&& <HelperTextItem variant="error" hasIcon>{state.errors.avatar}</HelperTextItem>
}
</HelperText>
{ avatarSrc !== ""
? <Avatar src={avatarSrc} style={avatarStyle} alt="Avatar image preview" onError={handleError}/>
: <Avatar src={noAvatarSrc} style={avatarStyle} alt="No avatar found" />
}
</FormGroup>
<FormGroup
fieldId="avatar-upload"
label={t('uploadLabel')}
labelIcon={
<HelpItem
helpText={t("avatarInfo")}
fieldLabelId="uploadLabel"
/>
}
>
<FileUpload
id="simple-file"
filename={filename}
filenamePlaceholder={t('dragdropInfo')}
browseButtonText={t('browseButton')}
onFileInputChange={handleFileInputChange}
clearButtonText={t('clearButton')}
onClearClick={handleClear}
/>
</FormGroup>
<ActionGroup>
<Button
id="save-btn" type="submit"
variant="primary"
isDisabled={filename === ""}
>
{t('doSave')}
</Button>
</ActionGroup>
</Form>
)
};
export default AvatarForm;