2022-02-16 10:09:31 +01:00
|
|
|
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 (
|
2022-09-28 18:56:07 +02:00
|
|
|
<Form id="avatarForm" method="post"
|
2022-02-16 10:09:31 +01:00
|
|
|
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}
|
2022-09-28 18:56:07 +02:00
|
|
|
validated={this.state.errors.avatar === '' ? 'success' : 'error'}
|
2022-02-16 10:09:31 +01:00
|
|
|
>
|
|
|
|
{ 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>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|