Browse Source

fix: AlwaysOnTop avatar

master
Bettenbuk Zoltan 5 years ago
parent
commit
a04982fd96

+ 26
- 15
react/features/always-on-top/AlwaysOnTop.js View File

2
 
2
 
3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
 
4
 
5
+// We need to reference these files directly to avoid loading things that are not available
6
+// in this environment (e.g. JitsiMeetJS or interfaceConfig)
7
+import StatelessAvatar from '../base/avatar/components/web/StatelessAvatar';
8
+import { getInitials } from '../base/avatar/functions';
9
+
5
 import Toolbar from './Toolbar';
10
 import Toolbar from './Toolbar';
6
 
11
 
7
 const { api } = window.alwaysOnTop;
12
 const { api } = window.alwaysOnTop;
17
 type State = {
22
 type State = {
18
     avatarURL: string,
23
     avatarURL: string,
19
     displayName: string,
24
     displayName: string,
25
+    formattedDisplayName: string,
20
     isVideoDisplayed: boolean,
26
     isVideoDisplayed: boolean,
21
     visible: boolean
27
     visible: boolean
22
 };
28
 };
42
         this.state = {
48
         this.state = {
43
             avatarURL: '',
49
             avatarURL: '',
44
             displayName: '',
50
             displayName: '',
51
+            formattedDisplayName: '',
45
             isVideoDisplayed: true,
52
             isVideoDisplayed: true,
46
             visible: true
53
             visible: true
47
         };
54
         };
78
      *
85
      *
79
      * @returns {void}
86
      * @returns {void}
80
      */
87
      */
81
-    _displayNameChangedListener({ formattedDisplayName, id }) {
88
+    _displayNameChangedListener({ displayname, formattedDisplayName, id }) {
82
         if (api._getOnStageParticipant() === id
89
         if (api._getOnStageParticipant() === id
83
-                && formattedDisplayName !== this.state.displayName) {
84
-            this.setState({ displayName: formattedDisplayName });
90
+                && (formattedDisplayName !== this.state.formattedDisplayName
91
+                    || displayname !== this.state.displayName)) {
92
+            // I think the API has a typo using lowercase n for the displayname
93
+            this.setState({
94
+                displayName: displayname,
95
+                formattedDisplayName
96
+            });
85
         }
97
         }
86
     }
98
     }
87
 
99
 
112
     _largeVideoChangedListener() {
124
     _largeVideoChangedListener() {
113
         const userID = api._getOnStageParticipant();
125
         const userID = api._getOnStageParticipant();
114
         const avatarURL = api.getAvatarURL(userID);
126
         const avatarURL = api.getAvatarURL(userID);
115
-        const displayName = api._getFormattedDisplayName(userID);
127
+        const displayName = api.getDisplayName(userID);
128
+        const formattedDisplayName = api._getFormattedDisplayName(userID);
116
         const isVideoDisplayed = Boolean(api._getLargeVideo());
129
         const isVideoDisplayed = Boolean(api._getLargeVideo());
117
 
130
 
118
         this.setState({
131
         this.setState({
119
             avatarURL,
132
             avatarURL,
120
             displayName,
133
             displayName,
134
+            formattedDisplayName,
121
             isVideoDisplayed
135
             isVideoDisplayed
122
         });
136
         });
123
     }
137
     }
161
      * @returns {ReactElement}
175
      * @returns {ReactElement}
162
      */
176
      */
