Avatar preview widget (#248)
* add prettier config file * add vue component for avatar input preview
This commit is contained in:
parent
5e0dc80295
commit
54c6b38d82
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": true
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue