Avatar preview widget (#248)

* add prettier config file

* add vue component for avatar input preview
This commit is contained in:
ggoshanov 2020-06-01 19:58:39 +03:00 committed by GitHub
parent 5e0dc80295
commit 54c6b38d82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 3 deletions

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"tabWidth": 4,
"useTabs": true
}

View File

@ -62,6 +62,10 @@
<label for="{{ form.avatar.id_for_label }}" class="form-label">
Обновить аватар
</label>
<user-avatar-input
input-id="{{ form.avatar.id_for_label }}"
current-avatar="{{ user.get_avatar }}"
></user-avatar-input>
{{ form.avatar }}
{% if form.avatar.errors %}<span class="form-row-errors">{{ form.avatar.errors }}</span>{% endif %}
</div>

View File

@ -0,0 +1,100 @@
<template>
<label
class="user-avatar-input"
:for="inputId"
>
<div
class="avatar profile-user-avatar"
:style="avatarStyle"
/>
</label>
</template>
<script>
const FILE_READER_STATE = {
EMPTY: 0,
LOADING: 1,
DONE: 2,
};
export default {
name: "UserAvatarInput",
props: {
// To allow "click-to-activate" on avatar
inputId: {
type: String,
required: true,
},
// For preview
currentAvatar: {
type: String,
required: false,
},
},
data() {
return {
inputElement: null,
imageData: null,
fileReader: null,
error: null,
};
},
methods: {
subscribeToInput(ele) {
ele.addEventListener("change", (e) => {
let file = e.target.files[0];
if (file) {
this.handleFile(file);
} else {
this.imageData = null;
this.fileReader = null;
this.error = null;
}
});
},
async handleFile(file) {
if (this.fileReader) {
this.fileReader.abort();
// reset state
this.imageData = null;
this.error = null;
this.progress = 0;
}
let fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.addEventListener("loadend", (ev) => {
this.imageData = fileReader.result;
});
fileReader.addEventListener("error", (ev) => {
this.error = fileReader.error;
});
},
init() {
// Find related input in DOM
this.inputElement = document.querySelector("#" + this.inputId);
// Assign listeners
if (this.inputElement) {
this.subscribeToInput(this.inputElement);
}
},
},
computed: {
avatarStyle() {
if (!this.imageData && !this.currentAvatar) {
return null;
}
return `background-image: url(${this.imageData || this.currentAvatar})`;
},
},
mounted() {
// Vue recreates elements, so using mounted hook instead of 'created'.
this.init();
},
};
</script>
<style scoped>
.user-avatar-input {
display: inline-block;
}
</style>

View File

@ -9,18 +9,19 @@ import CommentUpvote from "./components/CommentUpvote.vue";
import UserTag from "./components/UserTag.vue";
import PeopleMap from "./components/PeopleMap.vue";
import UserExpertiseWindow from "./components/UserExpertiseWindow.vue";
import UserAvatarInput from "./components/UserAvatarInput.vue";
import ClubApi from "./common/api.service";
Vue.component("post-upvote", PostUpvote);
Vue.component("post-subscription", PostSubscription);
Vue.component("comment-upvote", CommentUpvote);
Vue.component("user-expertise-window", UserExpertiseWindow);
Vue.component("user-tag", UserTag);
Vue.component("people-map", PeopleMap);
Vue.component("user-avatar-input", UserAvatarInput);
// Since our pages have user-generated content, any fool can insert "{{" on the page and break it.
// We have no other choice but to completely turn off template matching and leave it on only for components.
const noDelimiter = {replace: function(){}};
const noDelimiter = { replace: function () {} };
new Vue({
el: "#app",
@ -60,7 +61,7 @@ new Vue({
// Define helper function
function appendMarkdownTextareaValue(textarea, value) {
textarea.focus(); // on mobile
textarea.focus(); // on mobile
textarea.value = value;
const codeMirrorEditor = textarea.nextElementSibling.CodeMirror;
codeMirrorEditor.setValue(codeMirrorEditor.getValue() + value);