Refactor Picture upload
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
7a731f1ef8
commit
605239130e
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="root">
|
||||
<figure class="image" v-if="actualImageSrc">
|
||||
<img :src="actualImageSrc" />
|
||||
<figure class="image" v-if="imageSrc">
|
||||
<img :src="imageSrc" />
|
||||
</figure>
|
||||
<figure class="image is-128x128" v-else>
|
||||
<div class="image-placeholder">
|
||||
@ -9,12 +9,19 @@
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<b-upload @input="onFileChanged" :accept="accept">
|
||||
<a class="button is-primary">
|
||||
<b-icon icon="upload"></b-icon>
|
||||
<span>{{ $t("Click to upload") }}</span>
|
||||
</a>
|
||||
</b-upload>
|
||||
<div class="action-buttons">
|
||||
<b-field class="file is-primary">
|
||||
<b-upload @input="onFileChanged" :accept="accept" class="file-label">
|
||||
<span class="file-cta">
|
||||
<b-icon class="file-icon" icon="upload" />
|
||||
<span>{{ $t("Click to upload") }}</span>
|
||||
</span>
|
||||
</b-upload>
|
||||
</b-field>
|
||||
<b-button type="is-text" v-if="imageSrc" @click="removeOrClearPicture">
|
||||
{{ $t("Clear") }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -45,16 +52,22 @@ figure.image {
|
||||
color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { IPicture } from "@/types/picture.model";
|
||||
import { Component, Model, Prop, Vue, Watch } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class PictureUpload extends Vue {
|
||||
@Model("change", { type: File }) readonly pictureFile!: File;
|
||||
|
||||
@Prop({ type: String, required: false }) defaultImageSrc!: string;
|
||||
@Prop({ type: Object, required: false }) defaultImage!: IPicture;
|
||||
|
||||
@Prop({ type: String, required: false, default: "image/gif,image/png,image/jpeg,image/webp" })
|
||||
accept!: string;
|
||||
@ -70,24 +83,40 @@ export default class PictureUpload extends Vue {
|
||||
})
|
||||
textFallback!: string;
|
||||
|
||||
imageSrc: string | null = null;
|
||||
imageSrc: string | null = this.defaultImage ? this.defaultImage.url : null;
|
||||
|
||||
file!: File | null;
|
||||
|
||||
mounted(): void {
|
||||
this.updatePreview(this.pictureFile);
|
||||
if (this.pictureFile) {
|
||||
this.updatePreview(this.pictureFile);
|
||||
}
|
||||
}
|
||||
|
||||
@Watch("pictureFile")
|
||||
onPictureFileChanged(val: File): void {
|
||||
console.log("onPictureFileChanged", val);
|
||||
this.updatePreview(val);
|
||||
}
|
||||
|
||||
onFileChanged(file: File): void {
|
||||
@Watch("defaultImage")
|
||||
onDefaultImageChange(defaultImage: IPicture): void {
|
||||
console.log("onDefaultImageChange", defaultImage);
|
||||
this.imageSrc = defaultImage ? defaultImage.url : null;
|
||||
}
|
||||
|
||||
onFileChanged(file: File | null): void {
|
||||
this.$emit("change", file);
|
||||
|
||||
this.updatePreview(file);
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
private updatePreview(file?: File) {
|
||||
async removeOrClearPicture(): Promise<void> {
|
||||
this.onFileChanged(null);
|
||||
}
|
||||
|
||||
private updatePreview(file?: File | null) {
|
||||
if (file) {
|
||||
this.imageSrc = URL.createObjectURL(file);
|
||||
return;
|
||||
@ -95,9 +124,5 @@ export default class PictureUpload extends Vue {
|
||||
|
||||
this.imageSrc = null;
|
||||
}
|
||||
|
||||
get actualImageSrc(): string | null {
|
||||
return this.imageSrc || this.defaultImageSrc;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,6 +1,5 @@
|
||||
import gql from "graphql-tag";
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const UPLOAD_PICTURE = gql`
|
||||
mutation UploadPicture($file: Upload!, $alt: String, $name: String!) {
|
||||
uploadPicture(file: $file, alt: $alt, name: $name) {
|
||||
@ -9,3 +8,11 @@ export const UPLOAD_PICTURE = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const REMOVE_PICTURE = gql`
|
||||
mutation RemovePicture($id: ID!) {
|
||||
removePicture(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -69,7 +69,7 @@ interface IEventEditJSON {
|
||||
visibility: EventVisibility;
|
||||
joinOptions: EventJoinOptions;
|
||||
draft: boolean;
|
||||
picture: IPicture | { pictureId: string } | null;
|
||||
picture?: IPicture | { pictureId: string } | null;
|
||||
attributedToId: string | null;
|
||||
onlineAddress?: string;
|
||||
phoneAddress?: string;
|
||||
@ -234,7 +234,6 @@ export class EventModel implements IEvent {
|
||||
joinOptions: this.joinOptions,
|
||||
draft: this.draft,
|
||||
tags: this.tags.map((t) => t.title),
|
||||
picture: this.picture,
|
||||
onlineAddress: this.onlineAddress,
|
||||
phoneAddress: this.phoneAddress,
|
||||
physicalAddress: this.physicalAddress,
|
||||
|
@ -9,7 +9,7 @@ export async function buildFileFromIPicture(obj: IPicture | null | undefined): P
|
||||
return new File([blob], obj.name);
|
||||
}
|
||||
|
||||
export function buildFileVariable<T>(file: File | null, name: string, alt?: string): Record<string, unknown> {
|
||||
export function buildFileVariable(file: File | null, name: string, alt?: string): Record<string, unknown> {
|
||||
if (!file) return {};
|
||||
|
||||
return {
|
||||
|
@ -27,7 +27,7 @@
|
||||
<span v-else>{{ $t("I create an identity") }}</span>
|
||||
</h1>
|
||||
|
||||
<picture-upload v-model="avatarFile" :defaultImageSrc="avatarUrl" class="picture-upload" />
|
||||
<picture-upload v-model="avatarFile" :defaultImage="identity.avatar" class="picture-upload" />
|
||||
|
||||
<b-field horizontal :label="$t('Display name')">
|
||||
<b-input
|
||||
@ -124,6 +124,7 @@ h1 {
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Watch } from "vue-property-decorator";
|
||||
import { mixins } from "vue-class-component";
|
||||
import { IPicture } from "@/types/picture.model";
|
||||
import {
|
||||
CREATE_PERSON,
|
||||
CURRENT_ACTOR_CLIENT,
|
||||
@ -136,7 +137,7 @@ import { IPerson, Person } from "../../../types/actor";
|
||||
import PictureUpload from "../../../components/PictureUpload.vue";
|
||||
import { MOBILIZON_INSTANCE_HOST } from "../../../api/_entrypoint";
|
||||
import RouteName from "../../../router/name";
|
||||
import { buildFileVariable } from "../../../utils/image";
|
||||
import { buildFileFromIPicture, buildFileVariable } from "../../../utils/image";
|
||||
import { changeIdentity } from "../../../utils/auth";
|
||||
import identityEditionMixin from "../../../mixins/identityEdition";
|
||||
|
||||
@ -186,13 +187,6 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
|
||||
) as string;
|
||||
}
|
||||
|
||||
get avatarUrl(): string | null {
|
||||
if (this.identity && this.identity.avatar && this.identity.avatar.url) {
|
||||
return this.identity.avatar.url;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Watch("isUpdate")
|
||||
async isUpdateChanged(): Promise<void> {
|
||||
this.resetFields();
|
||||
@ -286,7 +280,6 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
|
||||
}
|
||||
},
|
||||
});
|
||||
this.avatarFile = null;
|
||||
|
||||
this.$notifier.success(
|
||||
this.$t("Identity {displayName} updated", {
|
||||
|
@ -10,7 +10,11 @@
|
||||
|
||||
<form ref="form">
|
||||
<subtitle>{{ $t("General information") }}</subtitle>
|
||||
<picture-upload v-model="pictureFile" :textFallback="$t('Headline picture')" />
|
||||
<picture-upload
|
||||
v-model="pictureFile"
|
||||
:textFallback="$t('Headline picture')"
|
||||
:defaultImage="event.picture"
|
||||
/>
|
||||
|
||||
<b-field :label="$t('Title')" :type="checkTitleLength[0]" :message="checkTitleLength[1]">
|
||||
<b-input size="is-large" aria-required="true" required v-model="event.title" />
|
||||
@ -676,6 +680,7 @@ export default class EditEvent extends Vue {
|
||||
__typename: "Person",
|
||||
id: organizerActor.id,
|
||||
participations: {
|
||||
__typename: "PaginatedParticipantList",
|
||||
total: 1,
|
||||
elements: [
|
||||
{
|
||||
@ -763,11 +768,13 @@ export default class EditEvent extends Vue {
|
||||
res.endsOn = null;
|
||||
}
|
||||
|
||||
const pictureObj = buildFileVariable(this.pictureFile, "picture");
|
||||
res = { ...res, ...pictureObj };
|
||||
if (this.pictureFile) {
|
||||
const pictureObj = buildFileVariable(this.pictureFile, "picture");
|
||||
res = { ...res, ...pictureObj };
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.event.picture) {
|
||||
if (this.event.picture && this.pictureFile) {
|
||||
const oldPictureFile = (await buildFileFromIPicture(this.event.picture)) as File;
|
||||
const oldPictureFileContent = await readFileAsync(oldPictureFile);
|
||||
const newPictureFileContent = await readFileAsync(this.pictureFile as File);
|
||||
|
@ -31,7 +31,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<section class="container section" v-if="isCurrentActorAGroupAdmin">
|
||||
<section class="container section" v-if="group && isCurrentActorAGroupAdmin">
|
||||
<form @submit.prevent="updateGroup">
|
||||
<b-field :label="$t('Group name')">
|
||||
<b-input v-model="group.name" />
|
||||
@ -43,7 +43,7 @@
|
||||
<picture-upload
|
||||
:textFallback="$t('Avatar')"
|
||||
v-model="avatarFile"
|
||||
:defaultImageSrc="group.avatar ? group.avatar.url : null"
|
||||
:defaultImage="group.avatar"
|
||||
/>
|
||||
</b-field>
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
<picture-upload
|
||||
:textFallback="$t('Banner')"
|
||||
v-model="bannerFile"
|
||||
:defaultImageSrc="group.banner ? group.banner.url : null"
|
||||
:defaultImage="group.banner"
|
||||
/>
|
||||
</b-field>
|
||||
<p class="label">{{ $t("Group visibility") }}</p>
|
||||
|
@ -12,7 +12,7 @@
|
||||
<picture-upload
|
||||
v-model="pictureFile"
|
||||
:textFallback="$t('Headline picture')"
|
||||
:defaultImageSrc="post.picture ? post.picture.url : null"
|
||||
:defaultImage="post.picture"
|
||||
/>
|
||||
|
||||
<b-field
|
||||
|
Loading…
Reference in New Issue
Block a user