Switch avatar rendering from canvas to SVG.

This delegates the calculation of the aspect ratio to the browser, and
generally simplifies the code.

Fixes #1156.
This commit is contained in:
Emmanuel Gil Peyrot 2018-10-28 18:58:23 +01:00 committed by JC Brand
parent 352c0797ad
commit ab5dd4a146
9 changed files with 66 additions and 60 deletions

View File

@ -9631,7 +9631,7 @@ body.reset {
#conversejs div, #conversejs span, #conversejs h1, #conversejs h2, #conversejs h3, #conversejs h4, #conversejs h5, #conversejs h6, #conversejs p, #conversejs blockquote, #conversejs div, #conversejs span, #conversejs h1, #conversejs h2, #conversejs h3, #conversejs h4, #conversejs h5, #conversejs h6, #conversejs p, #conversejs blockquote,
#conversejs pre, #conversejs a, #conversejs em, #conversejs img, #conversejs strong, #conversejs dl, #conversejs dt, #conversejs dd, #conversejs ol, #conversejs ul, #conversejs li, #conversejs pre, #conversejs a, #conversejs em, #conversejs img, #conversejs strong, #conversejs dl, #conversejs dt, #conversejs dd, #conversejs ol, #conversejs ul, #conversejs li,
#conversejs fieldset, #conversejs form, #conversejs legend, #conversejs table, #conversejs caption, #conversejs tbody, #conversejs fieldset, #conversejs form, #conversejs legend, #conversejs table, #conversejs caption, #conversejs tbody,
#conversejs tfoot, #conversejs thead, #conversejs tr, #conversejs th, #conversejs td, #conversejs article, #conversejs aside, #conversejs canvas, #conversejs details, #conversejs tfoot, #conversejs thead, #conversejs tr, #conversejs th, #conversejs td, #conversejs article, #conversejs aside, #conversejs details,
#conversejs embed, #conversejs figure, #conversejs figcaption, #conversejs footer, #conversejs header, #conversejs hgroup, #conversejs menu, #conversejs embed, #conversejs figure, #conversejs figcaption, #conversejs footer, #conversejs header, #conversejs hgroup, #conversejs menu,
#conversejs nav, #conversejs output, #conversejs ruby, #conversejs section, #conversejs summary, #conversejs time, #conversejs mark, #conversejs audio, #conversejs video { #conversejs nav, #conversejs output, #conversejs ruby, #conversejs section, #conversejs summary, #conversejs time, #conversejs mark, #conversejs audio, #conversejs video {
margin: 0; margin: 0;
@ -9669,7 +9669,7 @@ body.reset {
color: var(--subdued-color); } color: var(--subdued-color); }
#conversejs a.fa:hover, #conversejs a.far:hover, #conversejs a.fas:hover, #conversejs a:visited.fa:hover, #conversejs a:visited.far:hover, #conversejs a:visited.fas:hover, #conversejs a:not([href]):not([tabindex]).fa:hover, #conversejs a:not([href]):not([tabindex]).far:hover, #conversejs a:not([href]):not([tabindex]).fas:hover { #conversejs a.fa:hover, #conversejs a.far:hover, #conversejs a.fas:hover, #conversejs a:visited.fa:hover, #conversejs a:visited.far:hover, #conversejs a:visited.fas:hover, #conversejs a:not([href]):not([tabindex]).fa:hover, #conversejs a:not([href]):not([tabindex]).far:hover, #conversejs a:not([href]):not([tabindex]).fas:hover {
color: var(--gray-color); } color: var(--gray-color); }
#conversejs canvas { #conversejs svg {
border-radius: var(--chatbox-border-radius); } border-radius: var(--chatbox-border-radius); }
#conversejs .fa, #conversejs .far, #conversejs .fas { #conversejs .fa, #conversejs .far, #conversejs .fas {
color: var(--subdued-color); } color: var(--subdued-color); }

77
dist/converse.js vendored
View File

