diff --git a/config/dev.exs b/config/dev.exs index a58b0c969..97fb3b585 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -19,7 +19,6 @@ config :mobilizon, Mobilizon.Web.Endpoint, code_reloader: true, check_origin: false, watchers: [ - # yarn: ["run", "dev", cd: Path.expand("../js", __DIR__)] node: [ "node_modules/webpack/bin/webpack.js", "--mode", @@ -53,8 +52,8 @@ config :mobilizon, Mobilizon.Web.Endpoint, patterns: [ ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, ~r{priv/gettext/.*(po)$}, - ~r{lib/mobilizon_web/views/.*(ex)$}, - ~r{lib/mobilizon_web/templates/.*(eex)$} + ~r{lib/web/(live|views)/.*(ex)$}, + ~r{lib/web/templates/.*(eex)$} ] ] diff --git a/js/src/components/PictureUpload.vue b/js/src/components/PictureUpload.vue index 2fb40a9e7..64843b69d 100644 --- a/js/src/components/PictureUpload.vue +++ b/js/src/components/PictureUpload.vue @@ -1,7 +1,7 @@ @@ -45,16 +52,22 @@ figure.image { color: #eee; } } + +.action-buttons { + display: flex; + flex-direction: column; +} diff --git a/js/src/graphql/actor.ts b/js/src/graphql/actor.ts index 6c9f68e6c..958d16f9b 100644 --- a/js/src/graphql/actor.ts +++ b/js/src/graphql/actor.ts @@ -10,6 +10,7 @@ export const FETCH_PERSON = gql` summary preferredUsername suspended + mediaSize avatar { id name @@ -51,6 +52,7 @@ export const GET_PERSON = gql` summary preferredUsername suspended + mediaSize avatar { id name diff --git a/js/src/graphql/group.ts b/js/src/graphql/group.ts index 4815d5e4b..4c946b89d 100644 --- a/js/src/graphql/group.ts +++ b/js/src/graphql/group.ts @@ -84,6 +84,7 @@ export const GROUP_FIELDS_FRAGMENTS = gql` id url } + mediaSize organizedEvents( afterDatetime: $afterDateTime beforeDatetime: $beforeDateTime diff --git a/js/src/graphql/upload.ts b/js/src/graphql/upload.ts index b1d1cafb9..6d011cd1d 100644 --- a/js/src/graphql/upload.ts +++ b/js/src/graphql/upload.ts @@ -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 + } + } +`; diff --git a/js/src/graphql/user.ts b/js/src/graphql/user.ts index c6fc40776..4f85f388a 100644 --- a/js/src/graphql/user.ts +++ b/js/src/graphql/user.ts @@ -200,6 +200,7 @@ export const GET_USER = gql` currentSignInAt locale disabled + mediaSize defaultActor { id } diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json index 918c1a7d6..d03b83648 100644 --- a/js/src/i18n/en_US.json +++ b/js/src/i18n/en_US.json @@ -799,5 +799,6 @@ "Mobilizon uses a system of profiles to compartiment your activities. You will be able to create as many profiles as you want.": "Mobilizon uses a system of profiles to compartiment your activities. You will be able to create as many profiles as you want.", "Mobilizon is a federated software, meaning you can interact - depending on your admin's federation settings - with content from other instances, such as joining groups or events that were created elsewhere.": "Mobilizon is a federated software, meaning you can interact - depending on your admin federation settings - with content from other instances, such as joining groups or events that were created elsewhere.", "This instance, {instanceName} ({domain}), hosts your profile, so remember its name.": "This instance, {instanceName} ({domain}), hosts your profile, so remember its name.", - "If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:" + "If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:", + "Uploaded media size": "Uploaded media size" } diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json index 34663c42c..1e52c9b99 100644 --- a/js/src/i18n/fr_FR.json +++ b/js/src/i18n/fr_FR.json @@ -887,5 +887,6 @@ "Mobilizon uses a system of profiles to compartiment your activities. You will be able to create as many profiles as you want.": "Mobilizon utilise un système de profils pour compartimenter vos activités. Vous pourrez créer autant de profils que vous voulez.", "Mobilizon is a federated software, meaning you can interact - depending on your admin's federation settings - with content from other instances, such as joining groups or events that were created elsewhere.": "Mobilizon est un logiciel fédéré, ce qui signifie que vous pouvez interagir - en fonction des paramètres de fédération de votre administrateur·ice - avec du contenu d'autres instances, comme par exemple rejoindre des groupes ou des événements ayant été créés ailleurs.", "This instance, {instanceName} ({domain}), hosts your profile, so remember its name.": "Cette instance, {instanceName} ({domain}), héberge votre profil, donc notez bien son nom.", - "If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "Si l'on vous demande votre identité fédérée, elle est composée de votre nom d'utilisateur·ice et de votre instance. Par exemple, l'identité fédérée de votre premier profil est :" + "If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "Si l'on vous demande votre identité fédérée, elle est composée de votre nom d'utilisateur·ice et de votre instance. Par exemple, l'identité fédérée de votre premier profil est :", + "Uploaded media size": "Taille des médias téléversés" } diff --git a/js/src/types/actor/actor.model.ts b/js/src/types/actor/actor.model.ts index 65c838f22..7b11331f7 100644 --- a/js/src/types/actor/actor.model.ts +++ b/js/src/types/actor/actor.model.ts @@ -13,6 +13,7 @@ export interface IActor { url: string; name: string; domain: string | null; + mediaSize: number; summary: string; preferredUsername: string; suspended: boolean; @@ -30,6 +31,8 @@ export class Actor implements IActor { domain: string | null = null; + mediaSize = 0; + name = ""; preferredUsername = ""; diff --git a/js/src/types/current-user.model.ts b/js/src/types/current-user.model.ts index eed7d9eb9..3b2a29ea0 100644 --- a/js/src/types/current-user.model.ts +++ b/js/src/types/current-user.model.ts @@ -39,6 +39,7 @@ export interface IUser extends ICurrentUser { actors: IPerson[]; disabled: boolean; participations: Paginate; + mediaSize: number; drafts: IEvent[]; settings: IUserSettings; locale: string; diff --git a/js/src/types/event.model.ts b/js/src/types/event.model.ts index e9bedbef6..133a6f88c 100644 --- a/js/src/types/event.model.ts +++ b/js/src/types/event.model.ts @@ -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, diff --git a/js/src/utils/datetime.ts b/js/src/utils/datetime.ts index f55420d5f..e0b3210a3 100644 --- a/js/src/utils/datetime.ts +++ b/js/src/utils/datetime.ts @@ -18,4 +18,17 @@ function localeShortWeekDayNames(): string[] { return weekDayNames; } -export { localeMonthNames, localeShortWeekDayNames }; +// https://stackoverflow.com/a/18650828/10204399 +function formatBytes(bytes: number, decimals = 2): string { + if (bytes === 0) return "0 Bytes"; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`; +} + +export { localeMonthNames, localeShortWeekDayNames, formatBytes }; diff --git a/js/src/utils/image.ts b/js/src/utils/image.ts index 5c817168e..304b7e4b9 100644 --- a/js/src/utils/image.ts +++ b/js/src/utils/image.ts @@ -9,7 +9,7 @@ export async function buildFileFromIPicture(obj: IPicture | null | undefined): P return new File([blob], obj.name); } -export function buildFileVariable(file: File | null, name: string, alt?: string): Record { +export function buildFileVariable(file: File | null, name: string, alt?: string): Record { if (!file) return {}; return { diff --git a/js/src/views/Account/children/EditIdentity.vue b/js/src/views/Account/children/EditIdentity.vue index 6054c1e22..ddedce7b0 100644 --- a/js/src/views/Account/children/EditIdentity.vue +++ b/js/src/views/Account/children/EditIdentity.vue @@ -27,7 +27,7 @@ {{ $t("I create an identity") }} - + 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 { this.resetFields(); @@ -286,7 +280,6 @@ export default class EditIdentity extends mixins(identityEditionMixin) { } }, }); - this.avatarFile = null; this.$notifier.success( this.$t("Identity {displayName} updated", { diff --git a/js/src/views/Admin/AdminGroupProfile.vue b/js/src/views/Admin/AdminGroupProfile.vue index 8a27d06f7..6b7cb9279 100644 --- a/js/src/views/Admin/AdminGroupProfile.vue +++ b/js/src/views/Admin/AdminGroupProfile.vue @@ -198,6 +198,7 @@