// Widget for editing topic description. import React from 'react'; import { FormattedMessage } from 'react-intl'; import Tinode from 'tinode-sdk'; import AvatarCrop from './avatar-crop.jsx'; import AvatarUpload from './avatar-upload.jsx'; import InPlaceEdit from './in-place-edit.jsx'; import TagManager from './tag-manager.jsx'; import { AVATAR_SIZE, MAX_AVATAR_BYTES, MAX_EXTERN_ATTACHMENT_SIZE, MAX_INBAND_ATTACHMENT_SIZE, MAX_TITLE_LENGTH } from '../config.js'; import { imageScaled, blobToBase64, makeImageUrl } from '../lib/blob-helpers.js'; import { arrayEqual, theCard } from '../lib/utils.js'; export default class TopicDescEdit extends React.Component { constructor(props) { super(props); const topic = this.props.tinode.getTopic(this.props.topic); const acs = topic.getAccessMode(); this.state = { isMe: this.props.tinode.isMe(this.props.topic), owner: acs && acs.isOwner(), fullName: topic.public ? topic.public.fn : undefined, avatar: makeImageUrl(topic.public ? topic.public.photo : null), tags: topic.tags(), newAvatar: null, newAvatarMime: null, previousOnTags: topic.onTagsUpdated }; this.tnNewTags = this.tnNewTags.bind(this); this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this); this.handleImageUpdated = this.handleImageUpdated.bind(this); this.handleAvatarCropped = this.handleAvatarCropped.bind(this); this.uploadAvatar = this.uploadAvatar.bind(this); this.handleAvatarCropCancel = this.handleAvatarCropCancel.bind(this); this.handleTagsUpdated = this.handleTagsUpdated.bind(this); } componentDidMount() { const topic = this.props.tinode.getTopic(this.props.topic); topic.onTagsUpdated = this.tnNewTags; } componentWillUnmount() { const topic = this.props.tinode.getTopic(this.props.topic); topic.onTagsUpdated = this.state.previousOnTags; } tnNewTags(tags) { this.setState({tags: tags}); } handleFullNameUpdate(fn) { fn = fn.trim().substring(0, MAX_TITLE_LENGTH); if (fn) { this.setState({fullName: fn}); this.props.onUpdateTopicDesc(this.props.topic, theCard(fn, null)); } } // AvatarUpload widget has changed the avatar: received a new file or deleted the avatar. handleImageUpdated(mime, img) { // This will show the AvatarCropView is img is not null. this.setState({newAvatar: img, newAvatarMime: mime}); if (!img) { // Deleting the avatar. this.props.onUpdateTopicDesc(this.props.topic, theCard(null, Tinode.DEL_CHAR)); } } // AvatarCropView calls this method when the user has cropped the image. handleAvatarCropped(mime, blob, width, height) { const url = blob ? URL.createObjectURL(blob) : null; this.setState({avatar: url, newAvatar: null, newAvatarMime: null}); if (blob) { this.uploadAvatar(mime, blob, width, height); } } // Utility method for converting cropped avatar blob to bytes for sending inband or // for uploading it to the server out of band. uploadAvatar(mime, blob, width, height) { const readyToUpload = (mime, blob, width, height) => { if (blob.size > MAX_AVATAR_BYTES) { // Too large to send inband - uploading out of band and sending as a link. const uploader = this.props.tinode.getLargeFileHelper(); this.setState({uploading: true}); uploader.upload(blob) .then((url) => { this.props.onUpdateTopicDesc(this.props.topic, theCard(null, url)); }) .catch((err) => { this.props.onError(err, 'err'); }) .finally(() => { this.setState({uploading: false}); }); } else { this.setState({uploading: true}); // Convert blob to base64-encoded bits. blobToBase64(blob, (unused, base64bits) => { const du = makeImageUrl({data: base64bits, type: mime}); this.setState({source: du}); this.props.onUpdateTopicDesc(this.props.topic, theCard(null, du)); this.setState({uploading: false}); }); } }; if (width > AVATAR_SIZE || height > AVATAR_SIZE || width != height) { // Avatar is not square or too large even after cropping. Shrink it and make square. imageScaled(blob, AVATAR_SIZE, AVATAR_SIZE, MAX_EXTERN_ATTACHMENT_SIZE, true, readyToUpload, (err) => { this.props.onError(err, 'err'); }); } else { readyToUpload(mime, blob, width, height); } } handleAvatarCropCancel(img) { this.setState({newAvatar: null, newAvatarMime: null}); } handleTagsUpdated(tags) { // Check if tags have actually changed. if (arrayEqual(this.state.tags.slice(0), tags.slice(0))) { return; } this.props.onUpdateTags(tags); } render() { if (this.state.newAvatar) { return ( ); } const editable = this.state.isMe || this.state.owner; return ( <>
{this.state.me ? <>
{ (full_name_placeholder) => }
: <>
Unknown} readOnly={!this.state.owner} value={this.state.fullName} required={true} onFinished={this.handleFullNameUpdate} />
{ (private_placeholder) => }
}
{editable ? <>
{ (title_tag_manager) => } : null } ); } };