Use converse-avatar for setting avatar

This commit is contained in:
JC Brand 2021-11-19 11:45:30 +01:00
parent c62ea03e2e
commit 664f290001
15 changed files with 61 additions and 111 deletions

View File

@ -209,11 +209,13 @@ converse.plugins.add('converse-vcard', {
return;
} else {
message.vcard = getVCardForChatroomOccupant(message);
message.vcard.on('change', () => message.trigger('vcard:change'));
message.trigger('vcard:add');
}
}
_converse.initVCardCollection = async function () {
async function initVCardCollection () {
_converse.vcards = new _converse.VCards();
const id = `${_converse.bare_jid}-converse.vcards`;
initStorage(_converse.vcards, id);
@ -226,7 +228,12 @@ converse.plugins.add('converse-vcard', {
const vcards = _converse.vcards;
if (_converse.session) {
const jid = _converse.session.get('bare_jid');
_converse.xmppstatus.vcard = vcards.findWhere({'jid': jid}) || vcards.create({'jid': jid});
const status = _converse.xmppstatus;
status.vcard = vcards.findWhere({'jid': jid}) || vcards.create({'jid': jid});
if (status.vcard) {
status.vcard.on('change', () => status.trigger('vcard:change'));
status.trigger('vcard:add');
}
}
/**
* Triggered as soon as the `_converse.vcards` collection has been initialized and populated from cache.
@ -256,7 +263,7 @@ converse.plugins.add('converse-vcard', {
api.listen.on('clearSession', () => clearVCardsSession());
api.listen.on('messageInitialized', m => setVCardOnModel(m));
api.listen.on('rosterContactInitialized', m => setVCardOnModel(m));
api.listen.on('statusInitialized', _converse.initVCardCollection);
api.listen.on('statusInitialized', initVCardCollection);
/************************ BEGIN API ************************/

View File

@ -15,7 +15,11 @@ export default (o) => {
<div class="modal-body" class="d-flex">
<div class="row">
<div class="col-auto">
<converse-avatar class="avatar chat-msg__avatar" .model=${o.vcard} height="120" width="120"></converse-avatar>
<converse-avatar
class="avatar chat-msg__avatar"
.data=${o.vcard?.attributes}
nonce=${o.vcard?.get('vcard_updated')}
height="120" width="120"></converse-avatar>
</div>
<div class="col">
<ul class="occupant-details">

View File

@ -19,7 +19,12 @@ async function getDropdownButtons (promise) {
export default (o) => {
const i18n_profile = __("The User's Profile Image");
const avatar = html`<span title="${i18n_profile}"><converse-avatar class="avatar chat-msg__avatar" .model=${o.model.vcard} height="40" width="40"></converse-avatar></span>`;
const avatar = html`<span title="${i18n_profile}">
<converse-avatar
class="avatar chat-msg__avatar"
.data=${o.model.vcard?.attributes}
nonce=${o.model.vcard?.get('vcard_updated')}
height="40" width="40"></converse-avatar></span>`;
const display_name = o.model.getDisplayName();
const tpl_dropdown_btns = () => getDropdownButtons(o.heading_buttons_promise)

View File

@ -30,22 +30,10 @@ const ProfileModal = BootstrapModal.extend({
return tpl_profile_modal(Object.assign(
this.model.toJSON(),
this.model.vcard.toJSON(),
this.getAvatarData(),
{ 'view': this }
));
},
getAvatarData () {
const image_type = this.model.vcard.get('image_type');
const image_data = this.model.vcard.get('image');
const image = "data:" + image_type + ";base64," + image_data;
return {
'height': 128,
'width': 128,
image,
};
},
afterRender () {
this.tabs = sizzle('.nav-item .nav-link', this.el).map(e => new bootstrap.Tab(e));
},

View File

@ -6,12 +6,11 @@ import { _converse, api } from '@converse/headless/core';
class ProfileView extends CustomElement {
async initialize () {
initialize () {
this.model = _converse.xmppstatus;
this.listenTo(this.model, "vcard:add", this.requestUpdate);
this.listenTo(this.model, "change", this.requestUpdate);
await api.waitUntil('VCardsInitialized');
this.listenTo(this.model.vcard, "change", this.requestUpdate);
this.requestUpdate();
this.listenTo(this.model, "vcard:change", this.requestUpdate);
}
render () {

View File

@ -39,7 +39,10 @@ export default (el) => {
<div class="userinfo controlbox-padded">
<div class="controlbox-section profile d-flex">
<a class="show-profile" href="#" @click=${el.showProfileModal}>
<converse-avatar class="avatar align-self-center" .model=${el.model.vcard} height="40" width="40"></converse-avatar>
<converse-avatar class="avatar align-self-center"
.data=${el.model.vcard?.attributes}
nonce=${el.model.vcard?.get('vcard_updated')}
height="40" width="40"></converse-avatar>
</a>
<span class="username w-100 align-self-center">${fullname}</span>
${show_settings_button ? tpl_user_settings_button(el) : ''}

View File

@ -125,7 +125,7 @@ export default (o) => {
<form class="converse-form converse-form--modal profile-form" action="#">
<div class="row">
<div class="col-auto">
<converse-image-picker image="${o.image}" width="${o.width}" height="${o.height}"></converse-image-picker>
<converse-image-picker .data="${{image: o.image, image_type: o.image_type}}" width="128" height="128"></converse-image-picker>
</div>
<div class="col">
<div class="form-group">

View File

@ -25,7 +25,11 @@ export default (el, item) => {
const i18n_remove = __('Click to remove %1$s as a contact', display_name);
return html`
<a class="list-item-link cbox-list-item open-chat ${ num_unread ? 'unread-msgs' : '' }" title="${i18n_chat}" href="#" @click=${el.openChat}>
<converse-avatar class="avatar" .model=${el.model.vcard} height="30" width="30"></converse-avatar>
<converse-avatar
class="avatar"
.data=${el.model.vcard?.attributes}
nonce=${el.model.vcard?.get('vcard_updated')}
height="30" width="30"></converse-avatar>
<span class="${status_icon}" title="${desc_status}"></span>
${ num_unread ? html`<span class="msgs-indicator">${ num_unread }</span>` : '' }
<span class="contact-name contact-name--${el.show} ${ num_unread ? 'unread-msgs' : ''}">${display_name}</span>

View File

@ -9,9 +9,10 @@ export default class Avatar extends CustomElement {
static get properties () {
return {
model: { type: Object },
data: { type: Object },
width: { type: String },
height: { type: String },
nonce: { type: String }, // Used to trigger rerenders
}
}
@ -22,9 +23,14 @@ export default class Avatar extends CustomElement {
}
render () {
const image_type = this.model?.get('image_type') || _converse.DEFAULT_IMAGE_TYPE;
const image_data = this.model?.get('image') || _converse.DEFAULT_IMAGE;
const image = "data:" + image_type + ";base64," + image_data;
const image_type = this.data?.image_type || _converse.DEFAULT_IMAGE_TYPE;
let image;
if (this.data?.data_uri) {
image = this.data?.data_uri;
} else {
const image_data = this.data?.image || _converse.DEFAULT_IMAGE;
image = "data:" + image_type + ";base64," + image_data;
}
return tpl_avatar({
'classes': this.getAttribute('class'),
'height': this.height,

View File

@ -1,38 +0,0 @@
import tpl_avatar from 'shared/templates/avatar.js';
import { ElementView } from '@converse/skeletor/src/element';
import { View } from '@converse/skeletor/src/view';
import { converse } from '@converse/headless/core';
const u = converse.env.utils;
const AvatarMixin = {
renderAvatar (el) {
el = el || (this?.el ?? this);
const avatar_el = el.querySelector('canvas.avatar, svg.avatar');
if (avatar_el === null) {
return;
}
if (this.model.vcard) {
const data = {
'classes': avatar_el.getAttribute('class'),
'width': avatar_el.getAttribute('width'),
'height': avatar_el.getAttribute('height'),
'image_type': this.model.vcard.get('image_type'),
'image': this.model.vcard.get('image')
};
avatar_el.outerHTML = u.getElementFromTemplateResult(tpl_avatar(data)).outerHTML;
}
}
}
export const ViewWithAvatar = View.extend(AvatarMixin);
export class ElementViewWithAvatar extends ElementView {
renderAvatar (el) {
AvatarMixin.renderAvatar.call(this, el);
}
}

View File

@ -127,18 +127,6 @@ export default class Message extends CustomElement {
['chat', 'groupchat'].includes(this.model.get('type'));
}
getAvatarData () {
const image_type = this.model.vcard?.get('image_type') || _converse.DEFAULT_IMAGE_TYPE;
const image_data = this.model.vcard?.get('image') || _converse.DEFAULT_IMAGE;
const image = "data:" + image_type + ";base64," + image_data;
return {
'classes': 'chat-msg__avatar',
'height': 36,
'width': 36,
image,
};
}
onUnfurlAnimationEnd () {
if (this.model.get('url_preview_transition') === 'fade-out') {
this.model.save({

View File

@ -9,13 +9,13 @@ export default (el) => {
const size = filesize(el.model.file.size);
return html`
<div class="message chat-msg">
<converse-avatar class="avatar align-self-center" .model=${el.model.vcard} height="40" width="40"></converse-avatar>
${ el.shouldShowAvatar() ?
html`<a class="show-msg-author-modal" @click=${el.showUserModal}>
<converse-avatar class="avatar align-self-center" .model=${el.model.vcard} height="40" width="40"></converse-avatar>
<converse-avatar class="avatar align-self-center"
.data=${el.model.vcard?.attributes}
nonce=${el.model.vcard?.get('vcard_updated')}
height="40" width="40"></converse-avatar>
</a>` : '' }
<div class="chat-msg__content">
<span class="chat-msg__text">${i18n_uploading} <strong>${filename}</strong>, ${size}</span>
<progress value="${el.model.get('progress')}"/>

View File

@ -20,7 +20,11 @@ export default (el, o) => {
${ o.should_show_avatar ?
html`<a class="show-msg-author-modal" @click=${el.showUserModal}>
<converse-avatar class="avatar align-self-center" .model=${el.model.vcard} height="40" width="40"></converse-avatar>
<converse-avatar
class="avatar align-self-center"
.data=${el.model.vcard?.attributes}
nonce=${el.model.vcard?.get('vcard_updated')}
height="40" width="40"></converse-avatar>
</a>` : '' }
<div class="chat-msg__content chat-msg__content--${o.sender} ${o.is_me_message ? 'chat-msg__content--action' : ''}">

View File

@ -2,7 +2,6 @@ import { CustomElement } from './element.js';
import { __ } from 'i18n';
import { api } from "@converse/headless/core";
import { html } from 'lit';
import { renderAvatar } from "shared/directives/avatar.js";
const i18n_profile_picture = __('Your profile picture');
@ -12,20 +11,15 @@ export default class ImagePicker extends CustomElement {
static get properties () {
return {
'height': { type: Number },
'image': { type: String },
'data': { type: Object},
'width': { type: Number },
}
}
render () {
const avatar_data = {
'height': this.height,
'image': this.image,
'width': this.width,
};
return html`
<a class="change-avatar" @click=${this.openFileSelection} title="${i18n_profile_picture}">
${ renderAvatar(avatar_data) }
<converse-avatar class="avatar" .data=${this.data} height="${this.height}" width="${this.width}"></converse-avatar>
</a>
<input @change=${this.updateFilePreview} class="hidden" name="image" type="file"/>
`;
@ -39,7 +33,12 @@ export default class ImagePicker extends CustomElement {
updateFilePreview (ev) {
const file = ev.target.files[0];
const reader = new FileReader();
reader.onloadend = () => (this.image = reader.result);
reader.onloadend = () => {
this.data = {
'data_uri': reader.result,
'image_type': file.type
}
}
reader.readAsDataURL(file);
}
}

View File

@ -1,19 +0,0 @@
import tpl_avatar from 'shared/avatar/templates/avatar.js';
import { Directive, directive } from "lit/directive.js";
class AvatarDirective extends Directive {
render (o) { // eslint-disable-line class-methods-use-this
const data = {
'classes': o.classes ? `${o.classes} avatar` : 'avatar',
'height': o.width || 36,
'image': o.image,
'image_type': o.image_type,
'width': o.height || 36,
}
return tpl_avatar(data);
}
}
export const renderAvatar = directive(AvatarDirective);