163
     _renderVideoNotAvailableScreen() {
177
     _renderVideoNotAvailableScreen() {
164
-        const { avatarURL, displayName, isVideoDisplayed } = this.state;
178
+        const { avatarURL, displayName, formattedDisplayName, isVideoDisplayed } = this.state;
165
 
179
 
166
         if (isVideoDisplayed) {
180
         if (isVideoDisplayed) {
167
             return null;
181
             return null;
169
 
183
 
170
         return (
184
         return (
171
             <div id = 'videoNotAvailableScreen'>
185
             <div id = 'videoNotAvailableScreen'>
172
-                {
173
-                    avatarURL
174
-                        ? <div id = 'avatarContainer'>
175
-                            <img
176
-                                id = 'avatar'
177
-                                src = { avatarURL } />
178
-                        </div>
179
-                        : null
180
-                }
186
+                <div id = 'avatarContainer'>
187
+                    <StatelessAvatar
188
+                        id = 'avatar'
189
+                        initials = { getInitials(displayName) }
190
+                        url = { avatarURL } />)
191
+                </div>
181
                 <div
192
                 <div
182
                     className = 'displayname'
193
                     className = 'displayname'
183
                     id = 'displayname'>
194
                     id = 'displayname'>
184
-                    { displayName }
195
+                    { formattedDisplayName }
185
                 </div>
196
                 </div>
186
             </div>
197
             </div>
187
         );
198
         );

+ 37
- 0
react/features/base/avatar/components/AbstractStatelessAvatar.js View File

1
+// @flow
2
+
3
+import { PureComponent } from 'react';
4
+
5
+export type Props = {
6
+
7
+    /**
8
+     * Color of the (initials based) avatar, if needed.
9
+     */
10
+    color?: string,
11
+
12
+    /**
13
+     * Initials to be used to render the initials based avatars.
14
+     */
15
+    initials?: string,
16
+
17
+    /**
18
+     * Callback to signal the failure of the loading of the URL.
19
+     */
20
+    onAvatarLoadError?: Function,
21
+
22
+    /**
23
+     * Expected size of the avatar.
24
+     */
25
+    size?: number;
26
+
27
+    /**
28
+     * The URL of the avatar to render.
29
+     */
30
+    url?: ?string
31
+};
32
+
33
+/**
34
+ * Implements an abstract stateless avatar component that renders an avatar purely from what gets passed through
35
+ * props.
36
+ */
37
+export default class AbstractStatelessAvatar<P: Props> extends PureComponent<P> {}

react/features/base/avatar/components/AbstractAvatar.js → react/features/base/avatar/components/Avatar.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
-import { PureComponent } from 'react';
3
+import React, { PureComponent } from 'react';
4
 
4
 
5
 import { getParticipantById } from '../../participants';
5
 import { getParticipantById } from '../../participants';
6
+import { connect } from '../../redux';
6
 
7
 
7
 import { getAvatarColor, getInitials } from '../functions';
8
 import { getAvatarColor, getInitials } from '../functions';
8
 
9
 
10
+import { StatelessAvatar } from '.';
11
+
9
 export type Props = {
12
 export type Props = {
10
 
13
 
11
     /**
14
     /**
18
      */
21
      */
19
     _loadableAvatarUrl: ?string,
22
     _loadableAvatarUrl: ?string,
20
 
23
 
24
+    /**
25
+     * A prop to maintain compatibility with web.
26
+     */
27
+    className?: string,
28
+
21
     /**
29
     /**
22
      * A string to override the initials to generate a color of. This is handy if you don't want to make
30
      * A string to override the initials to generate a color of. This is handy if you don't want to make
23
      * the background color match the string that the initials are generated from.
31
      * the background color match the string that the initials are generated from.
30
      */
38
      */
31
     displayName?: string,
39
     displayName?: string,
32
 
40
 
41
+    /**
42
+     * ID of the element, if any.
43
+     */
44
+    id?: string,
45
+
33
     /**
46
     /**
34
      * The ID of the participant to render an avatar for (if it's a participant avatar).
47
      * The ID of the participant to render an avatar for (if it's a participant avatar).
35
      */
48
      */
41
     size: number,
54
     size: number,
42
 
55
 
43
     /**
56
     /**
44
-     * URI of the avatar, if any.
57
+     * URL of the avatar, if any.
45
      */
58
      */
46
-    uri: ?string,
59
+    url: ?string,
47
 }
60
 }
48
 
61
 
49
 type State = {
62
 type State = {
53
 export const DEFAULT_SIZE = 65;
66
 export const DEFAULT_SIZE = 65;
54
 
67
 
55
 /**
68
 /**
56
- * Implements an abstract class to render avatars in the app.
69
+ * Implements a class to render avatars in the app.
57
  */
70
  */
58
-export default class AbstractAvatar<P: Props> extends PureComponent<P, State> {
71
+class Avatar<P: Props> extends PureComponent<P, State> {
59
     /**
72
     /**
60
      * Instantiates a new {@code Component}.
73
      * Instantiates a new {@code Component}.
61
      *
74
      *
77
      * @inheritdoc
90
      * @inheritdoc
78
      */
91
      */
79
     componentDidUpdate(prevProps: P) {
92
     componentDidUpdate(prevProps: P) {
80
-        if (prevProps.uri !== this.props.uri) {
93
+        if (prevProps.url !== this.props.url) {
81
 
94
 
82
             // URI changed, so we need to try to fetch it again.
95
             // URI changed, so we need to try to fetch it again.
83
             // Eslint doesn't like this statement, but based on the React doc, it's safe if it's
96
             // Eslint doesn't like this statement, but based on the React doc, it's safe if it's
99
         const {
112
         const {
100
             _initialsBase,
113
             _initialsBase,
101
             _loadableAvatarUrl,
114
             _loadableAvatarUrl,
115
+            className,
102
             colorBase,
116
             colorBase,
103
-            uri
117
+            id,
118
+            size,
119
+            url
104
         } = this.props;
120
         } = this.props;
105
         const { avatarFailed } = this.state;
121
         const { avatarFailed } = this.state;
106
 
122
 
123
+        const avatarProps = {
124
+            className,
125
+            color: undefined,
126
+            id,
127
+            initials: undefined,
128
+            onAvatarLoadError: undefined,
129
+            size,
130
+            url: undefined
131
+        };
132
+
107
         // _loadableAvatarUrl is validated that it can be loaded, but uri (if present) is not, so
133
         // _loadableAvatarUrl is validated that it can be loaded, but uri (if present) is not, so
108
         // we still need to do a check for that. And an explicitly provided URI is higher priority than
134
         // we still need to do a check for that. And an explicitly provided URI is higher priority than
109
         // an avatar URL anyhow.
135
         // an avatar URL anyhow.
110
-        if ((uri && !avatarFailed) || _loadableAvatarUrl) {
111
-            return this._renderURLAvatar((!avatarFailed && uri) || _loadableAvatarUrl);
112
-        }
113
-
114
-        const _initials = getInitials(_initialsBase);
115
-
116
-        if (_initials) {
117
-            return this._renderInitialsAvatar(_initials, getAvatarColor(colorBase || _initialsBase));
136
+        const effectiveURL = (!avatarFailed && url) || _loadableAvatarUrl;
137
+
138
+        if (effectiveURL) {
139
+            avatarProps.onAvatarLoadError = this._onAvatarLoadError;
140
+            avatarProps.url = effectiveURL;
141
+        } else {
142
+            const initials = getInitials(_initialsBase);
143
+
144
+            if (initials) {
145
+                avatarProps.color = getAvatarColor(colorBase || _initialsBase);
146
+                avatarProps.initials = initials;
147
+            }
118
         }
148
         }
119
 
149
 
120
-        return this._renderDefaultAvatar();
150
+        return (
151
+            <StatelessAvatar
152
+                { ...avatarProps } />
153
+        );
121
     }
154
     }
122
 
155
 
123
     _onAvatarLoadError: () => void;
156
     _onAvatarLoadError: () => void;
132
             avatarFailed: true
165
             avatarFailed: true
133
         });
166
         });
134
     }
167
     }
135
-
136
-    /**
137
-     * Function to render the actual, platform specific default avatar component.
138
-     *
139
-     * @returns {React$Element<*>}
140
-     */
141
-    _renderDefaultAvatar: () => React$Element<*>
142
-
143
-    /**
144
-     * Function to render the actual, platform specific initials-based avatar component.
145
-     *
146
-     * @param {string} initials - The initials to use.
147
-     * @param {string} color - The color to use.
148
-     * @returns {React$Element<*>}
149
-     */
150
-    _renderInitialsAvatar: (string, string) => React$Element<*>
151
-
152
-    /**
153
-     * Function to render the actual, platform specific URL-based avatar component.
154
-     *
155
-     * @param {string} uri - The URI of the avatar.
156
-     * @returns {React$Element<*>}
157
-     */
158
-    _renderURLAvatar: ?string => React$Element<*>
159
 }
168
 }
160
 
169
 
161
 /**
170
 /**
168
 export function _mapStateToProps(state: Object, ownProps: Props) {
177
 export function _mapStateToProps(state: Object, ownProps: Props) {
169
     const { displayName, participantId } = ownProps;
178
     const { displayName, participantId } = ownProps;
170
     const _participant = participantId && getParticipantById(state, participantId);
179
     const _participant = participantId && getParticipantById(state, participantId);
171
-    const _initialsBase = (_participant && (_participant.name || _participant.email)) || displayName;
180
+    const _initialsBase = (_participant && _participant.name) || displayName;
172
 
181
 
173
     return {
182
     return {
174
         _initialsBase,
183
         _initialsBase,
175
         _loadableAvatarUrl: _participant && _participant.loadableAvatarUrl
184
         _loadableAvatarUrl: _participant && _participant.loadableAvatarUrl
176
     };
185
     };
177
 }
186
 }
187
+
188
+export default connect(_mapStateToProps)(Avatar);

+ 1
- 0
react/features/base/avatar/components/index.native.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
 export * from './native';
3
 export * from './native';
4
+export { default as Avatar } from './Avatar';

+ 1
- 0
react/features/base/avatar/components/index.web.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
 export * from './web';
3
 export * from './web';
4
+export { default as Avatar } from './Avatar';

+ 0
- 106
react/features/base/avatar/components/native/Avatar.js View File

1
-// @flow
2
-
3
-import React from 'react';
4
-import { Image, Text, View } from 'react-native';
5
-
6
-import { connect } from '../../../redux';
7
-import { type StyleType } from '../../../styles';
8
-
9
-import AbstractAvatar, {
10
-    _mapStateToProps,
11
-    type Props as AbstractProps,
12
-    DEFAULT_SIZE
13
-} from '../AbstractAvatar';
14
-
15
-import RemoteAvatar, { DEFAULT_AVATAR } from './RemoteAvatar';
16
-import styles from './styles';
17
-
18
-type Props = AbstractProps & {
19
-
20
-    /**
21
-     * External style of the component.
22
-     */
23
-    style?: StyleType
24
-}
25
-
26
-/**
27
- * Implements an avatar component that has 4 ways to render an avatar:
28
- *
29
- * - Based on an explicit avatar URI, if provided
30
- * - Gravatar, if there is any
31
- * - Based on initials generated from name or email
32
- * - Default avatar icon, if any of the above fails
33
- */
34
-class Avatar extends AbstractAvatar<Props> {
35
-
36
-    _onAvatarLoadError: () => void;
37
-
38
-    /**
39
-     * Implements {@code AbstractAvatar#_renderDefaultAvatar}.
40
-     *
41
-     * @inheritdoc
42
-     */
43
-    _renderDefaultAvatar() {
44
-        return this._wrapAvatar(
45
-            <Image
46
-                source = { DEFAULT_AVATAR }
47
-                style = { [
48
-                    styles.avatarContent(this.props.size || DEFAULT_SIZE),
49
-                    styles.staticAvatar
50
-                ] } />
51
-        );
52
-    }
53
-
54
-    /**
55
-     * Implements {@code AbstractAvatar#_renderGravatar}.
56
-     *
57
-     * @inheritdoc
58
-     */
59
-    _renderInitialsAvatar(initials, color) {
60
-        return this._wrapAvatar(
61
-            <View
62
-                style = { [
63
-                    styles.initialsContainer,
64
-                    {
65
-                        backgroundColor: color
66
-                    }
67
-                ] }>
68
-                <Text style = { styles.initialsText(this.props.size || DEFAULT_SIZE) }> { initials } </Text>
69
-            </View>
70
-        );
71
-    }
72
-
73
-    /**
74
-     * Implements {@code AbstractAvatar#_renderGravatar}.
75
-     *
76
-     * @inheritdoc
77
-     */
78
-    _renderURLAvatar(uri) {
79
-        return this._wrapAvatar(
80
-            <RemoteAvatar
81
-                onError = { this._onAvatarLoadError }
82
-                size = { this.props.size || DEFAULT_SIZE }
83
-                uri = { uri } />
84
-        );
85
-    }
86
-
87
-    /**
88
-     * Wraps an avatar into a common wrapper.
89
-     *
90
-     * @param {React#Component} avatar - The avatar component.
91
-     * @returns {React#Component}
92
-     */
93
-    _wrapAvatar(avatar) {
94
-        return (
95
-            <View
96
-                style = { [
97
-                    styles.avatarContainer(this.props.size || DEFAULT_SIZE),
98
-                    this.props.style
99
-                ] }>
100
-                { avatar }
101
-            </View>
102
-        );
103
-    }
104
-}
105
-
106
-export default connect(_mapStateToProps)(Avatar);

