fix(settings) Some cleanup + large preview of avatar

This commit is contained in:
Ferdinand Thiessen 2021-11-29 18:16:21 +01:00
parent 868a3f3383
commit 2baa813eb8
1 changed files with 76 additions and 74 deletions

View File

@ -1,4 +1,15 @@
<template> <template>
<q-dialog v-model="preview" auto-close square>
<q-card class="q-dialog-plugin">
<q-img :src="image" fit="contain">
<template #error>
<div style="width: 100%; height: 100%" class="row justify-center">
<img style="height: 100%; background: white" src="no-image.svg" />
</div>
</template>
</q-img>
</q-card>
</q-dialog>
<q-form @submit="save" @reset="reset"> <q-form @submit="save" @reset="reset">
<q-card-section class="fit row justify-start content-center items-center"> <q-card-section class="fit row justify-start content-center items-center">
<q-input <q-input
@ -62,26 +73,32 @@
/> />
<q-file <q-file
v-model="avatar" v-model="avatar"
class="col-xs-12 col-sm-6 q-pa-sm"
filled filled
clearable
label="Avatar" label="Avatar"
accept=".jpg, image/*" accept="image/*, .jpg, .png"
hint="Bilddateien nur JPEG" class="col-xs-12 col-sm-6 q-pa-sm"
:clearable="avatar.name !== '' || deleteAvatar" @clear="avatar = undefined"
@update:model-value="imagePreview"
@clear="clear"
@rejected="onAvatarRejected" @rejected="onAvatarRejected"
> >
<template #file="{ file }"> <template #before>
<div class="full-width row justify-center"> <q-avatar @click="preview = true">
<q-avatar size="265px"> <q-img
<q-img v-if="!avatarError || file.name !== ''" :src="image" @error="error" /> style="min-width: 100%; min-height: 100%"
<q-img v-else src="no-image.svg" /> :src="image"
@error="hasAvatar = false"
>
<template #error><img style="width: 100%" src="no-image.svg" /></template
></q-img>
</q-avatar> </q-avatar>
</div>
</template> </template>
<template #append v-if="!avatarError && !deleteAvatar"> <template #append>
<q-btn round color="negative" icon="mdi-trash-can" size="xs" @click="toDeleteAvatar" /> <q-icon
v-if="!avatar && hasAvatar"
:name="deleteAvatar ? 'mdi-delete-restore' : 'mdi-delete'"
class="cursor-pointer"
@click="deleteAvatar = !deleteAvatar"
/>
</template> </template>
</q-file> </q-file>
</q-card-section> </q-card-section>
@ -117,13 +134,15 @@ import { Notify } from 'quasar';
import { IsoDateInput, PasswordInput } from '@flaschengeist/api/components'; import { IsoDateInput, PasswordInput } from '@flaschengeist/api/components';
import { defineComponent, computed, ref, onBeforeMount, PropType, watchEffect } from 'vue'; import { defineComponent, computed, ref, onBeforeMount, PropType, watchEffect } from 'vue';
import { import {
avatarURL,
hasPermission, hasPermission,
notEmpty, notEmpty,
isEmail, isEmail,
useMainStore, useMainStore,
useUserStore, useUserStore,
api, isAxiosError,
} from '@flaschengeist/api'; } from '@flaschengeist/api';
import { PERMISSIONS } from '../../permissions';
export default defineComponent({ export default defineComponent({
name: 'MainUserSettings', name: 'MainUserSettings',
@ -146,27 +165,41 @@ export default defineComponent({
void userStore.getRoles(false); void userStore.getRoles(false);
}); });
const preview = ref(false);
const password = ref(''); const password = ref('');
const newPassword = ref(''); const newPassword = ref('');
const avatar = ref<File>(new File([], '', {})); const avatar = ref<File>();
const avatarError = ref(false);
const userModel = ref(Object.assign({}, props.user)); const userModel = ref(Object.assign({}, props.user));
const previewURL = ref<string>(); //< Preview of an avatar as data URL
const canSetRoles = computed(() => hasPermission('users_set_roles'));
const allRoles = computed(() => userStore.roles.map((role) => role.name)); const allRoles = computed(() => userStore.roles.map((role) => role.name));
const canSetRoles = computed(() => hasPermission(PERMISSIONS.SET_ROLES));
const isCurrentUser = computed(() => userModel.value.userid === mainStore.currentUser.userid); const isCurrentUser = computed(() => userModel.value.userid === mainStore.currentUser.userid);
const _deleteAvatar = ref(false); const deleteAvatar = ref(false);
const deleteAvatar = computed({ const hasAvatar = ref(true);
get: () => _deleteAvatar.value,
set: (val: boolean) => (_deleteAvatar.value = val),
});
/* Reset model if props changed */ /* Reset model if props changed */
watchEffect(() => { watchEffect(() => {
if (props.user.userid && props.user.userid !== userModel.value.userid) reset(); if (props.user.userid && props.user.userid !== userModel.value.userid) reset();
}); });
watchEffect(() => {
if (avatar.value && avatar.value instanceof File) {
let reader = new FileReader();
reader.onload = (e) => {
previewURL.value = <string | undefined>(e.target?.result || undefined);
};
reader.readAsDataURL(avatar.value);
} else previewURL.value = undefined;
});
/**
* Notify user is file does not fulfil the requirements
*/
function onAvatarRejected() { function onAvatarRejected() {
Notify.create({ Notify.create({
group: false, group: false,
@ -179,6 +212,9 @@ export default defineComponent({
avatar.value = new File([], '', {}); avatar.value = new File([], '', {});
} }
/**
* Save changes, emit user model, directly uploads / deletes avatar by using the API
*/
async function save() { async function save() {
let changed: FG.User = userModel.value; let changed: FG.User = userModel.value;
if (typeof changed.birthday === 'string') changed.birthday = new Date(changed.birthday); if (typeof changed.birthday === 'string') changed.birthday = new Date(changed.birthday);
@ -193,16 +229,14 @@ export default defineComponent({
emit('update:user', changed); emit('update:user', changed);
if (avatar.value instanceof File && avatar.value.name) if (avatar.value !== undefined && avatar.value.name) {
await userStore await userStore
.uploadAvatar(changed, avatar.value instanceof File ? avatar.value : avatar.value[0]) .uploadAvatar(changed, avatar.value)
.catch((response: Response) => { .catch((response) => isAxiosError(response, 400) && onAvatarRejected());
if (response && response.status == 400) { hasAvatar.value = true;
onAvatarRejected(); } else if (deleteAvatar.value) {
}
});
if (deleteAvatar.value) {
await userStore.deleteAvatar(changed); await userStore.deleteAvatar(changed);
hasAvatar.value = false
} }
password.value = ''; password.value = '';
} }
@ -211,9 +245,9 @@ export default defineComponent({
userModel.value = Object.assign({}, props.user); userModel.value = Object.assign({}, props.user);
password.value = ''; password.value = '';
newPassword.value = ''; newPassword.value = '';
imgsrc.value = undefined; avatar.value = undefined;
previewURL.value = undefined;
deleteAvatar.value = false; deleteAvatar.value = false;
avatarError.value = false;
} }
function isFreeUID(val: string) { function isFreeUID(val: string) {
@ -223,49 +257,23 @@ export default defineComponent({
); );
} }
const imgsrc = ref();
const image = computed(() => { const image = computed(() => {
if (imgsrc.value) { if (previewURL.value) {
return <string>imgsrc.value; return previewURL.value;
} }
if (props.user.avatar_url && !deleteAvatar.value) { if (!deleteAvatar.value) {
return `${api.defaults.baseURL || ''}${props.user.avatar_url}`; return avatarURL(props.user);
} }
return 'no-image.svg'; return 'no-image.svg';
}); });
function imagePreview() {
if (avatar.value && avatar.value instanceof File) {
let reader = new FileReader();
reader.onload = (e) => {
imgsrc.value = e.target?.result;
};
reader.readAsDataURL(avatar.value);
}
}
function clear(val: File) {
avatar.value = new File([], '', {});
imgsrc.value = undefined;
deleteAvatar.value = false;
}
function toDeleteAvatar() {
avatar.value = new File([], '', {});
imgsrc.value = undefined;
deleteAvatar.value = true;
}
function error() {
avatarError.value = true;
}
return { return {
allRoles, allRoles,
avatar, avatar,
canSetRoles, canSetRoles,
deleteAvatar,
hasAvatar,
image,
isCurrentUser, isCurrentUser,
isEmail, isEmail,
isFreeUID, isFreeUID,
@ -273,16 +281,10 @@ export default defineComponent({
notEmpty, notEmpty,
onAvatarRejected, onAvatarRejected,
password, password,
preview,
reset, reset,
save, save,
userModel, userModel,
image,
imagePreview,
clear,
deleteAvatar,
avatarError,
toDeleteAvatar,
error,
}; };
}, },
}); });