Commit 023249f6 authored by JC Brand's avatar JC Brand

Render profile avatar as canvas.

We now have uniform avatar rendering for the profile, messages and
chatboxes.

By rendering as canvas, we can avoid stretching the image.

In the process I also moved the ChatBoxViews collection into its own
plugin `converse-chatboxviews` and placed the AvatarAware views there.

fixes #1157
parent 9da214fb
......@@ -8960,7 +8960,8 @@ body.reset {
width: 100%; }
#conversejs .avatar {
border-radius: 10%;
border: 1px solid lightgrey; }
border: 1px solid lightgrey;
background: white; }
#conversejs .activated {
display: block !important; }
#conversejs .button-primary {
......
This diff is collapsed.
......@@ -410,6 +410,7 @@ body.reset {
.avatar {
border-radius: 10%;
border: 1px solid lightgrey;
background: white;
}
.activated {
......
......@@ -8,12 +8,10 @@
define([
"converse-core",
"filesize",
"templates/chatboxes.html",
"backbone.overview",
"utils/form",
"utils/emoji"
], factory);
}(this, function (converse, filesize, tpl_chatboxes) {
}(this, function (converse, filesize) {
"use strict";
const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, sizzle, utils, _ } = converse.env;
......@@ -27,20 +25,6 @@
dependencies: ["converse-roster", "converse-vcard"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
initStatus: function (reconnecting) {
const { _converse } = this.__super__;
if (!reconnecting) {
_converse.chatboxviews.closeAllChatBoxes();
}
return this.__super__.initStatus.apply(this, arguments);
}
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
......@@ -780,72 +764,6 @@
}
});
_converse.ChatBoxViews = Backbone.Overview.extend({
_ensureElement () {
/* Override method from backbone.js
* If the #conversejs element doesn't exist, create it.
*/
if (!this.el) {
let el = _converse.root.querySelector('#conversejs');
if (_.isNull(el)) {
el = document.createElement('div');
el.setAttribute('id', 'conversejs');
const body = _converse.root.querySelector('body');
if (body) {
body.appendChild(el);
} else {
// Perhaps inside a web component?
_converse.root.appendChild(el);
}
}
el.innerHTML = '';
this.setElement(el, false);
} else {
this.setElement(_.result(this, 'el'), false);
}
},
initialize () {
this.model.on("destroy", this.removeChat, this);
this.el.classList.add(`converse-${_converse.view_mode}`);
this.render();
},
render () {
try {
this.el.innerHTML = tpl_chatboxes();
} catch (e) {
this._ensureElement();
this.el.innerHTML = tpl_chatboxes();
}
this.row_el = this.el.querySelector('.row');
},
insertRowColumn (el) {
/* Add a new DOM element (likely a chat box) into the
* the row managed by this overview.
*/
this.row_el.insertAdjacentElement('afterBegin', el);
},
removeChat (item) {
this.remove(item.get('id'));
},
closeAllChatBoxes () {
/* This method gets overridden in src/converse-controlbox.js if
* the controlbox plugin is active.
*/
this.each(function (view) { view.close(); });
return this;
},
chatBoxMayBeShown (chatbox) {
return this.model.chatBoxMayBeShown(chatbox);
}
});
function autoJoinChats () {
/* Automatically join private chats, based on the
......@@ -892,13 +810,9 @@
_converse.api.listen.on('pluginsInitialized', () => {
_converse.chatboxes = new _converse.ChatBoxes();
_converse.chatboxviews = new _converse.ChatBoxViews({
'model': _converse.chatboxes
});
_converse.emit('chatBoxesInitialized');
});
_converse.api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes());
_converse.api.listen.on('presencesInitialized', () => _converse.chatboxes.onConnected());
/************************ END Event Handlers ************************/
......
// Converse.js
// http://conversejs.org
//
// Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
define([
"converse-core",
"templates/chatboxes.html",
"converse-chatboxes",
"backbone.overview"
], factory);
}(this, function (converse, tpl_chatboxes) {
"use strict";
const { Backbone, _ } = converse.env;
const AvatarMixin = {
renderAvatar () {
const canvas_el = this.el.querySelector('canvas');
if (_.isNull(canvas_el)) {
return;
}
const image_type = this.model.vcard.get('image_type'),
image = this.model.vcard.get('image'),
img_src = "data:" + image_type + ";base64," + image,
img = new Image();
img.onload = () => {
const ctx = canvas_el.getContext('2d'),
ratio = img.width / img.height;
ctx.clearRect(0, 0, canvas_el.width, canvas_el.height);
if (ratio < 1) {
const scaled_img_with = canvas_el.width*ratio,
x = Math.floor((canvas_el.width-scaled_img_with)/2);
ctx.drawImage(img, x, 0, scaled_img_with, canvas_el.height);
} else {
ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height*ratio);
}
};
img.src = img_src;
},
};
converse.plugins.add('converse-chatboxviews', {
dependencies: ["converse-chatboxes"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
initStatus: function (reconnecting) {
const { _converse } = this.__super__;
if (!reconnecting) {
_converse.chatboxviews.closeAllChatBoxes();
}
return this.__super__.initStatus.apply(this, arguments);
}
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
const { _converse } = this,
{ __ } = _converse;
_converse.api.promises.add([
'chatBoxViewsInitialized'
]);
_converse.ViewWithAvatar = Backbone.NativeView.extend(AvatarMixin);
_converse.VDOMViewWithAvatar = Backbone.VDOMView.extend(AvatarMixin);
_converse.ChatBoxViews = Backbone.Overview.extend({
_ensureElement () {
/* Override method from backbone.js
* If the #conversejs element doesn't exist, create it.
*/
if (!this.el) {
let el = _converse.root.querySelector('#conversejs');
if (_.isNull(el)) {
el = document.createElement('div');
el.setAttribute('id', 'conversejs');
const body = _converse.root.querySelector('body');
if (body) {
body.appendChild(el);
} else {
// Perhaps inside a web component?
_converse.root.appendChild(el);
}
}
el.innerHTML = '';
this.setElement(el, false);
} else {
this.setElement(_.result(this, 'el'), false);
}
},
initialize () {
this.model.on("destroy", this.removeChat, this);
this.el.classList.add(`converse-${_converse.view_mode}`);
this.render();
},
render () {
try {
this.el.innerHTML = tpl_chatboxes();
} catch (e) {
this._ensureElement();
this.el.innerHTML = tpl_chatboxes();
}
this.row_el = this.el.querySelector('.row');
},
insertRowColumn (el) {
/* Add a new DOM element (likely a chat box) into the
* the row managed by this overview.
*/
this.row_el.insertAdjacentElement('afterBegin', el);
},
removeChat (item) {
this.remove(item.get('id'));
},
closeAllChatBoxes () {
/* This method gets overridden in src/converse-controlbox.js if
* the controlbox plugin is active.
*/
this.each(function (view) { view.close(); });
return this;
},
chatBoxMayBeShown (chatbox) {
return this.model.chatBoxMayBeShown(chatbox);
}
});
/************************ BEGIN Event Handlers ************************/
_converse.api.waitUntil('rosterContactsFetched').then(() => {
_converse.roster.on('add', (contact) => {
/* When a new contact is added, check if we already have a
* chatbox open for it, and if so attach it to the chatbox.
*/
const chatbox = _converse.chatboxes.findWhere({'jid': contact.get('jid')});
if (chatbox) {
chatbox.addRelatedContact(contact);
}
});
});
_converse.api.listen.on('chatBoxesInitialized', () => {
_converse.chatboxviews = new _converse.ChatBoxViews({
'model': _converse.chatboxes
});
_converse.emit('chatBoxViewsInitialized');
});
_converse.api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes());
/************************ END Event Handlers ************************/
}
});
return converse;
}));
......@@ -26,7 +26,7 @@
"templates/status_message.html",
"templates/toolbar.html",
"converse-modal",
"converse-chatboxes",
"converse-chatboxviews",
"converse-message-view"
], factory);
}(this, function (
......@@ -64,7 +64,7 @@
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-chatboxes", "converse-disco", "converse-message-view", "converse-modal"],
dependencies: ["converse-chatboxviews", "converse-disco", "converse-message-view", "converse-modal"],
initialize () {
......@@ -1263,7 +1263,7 @@
}
});
_converse.on('chatBoxesInitialized', () => {
_converse.on('chatBoxViewsInitialized', () => {
const that = _converse.chatboxviews;
_converse.chatboxes.on('add', item => {
if (!that.get(item.get('id')) && item.get('type') === _converse.PRIVATE_CHAT_TYPE) {
......
......@@ -567,7 +567,7 @@
}
});
_converse.on('chatBoxesInitialized', () => {
_converse.on('chatBoxViewsInitialized', () => {
const that = _converse.chatboxviews;
_converse.chatboxes.on('add', item => {
if (item.get('type') === _converse.CONTROLBOX_TYPE) {
......@@ -598,7 +598,7 @@
Promise.all([
_converse.api.waitUntil('connectionInitialized'),
_converse.api.waitUntil('chatBoxesInitialized')
_converse.api.waitUntil('chatBoxViewsInitialized')
]).then(_converse.addControlBox).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
_converse.on('chatBoxesFetched', () => {
......
......@@ -77,6 +77,7 @@
'converse-bookmarks',
'converse-caps',
'converse-chatboxes',
'converse-chatboxviews',
'converse-chatview',
'converse-controlbox',
'converse-core',
......
......@@ -136,7 +136,7 @@
_converse.on('reconnected', registerHeadlineHandler);
_converse.on('chatBoxesInitialized', () => {
_converse.on('chatBoxViewsInitialized', () => {
const that = _converse.chatboxviews;
_converse.chatboxes.on('add', item => {
if (!that.get(item.get('id')) && item.get('type') === _converse.HEADLINES_TYPE) {
......
......@@ -40,32 +40,6 @@
const { _converse } = this,
{ __ } = _converse;
_converse.ViewWithAvatar = Backbone.NativeView.extend({
renderAvatar () {
const canvas_el = this.el.querySelector('canvas');
if (_.isNull(canvas_el)) {
return;
}
const image_type = this.model.vcard.get('image_type'),
image = this.model.vcard.get('image'),
img_src = "data:" + image_type + ";base64," + image,
img = new Image();
img.onload = () => {
const ctx = canvas_el.getContext('2d'),
ratio = img.width / img.height;
ctx.clearRect(0, 0, canvas_el.width, canvas_el.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;
},
});
_converse.MessageVersionsModal = _converse.BootstrapModal.extend({
......
......@@ -523,7 +523,7 @@
Promise.all([
_converse.api.waitUntil('connectionInitialized'),
_converse.api.waitUntil('chatBoxesInitialized')
_converse.api.waitUntil('chatBoxViewsInitialized')
]).then(() => {
_converse.minimized_chats = new _converse.MinimizedChats({
model: _converse.chatboxes
......
......@@ -1951,7 +1951,7 @@
}
/************************ BEGIN Event Handlers ************************/
_converse.on('chatBoxesInitialized', () => {
_converse.on('chatBoxViewsInitialized', () => {
const that = _converse.chatboxviews;
_converse.chatboxes.on('add', item => {
if (!that.get(item.get('id')) && item.get('type') === _converse.CHATROOMS_TYPE) {
......
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Copyright (c) 2013-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
......@@ -34,7 +34,7 @@
converse.plugins.add('converse-profile', {
dependencies: ["converse-modal", "converse-vcard"],
dependencies: ["converse-modal", "converse-vcard", "converse-chatboxviews"],
initialize () {
/* The initialize function gets called as soon as the plugin is
......@@ -196,7 +196,7 @@
}
});
_converse.XMPPStatusView = Backbone.VDOMView.extend({
_converse.XMPPStatusView = _converse.VDOMViewWithAvatar.extend({
tagName: "div",
events: {
"click a.show-profile": "showProfileModal",
......@@ -214,6 +214,7 @@
return tpl_profile_view(_.extend(
this.model.toJSON(),
this.model.vcard.toJSON(), {
'__': __,
'fullname': this.model.vcard.get('fullname') || _converse.bare_jid,
'status_message': this.model.get('status_message') ||
__("I am %1$s", this.getPrettyStatus(chat_status)),
......@@ -226,6 +227,10 @@
}));
},
afterRender () {
this.renderAvatar();
},
showProfileModal (ev) {
if (_.isUndefined(this.profile_modal)) {
this.profile_modal = new _converse.ProfileModal({model: this.model});
......
......@@ -20,7 +20,6 @@ if (typeof define !== 'undefined') {
"converse-mam", // XEP-0313 Message Archive Management
"converse-minimize", // Allows chat boxes to be minimized
"converse-muc", // XEP-0045 Multi-user chat
"converse-muc-views",
"converse-muc-views", // Views related to MUC
"converse-notification", // HTML5 Notifications
"converse-omemo",
......
......@@ -16,7 +16,7 @@
</div>
</div>
<div class="chatbox-buttons row no-gutters">
<a class="chatbox-btn close-chatbox-button fa fa-close" title={{{o.info_close}}}></a>
<a class="chatbox-btn show-user-details-modal fa fa-vcard" title="{{{o.info_details}}}"></a>
<a class="chatbox-btn close-chatbox-button fa fa-times" title={{{o.info_close}}}></a>
<a class="chatbox-btn show-user-details-modal fa fa-id-card" title="{{{o.info_details}}}"></a>
</div>
</div>
<div class="flyout box-flyout">
<div class="chat-head controlbox-head">
{[ if (!o.sticky_controlbox) { ]}
<a class="chatbox-btn close-chatbox-button fa fa-close"></a>
<a class="chatbox-btn close-chatbox-button fa fa-times"></a>
{[ } ]}
</div>
<div class="controlbox-panes"></div>
......
<div class="userinfo controlbox-padded">
<div class="profile d-flex">
<a class="show-profile" href="#">
<img alt="User Avatar" class="avatar align-self-center" height="40px" width="40px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
<canvas alt="o.__('Your avatar')" class="avatar align-self-center" height="40" width="40"></canvas>
</a>
<span class="username w-100 align-self-center">{{{o.fullname}}}</span>
<!-- <a class="chatbox-btn fa fa-vcard align-self-center" title="{{{o.title_your_profile}}}" data-toggle="modal" data-target="#userProfileModal"></a> -->
<!-- <a class="chatbox-btn fa fa-id-card align-self-center" title="{{{o.title_your_profile}}}" data-toggle="modal" data-target="#userProfileModal"></a> -->
<!-- <a class="chatbox-btn fa fa-cog align-self-center" title="{{{o.title_change_status}}}" data-toggle="modal" data-target="#settingsModal"></a> -->
{[ if (o._converse.allow_logout) { ]}
<a class="chatbox-btn logout fa fa-sign-out-alt align-self-center" title="{{{o.title_log_out}}}"></a>
......
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