+ 0
- 50
react/features/base/avatar/components/native/RemoteAvatar.js View File

1
-// @flow
2
-
3
-import React, { PureComponent } from 'react';
4
-import { Image } from 'react-native';
5
-
6
-import styles from './styles';
7
-
8
-export const DEFAULT_AVATAR = require('../../../../../../images/avatar.png');
9
-
10
-type Props = {
11
-
12
-    /**
13
-     * Callback for load errors.
14
-     */
15
-    onError: Function,
16
-
17
-    /**
18
-     * Size of the avatar.
19
-     */
20
-    size: number,
21
-
22
-    /**
23
-     * URI of the avatar to load.
24
-     */
25
-    uri: string
26
-};
27
-
28
-/**
29
- * Implements a private class that is used to fetch and render remote avatars based on an URI.
30
- */
31
-export default class RemoteAvatar extends PureComponent<Props> {
32
-
33
-    /**
34
-     * Implements {@code Component#render}.
35
-     *
36
-     * @inheritdoc
37
-     */
38
-    render() {
39
-        const { onError, size, uri } = this.props;
40
-
41
-        return (
42
-            <Image
43
-                defaultSource = { DEFAULT_AVATAR }
44
-                onError = { onError }
45
-                resizeMode = 'cover'
46
-                source = {{ uri }}
47
-                style = { styles.avatarContent(size) } />
48
-        );
49
-    }
50
-}