@ -58492,8 +58492,10 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var backbone_overview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! backbone.overview */ "./node_modules/backbone.overview/backbone.overview.js"); /* harmony import */ var backbone_overview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! backbone.overview */ "./node_modules/backbone.overview/backbone.overview.js");
/* harmony import */ var backbone_overview__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(backbone_overview__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var backbone_overview__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(backbone_overview__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); /* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js");
/* harmony import */ var templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! templates/chatboxes.html */ "./src/templates/chatboxes.html"); /* harmony import */ var templates_avatar_svg__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! templates/avatar.svg */ "./src/templates/avatar.svg");
/* harmony import */ var templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_4__); /* harmony import */ var templates_avatar_svg__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(templates_avatar_svg__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/chatboxes.html */ "./src/templates/chatboxes.html");
/* harmony import */ var templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_5__);
// Converse.js // Converse.js
// http://conversejs.org // http://conversejs.org
// //
@ -58504,6 +58506,7 @@ __webpack_require__.r(__webpack_exports__);
const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].env, const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].env,
Backbone = _converse$env.Backbone, Backbone = _converse$env.Backbone,
_ = _converse$env._, _ = _converse$env._,
@ -58519,27 +58522,12 @@ const AvatarMixin = {
} }
const image_type = this.model.vcard.get('image_type'), const image_type = this.model.vcard.get('image_type'),
image = this.model.vcard.get('image'), image = this.model.vcard.get('image');
img_src = "data:" + image_type + ";base64," + image, canvas_el.outerHTML = templates_avatar_svg__WEBPACK_IMPORTED_MODULE_4___default()({
img = new Image(); 'classes': canvas_el.getAttribute('class'),
return new Promise((resolve, reject) => { 'width': canvas_el.width,
img.onload = () => { 'height': canvas_el.height,
const ctx = canvas_el.getContext('2d'), 'image': "data:" + image_type + ";base64," + image
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);
}
resolve();
};
img.src = img_src;
}); });
} }
@ -58618,11 +58606,11 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins
render() { render() {
try { try {
this.el.innerHTML = templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_4___default()(); this.el.innerHTML = templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_5___default()();
} catch (e) { } catch (e) {
this._ensureElement(); this._ensureElement();
this.el.innerHTML = templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_4___default()(); this.el.innerHTML = templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_5___default()();
} }
this.row_el = this.el.querySelector('.row'); this.row_el = this.el.querySelector('.row');
@ -61766,14 +61754,13 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
msg_content.innerHTML = _.flow(_.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].geoUriToHttp, _, _converse.geouri_replacement), _.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addMentionsMarkup, _, this.model.get('references'), this.model.collection.chatbox), utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addHyperlinks, utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderNewLines, _.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addEmoji, _converse, _))(text); msg_content.innerHTML = _.flow(_.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].geoUriToHttp, _, _converse.geouri_replacement), _.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addMentionsMarkup, _, this.model.get('references'), this.model.collection.chatbox), utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addHyperlinks, utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderNewLines, _.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addEmoji, _converse, _))(text);
} }
const promises = []; const promise = utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderImageURLs(_converse, msg_content);
promises.push(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderImageURLs(_converse, msg_content));
if (this.model.get('type') !== 'headline') { if (this.model.get('type') !== 'headline') {
promises.push(this.renderAvatar(msg)); this.renderAvatar(msg);
} }
await Promise.all(promises); await promise;
this.replaceElement(msg); this.replaceElement(msg);
this.model.collection.trigger('rendered', this); this.model.collection.trigger('rendered', this);
}, },
@ -100974,6 +100961,34 @@ return __p
/***/ }), /***/ }),
/***/ "./src/templates/avatar.svg":
/*!**********************************!*\
!*** ./src/templates/avatar.svg ***!
\**********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
var _ = {escape:__webpack_require__(/*! ./node_modules/lodash/escape.js */ "./node_modules/lodash/escape.js")};
module.exports = function(o) {
var __t, __p = '', __e = _.escape;
__p += '<!-- src/templates/avatar.svg -->\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="' +
__e(o.classes) +
'" width="' +
__e(o.width) +
'" height="' +
__e(o.height) +
'">\n <image width="' +
__e(o.width) +
'" height="' +
__e(o.height) +
'" preserveAspectRatio="xMidYMid meet" xlink:href="' +
__e(o.image) +
'"/>\n</svg>\n';
return __p
};
/***/ }),
/***/ "./src/templates/bookmark.html": /***/ "./src/templates/bookmark.html":
/*!*************************************!*\ /*!*************************************!*\
!*** ./src/templates/bookmark.html ***! !*** ./src/templates/bookmark.html ***!
@ -102892,7 +102907,7 @@ __e(o.image) +
} ; } ;
__p += '\n '; __p += '\n ';
if (!o.image) { ; if (!o.image) { ;
__p += '\n <canvas class="avatar" height="100px" width="100px"/>\n '; __p += '\n <canvas class="avatar" height="100px" width="100px"></canvas>\n ';
} ; } ;
__p += '\n </a>\n <input class="hidden" name="image" type="file">\n </div>\n <div class="col">\n <div class="form-group">\n <label class="col-form-label">' + __p += '\n </a>\n <input class="hidden" name="image" type="file">\n </div>\n <div class="col">\n <div class="form-group">\n <label class="col-form-label">' +
__e(o.label_jid) + __e(o.label_jid) +
@ -102989,7 +103004,7 @@ var _ = {escape:__webpack_require__(/*! ./node_modules/lodash/escape.js */ "./no
module.exports = function(o) { module.exports = function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join; var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') } function print() { __p += __j.call(arguments, '') }
__p += '<!-- src/templates/profile_view.html -->\n<div class="userinfo controlbox-padded">\n<div class="controlbox-section profile d-flex">\n <a class="show-profile" href="#">\n <canvas alt="o.__(\'Your avatar\')" class="avatar align-self-center" height="40" width="40"></canvas>\n </a>\n <span class="username w-100 align-self-center">' + __p += '<!-- src/templates/profile_view.html -->\n<div class="userinfo controlbox-padded">\n<div class="controlbox-section profile d-flex">\n <a class="show-profile" href="#">\n <canvas class="avatar align-self-center" height="40" width="40"></canvas>\n </a>\n <span class="username w-100 align-self-center">' +
__e(o.fullname) + __e(o.fullname) +
'</span>\n <a class="controlbox-heading__btn show-client-info fa fa-info-circle align-self-center" title="' + '</span>\n <a class="controlbox-heading__btn show-client-info fa fa-info-circle align-self-center" title="' +
__e(o.info_details) + __e(o.info_details) +

View File

@ -196,7 +196,7 @@ body.reset {
div, span, h1, h2, h3, h4, h5, h6, p, blockquote, div, span, h1, h2, h3, h4, h5, h6, p, blockquote,
pre, a, em, img, strong, dl, dt, dd, ol, ul, li, pre, a, em, img, strong, dl, dt, dd, ol, ul, li,
fieldset, form, legend, table, caption, tbody, fieldset, form, legend, table, caption, tbody,
tfoot, thead, tr, th, td, article, aside, canvas, details, tfoot, thead, tr, th, td, article, aside, details,
embed, figure, figcaption, footer, header, hgroup, menu, embed, figure, figcaption, footer, header, hgroup, menu,
nav, output, ruby, section, summary, time, mark, audio, video { nav, output, ruby, section, summary, time, mark, audio, video {
margin: 0; margin: 0;
@ -254,7 +254,7 @@ body.reset {
} }
} }
canvas { svg {
border-radius: var(--chatbox-border-radius); border-radius: var(--chatbox-border-radius);
} }

View File

@ -8,6 +8,7 @@ import "@converse/headless/converse-chatboxes";
import "backbone.nativeview"; import "backbone.nativeview";
import "backbone.overview"; import "backbone.overview";
import converse from "@converse/headless/converse-core"; import converse from "@converse/headless/converse-core";
import tpl_avatar from "templates/avatar.svg";
import tpl_chatboxes from "templates/chatboxes.html"; import tpl_chatboxes from "templates/chatboxes.html";
const { Backbone, _, utils } = converse.env; const { Backbone, _, utils } = converse.env;
@ -22,25 +23,13 @@ const AvatarMixin = {
return; return;
} }
const image_type = this.model.vcard.get('image_type'), const image_type = this.model.vcard.get('image_type'),
image = this.model.vcard.get('image'), image = this.model.vcard.get('image');
img_src = "data:" + image_type + ";base64," + image,
img = new Image();
return new Promise((resolve, reject) => { canvas_el.outerHTML = tpl_avatar({
img.onload = () => { 'classes': canvas_el.getAttribute('class'),
const ctx = canvas_el.getContext('2d'), 'width': canvas_el.width,
ratio = img.width / img.height; 'height': canvas_el.height,
ctx.clearRect(0, 0, canvas_el.width, canvas_el.height); 'image': "data:" + image_type + ";base64," + image,
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);
}
resolve();
};
img.src = img_src;
}); });
}, },
}; };

