From 80c2aa51b63d587ab5240b9a1fab5966a6e1d02f Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 21 Feb 2020 10:41:33 +0000
Subject: [PATCH] Transition BaseAvatar to hooks
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
.eslintignore.errorfiles | 1 -
src/components/views/avatars/BaseAvatar.js | 311 ++++++++++-----------
2 files changed, 144 insertions(+), 168 deletions(-)
diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles
index 36b03b121c..e326f15002 100644
--- a/.eslintignore.errorfiles
+++ b/.eslintignore.errorfiles
@@ -7,7 +7,6 @@ src/components/structures/RoomView.js
src/components/structures/ScrollPanel.js
src/components/structures/SearchBox.js
src/components/structures/UploadBar.js
-src/components/views/avatars/BaseAvatar.js
src/components/views/avatars/MemberAvatar.js
src/components/views/create_room/RoomAlias.js
src/components/views/dialogs/DeactivateAccountDialog.js
diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js
index 4c34cee853..1b5b28e1e3 100644
--- a/src/components/views/avatars/BaseAvatar.js
+++ b/src/components/views/avatars/BaseAvatar.js
@@ -2,7 +2,7 @@
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,206 +17,183 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
+import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
import * as AvatarLogic from '../../../Avatar';
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from '../elements/AccessibleButton';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import {useEventEmitter} from "../../../hooks/useEventEmitter";
-export default createReactClass({
- displayName: 'BaseAvatar',
+const useImageUrl = ({url, urls, idName, name, defaultToInitialLetter}) => {
+ const [imageUrls, setUrls] = useState([]);
+ const [urlsIndex, setIndex] = useState();
- propTypes: {
- name: PropTypes.string.isRequired, // The name (first initial used as default)
- idName: PropTypes.string, // ID for generating hash colours
- title: PropTypes.string, // onHover title text
- url: PropTypes.string, // highest priority of them all, shortcut to set in urls[0]
- urls: PropTypes.array, // [highest_priority, ... , lowest_priority]
- width: PropTypes.number,
- height: PropTypes.number,
- // XXX resizeMethod not actually used.
- resizeMethod: PropTypes.string,
- defaultToInitialLetter: PropTypes.bool, // true to add default url
- inputRef: PropTypes.oneOfType([
- // Either a function
- PropTypes.func,
- // Or the instance of a DOM native element
- PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
- ]),
- },
-
- statics: {
- contextType: MatrixClientContext,
- },
-
- getDefaultProps: function() {
- return {
- width: 40,
- height: 40,
- resizeMethod: 'crop',
- defaultToInitialLetter: true,
- };
- },
-
- getInitialState: function() {
- return this._getState(this.props);
- },
-
- componentDidMount() {
- this.unmounted = false;
- this.context.on('sync', this.onClientSync);
- },
-
- componentWillUnmount() {
- this.unmounted = true;
- this.context.removeListener('sync', this.onClientSync);
- },
-
- componentWillReceiveProps: function(nextProps) {
- // work out if we need to call setState (if the image URLs array has changed)
- const newState = this._getState(nextProps);
- const newImageUrls = newState.imageUrls;
- const oldImageUrls = this.state.imageUrls;
- if (newImageUrls.length !== oldImageUrls.length) {
- this.setState(newState); // detected a new entry
- } else {
- // check each one to see if they are the same
- for (let i = 0; i < newImageUrls.length; i++) {
- if (oldImageUrls[i] !== newImageUrls[i]) {
- this.setState(newState); // detected a diff
- break;
- }
- }
+ const onError = () => {
+ const nextIndex = urlsIndex + 1;
+ if (nextIndex < imageUrls.length) {
+ // try the next one
+ setIndex(nextIndex);
}
- },
+ };
- onClientSync: function(syncState, prevState) {
- if (this.unmounted) return;
+ const defaultImageUrl = useMemo(() => AvatarLogic.defaultAvatarUrlForString(idName || name), [idName, name]);
- // Consider the client reconnected if there is no error with syncing.
- // This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
- const reconnected = syncState !== "ERROR" && prevState !== syncState;
- if (reconnected &&
- // Did we fall back?
- this.state.urlsIndex > 0
- ) {
- // Start from the highest priority URL again
- this.setState({
- urlsIndex: 0,
- });
- }
- },
-
- _getState: function(props) {
+ useEffect(() => {
// work out the full set of urls to try to load. This is formed like so:
- // imageUrls: [ props.url, props.urls, default image ]
+ // imageUrls: [ props.url, ...props.urls, default image ]
- let urls = [];
+ let _urls = [];
if (!SettingsStore.getValue("lowBandwidth")) {
- urls = props.urls || [];
+ _urls = urls || [];
- if (props.url) {
- urls.unshift(props.url); // put in urls[0]
+ if (url) {
+ _urls.unshift(url); // put in urls[0]
}
}
- let defaultImageUrl = null;
- if (props.defaultToInitialLetter) {
- defaultImageUrl = AvatarLogic.defaultAvatarUrlForString(
- props.idName || props.name,
- );
- urls.push(defaultImageUrl); // lowest priority
+ if (defaultToInitialLetter) {
+ _urls.push(defaultImageUrl); // lowest priority
}
// deduplicate URLs
- urls = Array.from(new Set(urls));
+ _urls = Array.from(new Set(_urls));
- return {
- imageUrls: urls,
- defaultImageUrl: defaultImageUrl,
- urlsIndex: 0,
- };
- },
+ setIndex(0);
+ setUrls(_urls);
+ }, [url, ...(urls || [])]); // eslint-disable-line react-hooks/exhaustive-deps
- onError: function(ev) {
- const nextIndex = this.state.urlsIndex + 1;
- if (nextIndex < this.state.imageUrls.length) {
- // try the next one
- this.setState({
- urlsIndex: nextIndex,
- });
+ const cli = useContext(MatrixClientContext);
+ const onClientSync = useCallback((syncState, prevState) => {
+ // Consider the client reconnected if there is no error with syncing.
+ // This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
+ const reconnected = syncState !== "ERROR" && prevState !== syncState;
+ if (reconnected && urlsIndex > 0 ) { // Did we fall back?
+ // Start from the highest priority URL again
+ setIndex(0);
}
- },
+ }, [urlsIndex]);
+ useEventEmitter(cli, "sync", onClientSync);
- render: function() {
- const imageUrl = this.state.imageUrls[this.state.urlsIndex];
+ const imageUrl = imageUrls[urlsIndex];
+ return [imageUrl, imageUrl === defaultImageUrl, onError];
+};
- const {
- name, idName, title, url, urls, width, height, resizeMethod,
- defaultToInitialLetter, onClick, inputRef,
- ...otherProps
- } = this.props;
+const BaseAvatar = (props) => {
+ const {
+ name,
+ idName,
+ title,
+ url,
+ urls,
+ width=40,
+ height=40,
+ resizeMethod="crop", // eslint-disable-line no-unused-vars
+ defaultToInitialLetter=true,
+ onClick,
+ inputRef,
+ ...otherProps
+ } = props;
- if (imageUrl === this.state.defaultImageUrl) {
- const initialLetter = AvatarLogic.getInitialLetter(name);
- const textNode = (
-
- { initialLetter }
-
- );
- const imgNode = (
-
- );
- if (onClick != null) {
- return (
-
- { textNode }
- { imgNode }
-
- );
- } else {
- return (
-
- { textNode }
- { imgNode }
-
- );
- }
- }
+ lineHeight: height + "px",
+ }}
+ >
+ { initialLetter }
+
+ );
+ const imgNode = (
+
+ );
+
if (onClick != null) {
return (
+ >
+ { textNode }
+ { imgNode }
+
);
} else {
return (
-
+
+ { textNode }
+ { imgNode }
+
);
}
- },
-});
+ }
+
+ if (onClick != null) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+};
+
+BaseAvatar.displayName = "BaseAvatar";
+
+BaseAvatar.propTypes = {
+ name: PropTypes.string.isRequired, // The name (first initial used as default)
+ idName: PropTypes.string, // ID for generating hash colours
+ title: PropTypes.string, // onHover title text
+ url: PropTypes.string, // highest priority of them all, shortcut to set in urls[0]
+ urls: PropTypes.array, // [highest_priority, ... , lowest_priority]
+ width: PropTypes.number,
+ height: PropTypes.number,
+ // XXX resizeMethod not actually used.
+ resizeMethod: PropTypes.string,
+ defaultToInitialLetter: PropTypes.bool, // true to add default url
+ onClick: PropTypes.func,
+ inputRef: PropTypes.oneOfType([
+ // Either a function
+ PropTypes.func,
+ // Or the instance of a DOM native element
+ PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
+ ]),
+};
+
+export default BaseAvatar;