+ 112
- 0
react/features/base/avatar/components/native/StatelessAvatar.js View File

1
+// @flow
2
+
3
+import React from 'react';
4
+import { Image, Text, View } from 'react-native';
5
+
6
+import { type StyleType } from '../../../styles';
7
+
8
+import AbstractStatelessAvatar, { type Props as AbstractProps } from '../AbstractStatelessAvatar';
9
+
10
+import styles from './styles';
11
+
12
+type Props = AbstractProps & {
13
+
14
+    /**
15
+     * External style passed to the componant.
16
+     */
17
+    style?: StyleType
18
+};
19
+
20
+const DEFAULT_AVATAR = require('../../../../../../images/avatar.png');
21
+
22
+/**
23
+ * Implements a stateless avatar component that renders an avatar purely from what gets passed through
24
+ * props.
25
+ */
26
+export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
27
+    /**
28
+     * Implements {@code Component#render}.
29
+     *
30
+     * @inheritdoc
31
+     */
32
+    render() {
33
+        const { initials, size, style, url } = this.props;
34
+
35
+        let avatar;
36
+
37
+        if (url) {
38
+            avatar = this._renderURLAvatar();
39
+        } else if (initials) {
40
+            avatar = this._renderInitialsAvatar();
41
+        } else {
42
+            avatar = this._renderDefaultAvatar();
43
+        }
44
+
45
+        return (
46
+            <View
47
+                style = { [
48
+                    styles.avatarContainer(size),
49
+                    style
50
+                ] }>
51
+                { avatar }
52
+            </View>
53
+        );
54
+    }
55
+
56
+    /**
57
+     * Renders the default avatar.
58
+     *
59
+     * @returns {React$Element<*>}
60
+     */
61
+    _renderDefaultAvatar() {
62
+        const { size } = this.props;
63
+
64
+        return (
65
+            <Image
66
+                source = { DEFAULT_AVATAR }
67
+                style = { [
68
+                    styles.avatarContent(size),
69
+                    styles.staticAvatar
70
+                ] } />
71
+        );
72
+    }
73
+
74
+    /**
75
+     * Renders the initials-based avatar.
76
+     *
77
+     * @returns {React$Element<*>}
78
+     */
79
+    _renderInitialsAvatar() {
80
+        const { color, initials, size } = this.props;
81
+
82
+        return (
83
+            <View
84
+                style = { [
85
+                    styles.initialsContainer,
86
+                    {
87
+                        backgroundColor: color
88
+                    }
89
+                ] }>
90
+                <Text style = { styles.initialsText(size) }> { initials } </Text>
91
+            </View>
92
+        );
93
+    }
94
+
95
+    /**
96
+     * Renders the url-based avatar.
97
+     *
98
+     * @returns {React$Element<*>}
99
+     */
100
+    _renderURLAvatar() {
101
+        const { onAvatarLoadError, size, url } = this.props;
102
+
103
+        return (
104
+            <Image
105
+                defaultSource = { DEFAULT_AVATAR }
106
+                onError = { onAvatarLoadError }
107
+                resizeMode = 'cover'
108
+                source = {{ uri: url }}
109
+                style = { styles.avatarContent(size) } />
110
+        );
111
+    }
112
+}

