diff --git a/.gitignore b/.gitignore index 29535ac..649ba59 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ coverage # app src/data.json public/answers +public/homepage.webp +public/logo.png +public/favicon/*.png diff --git a/README.md b/README.md index aac3380..72b20f0 100644 --- a/README.md +++ b/README.md @@ -2,38 +2,39 @@ Vous trouverez ici le code source de l'application de Scores de [Ceiba Conseil](https://www.ceiba-conseil.com/). -## Information +## Information -- Les emojis ont été pris depuis le site https://twemoji-cheatsheet.vercel.app/ +- Le projet est développé par _[Weko](https://weko.io)_ et hébergé par _[RésiLien](https://resilien.fr)_ +- L'application utilise [un script](./scripts/fetchData.js) permettant d'importer des contenus depuis [une interface d'administration](https://admin.ceiba-conseil.com) +- [Un outil automatique](https://ci.resilien.fr/Weko/ceiba-scores) permet une compilation automatique du projet lorsqu'un changement est effectué coté administration +- [Les emojis](https://twemoji-cheatsheet.vercel.app/) sont libres de droit proposé par _Twitter_ +- _RésiLien_ reçoit des notifications automatiques lorsqu'un problème arrive lors de la compilation sur le canal _Matrix_ suivant https://matrix.to/#/#resilien-monitoring:converser.eu -## Recommended IDE Setup +## Développement -[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.vscode-typescript-vue-plugin). +L'application a été développée avec le framework Javascript _[Vue.js](https://vuejs.org/)_ et l'outil _[Vite](https://vitejs.dev/)_. +Il utilise _[NPM](https://www.npmjs.com/)_ pour la gestion des dépendances Javascript. -## Customize configuration +### Recommandation pour votre environnement de développement -See [Vite Configuration Reference](https://vitejs.dev/config/). +_[VSCode](https://code.visualstudio.com/)_ + _[Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)_ (et désactiver _Vetur_) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.vscode-typescript-vue-plugin). -## Project Setup +Une configuration spécifique à _Vite_ peut être mise en place, voir [la référence](https://vitejs.dev/config/). -```sh -npm install -``` +### Commandes -### Compile and Hot-Reload for Development +- Installation des dépendances : `npm install` +- Import des données de l'administration : `npm run fetchData` +- Compilation avec du _Hot-Reload_ (Rechargement à chaud) pour le développement : `npm run dev` +- Compilation et minification pour la production : `npm run build` +- Les règles de mise en forme sont assurées par _[ESLint](https://eslint.org/)_ : `npm run lint` -```sh -npm run dev -``` +## Logiciels utilisés -### Compile and Minify for Production +_RésiLien_ met en place des outils open source eprouvé dans le temps et dont les communautés sont assez grandes pour permettre une perenité de ses projets. -```sh -npm run build -``` - -### Lint with [ESLint](https://eslint.org/) - -```sh -npm run lint -``` +Voici les différentes briques utilisé autour du projet : +- _[Garage](https://garagehq.deuxfleurs.fr/)_ pour l'hébergement développé par l'association [Deuxfleurs](https://deuxfleurs.fr/) +- _[Directus](https://directus.io/)_ pour l'administration +- _[Drone](https://drone.io)_ pour la compilation automatique du projet +- _[Matrix](https://fr.wikipedia.org/wiki/Matrix_(protocole))_ est un protocole ouvert pour de la communication en temps réel diff --git a/index.html b/index.html index cd6d1ab..6ff2131 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,22 @@ - + + + + + + + + + + + + + + + + Scores Ceiba diff --git a/package-lock.json b/package-lock.json index cad894a..958a67b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@splidejs/vue-splide": "^0.5.18", + "html2canvas": "^1.4.1", "pinia": "^2.0.11", "pinia-plugin-persist": "^1.0.0", "sass": "^1.49.9", @@ -542,6 +543,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -844,6 +853,14 @@ "node": ">= 8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "2.6.20", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", @@ -1779,6 +1796,18 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -3059,6 +3088,14 @@ "node": ">= 8" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3124,6 +3161,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -3814,6 +3859,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4019,6 +4069,14 @@ "which": "^2.0.1" } }, + "css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "requires": { + "utrie": "^1.0.2" + } + }, "csstype": { "version": "2.6.20", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", @@ -4622,6 +4680,15 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "requires": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + } + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -5490,6 +5557,14 @@ "terser": "^5.7.2" } }, + "text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "requires": { + "utrie": "^1.0.2" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5540,6 +5615,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "requires": { + "base64-arraybuffer": "^1.0.2" + } + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", diff --git a/package.json b/package.json index bbd06ae..6bb1093 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@splidejs/vue-splide": "^0.5.18", + "html2canvas": "^1.4.1", "pinia": "^2.0.11", "pinia-plugin-persist": "^1.0.0", "sass": "^1.49.9", diff --git a/public/arbre.webp b/public/arbre.webp deleted file mode 100644 index a8ce735..0000000 Binary files a/public/arbre.webp and /dev/null differ diff --git a/public/favicon/browserconfig.xml b/public/favicon/browserconfig.xml new file mode 100644 index 0000000..1a8136d --- /dev/null +++ b/public/favicon/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff \ No newline at end of file diff --git a/public/favicon/manifest.json b/public/favicon/manifest.json new file mode 100644 index 0000000..6139bd1 --- /dev/null +++ b/public/favicon/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "App", + "icons": [ + { + "src": "\/favicon\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/favicon\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/favicon\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/favicon\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/favicon\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/favicon\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} \ No newline at end of file diff --git a/public/test.html b/public/test.html new file mode 100644 index 0000000..a4738b7 --- /dev/null +++ b/public/test.html @@ -0,0 +1,91 @@ + + + + + + + + Navigator: share() method - sharing_files - code sample + + + + +
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/scripts/fetchData.js b/scripts/fetchData.js index d69c915..8c54013 100644 --- a/scripts/fetchData.js +++ b/scripts/fetchData.js @@ -30,7 +30,7 @@ async function fetchAsset(uuid) { return fetch(url); } -async function fetchData() { +async function fetchScoresData() { const fields = [ "*", "translations.*", @@ -72,18 +72,7 @@ async function fetchData() { for (const answer of question.questions_id.answers) { const uuid = answer.answers_id.image; if (uuid) { - console.log(`${folder}/${uuid}`); - const response = await fetchAsset(uuid); - try { - const thumbnail = sharp().resize({ height: 200 }).webp(); - await streamPipeline( - response.body, - thumbnail, - createWriteStream(`${folder}/${uuid}.webp`) - ); - } catch (err) { - console.log(err); - } + await downloadImage(uuid, `${folder}/${uuid}.webp`, { height: 200 }); } } } @@ -92,4 +81,231 @@ async function fetchData() { // await sharp('src/assets/arbre.png').resize({ width: 440, height: 690 }).webp().toFile('public/arbre.webp') } +async function transformImage(response, path, options, format = "webp") { + try { + let thumbnail; + console.log(path); + if (format == "webp") { + thumbnail = sharp().resize(options).webp(); + } else { + thumbnail = sharp().resize(options).toFormat("png"); + } + const stream = createWriteStream(path); + await streamPipeline(response.body, thumbnail, stream); + await stream; + } catch (err) { + console.log(err); + } +} + +async function downloadImage(uuid, path, options, format = "webp") { + const response = await fetchAsset(uuid); + await transformImage(response, path, options, format); +} + +async function fetchHomepageData() { + const fields = ["*"]; + const url = `/items/homepage?${fields + .map((item) => `fields[]=${item}`) + .join("&")}`; + const homepage = (await fetchJSONApi(url)).data; + + await downloadImage(homepage.image, `public/homepage.webp`, { + width: 1280, + height: 720, + fit: sharp.fit.cover, + }); + + const favicons = [ + { + path: `public/favicon/android-icon-192x192.png`, + options: { + width: 192, + height: 192, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/apple-icon-57x57.png`, + options: { + width: 57, + height: 57, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/apple-icon-60x60.png`, + options: { + width: 60, + height: 60, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/apple-icon-72x72.png`, + options: { + width: 72, + height: 72, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/apple-icon-76x76.png`, + options: { + width: 76, + height: 76, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/apple-icon-114x114.png`, + options: { + width: 114, + height: 114, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/apple-icon-120x120.png`, + options: { + width: 120, + height: 120, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/apple-icon-144x144.png`, + options: { + width: 144, + height: 144, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/apple-icon-152x152.png`, + options: { + width: 152, + height: 152, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/apple-icon-180x180.png`, + options: { + width: 180, + height: 180, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/favicon-16x16.png`, + options: { + width: 16, + height: 16, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/favicon-32x32.png`, + options: { + width: 32, + height: 32, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/favicon-96x96.png`, + options: { + width: 96, + height: 96, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/favicon-256x256.png`, + options: { + width: 256, + height: 256, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/ms-icon-70x70.png`, + options: { + width: 70, + height: 70, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/ms-icon-144x144.png`, + options: { + width: 144, + height: 144, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/ms-icon-150x150.png`, + options: { + width: 150, + height: 150, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + { + path: `public/favicon/ms-icon-310x310.png`, + options: { + width: 310, + height: 310, + fit: sharp.fit.contain, + background: { r: 255, g: 255, b: 255, alpha: 0 }, + }, + format: "png", + }, + ]; + for (const favicon of favicons) { + downloadImage(homepage.logo, favicon.path, favicon.options, favicon.format); + } +} + +async function fetchData() { + await fetchHomepageData(); + await fetchScoresData(); +} + fetchData(); diff --git a/src/assets/arbre.png b/src/assets/arbre.png deleted file mode 100644 index 088935a..0000000 Binary files a/src/assets/arbre.png and /dev/null differ diff --git a/src/assets/base.css b/src/assets/base.css index 6dc337d..26ff92e 100644 --- a/src/assets/base.css +++ b/src/assets/base.css @@ -8,6 +8,7 @@ --header-size-small: 64px; --header-size-big: 128px; + --header-size: var(--header-size-small); } /* semantic color variables for this project */ @@ -19,7 +20,6 @@ --color-highlight-background: var(--color-green); --color-highlight-text: var(--color-white); --color-highlight-text-invert: var(--color-black); - --header-size: var(--header-size-small) } body.theme-dark { @@ -44,7 +44,7 @@ body.theme-dark { } } -@media (min-width: 1024px) { +@media (min-height: 800px) { :root { --header-size: var(--header-size-big); } diff --git a/src/components/Question.vue b/src/components/Question.vue index 4685473..60faccf 100644 --- a/src/components/Question.vue +++ b/src/components/Question.vue @@ -93,6 +93,7 @@ function slideMove(splide, newIndex) { width: 1.6rem .splide__pagination bottom: -1.5em + display: none .splide__pagination__page width: .7rem height: .7rem @@ -108,7 +109,7 @@ legend text-align: center font-size: 1.4rem line-height: 2rem - margin: 1rem + font-weight: bold .choices list-style-type: none @@ -116,12 +117,15 @@ legend display: inline-block padding-left: 0 + label + cursor: pointer + input[type=radio] display: none & + label position: relative - padding-left: 2rem + padding: .2rem .2rem .2rem 2rem & + label::before, & + label::after display: block @@ -130,20 +134,22 @@ legend content:'' border-radius: 1rem & + label::before - bottom: 0 + bottom: .3rem left: 0 background-color: var(--color-green) width: 1rem height: 1rem & + label::after - bottom: 3px + bottom: calc(3px + .3rem) left: 3px width: calc(1rem - 6px) height: calc(1rem - 6px) + &:checked + label + text-shadow: -0.06ex 0 0 currentColor, 0.06ex 0 0 currentColor &:checked + label::before - background-color: white - &:checked + label::after background-color: var(--color-green) + &:checked + label::after + background-color: white .main height: 100% @@ -163,6 +169,9 @@ legend max-width: 100% min-width: 280px + @media (max-height: 600px) + margin: 1rem auto + h2 font-size: 2rem diff --git a/src/components/Score.vue b/src/components/Score.vue index 7c57745..f623677 100644 --- a/src/components/Score.vue +++ b/src/components/Score.vue @@ -9,6 +9,7 @@ import { Splide, SplideSlide } from "@splidejs/vue-splide"; import Question from "./Question.vue"; import "@splidejs/splide/dist/css/splide.min.css"; import ScoreHeader from "./ScoreHeader.vue"; +import html2canvas from "html2canvas"; const props = defineProps({ id: { @@ -110,6 +111,16 @@ function nextQuestion() { console.log(slides); }, 100); } + +async function share() { + console.log(document.querySelector(".latest")); + const canvas = await html2canvas(document.querySelector("html")); + let anchor = document.createElement("a"); + anchor.download = "download.png"; + anchor.href = canvas.toDataURL("image/png"); + anchor.click(); + anchor.remove(); +}