Create an image picker component and use it in the profile modal
This commit is contained in:
parent
c82e3e9bda
commit
09a79d609f
@ -198,6 +198,7 @@
|
||||
"no-unused-expressions": "off",
|
||||
"no-use-before-define": "off",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-catch": "off",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-concat": "off",
|
||||
"no-useless-constructor": "error",
|
||||
|
46
src/components/image_picker.js
Normal file
46
src/components/image_picker.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { CustomElement } from './element.js';
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import { html } from 'lit-element';
|
||||
import { renderAvatar } from "../templates/directives/avatar.js";
|
||||
|
||||
const i18n_alt_avatar = __('Your avatar image');
|
||||
|
||||
|
||||
export class ImagePicker extends CustomElement {
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
'height': { type: Number },
|
||||
'image': { type: String },
|
||||
'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_alt_avatar}">
|
||||
${ renderAvatar(avatar_data) }
|
||||
</a>
|
||||
<input @change=${this.updateFilePreview} class="hidden" name="image" type="file"/>
|
||||
`;
|
||||
}
|
||||
|
||||
openFileSelection (ev) {
|
||||
ev.preventDefault();
|
||||
this.querySelector('input[type="file"]').click();
|
||||
}
|
||||
|
||||
updateFilePreview (ev) {
|
||||
const file = ev.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => (this.image = reader.result);
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('converse-image-picker', ImagePicker);
|
@ -115,7 +115,7 @@ class Message extends CustomElement {
|
||||
const size = filesize(this.model.file.size);
|
||||
return html`
|
||||
<div class="message chat-msg">
|
||||
${ renderAvatar(this) }
|
||||
${ renderAvatar(this.getAvatarData()) }
|
||||
<div class="chat-msg__content">
|
||||
<span class="chat-msg__text">${i18n_uploading} <strong>${filename}</strong>, ${size}</span>
|
||||
<progress value="${this.progress}"/>
|
||||
@ -132,7 +132,7 @@ class Message extends CustomElement {
|
||||
${this.isFollowup() ? 'chat-msg--followup' : ''}"
|
||||
data-isodate="${this.time}" data-msgid="${this.msgid}" data-from="${this.from}" data-encrypted="${this.is_encrypted}">
|
||||
|
||||
${ renderAvatar(this) }
|
||||
${ (this.is_me_message || this.type === 'headline') ? '' : renderAvatar(this.getAvatarData()) }
|
||||
<div class="chat-msg__content chat-msg__content--${this.sender} ${this.is_me_message ? 'chat-msg__content--action' : ''}">
|
||||
<span class="chat-msg__heading">
|
||||
${ (this.is_me_message) ? html`
|
||||
@ -165,6 +165,18 @@ class Message extends CustomElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
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 {
|
||||
'classes': 'chat-msg__avatar',
|
||||
'height': 36,
|
||||
'width': 36,
|
||||
image,
|
||||
};
|
||||
}
|
||||
|
||||
async onRetryClicked () {
|
||||
this.show_spinner = true;
|
||||
await this.model.error.retry();
|
||||
|
@ -38,8 +38,6 @@ converse.plugins.add('converse-profile', {
|
||||
_converse.ProfileModal = BootstrapModal.extend({
|
||||
id: "user-profile-modal",
|
||||
events: {
|
||||
'change input[type="file"': "updateFilePreview",
|
||||
'click .change-avatar': "openFileSelection",
|
||||
'submit .profile-form': 'onFormSubmitted'
|
||||
},
|
||||
|
||||
@ -58,31 +56,27 @@ converse.plugins.add('converse-profile', {
|
||||
toHTML () {
|
||||
return tpl_profile_modal(Object.assign(
|
||||
this.model.toJSON(),
|
||||
this.model.vcard.toJSON(), {
|
||||
'_converse': _converse,
|
||||
'utils': u,
|
||||
'view': this
|
||||
}));
|
||||
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));
|
||||
},
|
||||
|
||||
openFileSelection (ev) {
|
||||
ev.preventDefault();
|
||||
this.el.querySelector('input[type="file"]').click();
|
||||
},
|
||||
|
||||
updateFilePreview (ev) {
|
||||
const file = ev.target.files[0],
|
||||
reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
this.el.querySelector('.avatar').setAttribute('src', reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
},
|
||||
|
||||
async setVCard (data) {
|
||||
try {
|
||||
await api.vcard.set(_converse.bare_jid, data);
|
||||
@ -99,10 +93,9 @@ converse.plugins.add('converse-profile', {
|
||||
|
||||
onFormSubmitted (ev) {
|
||||
ev.preventDefault();
|
||||
const reader = new FileReader(),
|
||||
form_data = new FormData(ev.target),
|
||||
image_file = form_data.get('image');
|
||||
|
||||
const reader = new FileReader();
|
||||
const form_data = new FormData(ev.target);
|
||||
const image_file = form_data.get('image');
|
||||
const data = {
|
||||
'fn': form_data.get('fn'),
|
||||
'nickname': form_data.get('nickname'),
|
||||
|
@ -1,31 +1,29 @@
|
||||
import tpl_avatar from "templates/avatar.svg";
|
||||
import xss from "xss/dist/xss";
|
||||
import { directive, html } from "lit-html";
|
||||
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
||||
import { unsafeSVG } from 'lit-html/directives/unsafe-svg.js';
|
||||
|
||||
|
||||
export const renderAvatar = directive(o => part => {
|
||||
if (o.type === 'headline' || o.is_me_message) {
|
||||
part.setValue('');
|
||||
return;
|
||||
}
|
||||
|
||||
if (o.model.vcard) {
|
||||
const data = {
|
||||
'classes': 'avatar chat-msg__avatar',
|
||||
'width': 36,
|
||||
'height': 36,
|
||||
}
|
||||
const image_type = o.model.vcard.get('image_type');
|
||||
const image = o.model.vcard.get('image');
|
||||
data['image'] = "data:" + image_type + ";base64," + image;
|
||||
const avatar = tpl_avatar(data);
|
||||
const opts = {
|
||||
const whitelist_opts = {
|
||||
'whiteList': {
|
||||
'svg': ['xmlns', 'xmlns:xlink', 'class', 'width', 'height'],
|
||||
'image': ['width', 'height', 'preserveAspectRatio', 'xlink:href']
|
||||
}
|
||||
};
|
||||
part.setValue(html`${unsafeHTML(xss.filterXSS(avatar, opts))}`);
|
||||
};
|
||||
const tpl_svg = (o) => xss.filterXSS(`<image width="${o.width}" height="${o.height}" preserveAspectRatio="xMidYMid meet" xlink:href="${o.image}"/>`, whitelist_opts);
|
||||
|
||||
const tpl_avatar = (o) => html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="avatar ${o.classes}" width="${o.width}" height="${o.height}">
|
||||
${ unsafeSVG(tpl_svg(o)) }
|
||||
</svg>
|
||||
`;
|
||||
|
||||
|
||||
export const renderAvatar = directive(o => part => {
|
||||
const data = {
|
||||
'classes': o.classes || '',
|
||||
'height': o.width || 36,
|
||||
'image': o.image,
|
||||
'width': o.height || 36,
|
||||
}
|
||||
part.setValue(tpl_avatar(data));
|
||||
});
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { html } from "lit-html";
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import avatar from "./avatar.js";
|
||||
import "../components/image_picker.js";
|
||||
import spinner from "./spinner.js";
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons"
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import { _converse, converse } from "@converse/headless/converse-core";
|
||||
import { html } from "lit-html";
|
||||
import { modal_header_close_button } from "./buttons";
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
const alt_avatar = __('Your avatar image');
|
||||
const heading_profile = __('Your Profile');
|
||||
const i18n_fingerprint_checkbox_label = __('Checkbox for selecting the following fingerprint');
|
||||
const i18n_device_without_fingerprint = __('Device without a fingerprint');
|
||||
@ -38,7 +39,7 @@ const navigation = html`
|
||||
|
||||
|
||||
const fingerprint = (o) => html`
|
||||
<span class="fingerprint">${o.utils.formatFingerprint(o.view.current_device.get('bundle').fingerprint)}</span>`;
|
||||
<span class="fingerprint">${u.formatFingerprint(o.view.current_device.get('bundle').fingerprint)}</span>`;
|
||||
|
||||
|
||||
const device_with_fingerprint = (o) => html`
|
||||
@ -46,7 +47,7 @@ const device_with_fingerprint = (o) => html`
|
||||
<label>
|
||||
<input type="checkbox" value="${o.device.get('id')}"
|
||||
aria-label="${i18n_fingerprint_checkbox_label}"/>
|
||||
<span class="fingerprint">${o.utils.formatFingerprint(o.device.get('bundle').fingerprint)}</span>
|
||||
<span class="fingerprint">${u.formatFingerprint(o.device.get('bundle').fingerprint)}</span>
|
||||
</label>
|
||||
</li>
|
||||
`;
|
||||
@ -108,16 +109,13 @@ export default (o) => html`
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert"></span>
|
||||
${o._converse.pluggable.plugins['converse-omemo'].enabled(o._converse) && navigation}
|
||||
${_converse.pluggable.plugins['converse-omemo'].enabled(_converse) && navigation}
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="profile-tabpanel" role="tabpanel" aria-labelledby="profile-tab">
|
||||
<form class="converse-form converse-form--modal profile-form" action="#">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<a class="change-avatar" href="#">
|
||||
${o.image ? avatar(Object.assign({'alt_text': alt_avatar}, o)) : '<canvas class="avatar" height="100px" width="100px"></canvas>'}
|
||||
</a>
|
||||
<input class="hidden" name="image" type="file"/>
|
||||
<converse-image-picker image="${o.image}" width="${o.width}" height="${o.height}"></converse-image-picker>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
@ -153,10 +151,9 @@ export default (o) => html`
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
${ o._converse.pluggable.plugins['converse-omemo'].enabled(o._converse) && omemo_page(o) }
|
||||
${ _converse.pluggable.plugins['converse-omemo'].enabled(_converse) && omemo_page(o) }
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">${modal_close_button}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
Loading…
Reference in New Issue
Block a user