From 6a52ca0d91f86966541a990012cc21cc748d8a6d Mon Sep 17 00:00:00 2001 From: Thomas Citharel <tcit@tcit.fr> Date: Fri, 11 Dec 2020 15:27:04 +0100 Subject: [PATCH 1/3] Produce and use webp pictures with different sizes Signed-off-by: Thomas Citharel <tcit@tcit.fr> --- js/package.json | 5 +- ...lustration-E_realisation.jpg => error.jpg} | Bin ...ation-evenement.jpg => event_creation.jpg} | Bin ...on-illustration-C_groupe.jpg => group.jpg} | Bin ...lustration-A_homepage.jpg => homepage.jpg} | Bin ...tion-D_realisation.jpg => realisation.jpg} | Bin .../pics/{2020-10-06_Rose.jpg => rose.jpg} | Bin js/scripts/build/pictures.sh | 90 ++++++++++++++++++ js/src/components/Footer.vue | 18 +++- js/src/utils/support.ts | 10 ++ js/src/views/Event/MyEvents.vue | 21 +++- js/src/views/Group/MyGroups.vue | 22 ++++- js/src/views/Home.vue | 72 ++++++++++++-- js/src/views/PageNotFound.vue | 22 ++++- js/yarn.lock | 24 ++++- 15 files changed, 267 insertions(+), 17 deletions(-) rename js/public/img/pics/{2020-10-06-mobilizon-illustration-E_realisation.jpg => error.jpg} (100%) rename js/public/img/pics/{2020-10-06-mobilizon-illustration-B_creation-evenement.jpg => event_creation.jpg} (100%) rename js/public/img/pics/{2020-10-06-mobilizon-illustration-C_groupe.jpg => group.jpg} (100%) rename js/public/img/pics/{2020-10-06-mobilizon-illustration-A_homepage.jpg => homepage.jpg} (100%) rename js/public/img/pics/{2020-10-06-mobilizon-illustration-D_realisation.jpg => realisation.jpg} (100%) rename js/public/img/pics/{2020-10-06_Rose.jpg => rose.jpg} (100%) create mode 100755 js/scripts/build/pictures.sh create mode 100644 js/src/utils/support.ts diff --git a/js/package.json b/js/package.json index 7f2bb5663..8598a0d92 100644 --- a/js/package.json +++ b/js/package.json @@ -4,7 +4,9 @@ "private": true, "scripts": { "serve": "vue-cli-service serve", - "build": "vue-cli-service build --modern", + "build:assets": "vue-cli-service build --modern", + "build:pictures": "scripty", + "build": "yarn run build:assets && yarn run build:pictures", "test:unit": "LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 vue-cli-service test:unit", "test:e2e": "vue-cli-service test:e2e", "lint": "vue-cli-service lint" @@ -87,6 +89,7 @@ "prettier-eslint": "^12.0.0", "sass": "^1.29.0", "sass-loader": "^10.0.1", + "scripty": "^2.0.0", "typescript": "~4.1.2", "vue-cli-plugin-svg": "~0.1.3", "vue-i18n-extract": "^1.0.2", diff --git a/js/public/img/pics/2020-10-06-mobilizon-illustration-E_realisation.jpg b/js/public/img/pics/error.jpg similarity index 100% rename from js/public/img/pics/2020-10-06-mobilizon-illustration-E_realisation.jpg rename to js/public/img/pics/error.jpg diff --git a/js/public/img/pics/2020-10-06-mobilizon-illustration-B_creation-evenement.jpg b/js/public/img/pics/event_creation.jpg similarity index 100% rename from js/public/img/pics/2020-10-06-mobilizon-illustration-B_creation-evenement.jpg rename to js/public/img/pics/event_creation.jpg diff --git a/js/public/img/pics/2020-10-06-mobilizon-illustration-C_groupe.jpg b/js/public/img/pics/group.jpg similarity index 100% rename from js/public/img/pics/2020-10-06-mobilizon-illustration-C_groupe.jpg rename to js/public/img/pics/group.jpg diff --git a/js/public/img/pics/2020-10-06-mobilizon-illustration-A_homepage.jpg b/js/public/img/pics/homepage.jpg similarity index 100% rename from js/public/img/pics/2020-10-06-mobilizon-illustration-A_homepage.jpg rename to js/public/img/pics/homepage.jpg diff --git a/js/public/img/pics/2020-10-06-mobilizon-illustration-D_realisation.jpg b/js/public/img/pics/realisation.jpg similarity index 100% rename from js/public/img/pics/2020-10-06-mobilizon-illustration-D_realisation.jpg rename to js/public/img/pics/realisation.jpg diff --git a/js/public/img/pics/2020-10-06_Rose.jpg b/js/public/img/pics/rose.jpg similarity index 100% rename from js/public/img/pics/2020-10-06_Rose.jpg rename to js/public/img/pics/rose.jpg diff --git a/js/scripts/build/pictures.sh b/js/scripts/build/pictures.sh new file mode 100755 index 000000000..d19fec20a --- /dev/null +++ b/js/scripts/build/pictures.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +set -eu + +output_dir="../priv/static/img/pics" +resolutions=( + 480 + 1024 + 1920 +) +ignore=( + homepage_background.png +) + +file_extension () { + filename=$(basename -- "$file") + echo "${filename##*.}" +} + +file_name () { + filename=$(basename -- "$file") + echo "${filename%.*}" +} + +convert_image () { + name=$(file_name) + extension=$(file_extension) + res="$1w" + output="$output_dir/$name-$res.$extension" + convert -geometry "$resolution"x $file $output +} + +produce_webp () { + name=$(file_name) + output="$output_dir/$name.webp" + cwebp $file -quiet -o $output +} + +progress() { + local w=80 p=$1; shift + # create a string of spaces, then change them to dots + printf -v dots "%*s" "$(( $p*$w/100 ))" ""; dots=${dots// /.}; + # print those dots on a fixed-width space plus the percentage etc. + printf "\r\e[K|%-*s| %3d %% %s" "$w" "$dots" "$p" "$*"; +} + + +echo "Generating responsive versions of the pictures…" + +if ! command -v convert &> /dev/null +then + echo "$(tput setaf 1)ERROR: The convert command could not be found. You need to install ImageMagick.$(tput sgr 0)" + exit 1 +fi + +nb_files=$( shopt -s nullglob ; set -- $output_dir/* ; echo $#) + +tasks=$((${#resolutions[@]}*$nb_files)) +i=1 +for file in $output_dir/* +do + if [[ -f $file ]]; then + for resolution in "${resolutions[@]}"; do + convert_image $resolution + progress $(($i*100/$tasks)) still working... + i=$((i+1)) + done + fi +done +echo -e "\nDone!" + +echo "Generating optimized versions of the pictures…" + +if ! command -v cwebp &> /dev/null +then + echo "$(tput setaf 1)ERROR: The cwebp command could not be found. You need to install webp.$(tput sgr 0)" + exit 1 +fi + +nb_files=$( shopt -s nullglob ; set -- $output_dir/* ; echo $#) +i=1 +for file in $output_dir/* +do + if [[ -f $file ]]; then + produce_webp + progress $(($i*100/$nb_files)) still working... + i=$((i+1)) + fi +done +echo -e "\nDone!" \ No newline at end of file diff --git a/js/src/components/Footer.vue b/js/src/components/Footer.vue index 50d4eaf7f..b82328f1f 100644 --- a/js/src/components/Footer.vue +++ b/js/src/components/Footer.vue @@ -1,6 +1,22 @@ <template> <footer class="footer" ref="footer"> - <img :src="`/img/pics/footer_${random}.jpg`" alt="" /> + <picture> + <source + :srcset="`/img/pics/footer_${random}-1024w.webp 1x, /img/pics/footer_${random}-1920w.webp 2x`" + type="image/webp" + /> + <source + :srcset="`/img/pics/footer_${random}-1024w.jpg 1x, /img/pics/footer_${random}-1920w.jpg 2x`" + type="image/jpeg" + /> + <img + :src="`/img/pics/footer_${random}-1024w.jpg`" + alt="" + width="5234" + height="2189" + loading="lazy" + /> + </picture> <ul> <li> <b-select diff --git a/js/src/utils/support.ts b/js/src/utils/support.ts new file mode 100644 index 000000000..b49ef7d6c --- /dev/null +++ b/js/src/utils/support.ts @@ -0,0 +1,10 @@ +export function supportsWebPFormat(): boolean { + const elem = document.createElement("canvas"); + + if (elem.getContext && elem.getContext("2d")) { + // was able or not to get WebP representation + return elem.toDataURL("image/webp").indexOf("data:image/webp") === 0; + } + // very old browser like IE 8, canvas not supported + return false; +} diff --git a/js/src/views/Event/MyEvents.vue b/js/src/views/Event/MyEvents.vue index 32083c01b..2d48a6a53 100644 --- a/js/src/views/Event/MyEvents.vue +++ b/js/src/views/Event/MyEvents.vue @@ -101,7 +101,7 @@ > <div class="columns is-vertical is-centered"> <div class="column is-three-quarters"> - <div class="img-container" /> + <div class="img-container" :class="{ webp: supportsWebPFormat }" /> <div class="content has-text-centered"> <p> {{ $t("You didn't create or join any event yet.") }} @@ -129,6 +129,7 @@ import { Component, Vue } from "vue-property-decorator"; import { ParticipantRole } from "@/types/enums"; import RouteName from "@/router/name"; +import { supportsWebPFormat } from "@/utils/support"; import { IParticipant, Participant } from "../../types/participant.model"; import { LOGGED_USER_PARTICIPATIONS, @@ -211,6 +212,8 @@ export default class MyEvents extends Vue { RouteName = RouteName; + supportsWebPFormat = supportsWebPFormat; + static monthlyParticipations( participations: IParticipant[], revertSort = false @@ -355,7 +358,21 @@ section { .not-found { .img-container { - background-image: url("/img/pics/2020-10-06-mobilizon-illustration-B_creation-evenement.jpg"); + background-image: url("/img/pics/event_creation-480w.jpg"); + @media (min-resolution: 2dppx) { + & { + background-image: url("/img/pics/event_creation-1024w.jpg"); + } + } + + &.webp { + background-image: url("/img/pics/event_creation-480w.webp"); + @media (min-resolution: 2dppx) { + & { + background-image: url("/img/pics/event_creation-1024w.webp"); + } + } + } max-width: 450px; height: 300px; box-shadow: 0 0 8px 8px white inset; diff --git a/js/src/views/Group/MyGroups.vue b/js/src/views/Group/MyGroups.vue index 43473ddaa..9ee2cf5a0 100644 --- a/js/src/views/Group/MyGroups.vue +++ b/js/src/views/Group/MyGroups.vue @@ -46,7 +46,7 @@ > <div class="columns is-vertical is-centered"> <div class="column is-three-quarters"> - <div class="img-container" /> + <div class="img-container" :class="{ webp: supportsWebPFormat }" /> <div class="content has-text-centered"> <p> {{ $t("You are not part of any group.") }} @@ -81,6 +81,7 @@ import { IGroup, usernameWithDomain } from "@/types/actor"; import { Route } from "vue-router"; import { IMember } from "@/types/actor/member.model"; import { MemberRole } from "@/types/enums"; +import { supportsWebPFormat } from "@/utils/support"; import RouteName from "../../router/name"; @Component({ @@ -119,6 +120,8 @@ export default class MyGroups extends Vue { limit = 10; + supportsWebPFormat = supportsWebPFormat; + acceptInvitation(member: IMember): Promise<Route> { return this.$router.push({ name: RouteName.GROUP, @@ -199,7 +202,22 @@ section { .not-found { .img-container { - background-image: url("/img/pics/2020-10-06-mobilizon-illustration-C_groupe.jpg"); + background-image: url("/img/pics/group-480w.jpg"); + + @media (min-resolution: 2dppx) { + & { + background-image: url("/img/pics/group-1024w.jpg"); + } + } + &.webp { + background-image: url("/img/pics/group-480w.webp"); + @media (min-resolution: 2dppx) { + & { + background-image: url("/img/pics/group-1024w.webp"); + } + } + } + max-width: 450px; height: 300px; box-shadow: 0 0 8px 8px white inset; diff --git a/js/src/views/Home.vue b/js/src/views/Home.vue index 41c975c25..993501e50 100644 --- a/js/src/views/Home.vue +++ b/js/src/views/Home.vue @@ -2,6 +2,7 @@ <div id="homepage"> <section class="hero" + :class="{ webp: supportsWebPFormat }" v-if="config && (!currentUser.id || !currentActor.id)" > <div class="hero-body"> @@ -72,10 +73,59 @@ </div> <div id="picture" v-if="config && (!currentUser.id || !currentActor.id)"> <div class="picture-container"> - <img - src="/img/pics/2020-10-06-mobilizon-illustration-A_homepage.jpg" - alt="" - /> + <picture> + <source + media="(max-width: 799px)" + srcset="/img/pics/homepage-480w.webp" + type="image/webp" + /> + <source + media="(max-width: 799px)" + srcset="/img/pics/homepage-480w.jpg" + type="image/jpeg" + /> + + <source + media="(max-width: 1024px)" + srcset="/img/pics/homepage-1024w.webp" + type="image/webp" + /> + <source + media="(max-width: 1024px)" + srcset="/img/pics/homepage-1024w.jpg" + type="image/jpeg" + /> + + <source + media="(max-width: 1920px)" + srcset="/img/pics/homepage-1920w.webp" + type="image/webp" + /> + <source + media="(max-width: 1920px)" + srcset="/img/pics/homepage-1920w.jpg" + type="image/jpeg" + /> + + <source + media="(min-width: 1921px)" + srcset="/img/pics/homepage.webp" + type="image/webp" + /> + <source + media="(min-width: 1921px)" + srcset="/img/pics/homepage.jpg" + type="image/jpeg" + /> + + <img + src="/img/pics/homepage-1024w.jpg" + width="3840" + height="2719" + alt="" + loading="lazy" + /> + </picture> </div> <div class="container section"> <div class="columns"> @@ -221,6 +271,7 @@ import { Component, Vue, Watch } from "vue-property-decorator"; import { ParticipantRole } from "@/types/enums"; import { Paginate } from "@/types/paginate"; +import { supportsWebPFormat } from "@/utils/support"; import { IParticipant, Participant } from "../types/participant.model"; import { FETCH_EVENTS } from "../graphql/event"; import EventListCard from "../components/Event/EventListCard.vue"; @@ -296,7 +347,10 @@ import Subtitle from "../components/Utils/Subtitle.vue"; }, }) export default class Home extends Vue { - events!: Paginate<IEvent>; + events: Paginate<IEvent> = { + elements: [], + total: 0, + }; locations = []; @@ -316,6 +370,8 @@ export default class Home extends Vue { currentUserParticipations: IParticipant[] = []; + supportsWebPFormat = supportsWebPFormat; + // get displayed_name() { // return this.loggedPerson && this.loggedPerson.name === null // ? this.loggedPerson.preferredUsername @@ -531,7 +587,11 @@ section.hero { height: 100%; opacity: 0.3; z-index: -1; - background: url("/img/pics/homepage_background.png"); + background: url("/img/pics/homepage_background-1024w.png"); + background-size: cover; + } + &.webp::before { + background-image: url("/img/pics/homepage_background-1024w.webp"); } & > .hero-body { diff --git a/js/src/views/PageNotFound.vue b/js/src/views/PageNotFound.vue index c5d70c07e..afce22e21 100644 --- a/js/src/views/PageNotFound.vue +++ b/js/src/views/PageNotFound.vue @@ -2,10 +2,24 @@ <section class="section container has-text-centered not-found"> <div class="columns is-vertical is-centered"> <div class="column is-half"> - <img - src="/img/pics/2020-10-06-mobilizon-illustration-E_realisation.jpg" - alt="" - /> + <picture> + <source + srcset="/img/pics/error-480w.webp 1x, /img/pics/error-1024w.webp 2x" + type="image/webp" + /> + <source + srcset="/img/pics/error-480w.jpg 1x, /img/pics/error-1024w.jpg 2x" + type="image/jpeg" + /> + + <img + :src="`/img/pics/error-480w.jpg`" + alt="" + width="2616" + height="1698" + loading="lazy" + /> + </picture> <h1 class="title"> {{ $t("The page you're looking for doesn't exist.") }} </h1> diff --git a/js/yarn.lock b/js/yarn.lock index 2ad9ab1c6..ff83d75cf 100644 --- a/js/yarn.lock +++ b/js/yarn.lock @@ -2701,7 +2701,7 @@ async@2.6.1: dependencies: lodash "^4.17.10" -async@^2.6.2: +async@^2.6.1, async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -10489,6 +10489,11 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1: expand-tilde "^2.0.0" global-modules "^1.0.0" +resolve-from@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" + integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -10499,6 +10504,13 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-pkg@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-1.0.0.tgz#e19a15e78aca2e124461dc92b2e3943ef93494d9" + integrity sha1-4ZoV54rKLhJEYdySsuOUPvk0lNk= + dependencies: + resolve-from "^2.0.0" + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -10739,6 +10751,16 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" +scripty@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/scripty/-/scripty-2.0.0.tgz#25761bb2e237a7563f705d87357db07791d38459" + integrity sha512-vbd4FPeuNwYNGtRtYa1wDZLPCx5PpW6VrldCEiBGqPz7Je1xZOgNvVPD2axymvqNghBIRiXxAU+JwYrOzvuLJg== + dependencies: + async "^2.6.1" + glob "^7.0.3" + lodash "^4.17.11" + resolve-pkg "^1.0.0" + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" From 71f1701ce83b00be56c2d9c508ef87e98bb2739d Mon Sep 17 00:00:00 2001 From: Thomas Citharel <tcit@tcit.fr> Date: Fri, 11 Dec 2020 15:27:31 +0100 Subject: [PATCH 2/3] Add back pwa support Signed-off-by: Thomas Citharel <tcit@tcit.fr> --- js/src/main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/js/src/main.ts b/js/src/main.ts index 5fcc4addb..125100ad7 100644 --- a/js/src/main.ts +++ b/js/src/main.ts @@ -12,6 +12,7 @@ import { NotifierPlugin } from "./plugins/notifier"; import filters from "./filters"; import { i18n } from "./utils/i18n"; import apolloProvider from "./vue-apollo"; +import "./registerServiceWorker"; Vue.config.productionTip = false; From c43aeb8a3e686ffafa66a23f1f4c9e2eb7e561bc Mon Sep 17 00:00:00 2001 From: Thomas Citharel <tcit@tcit.fr> Date: Fri, 11 Dec 2020 15:44:48 +0100 Subject: [PATCH 3/3] Update CI Signed-off-by: Thomas Citharel <tcit@tcit.fr> --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ba1304b14..5350a2cfa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,7 +41,7 @@ lint: - yarn install #- yarn run lint || export EXITVALUE=1 - yarn run prettier -c . || export EXITVALUE=1 - - yarn run build + - yarn run build:assets - cd ../ - exit $EXITVALUE artifacts: @@ -69,7 +69,7 @@ exunit: before_script: - cd js - yarn install - - yarn run build + - yarn run build:assets - cd ../ - mix deps.get - MIX_ENV=test mix ecto.create