From 16c52c3802d9a3bf0fc5bdafbf3f21246edafa56 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 11 Aug 2021 16:06:30 +0200 Subject: [PATCH 1/5] Fix broken popup warning about changes in event edition Well, partially at least Signed-off-by: Thomas Citharel --- js/src/i18n/fr_FR.json | 2 +- js/src/main.ts | 2 - js/src/types/event.model.ts | 78 ++++++++++++++++++++----------------- js/src/views/Event/Edit.vue | 70 ++++++++++++++++++++------------- 4 files changed, 86 insertions(+), 66 deletions(-) diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json index 0615a1480..a4447f023 100644 --- a/js/src/i18n/fr_FR.json +++ b/js/src/i18n/fr_FR.json @@ -99,7 +99,7 @@ "Are you sure you want to delete this event? This action cannot be undone. You may want to engage the discussion with the event creator or edit its event instead.": "Êtes-vous certain⋅e de vouloir supprimer cet événement ? Cette action n'est pas réversible. Vous voulez peut-être engager la discussion avec le créateur de l'événement ou bien modifier son événement à la place.", "Are you sure you want to suspend this group? All members - including remote ones - will be notified and removed from the group, and all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed.": "Êtes-vous certain·e de vouloir suspendre ce groupe ? Tous les membres - y compris ceux·elles sur d'autres instances - seront notifié·e·s et supprimé·e·s du groupe, et toutes les données associées au groupe (événements, billets, discussions, todos…) seront irrémédiablement détruites.", "Are you sure you want to suspend this group? As this group originates from instance {instance}, this will only remove local members and delete the local data, as well as rejecting all the future data.": "Êtes-vous certain·e de vouloir suspendre ce groupe ? Comme ce groupe provient de l'instance {instance}, cela supprimera seulement les membres locaux et supprimera les données locales, et rejettera également toutes les données futures.", - "Are you sure you want to cancel the event creation? You'll lose all modifications.": "Étes-vous certain⋅e de vouloir annuler la création de l'événement ? Vous allez perdre toutes vos modifications.", + "Are you sure you want to cancel the event creation? You'll lose all modifications.": "Êtes-vous certain⋅e de vouloir annuler la création de l'événement ? Vous allez perdre toutes vos modifications.", "Are you sure you want to cancel the event edition? You'll lose all modifications.": "Êtes-vous certain⋅e de vouloir annuler la modification de l'événement ? Vous allez perdre toutes vos modifications.", "Are you sure you want to cancel your participation at event \"{title}\"?": "Êtes-vous certain⋅e de vouloir annuler votre participation à l'événement « {title} » ?", "Are you sure you want to delete this entire discussion?": "Êtes-vous certain⋅e de vouloir supprimer l'entièreté de cette discussion ?", diff --git a/js/src/main.ts b/js/src/main.ts index 2ace9af59..cae711f1a 100644 --- a/js/src/main.ts +++ b/js/src/main.ts @@ -1,5 +1,3 @@ -// The Vue build version to load with the `import` command -// (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from "vue"; import Buefy from "buefy"; import Component from "vue-class-component"; diff --git a/js/src/types/event.model.ts b/js/src/types/event.model.ts index eb3c02469..c724e0aa3 100644 --- a/js/src/types/event.model.ts +++ b/js/src/types/event.model.ts @@ -203,41 +203,47 @@ export class EventModel implements IEvent { } toEditJSON(): IEventEditJSON { - return { - id: this.id, - title: this.title, - description: this.description, - beginsOn: this.beginsOn.toISOString(), - endsOn: this.endsOn ? this.endsOn.toISOString() : null, - status: this.status, - visibility: this.visibility, - joinOptions: this.joinOptions, - draft: this.draft, - tags: this.tags.map((t) => t.title), - onlineAddress: this.onlineAddress, - phoneAddress: this.phoneAddress, - physicalAddress: this.removeTypeName(this.physicalAddress), - options: this.removeTypeName(this.options), - metadata: this.metadata.map(({ key, value, type, title }) => ({ - key, - value, - type, - title, - })), - attributedToId: - this.attributedTo && this.attributedTo.id ? this.attributedTo.id : null, - contacts: this.contacts.map(({ id }) => ({ - id, - })), - }; - } - - private removeTypeName(entity: any): any { - if (entity?.__typename) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { __typename, ...purgedEntity } = entity; - return purgedEntity; - } - return entity; + return toEditJSON(this); } } + +function removeTypeName(entity: any): any { + if (entity?.__typename) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { __typename, ...purgedEntity } = entity; + return purgedEntity; + } + return entity; +} + +export function toEditJSON(event: IEvent): IEventEditJSON { + return { + id: event.id, + title: event.title, + description: event.description, + beginsOn: event.beginsOn.toISOString(), + endsOn: event.endsOn ? event.endsOn.toISOString() : null, + status: event.status, + visibility: event.visibility, + joinOptions: event.joinOptions, + draft: event.draft, + tags: event.tags.map((t) => t.title), + onlineAddress: event.onlineAddress, + phoneAddress: event.phoneAddress, + physicalAddress: removeTypeName(event.physicalAddress), + options: removeTypeName(event.options), + metadata: event.metadata.map(({ key, value, type, title }) => ({ + key, + value, + type, + title, + })), + attributedToId: + event.attributedTo && event.attributedTo.id + ? event.attributedTo.id + : null, + contacts: event.contacts.map(({ id }) => ({ + id, + })), + }; +} diff --git a/js/src/views/Event/Edit.vue b/js/src/views/Event/Edit.vue index eaacac57b..8cb7fdffc 100644 --- a/js/src/views/Event/Edit.vue +++ b/js/src/views/Event/Edit.vue @@ -463,7 +463,7 @@ import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete. import EventMetadataList from "@/components/Event/EventMetadataList.vue"; import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue"; import Subtitle from "@/components/Utils/Subtitle.vue"; -import { Route } from "vue-router"; +import { RawLocation, Route } from "vue-router"; import { formatList } from "@/utils/i18n"; import { ActorType, @@ -481,7 +481,7 @@ import { EVENT_PERSON_PARTICIPATION, FETCH_EVENT, } from "../../graphql/event"; -import { EventModel, IEvent } from "../../types/event.model"; +import { EventModel, IEvent, toEditJSON } from "../../types/event.model"; import { CURRENT_ACTOR_CLIENT, IDENTITIES, @@ -586,6 +586,8 @@ export default class EditEvent extends Vue { event: IEvent = new EventModel(); + unmodifiedEvent: IEvent = new EventModel(); + identities: IActor[] = []; person!: IPerson; @@ -687,12 +689,13 @@ export default class EditEvent extends Vue { if (!(this.isUpdate || this.isDuplicate)) { this.initializeEvent(); } else { - this.event = { + this.event = new EventModel({ ...this.event, options: cloneDeep(this.event.options), description: this.event.description || "", - }; + }); } + this.unmodifiedEvent = cloneDeep(this.event); } createOrUpdateDraft(e: Event): void { @@ -813,8 +816,8 @@ export default class EditEvent extends Vue { } get updateEventMessage(): string { - // if (this.unmodifiedEvent.draft && !this.event.draft) - // return this.$i18n.t("The event has been updated and published") as string; + if (this.unmodifiedEvent.draft && !this.event.draft) + return this.$i18n.t("The event has been updated and published") as string; return ( this.event.draft ? this.$i18n.t("The draft event has been updated") @@ -910,7 +913,7 @@ export default class EditEvent extends Vue { * Build variables for Event GraphQL creation query */ private async buildVariables() { - let res = new EventModel(this.event).toEditJSON(); + let res = toEditJSON(new EventModel(this.event)); const organizerActor = this.event.organizerActor?.id ? this.event.organizerActor : this.organizerActor; @@ -984,10 +987,12 @@ export default class EditEvent extends Vue { /** * Confirm cancel */ - confirmGoElsewhere(callback: () => any): void { - if (!this.isEventModified) { - callback(); - } + confirmGoElsewhere(): Promise { + // TODO: Make calculation of changes work again and bring this back + // If the event wasn't modified, no need to warn + // if (!this.isEventModified) { + // return Promise.resolve(true); + // } const title: string = this.isUpdate ? (this.$t("Cancel edition") as string) : (this.$t("Cancel creation") as string); @@ -1001,14 +1006,17 @@ export default class EditEvent extends Vue { { title: this.event.title } ) as string); - this.$buefy.dialog.confirm({ - title, - message, - confirmText: this.$t("Abandon editing") as string, - cancelText: this.$t("Continue editing") as string, - type: "is-warning", - hasIcon: true, - onConfirm: callback, + return new Promise((resolve) => { + this.$buefy.dialog.confirm({ + title, + message, + confirmText: this.$t("Abandon editing") as string, + cancelText: this.$t("Continue editing") as string, + type: "is-warning", + hasIcon: true, + onConfirm: () => resolve(true), + onCancel: () => resolve(false), + }); }); } @@ -1016,21 +1024,29 @@ export default class EditEvent extends Vue { * Confirm cancel */ confirmGoBack(): void { - this.confirmGoElsewhere(() => this.$router.go(-1)); + this.$router.go(-1); } // eslint-disable-next-line consistent-return - beforeRouteLeave(to: Route, from: Route, next: () => void): void { + async beforeRouteLeave( + to: Route, + from: Route, + next: (to?: RawLocation | false | ((vm: any) => void)) => void + ): Promise { if (to.name === RouteName.EVENT) return next(); - this.confirmGoElsewhere(() => next()); + if (await this.confirmGoElsewhere()) { + return next(); + } + return next(false); } get isEventModified(): boolean { - // return ( - // JSON.stringify(this.event.toEditJSON()) !== - // JSON.stringify(this.unmodifiedEvent) - // ); - return false; + return ( + this.event && + this.unmodifiedEvent && + JSON.stringify(toEditJSON(this.event)) !== + JSON.stringify(this.unmodifiedEvent) + ); } get beginsOn(): Date { From 70bb3f3dd7317a8722f0028c19088cbef1216a43 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 12 Aug 2021 10:10:45 +0200 Subject: [PATCH 2/5] Put updated sizes in the upload when resizing Closes #823 Signed-off-by: Thomas Citharel --- lib/web/upload/filter/resize.ex | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/web/upload/filter/resize.ex b/lib/web/upload/filter/resize.ex index 424d578aa..12c34ea7a 100644 --- a/lib/web/upload/filter/resize.ex +++ b/lib/web/upload/filter/resize.ex @@ -6,22 +6,27 @@ defmodule Mobilizon.Web.Upload.Filter.Resize do """ @behaviour Mobilizon.Web.Upload.Filter + alias Mobilizon.Web.Upload @maximum_width 1_920 @maximum_height 1_080 - def filter(%Mobilizon.Web.Upload{ - tempfile: file, - content_type: "image" <> _, - width: width, - height: height - }) do + def filter( + %Upload{ + tempfile: file, + content_type: "image" <> _, + width: width, + height: height + } = upload + ) do + {new_width, new_height} = sizes = limit_sizes({width, height}) + file |> Mogrify.open() - |> Mogrify.resize(string(limit_sizes({width, height}))) + |> Mogrify.resize(string(sizes)) |> Mogrify.save(in_place: true) - {:ok, :filtered} + {:ok, :filtered, %Upload{upload | width: new_width, height: new_height}} end def filter(_), do: {:ok, :noop} From 1280b66f419a92fc04be3e9e61be6c40d5ef08f6 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 12 Aug 2021 10:11:46 +0200 Subject: [PATCH 3/5] Catch blurhash decoding errors Signed-off-by: Thomas Citharel --- js/src/components/Event/EventBanner.vue | 3 ++- js/src/components/Image/BlurhashImg.vue | 14 +++++++++----- js/src/components/Image/LazyImageWrapper.vue | 3 ++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/js/src/components/Event/EventBanner.vue b/js/src/components/Event/EventBanner.vue index 1e82f0c38..f2d429ab2 100644 --- a/js/src/components/Event/EventBanner.vue +++ b/js/src/components/Event/EventBanner.vue @@ -5,6 +5,7 @@ diff --git a/js/src/components/Image/BlurhashImg.vue b/js/src/components/Image/BlurhashImg.vue index 9181991a1..a4017a5fc 100644 --- a/js/src/components/Image/BlurhashImg.vue +++ b/js/src/components/Image/BlurhashImg.vue @@ -7,17 +7,21 @@ import { decode } from "blurhash"; import { Component, Prop, Ref, Vue } from "vue-property-decorator"; @Component -export default class extends Vue { +export default class BlurhashImg extends Vue { @Prop({ type: String, required: true }) hash!: string; @Prop({ type: Number, default: 1 }) aspectRatio!: string; @Ref("canvas") readonly canvas!: any; mounted(): void { - const pixels = decode(this.hash, 32, 32); - const imageData = new ImageData(pixels, 32, 32); - const context = this.canvas.getContext("2d"); - context.putImageData(imageData, 0, 0); + try { + const pixels = decode(this.hash, 32, 32); + const imageData = new ImageData(pixels, 32, 32); + const context = this.canvas.getContext("2d"); + context.putImageData(imageData, 0, 0); + } catch (e) { + console.error(e); + } } } diff --git a/js/src/components/Image/LazyImageWrapper.vue b/js/src/components/Image/LazyImageWrapper.vue index af5b43493..1b3e1313a 100644 --- a/js/src/components/Image/LazyImageWrapper.vue +++ b/js/src/components/Image/LazyImageWrapper.vue @@ -9,6 +9,7 @@