+ 1
- 1
react/features/base/avatar/components/native/index.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
-export { default as Avatar } from './Avatar';
3
+export { default as StatelessAvatar } from './StatelessAvatar';

+ 5
- 3
react/features/base/avatar/components/native/styles.js View File

2
 
2
 
3
 import { ColorPalette } from '../../../styles';
3
 import { ColorPalette } from '../../../styles';
4
 
4
 
5
+const DEFAULT_SIZE = 65;
6
+
5
 /**
7
 /**
6
  * The styles of the feature base/participants.
8
  * The styles of the feature base/participants.
7
  */
9
  */
8
 export default {
10
 export default {
9
 
11
 
10
-    avatarContainer: (size: number) => {
12
+    avatarContainer: (size: number = DEFAULT_SIZE) => {
11
         return {
13
         return {
12
             alignItems: 'center',
14
             alignItems: 'center',
13
             borderRadius: size / 2,
15
             borderRadius: size / 2,
18
         };
20
         };
19
     },
21
     },
20
 
22
 
21
-    avatarContent: (size: number) => {
23
+    avatarContent: (size: number = DEFAULT_SIZE) => {
22
         return {
24
         return {
23
             height: size,
25
             height: size,
24
             width: size
26
             width: size
32
         justifyContent: 'center'
34
         justifyContent: 'center'
33
     },
35
     },
34
 
36
 
35
-    initialsText: (size: number) => {
37
+    initialsText: (size: number = DEFAULT_SIZE) => {
36
         return {
38
         return {
37
             color: 'rgba(255, 255, 255, 0.6)',
39
             color: 'rgba(255, 255, 255, 0.6)',
38
             fontSize: size * 0.5,
40
             fontSize: size * 0.5,

+ 0
- 98
react/features/base/avatar/components/web/Avatar.js View File

1
-// @flow
2
-
3
-import React from 'react';
4
-
5
-import { connect } from '../../../redux';
6
-
7
-import AbstractAvatar, {
8
-    _mapStateToProps,
9
-    type Props as AbstractProps
10
-} from '../AbstractAvatar';
11
-
12
-type Props = AbstractProps & {
13
-    className?: string,
14
-    id: string
15
-};
16
-
17
-/**
18
- * Implements an avatar as a React/Web {@link Component}.
19
- */
20
-class Avatar extends AbstractAvatar<Props> {
21
-    /**
22
-     * Constructs a style object to be used on the avatars.
23
-     *
24
-     * @param {string?} color - The desired background color.
25
-     * @returns {Object}
26
-     */
27
-    _getAvatarStyle(color) {
28
-        const { size } = this.props;
29
-
30
-        return {
31
-            backgroundColor: color || undefined,
32
-            fontSize: size ? size * 0.5 : '180%',
33
-            height: size || '100%',
34
-            width: size || '100%'
35
-        };
36
-    }
37
-
38
-    /**
39
-     * Constructs a list of class names required for the avatar component.
40
-     *
41
-     * @param {string} additional - Any additional class to add.
42
-     * @returns {string}
43
-     */
44
-    _getAvatarClassName(additional) {
45
-        return `avatar ${additional || ''} ${this.props.className || ''}`;
46
-    }
47
-
48
-    _onAvatarLoadError: () => void;
49
-
50
-    /**
51
-     * Implements {@code AbstractAvatar#_renderDefaultAvatar}.
52
-     *
53
-     * @inheritdoc
54
-     */
55
-    _renderDefaultAvatar() {
56
-        return (
57
-            <img
58
-                className = { this._getAvatarClassName('defaultAvatar') }
59
-                id = { this.props.id }
60
-                src = 'images/avatar.png'
61
-                style = { this._getAvatarStyle() } />
62
-        );
63
-    }
64
-
65
-    /**
66
-     * Implements {@code AbstractAvatar#_renderGravatar}.
67
-     *
68
-     * @inheritdoc
69
-     */
70
-    _renderInitialsAvatar(initials, color) {
71
-        return (
72
-            <div
73
-                className = { this._getAvatarClassName() }
74
-                id = { this.props.id }
75
-                style = { this._getAvatarStyle(color) }>
76
-                { initials }
77
-            </div>
78
-        );
79
-    }
80
-
81
-    /**
82
-     * Implements {@code AbstractAvatar#_renderGravatar}.
83
-     *
84
-     * @inheritdoc
85
-     */
86
-    _renderURLAvatar(uri) {
87
-        return (
88
-            <img
89
-                className = { this._getAvatarClassName() }
90
-                id = { this.props.id }
91
-                onError = { this._onAvatarLoadError }
92
-                src = { uri }
93
-                style = { this._getAvatarStyle() } />
94
-        );
95
-    }
96
-}
97
-
98
-export default connect(_mapStateToProps)(Avatar);

+ 96
- 0
react/features/base/avatar/components/web/StatelessAvatar.js View File

1
+// @flow
2
+
3
+import React from 'react';
4
+
5
+import AbstractStatelessAvatar, { type Props as AbstractProps } from '../AbstractStatelessAvatar';
6
+
7
+type Props = AbstractProps & {
8
+
9
+    /**
10
+     * External class name passed through props.
11
+     */
12
+    className?: string,
13
+
14
+    /**
15
+     * The default avatar URL if we want to override the app bundled one (e.g. AlwaysOnTop)
16
+     */
17
+    defaultAvatar?: string,
18
+
19
+    /**
20
+     * ID of the component to be rendered.
21
+     */
22
+    id?: string
23
+};
24
+
25
+/**
26
+ * Implements a stateless avatar component that renders an avatar purely from what gets passed through
27
+ * props.
28
+ */
29
+export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
30
+    /**
31
+     * Implements {@code Component#render}.
32
+     *
33
+     * @inheritdoc
34
+     */
35
+    render() {
36
+        const { initials, url } = this.props;
37
+
38
+        if (url) {
39
+            return (
40
+                <img
41
+                    className = { this._getAvatarClassName() }
42
+                    id = { this.props.id }
43
+                    onError = { this.props.onAvatarLoadError }
44
+                    src = { url }
45
+                    style = { this._getAvatarStyle() } />
46
+            );
47
+        }
48
+
49
+        if (initials) {
50
+            return (
51
+                <div
52
+                    className = { this._getAvatarClassName() }
53
+                    id = { this.props.id }
54
+                    style = { this._getAvatarStyle(this.props.color) }>
55
+                    { initials }
56
+                </div>
57
+            );
58
+        }
59
+
60
+        // default avatar
61
+        return (
62
+            <img
63
+                className = { this._getAvatarClassName('defaultAvatar') }
64
+                id = { this.props.id }
65
+                src = { this.props.defaultAvatar || 'images/avatar.png' }
66
+                style = { this._getAvatarStyle() } />
67
+        );
68
+    }
69
+
70
+    /**
71
+     * Constructs a style object to be used on the avatars.
72
+     *
73
+     * @param {string?} color - The desired background color.
74
+     * @returns {Object}
75
+     */
76
+    _getAvatarStyle(color) {
77
+        const { size } = this.props;
78
+
79
+        return {
80
+            backgroundColor: color || undefined,
81
+            fontSize: size ? size * 0.5 : '180%',
82
+            height: size || '100%',
83
+            width: size || '100%'
84
+        };
85
+    }
86
+
87
+    /**
88
+     * Constructs a list of class names required for the avatar component.
89
+     *
90
+     * @param {string} additional - Any additional class to add.
91
+     * @returns {string}
92
+     */
93
+    _getAvatarClassName(additional) {
94
+        return `avatar ${additional || ''} ${this.props.className || ''}`;
95
+    }
96
+}

+ 1
- 1
react/features/base/avatar/components/web/index.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
-export { default as Avatar } from './Avatar';
3
+export { default as StatelessAvatar } from './StatelessAvatar';

+ 1
- 0
react/features/base/avatar/index.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
 export * from './components';
3
 export * from './components';
4
+export * from './functions';

+ 1
- 1
react/features/base/react/components/native/AvatarListItem.js View File

91
                     displayName = { title }
91
                     displayName = { title }
92
                     size = { avatarSize }
92
                     size = { avatarSize }
93
                     style = { avatarStyle }
93
                     style = { avatarStyle }
94
-                    uri = { avatar } />
94
+                    url = { avatar } />
95
                 <Container style = { styles.listItemDetails }>
95
                 <Container style = { styles.listItemDetails }>
96
                     <Text
96
                     <Text
97
                         numberOfLines = { 1 }
97
                         numberOfLines = { 1 }

+ 19
- 0
react/features/base/util/helpers.js View File

18
     return deferred;
18
     return deferred;
19
 }
19
 }
20
 
20
 
21
+/**
22
+ * Returns the base URL of the app.
23
+ *
24
+ * @param {Object} w - Window object to use instead of the built in one.
25
+ * @returns {string}
26
+ */
27
+export function getBaseUrl(w: Object = window) {
28
+    const doc = w.document;
29
+    const base = doc.querySelector('base');
30
+
31
+    if (base && base.href) {
32
+        return base.href;
33
+    }
34
+
35
+    const { protocol, host } = w.location;
36
+
37
+    return `${protocol}//${host}`;
38
+}
39
+
21
 /**
40
 /**
22
  * Returns the namespace for all global variables, functions, etc that we need.
41
  * Returns the namespace for all global variables, functions, etc that we need.
23
  *
42
  *

+ 32
- 8
react/features/external-api/middleware.js View File

10
 import {
10
 import {
11
     PARTICIPANT_KICKED,
11
     PARTICIPANT_KICKED,
12
     SET_LOADABLE_AVATAR_URL,
12
     SET_LOADABLE_AVATAR_URL,
13
-    getAvatarURLByParticipantId,
14
     getLocalParticipant,
13
     getLocalParticipant,
15
     getParticipantById
14
     getParticipantById
16
 } from '../base/participants';
15
 } from '../base/participants';
17
 import { MiddlewareRegistry } from '../base/redux';
16
 import { MiddlewareRegistry } from '../base/redux';
17
+import { getBaseUrl } from '../base/util';
18
 import { appendSuffix } from '../display-name';
18
 import { appendSuffix } from '../display-name';
19
 import { SUBMIT_FEEDBACK } from '../feedback';
19
 import { SUBMIT_FEEDBACK } from '../feedback';
20
 import { SET_FILMSTRIP_VISIBLE } from '../filmstrip';
20
 import { SET_FILMSTRIP_VISIBLE } from '../filmstrip';
39
 
39
 
40
         const result = next(action);
40
         const result = next(action);
41
 
41
 
42
-        if (participant && (participant.loadableAvatarUrl !== loadableAvatarUrl)) {
43
-            APP.API.notifyAvatarChanged(
44
-                id,
45
-                loadableAvatarUrl
46
-            );
42
+        if (participant) {
43
+            if (loadableAvatarUrl) {
44
+                participant.loadableAvatarUrl !== loadableAvatarUrl && APP.API.notifyAvatarChanged(
45
+                    id,
46
+                    loadableAvatarUrl
47
+                );
48
+            } else {
49
+                // There is no loadable explicit URL. In this case the Avatar component would
50
+                // decide to render initials or the default avatar, but the external API needs
51
+                // a URL when it needs to be rendered, so if there is no initials, we return the default
52
+                // Avatar URL as if it was a usual avatar URL. If there are (or may be) initials
53
+                // we send undefined to signal the api user that it's not an URL that needs to be rendered.
54
+                //
55
+                // NOTE: we may implement a special URL format later to signal that the avatar is based
56
+                // on initials, that API consumers can handle as they want, e.g. initials://jm
57
+                APP.API.notifyAvatarChanged(
58
+                    id,
59
+                    participant.name ? undefined : _getDefaultAvatarUrl()
60
+                );
61
+            }
47
         }
62
         }
48
 
63
 
49
         return result;
64
         return result;
65
     case CONFERENCE_JOINED: {
80
     case CONFERENCE_JOINED: {
66
         const state = store.getState();
81
         const state = store.getState();
67
         const { room } = state['features/base/conference'];
82
         const { room } = state['features/base/conference'];
68
-        const { name, id } = getLocalParticipant(state);
83
+        const { loadableAvatarUrl, name, id } = getLocalParticipant(state);
69
 
84
 
70
         APP.API.notifyConferenceJoined(
85
         APP.API.notifyConferenceJoined(
71
             room,
86
             room,
76
                     name,
91
                     name,
77
                     interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME
92
                     interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME
78
                 ),
93
                 ),
79
-                avatarURL: getAvatarURLByParticipantId(state, id)
94
+                avatarURL: loadableAvatarUrl
80
             }
95
             }
81
         );
96
         );
82
         break;
97
         break;
125
 
140
 
126
     return result;
141
     return result;
127
 });
142
 });
143
+
144
+/**
145
+ * Returns the absolute URL of the default avatar.
146
+ *
147
+ * @returns {string}
148
+ */
149
+function _getDefaultAvatarUrl() {
150
+    return new URL('images/avatar.png', getBaseUrl()).href;
151
+}

+ 1
- 1
react/features/mobile/incoming-call/components/IncomingCallPage.js View File

130
                 <View style = { styles.avatar }>
130
                 <View style = { styles.avatar }>
131
                     <Avatar
131
                     <Avatar
132
                         size = { CALLER_AVATAR_SIZE }
132
                         size = { CALLER_AVATAR_SIZE }
133
-                        uri = { this.props._callerAvatarURL } />
133
+                        url = { this.props._callerAvatarURL } />
134
                 </View>
134
                 </View>
135
             </View>
135
             </View>
136
         );
136
         );

Loading…
Cancel
Save