View File

@ -155,12 +155,11 @@ converse.plugins.add('converse-message-view', {
_.partial(u.addEmoji, _converse, _) _.partial(u.addEmoji, _converse, _)
)(text); )(text);
} }
const promises = []; const promise = u.renderImageURLs(_converse, msg_content);
promises.push(u.renderImageURLs(_converse, msg_content));
if (this.model.get('type') !== 'headline') { if (this.model.get('type') !== 'headline') {
promises.push(this.renderAvatar(msg)); this.renderAvatar(msg);
} }
await Promise.all(promises); await promise;
this.replaceElement(msg); this.replaceElement(msg);
this.model.collection.trigger('rendered', this); this.model.collection.trigger('rendered', this);
}, },

3
src/templates/avatar.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="{{{o.classes}}}" width="{{{o.width}}}" height="{{{o.height}}}">
<image width="{{{o.width}}}" height="{{{o.height}}}" preserveAspectRatio="xMidYMid meet" xlink:href="{{{o.image}}}"/>
</svg>

After

Width:  |  Height:  |  Size: 283 B

View File

@ -26,7 +26,7 @@
<img alt="{{{o.alt_avatar}}}" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/> <img alt="{{{o.alt_avatar}}}" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
{[ } ]} {[ } ]}
{[ if (!o.image) { ]} {[ if (!o.image) { ]}
<canvas class="avatar" height="100px" width="100px"/> <canvas class="avatar" height="100px" width="100px"></canvas>
{[ } ]} {[ } ]}
</a> </a>
<input class="hidden" name="image" type="file"> <input class="hidden" name="image" type="file">

