Commit 53f5627b authored by JC Brand's avatar JC Brand

Add support for rendering avatars in groupchats

parent c14ef3bb
...@@ -1306,6 +1306,8 @@ Fetches the VCard associated with a particular `Backbone.Model` instance ...@@ -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
......
...@@ -53,6 +53,7 @@ ...@@ -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">
......
...@@ -87,6 +87,15 @@ ...@@ -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);
......
...@@ -53,6 +53,9 @@ ...@@ -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 @@ ...@@ -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, let template, text = this.model.get('message');
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'), 'label_show': __('Show more')
'image_type': image_type,
'image': image
}) })
)); ));
...@@ -119,7 +117,42 @@ ...@@ -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) {
......
...@@ -99,8 +99,8 @@ ...@@ -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 'id': b64_sha1(`converse.roomspanel${_converse.bare_jid}`), // Required by sessionStorage
browserStorage: new Backbone.BrowserStorage[_converse.storage]( 'browserStorage': new Backbone.BrowserStorage[_converse.storage](
b64_sha1(`converse.roomspanel${_converse.bare_jid}`)) b64_sha1(`converse.roomspanel${_converse.bare_jid}`))
}))() }))()
}); });
...@@ -1585,8 +1585,6 @@ ...@@ -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,
......
...@@ -185,8 +185,16 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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': '',
......
...@@ -178,12 +178,15 @@ ...@@ -178,12 +178,15 @@
}, },
'update' (model, force) { 'update' (model, force) {
this.get(model, force).then((vcard) => { return new Promise((resolve, reject) => {
model.save(_.extend( this.get(model, force).then((vcard) => {
_.pick(vcard, ['fullname', 'url', 'image_type', 'image', 'vcard_updated']), model.save(_.extend(
{'vcard_updated': moment().format()} _.pick(vcard, ['fullname', 'url', 'image_type', 'image', 'vcard_updated']),
)); {'vcard_updated': moment().format()}
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); ));
resolve();
});
});
} }
} }
}); });
......
<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">
......
<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>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment