Add support for rendering avatars in groupchats

This commit is contained in:
JC Brand 2018-05-01 18:18:02 +02:00
parent c14ef3bb75
commit 53f5627b72
9 changed files with 103 additions and 31 deletions

View File

@ -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
returned VCard data.
Returns a promise;
Example:
.. code-block:: javascript

View File

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

View File

@ -87,6 +87,15 @@
},
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')) {
this.on('change:put', this.uploadFile, this);

View File

@ -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:progress', this.renderFileUploadProgresBar, this);
this.model.on('change:type', this.render, this);
@ -66,33 +69,28 @@
} else if (this.model.get('type') === 'error') {
return this.renderErrorMessage();
}
let template, image, image_type,
text = this.model.get('message');
let template, text = this.model.get('message');
if (this.isMeCommand()) {
template = tpl_action;
text = this.model.get('message').replace(/^\/me/, '');
} else {
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 msg = u.stringToElement(template(
_.extend(this.model.toJSON(), {
'pretty_time': moment_time.format(_converse.time_format),
'time': moment_time.format(),
'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(() => {
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) {

View File

@ -99,8 +99,8 @@
const { _converse } = this.__super__;
this.roomspanel = new _converse.RoomsPanel({
'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}`))
}))()
});
@ -1585,8 +1585,6 @@
this.chatroomview.model.on('change:unmoderated', 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.model.fetch({
'add': true,

View File

@ -185,8 +185,16 @@
initialize() {
this.constructor.__super__.initialize.apply(this, arguments);
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);
},
@ -280,8 +288,16 @@
* (String) exit_msg: Optional message to indicate your
* 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.reset();
if (_converse.connection.connected) {
this.sendUnavailablePresence(exit_msg);
}
@ -304,13 +320,14 @@
getOutgoingMessageAttributes (text, spoiler_hint) {
const is_spoiler = this.get('composing_spoiler');
return {
'from': `${this.get('jid')}/${this.get('nick')}`,
'fullname': this.get('nick'),
'username': this.get('nick'),
'is_spoiler': is_spoiler,
'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined,
'sender': 'me',
'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({
defaults: {
'muc_domain': '',

View File

@ -178,12 +178,15 @@
},
'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();
});
});
}
}
});

View File

@ -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}}}">
{[ 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">
<span class="chat-msg-heading">

View File

@ -1,5 +1,5 @@
<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">
<span class="chat-msg-heading">
<span class="chat-msg-author">{{{o.username}}}</span>