View File

@ -1,7 +1,7 @@
<div class="userinfo controlbox-padded"> <div class="userinfo controlbox-padded">
<div class="controlbox-section profile d-flex"> <div class="controlbox-section profile d-flex">
<a class="show-profile" href="#"> <a class="show-profile" href="#">
<canvas alt="o.__('Your avatar')" class="avatar align-self-center" height="40" width="40"></canvas> <canvas class="avatar align-self-center" height="40" width="40"></canvas>
</a> </a>
<span class="username w-100 align-self-center">{{{o.fullname}}}</span> <span class="username w-100 align-self-center">{{{o.fullname}}}</span>
<a class="controlbox-heading__btn show-client-info fa fa-info-circle align-self-center" title="{{{o.info_details}}}"></a> <a class="controlbox-heading__btn show-client-info fa fa-info-circle align-self-center" title="{{{o.info_details}}}"></a>

View File

@ -36,7 +36,7 @@ const config = {
use: "exports-loader?filterXSS,filterCSS" use: "exports-loader?filterXSS,filterCSS"
}, },
{ {
test: /\.html$/, test: /\.(html|svg)$/,
exclude: /node_modules/, exclude: /node_modules/,
use: [{ use: [{
loader: 'lodash-template-webpack-loader', loader: 'lodash-template-webpack-loader',