Merge branch 'session-issues' into 'master'
Various issues Closes #823 See merge request framasoft/mobilizon!1025
This commit is contained in:
commit
d2fed8f91a
|
@ -66,10 +66,11 @@ config :mime, :types, %{
|
||||||
config :mobilizon, Mobilizon.Web.Upload,
|
config :mobilizon, Mobilizon.Web.Upload,
|
||||||
uploader: Mobilizon.Web.Upload.Uploader.Local,
|
uploader: Mobilizon.Web.Upload.Uploader.Local,
|
||||||
filters: [
|
filters: [
|
||||||
Mobilizon.Web.Upload.Filter.Dedupe,
|
|
||||||
Mobilizon.Web.Upload.Filter.AnalyzeMetadata,
|
Mobilizon.Web.Upload.Filter.AnalyzeMetadata,
|
||||||
Mobilizon.Web.Upload.Filter.Resize,
|
Mobilizon.Web.Upload.Filter.Resize,
|
||||||
Mobilizon.Web.Upload.Filter.Optimize
|
Mobilizon.Web.Upload.Filter.Optimize,
|
||||||
|
Mobilizon.Web.Upload.Filter.BlurHash,
|
||||||
|
Mobilizon.Web.Upload.Filter.Dedupe
|
||||||
],
|
],
|
||||||
allow_list_mime_types: ["image/gif", "image/jpeg", "image/png", "image/webp"],
|
allow_list_mime_types: ["image/gif", "image/jpeg", "image/png", "image/webp"],
|
||||||
link_name: true,
|
link_name: true,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { IMedia } from "@/types/media.model";
|
import { IMedia } from "@/types/media.model";
|
||||||
|
import { PropType } from "vue";
|
||||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||||
import LazyImageWrapper from "../Image/LazyImageWrapper.vue";
|
import LazyImageWrapper from "../Image/LazyImageWrapper.vue";
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ import LazyImageWrapper from "../Image/LazyImageWrapper.vue";
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class EventBanner extends Vue {
|
export default class EventBanner extends Vue {
|
||||||
@Prop({ required: true, default: null })
|
@Prop({ required: true, default: null, type: Object as PropType<IMedia> })
|
||||||
picture!: IMedia | null;
|
picture!: IMedia | null;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,17 +7,21 @@ import { decode } from "blurhash";
|
||||||
import { Component, Prop, Ref, Vue } from "vue-property-decorator";
|
import { Component, Prop, Ref, Vue } from "vue-property-decorator";
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class extends Vue {
|
export default class BlurhashImg extends Vue {
|
||||||
@Prop({ type: String, required: true }) hash!: string;
|
@Prop({ type: String, required: true }) hash!: string;
|
||||||
@Prop({ type: Number, default: 1 }) aspectRatio!: string;
|
@Prop({ type: Number, default: 1 }) aspectRatio!: string;
|
||||||
|
|
||||||
@Ref("canvas") readonly canvas!: any;
|
@Ref("canvas") readonly canvas!: any;
|
||||||
|
|
||||||
mounted(): void {
|
mounted(): void {
|
||||||
|
try {
|
||||||
const pixels = decode(this.hash, 32, 32);
|
const pixels = decode(this.hash, 32, 32);
|
||||||
const imageData = new ImageData(pixels, 32, 32);
|
const imageData = new ImageData(pixels, 32, 32);
|
||||||
const context = this.canvas.getContext("2d");
|
const context = this.canvas.getContext("2d");
|
||||||
context.putImageData(imageData, 0, 0);
|
context.putImageData(imageData, 0, 0);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { IMedia } from "@/types/media.model";
|
import { IMedia } from "@/types/media.model";
|
||||||
|
import { PropType } from "vue";
|
||||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||||
import LazyImage from "../Image/LazyImage.vue";
|
import LazyImage from "../Image/LazyImage.vue";
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ const DEFAULT_PICTURE = {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class LazyImageWrapper extends Vue {
|
export default class LazyImageWrapper extends Vue {
|
||||||
@Prop({ required: true })
|
@Prop({ required: false, type: Object as PropType<IMedia | null> })
|
||||||
picture!: IMedia | null;
|
picture!: IMedia | null;
|
||||||
|
|
||||||
get pictureOrDefault(): Partial<IMedia> {
|
get pictureOrDefault(): Partial<IMedia> {
|
||||||
|
|
|
@ -7,12 +7,18 @@
|
||||||
:center="[lat, lon]"
|
:center="[lat, lon]"
|
||||||
@click="clickMap"
|
@click="clickMap"
|
||||||
@update:zoom="updateZoom"
|
@update:zoom="updateZoom"
|
||||||
|
:options="{ zoomControl: false }"
|
||||||
>
|
>
|
||||||
<l-tile-layer
|
<l-tile-layer
|
||||||
:url="config.maps.tiles.endpoint"
|
:url="config.maps.tiles.endpoint"
|
||||||
:attribution="attribution"
|
:attribution="attribution"
|
||||||
>
|
>
|
||||||
</l-tile-layer>
|
</l-tile-layer>
|
||||||
|
<l-control-zoom
|
||||||
|
position="topleft"
|
||||||
|
:zoomInTitle="$t('Zoom in')"
|
||||||
|
:zoomOutTitle="$t('Zoom out')"
|
||||||
|
></l-control-zoom>
|
||||||
<v-locatecontrol :options="{ icon: 'mdi mdi-map-marker' }" />
|
<v-locatecontrol :options="{ icon: 'mdi mdi-map-marker' }" />
|
||||||
<l-marker
|
<l-marker
|
||||||
:lat-lng="[lat, lon]"
|
:lat-lng="[lat, lon]"
|
||||||
|
@ -34,7 +40,14 @@
|
||||||
import { Icon, LatLng, LeafletMouseEvent, LeafletEvent } from "leaflet";
|
import { Icon, LatLng, LeafletMouseEvent, LeafletEvent } from "leaflet";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||||
import { LMap, LTileLayer, LMarker, LPopup, LIcon } from "vue2-leaflet";
|
import {
|
||||||
|
LMap,
|
||||||
|
LTileLayer,
|
||||||
|
LMarker,
|
||||||
|
LPopup,
|
||||||
|
LIcon,
|
||||||
|
LControlZoom,
|
||||||
|
} from "vue2-leaflet";
|
||||||
import Vue2LeafletLocateControl from "@/components/Map/Vue2LeafletLocateControl.vue";
|
import Vue2LeafletLocateControl from "@/components/Map/Vue2LeafletLocateControl.vue";
|
||||||
import { CONFIG } from "../graphql/config";
|
import { CONFIG } from "../graphql/config";
|
||||||
import { IConfig } from "../types/config.model";
|
import { IConfig } from "../types/config.model";
|
||||||
|
@ -46,6 +59,7 @@ import { IConfig } from "../types/config.model";
|
||||||
LMarker,
|
LMarker,
|
||||||
LPopup,
|
LPopup,
|
||||||
LIcon,
|
LIcon,
|
||||||
|
LControlZoom,
|
||||||
"v-locatecontrol": Vue2LeafletLocateControl,
|
"v-locatecontrol": Vue2LeafletLocateControl,
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
* to try to trigger location manually (not done ATM)
|
* to try to trigger location manually (not done ATM)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import L, { DomEvent } from "leaflet";
|
import { DomEvent } from "leaflet";
|
||||||
import { findRealParent, propsBinder } from "vue2-leaflet";
|
import { findRealParent, propsBinder } from "vue2-leaflet";
|
||||||
import "leaflet.locatecontrol";
|
import Locatecontrol from "leaflet.locatecontrol";
|
||||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -37,12 +37,20 @@ export default class Vue2LeafletLocateControl extends Vue {
|
||||||
parentContainer: any;
|
parentContainer: any;
|
||||||
|
|
||||||
mounted(): void {
|
mounted(): void {
|
||||||
this.mapObject = L.control.locate(this.options);
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
this.mapObject = new Locatecontrol({
|
||||||
|
...this.options,
|
||||||
|
strings: { title: this.$t("Show me where I am") as string },
|
||||||
|
});
|
||||||
DomEvent.on(this.mapObject, this.$listeners as any);
|
DomEvent.on(this.mapObject, this.$listeners as any);
|
||||||
propsBinder(this, this.mapObject, this.$props);
|
propsBinder(this, this.mapObject, this.$props);
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
this.parentContainer = findRealParent(this.$parent);
|
this.parentContainer = findRealParent(this.$parent);
|
||||||
this.mapObject.addTo(this.parentContainer.mapObject, !this.visible);
|
this.mapObject.addTo(this.parentContainer.mapObject, !this.visible);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$emit("ready", this.mapObject);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public locate(): void {
|
public locate(): void {
|
||||||
|
|
|
@ -1125,5 +1125,8 @@
|
||||||
"Booking": "Booking",
|
"Booking": "Booking",
|
||||||
"Filter by profile or group name": "Filter by profile or group name",
|
"Filter by profile or group name": "Filter by profile or group name",
|
||||||
"Filter by name": "Filter by name",
|
"Filter by name": "Filter by name",
|
||||||
"Redirecting in progress…": "Redirecting in progress…"
|
"Redirecting in progress…": "Redirecting in progress…",
|
||||||
|
"Zoom in": "Zoom in",
|
||||||
|
"Zoom out": "Zoom out",
|
||||||
|
"Show me where I am": "Show me where I am"
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@
|
||||||
"Are you sure you want to <b>delete</b> 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 <b>supprimer</b> 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 <b>delete</b> 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 <b>supprimer</b> 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 <b>suspend</b> this group? All members - including remote ones - will be notified and removed from the group, and <b>all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed</b>.": "Êtes-vous certain·e de vouloir <b>suspendre</b> ce groupe ? Tous les membres - y compris ceux·elles sur d'autres instances - seront notifié·e·s et supprimé·e·s du groupe, et <b>toutes les données associées au groupe (événements, billets, discussions, todos…) seront irrémédiablement détruites</b>.",
|
"Are you sure you want to <b>suspend</b> this group? All members - including remote ones - will be notified and removed from the group, and <b>all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed</b>.": "Êtes-vous certain·e de vouloir <b>suspendre</b> ce groupe ? Tous les membres - y compris ceux·elles sur d'autres instances - seront notifié·e·s et supprimé·e·s du groupe, et <b>toutes les données associées au groupe (événements, billets, discussions, todos…) seront irrémédiablement détruites</b>.",
|
||||||
"Are you sure you want to <b>suspend</b> 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 <b>suspendre</b> 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 <b>suspend</b> 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 <b>suspendre</b> 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 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 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 ?",
|
"Are you sure you want to delete this entire discussion?": "Êtes-vous certain⋅e de vouloir supprimer l'entièreté de cette discussion ?",
|
||||||
|
@ -1216,5 +1216,8 @@
|
||||||
"Booking": "Réservations",
|
"Booking": "Réservations",
|
||||||
"Filter by profile or group name": "Filter par nom du profil ou du groupe",
|
"Filter by profile or group name": "Filter par nom du profil ou du groupe",
|
||||||
"Filter by name": "Filtrer par nom",
|
"Filter by name": "Filtrer par nom",
|
||||||
"Redirecting in progress…": "Redirection en cours…"
|
"Redirecting in progress…": "Redirection en cours…",
|
||||||
|
"Zoom in": "Zoomer",
|
||||||
|
"Zoom out": "Dézoomer",
|
||||||
|
"Show me where I am": "Afficher ma position"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 Vue from "vue";
|
||||||
import Buefy from "buefy";
|
import Buefy from "buefy";
|
||||||
import Component from "vue-class-component";
|
import Component from "vue-class-component";
|
||||||
|
|
|
@ -203,41 +203,47 @@ export class EventModel implements IEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
toEditJSON(): IEventEditJSON {
|
toEditJSON(): IEventEditJSON {
|
||||||
return {
|
return toEditJSON(this);
|
||||||
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 {
|
function removeTypeName(entity: any): any {
|
||||||
if (entity?.__typename) {
|
if (entity?.__typename) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { __typename, ...purgedEntity } = entity;
|
const { __typename, ...purgedEntity } = entity;
|
||||||
return purgedEntity;
|
return purgedEntity;
|
||||||
}
|
}
|
||||||
return entity;
|
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,
|
||||||
|
})),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -463,7 +463,7 @@ import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.
|
||||||
import EventMetadataList from "@/components/Event/EventMetadataList.vue";
|
import EventMetadataList from "@/components/Event/EventMetadataList.vue";
|
||||||
import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue";
|
import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue";
|
||||||
import Subtitle from "@/components/Utils/Subtitle.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 { formatList } from "@/utils/i18n";
|
||||||
import {
|
import {
|
||||||
ActorType,
|
ActorType,
|
||||||
|
@ -481,7 +481,7 @@ import {
|
||||||
EVENT_PERSON_PARTICIPATION,
|
EVENT_PERSON_PARTICIPATION,
|
||||||
FETCH_EVENT,
|
FETCH_EVENT,
|
||||||
} from "../../graphql/event";
|
} from "../../graphql/event";
|
||||||
import { EventModel, IEvent } from "../../types/event.model";
|
import { EventModel, IEvent, toEditJSON } from "../../types/event.model";
|
||||||
import {
|
import {
|
||||||
CURRENT_ACTOR_CLIENT,
|
CURRENT_ACTOR_CLIENT,
|
||||||
IDENTITIES,
|
IDENTITIES,
|
||||||
|
@ -586,6 +586,8 @@ export default class EditEvent extends Vue {
|
||||||
|
|
||||||
event: IEvent = new EventModel();
|
event: IEvent = new EventModel();
|
||||||
|
|
||||||
|
unmodifiedEvent: IEvent = new EventModel();
|
||||||
|
|
||||||
identities: IActor[] = [];
|
identities: IActor[] = [];
|
||||||
|
|
||||||
person!: IPerson;
|
person!: IPerson;
|
||||||
|
@ -687,12 +689,13 @@ export default class EditEvent extends Vue {
|
||||||
if (!(this.isUpdate || this.isDuplicate)) {
|
if (!(this.isUpdate || this.isDuplicate)) {
|
||||||
this.initializeEvent();
|
this.initializeEvent();
|
||||||
} else {
|
} else {
|
||||||
this.event = {
|
this.event = new EventModel({
|
||||||
...this.event,
|
...this.event,
|
||||||
options: cloneDeep(this.event.options),
|
options: cloneDeep(this.event.options),
|
||||||
description: this.event.description || "",
|
description: this.event.description || "",
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
this.unmodifiedEvent = cloneDeep(this.event);
|
||||||
}
|
}
|
||||||
|
|
||||||
createOrUpdateDraft(e: Event): void {
|
createOrUpdateDraft(e: Event): void {
|
||||||
|
@ -813,8 +816,8 @@ export default class EditEvent extends Vue {
|
||||||
}
|
}
|
||||||
|
|
||||||
get updateEventMessage(): string {
|
get updateEventMessage(): string {
|
||||||
// if (this.unmodifiedEvent.draft && !this.event.draft)
|
if (this.unmodifiedEvent.draft && !this.event.draft)
|
||||||
// return this.$i18n.t("The event has been updated and published") as string;
|
return this.$i18n.t("The event has been updated and published") as string;
|
||||||
return (
|
return (
|
||||||
this.event.draft
|
this.event.draft
|
||||||
? this.$i18n.t("The draft event has been updated")
|
? 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
|
* Build variables for Event GraphQL creation query
|
||||||
*/
|
*/
|
||||||
private async buildVariables() {
|
private async buildVariables() {
|
||||||
let res = new EventModel(this.event).toEditJSON();
|
let res = toEditJSON(new EventModel(this.event));
|
||||||
const organizerActor = this.event.organizerActor?.id
|
const organizerActor = this.event.organizerActor?.id
|
||||||
? this.event.organizerActor
|
? this.event.organizerActor
|
||||||
: this.organizerActor;
|
: this.organizerActor;
|
||||||
|
@ -984,10 +987,12 @@ export default class EditEvent extends Vue {
|
||||||
/**
|
/**
|
||||||
* Confirm cancel
|
* Confirm cancel
|
||||||
*/
|
*/
|
||||||
confirmGoElsewhere(callback: () => any): void {
|
confirmGoElsewhere(): Promise<boolean> {
|
||||||
if (!this.isEventModified) {
|
// TODO: Make calculation of changes work again and bring this back
|
||||||
callback();
|
// If the event wasn't modified, no need to warn
|
||||||
}
|
// if (!this.isEventModified) {
|
||||||
|
// return Promise.resolve(true);
|
||||||
|
// }
|
||||||
const title: string = this.isUpdate
|
const title: string = this.isUpdate
|
||||||
? (this.$t("Cancel edition") as string)
|
? (this.$t("Cancel edition") as string)
|
||||||
: (this.$t("Cancel creation") as string);
|
: (this.$t("Cancel creation") as string);
|
||||||
|
@ -1001,6 +1006,7 @@ export default class EditEvent extends Vue {
|
||||||
{ title: this.event.title }
|
{ title: this.event.title }
|
||||||
) as string);
|
) as string);
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
this.$buefy.dialog.confirm({
|
this.$buefy.dialog.confirm({
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
|
@ -1008,7 +1014,9 @@ export default class EditEvent extends Vue {
|
||||||
cancelText: this.$t("Continue editing") as string,
|
cancelText: this.$t("Continue editing") as string,
|
||||||
type: "is-warning",
|
type: "is-warning",
|
||||||
hasIcon: true,
|
hasIcon: true,
|
||||||
onConfirm: callback,
|
onConfirm: () => resolve(true),
|
||||||
|
onCancel: () => resolve(false),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1016,21 +1024,29 @@ export default class EditEvent extends Vue {
|
||||||
* Confirm cancel
|
* Confirm cancel
|
||||||
*/
|
*/
|
||||||
confirmGoBack(): void {
|
confirmGoBack(): void {
|
||||||
this.confirmGoElsewhere(() => this.$router.go(-1));
|
this.$router.go(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
// 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<void> {
|
||||||
if (to.name === RouteName.EVENT) return next();
|
if (to.name === RouteName.EVENT) return next();
|
||||||
this.confirmGoElsewhere(() => next());
|
if (await this.confirmGoElsewhere()) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
return next(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEventModified(): boolean {
|
get isEventModified(): boolean {
|
||||||
// return (
|
return (
|
||||||
// JSON.stringify(this.event.toEditJSON()) !==
|
this.event &&
|
||||||
// JSON.stringify(this.unmodifiedEvent)
|
this.unmodifiedEvent &&
|
||||||
// );
|
JSON.stringify(toEditJSON(this.event)) !==
|
||||||
return false;
|
JSON.stringify(this.unmodifiedEvent)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get beginsOn(): Date {
|
get beginsOn(): Date {
|
||||||
|
|
|
@ -20,13 +20,7 @@ defmodule Mobilizon.Web.Upload.Filter.AnalyzeMetadata do
|
||||||
|> Mogrify.open()
|
|> Mogrify.open()
|
||||||
|> Mogrify.verbose()
|
|> Mogrify.verbose()
|
||||||
|
|
||||||
upload =
|
{:ok, :filtered, %Upload{upload | width: image.width, height: image.height}}
|
||||||
upload
|
|
||||||
|> Map.put(:width, image.width)
|
|
||||||
|> Map.put(:height, image.height)
|
|
||||||
|> Map.put(:blurhash, get_blurhash(file))
|
|
||||||
|
|
||||||
{:ok, :filtered, upload}
|
|
||||||
rescue
|
rescue
|
||||||
e in ErlangError ->
|
e in ErlangError ->
|
||||||
Logger.warn("#{__MODULE__}: #{inspect(e)}")
|
Logger.warn("#{__MODULE__}: #{inspect(e)}")
|
||||||
|
@ -34,14 +28,4 @@ defmodule Mobilizon.Web.Upload.Filter.AnalyzeMetadata do
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(_), do: {:ok, :noop}
|
def filter(_), do: {:ok, :noop}
|
||||||
|
|
||||||
defp get_blurhash(file) do
|
|
||||||
case :eblurhash.magick(to_charlist(file)) do
|
|
||||||
{:ok, blurhash} ->
|
|
||||||
to_string(blurhash)
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
31
lib/web/upload/filter/blurhash.ex
Normal file
31
lib/web/upload/filter/blurhash.ex
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
defmodule Mobilizon.Web.Upload.Filter.BlurHash do
|
||||||
|
@moduledoc """
|
||||||
|
Computes blurhash from the upload
|
||||||
|
"""
|
||||||
|
require Logger
|
||||||
|
alias Mobilizon.Web.Upload
|
||||||
|
|
||||||
|
@behaviour Mobilizon.Web.Upload.Filter
|
||||||
|
|
||||||
|
@spec filter(Upload.t()) ::
|
||||||
|
{:ok, :filtered, Upload.t()} | {:ok, :noop} | {:error, String.t()}
|
||||||
|
def filter(%Upload{tempfile: file, content_type: "image" <> _} = upload) do
|
||||||
|
{:ok, :filtered, %Upload{upload | blurhash: generate_blurhash(file)}}
|
||||||
|
rescue
|
||||||
|
e in ErlangError ->
|
||||||
|
Logger.warn("#{__MODULE__}: #{inspect(e)}")
|
||||||
|
{:ok, :noop}
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter(_), do: {:ok, :noop}
|
||||||
|
|
||||||
|
defp generate_blurhash(file) do
|
||||||
|
case :eblurhash.magick(to_charlist(file)) do
|
||||||
|
{:ok, blurhash} ->
|
||||||
|
to_string(blurhash)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,22 +6,27 @@ defmodule Mobilizon.Web.Upload.Filter.Resize do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@behaviour Mobilizon.Web.Upload.Filter
|
@behaviour Mobilizon.Web.Upload.Filter
|
||||||
|
alias Mobilizon.Web.Upload
|
||||||
|
|
||||||
@maximum_width 1_920
|
@maximum_width 1_920
|
||||||
@maximum_height 1_080
|
@maximum_height 1_080
|
||||||
|
|
||||||
def filter(%Mobilizon.Web.Upload{
|
def filter(
|
||||||
|
%Upload{
|
||||||
tempfile: file,
|
tempfile: file,
|
||||||
content_type: "image" <> _,
|
content_type: "image" <> _,
|
||||||
width: width,
|
width: width,
|
||||||
height: height
|
height: height
|
||||||
}) do
|
} = upload
|
||||||
|
) do
|
||||||
|
{new_width, new_height} = sizes = limit_sizes({width, height})
|
||||||
|
|
||||||
file
|
file
|
||||||
|> Mogrify.open()
|
|> Mogrify.open()
|
||||||
|> Mogrify.resize(string(limit_sizes({width, height})))
|
|> Mogrify.resize(string(sizes))
|
||||||
|> Mogrify.save(in_place: true)
|
|> Mogrify.save(in_place: true)
|
||||||
|
|
||||||
{:ok, :filtered}
|
{:ok, :filtered, %Upload{upload | width: new_width, height: new_height}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(_), do: {:ok, :noop}
|
def filter(_), do: {:ok, :noop}
|
||||||
|
|
|
@ -62,9 +62,10 @@ defmodule Mobilizon.Web.Upload do
|
||||||
path: String.t(),
|
path: String.t(),
|
||||||
size: integer(),
|
size: integer(),
|
||||||
width: integer(),
|
width: integer(),
|
||||||
height: integer()
|
height: integer(),
|
||||||
|
blurhash: String.t()
|
||||||
}
|
}
|
||||||
defstruct [:id, :name, :tempfile, :content_type, :path, :size, :width, :height]
|
defstruct [:id, :name, :tempfile, :content_type, :path, :size, :width, :height, :blurhash]
|
||||||
|
|
||||||
@spec store(source, options :: [option()]) :: {:ok, map()} | {:error, any()}
|
@spec store(source, options :: [option()]) :: {:ok, map()} | {:error, any()}
|
||||||
def store(upload, opts \\ []) do
|
def store(upload, opts \\ []) do
|
||||||
|
|
Loading…
Reference in a new issue