Browse Source

Add support for rendering avatars in groupchats

JC Brand 7 years ago
parent
commit
53f5627b72

+ 2 - 0
docs/source/developer_api.rst

@@ -1306,6 +1306,8 @@ Fetches the VCard associated with a particular `Backbone.Model` instance
 (by using its `jid` or `muc_jid` attribute) and then updates the model with the
 (by using its `jid` or `muc_jid` attribute) and then updates the model with the
 returned VCard data.
 returned VCard data.
 
 
+Returns a promise;
+
 Example:
 Example:
 
 
 .. code-block:: javascript
 .. code-block:: javascript

+ 1 - 0
mockup/chatroom.html

@@ -53,6 +53,7 @@
                                 </div>
                                 </div>
 
 
 								<div class="message chat-msg">
 								<div class="message chat-msg">
+                                    <div class="avatar montague"></div>
                                     <canvas data-avatar="/mockup/images/romeo.jpg" height="36" width="36" class="avatar"></canvas>
                                     <canvas data-avatar="/mockup/images/romeo.jpg" height="36" width="36" class="avatar"></canvas>
                                     <div class="chat-msg-content">
                                     <div class="chat-msg-content">
                                         <span class="chat-msg-heading">
                                         <span class="chat-msg-heading">

+ 9 - 0
src/converse-chatboxes.js

@@ -87,6 +87,15 @@
                 },
                 },
 
 
                 initialize () {
                 initialize () {
+                    if (this.get('type') === 'groupchat') {
+                        this.avatar = this.collection.chatbox.avatars.findWhere({'muc_jid': this.get('from')});
+                        if (_.isNil(this.avatar)) {
+                            this.avatar = this.collection.chatbox.avatars.create({
+                                'muc_jid': this.get('from')
+                            });
+                        }
+                    }
+
                     if (this.get('file')) {
                     if (this.get('file')) {
                         this.on('change:put', this.uploadFile, this);
                         this.on('change:put', this.uploadFile, this);
 
 

+ 49 - 16
src/converse-message-view.js

@@ -53,6 +53,9 @@
                             })
                             })
                         }
                         }
                     });
                     });
+                    if (this.model.get('type') === 'groupchat') {
+                        this.model.avatar.on('change:image', this.renderAvatar, this);
+                    }
                     this.model.on('change:fullname', this.render, this);
                     this.model.on('change:fullname', this.render, this);
                     this.model.on('change:progress', this.renderFileUploadProgresBar, this);
                     this.model.on('change:progress', this.renderFileUploadProgresBar, this);
                     this.model.on('change:type', this.render, this);
                     this.model.on('change:type', this.render, this);
@@ -66,33 +69,28 @@
                     } else if (this.model.get('type') === 'error') {
                     } else if (this.model.get('type') === 'error') {
                         return this.renderErrorMessage();
                         return this.renderErrorMessage();
                     }
                     }
-                    let template, image, image_type,
-                        text = this.model.get('message');
-
+                    let template, text = this.model.get('message');
                     if (this.isMeCommand()) {
                     if (this.isMeCommand()) {
                         template = tpl_action;
                         template = tpl_action;
                         text = this.model.get('message').replace(/^\/me/, '');
                         text = this.model.get('message').replace(/^\/me/, '');
                     } else {
                     } else {
                         template = this.model.get('is_spoiler') ? tpl_spoiler_message : tpl_message;
                         template = this.model.get('is_spoiler') ? tpl_spoiler_message : tpl_message;
-                        if (this.model.get('type') !== 'headline') {
-                            if (this.model.get('sender') === 'me') {
-                                image_type = _converse.xmppstatus.get('image_type');
-                                image = _converse.xmppstatus.get('image');
-                            } else {
-                                image_type = this.chatbox.get('image_type');
-                                image = this.chatbox.get('image');
-                            }
-                        }
                     }
                     }
+
+                    let username;
+                    if (this.chatbox.get('type') === 'chatroom') {
+                        username = this.model.get('nick');
+                    } else {
+                        username = this.model.get('fullname') || this.model.get('from');
+                    }
+
                     const moment_time = moment(this.model.get('time'));
                     const moment_time = moment(this.model.get('time'));
                     const msg = u.stringToElement(template(
                     const msg = u.stringToElement(template(
                         _.extend(this.model.toJSON(), {
                         _.extend(this.model.toJSON(), {
                             'pretty_time': moment_time.format(_converse.time_format),
                             'pretty_time': moment_time.format(_converse.time_format),
                             'time': moment_time.format(),
                             'time': moment_time.format(),
                             'extra_classes': this.getExtraMessageClasses(),
                             'extra_classes': this.getExtraMessageClasses(),
-                            'label_show': __('Show more'),
-                            'image_type': image_type,
-                            'image': image
+                            'label_show': __('Show more')
                         })
                         })
                     ));
                     ));
 
 
