keycloak-d4science-spi-parent/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/src/app/content/d4science-page/AvatarForm.tsx

191 lines
8.0 KiB
TypeScript

import * as React from 'react';
import * as CSS from 'csstype';
import { Form, FormGroup, ActionGroup, FileUpload, Avatar, Button, Tooltip } from "@patternfly/react-core";
import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons';
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
import { ContentAlert } from '../ContentAlert';
import { Msg } from '../../widgets/Msg';
interface AvatarFormProps {
accountUrl: string;
}
interface AvatarFormState {
errors: any;
imageBlob: any;
filename: string;
avatarUrl: string;
avatarSrc: string;
noAvatarSrc: string;
}
export class AvatarForm extends React.Component<AvatarFormProps, AvatarFormState> {
static contextType = AccountServiceContext;
context: React.ContextType<typeof AccountServiceContext>;
private handleFileInputChange: any;
constructor(props: AvatarFormProps, context: React.ContextType<typeof AccountServiceContext>) {
super(props);
this.context = context;
var currentAvatar = props.accountUrl + "-avatar"
this.state = {
errors: {avatar: ''},
imageBlob: null,
filename: "",
avatarUrl: currentAvatar,
avatarSrc: currentAvatar,
noAvatarSrc: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMiwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzNiAzNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzYgMzY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRjBGMEYwO30KCS5zdDF7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRDJEMkQyO30KCS5zdDJ7ZmlsbDojQjhCQkJFO30KCS5zdDN7ZmlsbDojRDJEMkQyO30KPC9zdHlsZT4KPHJlY3QgY2xhc3M9InN0MCIgd2lkdGg9IjM2IiBoZWlnaHQ9IjM2Ii8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0xNy43LDIwLjFjLTMuNSwwLTYuNC0yLjktNi40LTYuNHMyLjktNi40LDYuNC02LjRzNi40LDIuOSw2LjQsNi40UzIxLjMsMjAuMSwxNy43LDIwLjF6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMy4zLDM2bDAtNi43Yy0yLDAuNC0yLjksMS40LTMuMSwzLjVMMTAuMSwzNkgxMy4zeiIvPgo8cGF0aCBjbGFzcz0ic3QzIiBkPSJNMTAuMSwzNmwwLjEtMy4yYzAuMi0yLjEsMS4xLTMuMSwzLjEtMy41bDAsNi43aDkuNGwwLTYuN2MyLDAuNCwyLjksMS40LDMuMSwzLjVsMC4xLDMuMmg0LjcKCWMtMC40LTMuOS0xLjMtOS0yLjktMTFjLTEuMS0xLjQtMi4zLTIuMi0zLjUtMi42cy0xLjgtMC42LTYuMy0wLjZzLTYuMSwwLjctNi4xLDAuN2MtMS4yLDAuNC0yLjQsMS4yLTMuNCwyLjYKCUM2LjcsMjcsNS44LDMyLjIsNS40LDM2SDEwLjF6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0yNS45LDM2bC0wLjEtMy4yYy0wLjItMi4xLTEuMS0zLjEtMy4xLTMuNWwwLDYuN0gyNS45eiIvPgo8L3N2Zz4="
}
var reader = new FileReader()
reader.onloadend = function (event: any) {
var imgData = String(event.target!.result)
this.imageScale(imgData, (blob: Blob) => {
this.setState({
imageBlob: blob,
avatarSrc: URL.createObjectURL(blob)
})
})
}.bind(this)
this.handleFileInputChange = (file: File, filename: string) => {
if (filename != "") {
this.setState({ filename: filename })
reader.readAsDataURL(file)
} else {
this.setState({
imageBlob: null,
filename: "",
avatarSrc: currentAvatar
})
}
}
}
private 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)
}
}
private handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault()
const form = event.target as HTMLFormElement
var formData = new FormData(form)
formData.append("image", this.state.imageBlob)
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (200 <= xhr.status && xhr.status <= 204) {
ContentAlert.success('avatarUpdatedMessage')
} else {
ContentAlert.danger(xhr.response)
}
// force reload avatar
this.setState({
errors: {avatar: ""},
imageBlob: null,
filename: "",
avatarSrc: this.state.avatarUrl
})
}
}
xhr.open(form.method, form.action, true);
xhr.send(formData);
}
private handleError = (event: any) => {
this.setState({
errors: {avatar: Msg.localize('error-noAvatarFound')},
avatarSrc: ""
})
}
render() {
const { filename, avatarUrl, avatarSrc, noAvatarSrc } = this.state
const avatarStyle: CSS.Properties = {
objectFit: 'cover',
width: '150px', height: '150px',
border: '1px solid lightgray',
boxShadow: 'lightgray 6px 3px 10px 2px'
}
return (
<Form id="avatarForm" method="post"
action={avatarUrl} encType="multipart/form-data"
onSubmit={event => this.handleSubmit(event)}
>
<FormGroup label={Msg.localize('avatarLabel')}
fieldId="avatar-current-or-preview"
helperTextInvalid={this.state.errors.avatar}
validated={this.state.errors.avatar === '' ? 'success' : 'error'}
>
{ avatarSrc !== ""
? <Avatar src={avatarSrc} style={avatarStyle} alt="Avatar image preview" onError={this.handleError}/>
: <Avatar src={noAvatarSrc} style={avatarStyle} alt="No avatar found" />
}
</FormGroup>
<FormGroup
fieldId="avatar-upload"
label={<span>
<Msg msgKey="uploadLabel" />
{' '}
<Tooltip content={<Msg msgKey="avatarInfo" />}>
<OutlinedQuestionCircleIcon />
</Tooltip>
</span>}
>
<FileUpload
id="simple-file"
filename={filename}
filenamePlaceholder={Msg.localize('dragdropInfo')}
browseButtonText={Msg.localize('browseButton')}
clearButtonText={Msg.localize('clearButton')}
onChange={this.handleFileInputChange}
/>
</FormGroup>
<ActionGroup>
<Button
id="save-btn" type="submit"
variant="primary"
isDisabled={filename === ""}
>
<Msg msgKey="doSave" />
</Button>
</ActionGroup>
</Form>
)
}
}