@@ -119,7 +117,42 @@
                     u.renderImageURLs(_converse, msg_content).then(() => {
                     u.renderImageURLs(_converse, msg_content).then(() => {
                         this.model.collection.trigger('rendered');
                         this.model.collection.trigger('rendered');
                     });
                     });
-                    return this.replaceElement(msg);
+                    this.replaceElement(msg);
+                    if (this.model.get('type') !== 'headline') {
+                        this.renderAvatar();
+                    }
+                },
+
+                renderAvatar () {
+                    const canvas_el = this.el.querySelector('canvas');
+                    if (_.isNull(canvas_el)) {
+                        return;
+                    }
+                    let image, image_type;
+
+                    if (this.chatbox.get('type') === 'chatroom') {
+                        image_type = this.model.avatar.get('image_type');
+                        image = this.model.avatar.get('image');
+                    } else if (this.model.get('sender') === 'me') {
+                        image_type = _converse.xmppstatus.get('image_type');
+                        image = _converse.xmppstatus.get('image');
+                    } else {
+                        image_type = this.chatbox.get('image_type');
+                        image = this.chatbox.get('image');
+                    }
+
+                    const img_src = "data:" + image_type + ";base64," + image,
+                          img = new Image();
+                    img.onload = () => {
+                        const ctx = canvas_el.getContext('2d'),
+                              ratio = img.width / img.height;
+                        if (ratio < 1) {
+                            ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * (1 / ratio));
+                        } else {
+                            ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * ratio);
+                        }
+                    };
+                    img.src = img_src;
                 },
                 },
 
 
                 replaceElement (msg) {
                 replaceElement (msg) {

+ 2 - 4
src/converse-muc-views.js

@@ -99,8 +99,8 @@
                     const { _converse } = this.__super__;
                     const { _converse } = this.__super__;
                     this.roomspanel = new _converse.RoomsPanel({
                     this.roomspanel = new _converse.RoomsPanel({
                         'model': new (_converse.RoomsPanelModel.extend({
                         'model': new (_converse.RoomsPanelModel.extend({
-                            id: b64_sha1(`converse.roomspanel${_converse.bare_jid}`), // Required by sessionStorage
-                            browserStorage: new Backbone.BrowserStorage[_converse.storage](
+                            'id': b64_sha1(`converse.roomspanel${_converse.bare_jid}`), // Required by sessionStorage
+                            'browserStorage': new Backbone.BrowserStorage[_converse.storage](
                                 b64_sha1(`converse.roomspanel${_converse.bare_jid}`))
                                 b64_sha1(`converse.roomspanel${_converse.bare_jid}`))
                         }))()
                         }))()
                     });
                     });
@@ -1585,8 +1585,6 @@
                     this.chatroomview.model.on('change:unmoderated', this.onFeatureChanged, this);
                     this.chatroomview.model.on('change:unmoderated', this.onFeatureChanged, this);
                     this.chatroomview.model.on('change:unsecured', this.onFeatureChanged, this);
                     this.chatroomview.model.on('change:unsecured', this.onFeatureChanged, this);
 
 
-                    const id = b64_sha1(`converse.occupants${_converse.bare_jid}${this.chatroomview.model.get('jid')}`);
-                    this.model.browserStorage = new Backbone.BrowserStorage.session(id);
                     this.render();
                     this.render();
                     this.model.fetch({
                     this.model.fetch({
                         'add': true,
                         'add': true,

+ 29 - 3
src/converse-muc.js

@@ -185,8 +185,16 @@
                 initialize() {
                 initialize() {
                     this.constructor.__super__.initialize.apply(this, arguments);
                     this.constructor.__super__.initialize.apply(this, arguments);
                     this.occupants = new _converse.ChatRoomOccupants();
                     this.occupants = new _converse.ChatRoomOccupants();
-                    this.registerHandlers();
+                    this.occupants.browserStorage = new Backbone.BrowserStorage.session(
+                        b64_sha1(`converse.occupants-${_converse.bare_jid}${this.get('jid')}`)
+                    );
+                    this.avatars = new _converse.Avatars();
+                    this.avatars.browserStorage = new Backbone.BrowserStorage.session(
+                        b64_sha1(`converse.avatars-${_converse.bare_jid}${this.get('jid')}`)
+                    );
+                    this.avatars.fetch();
 
 
+                    this.registerHandlers();
                     this.on('change:chat_state', this.sendChatState, this);
                     this.on('change:chat_state', this.sendChatState, this);
                 },
                 },
 
 
@@ -280,8 +288,16 @@
                      *  (String) exit_msg: Optional message to indicate your
                      *  (String) exit_msg: Optional message to indicate your
                      *      reason for leaving.
                      *      reason for leaving.
                      */
                      */
-                    this.occupants.reset();
+                    if (_converse.connection.mock) {
+                        // Clear for tests, but keep otherwise.
+                        // We can only get avatars for current occupants in a
+                        // room, so we'd rather cache avatars in the hopes of
+                        // having more hits.
+                        this.avatars.browserStorage._clear();
+                        this.avatars.reset();
+                    }
                     this.occupants.browserStorage._clear();
                     this.occupants.browserStorage._clear();
+                    this.occupants.reset();
                     if (_converse.connection.connected) {
                     if (_converse.connection.connected) {
                         this.sendUnavailablePresence(exit_msg);
                         this.sendUnavailablePresence(exit_msg);
                     }
                     }
@@ -304,13 +320,14 @@
                 getOutgoingMessageAttributes (text, spoiler_hint) {
                 getOutgoingMessageAttributes (text, spoiler_hint) {
                     const is_spoiler = this.get('composing_spoiler');
                     const is_spoiler = this.get('composing_spoiler');
                     return {
                     return {
+                        'from': `${this.get('jid')}/${this.get('nick')}`,
                         'fullname': this.get('nick'),
                         'fullname': this.get('nick'),
                         'username': this.get('nick'),
                         'username': this.get('nick'),
                         'is_spoiler': is_spoiler,
                         'is_spoiler': is_spoiler,
                         'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined,
                         'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined,
                         'sender': 'me',
                         'sender': 'me',
                         'spoiler_hint': is_spoiler ? spoiler_hint : undefined,
                         'spoiler_hint': is_spoiler ? spoiler_hint : undefined,
-                        'type': 'groupchat',
+                        'type': 'groupchat'
                     };
                     };
                 },
                 },
 
 
@@ -1014,6 +1031,15 @@
             });
             });
 
 
 
 
+            _converse.Avatars = Backbone.Collection.extend({
+                model: _converse.ModelWithDefaultAvatar,
+
+                initialize () {
+                    this.on('add', (avatar) => _converse.api.vcard.update(avatar));
+                }
+            });
+
+
             _converse.RoomsPanelModel = Backbone.Model.extend({
             _converse.RoomsPanelModel = Backbone.Model.extend({
                 defaults: {
                 defaults: {
                     'muc_domain': '',
                     'muc_domain': '',

+ 9 - 6
src/converse-vcard.js

@@ -178,12 +178,15 @@
                     },
                     },
 
 
                     'update' (model, force) {
                     'update' (model, force) {
-                        this.get(model, force).then((vcard) => {
-                            model.save(_.extend(
-                                _.pick(vcard, ['fullname', 'url', 'image_type', 'image', 'vcard_updated']),
-                                {'vcard_updated': moment().format()}
-                            ));
-                        }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
+                        return new Promise((resolve, reject) => {
+                            this.get(model, force).then((vcard) => {
+                                model.save(_.extend(
+                                    _.pick(vcard, ['fullname', 'url', 'image_type', 'image', 'vcard_updated']),
+                                    {'vcard_updated': moment().format()}
+                                ));
+                                resolve();
+                            });
+                        });
                     }
                     }
                 }
                 }
             });
             });

+ 1 - 1
src/templates/message.html

@@ -1,6 +1,6 @@
 <div class="message chat-msg {{{o.type}}} {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}" data-from="{{{o.from}}}">
 <div class="message chat-msg {{{o.type}}} {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}" data-from="{{{o.from}}}">
     {[ if (o.type !== 'headline') { ]}
     {[ if (o.type !== 'headline') { ]}
-    <img alt="User Avatar" class="avatar" height="36px" width="36px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
+    <canvas class="avatar" height="36" width="36"></canvas>
     {[ } ]}
     {[ } ]}
     <div class="chat-msg-content">
     <div class="chat-msg-content">
         <span class="chat-msg-heading">
         <span class="chat-msg-heading">

+ 1 - 1
src/templates/spoiler_message.html

@@ -1,5 +1,5 @@
 <div class="message chat-msg {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}">
 <div class="message chat-msg {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}">
-    <img alt="User Avatar" class="avatar" height="36px" width="36px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
+    <canvas class="avatar" height="36" width="36"></canvas>
     <div class="chat-msg-content">
     <div class="chat-msg-content">
         <span class="chat-msg-heading">
         <span class="chat-msg-heading">
             <span class="chat-msg-author">{{{o.username}}}</span>
             <span class="chat-msg-author">{{{o.username}}}</span>